400 lines
9.9 KiB
JavaScript
400 lines
9.9 KiB
JavaScript
import {
|
||
defineStore
|
||
} from "pinia";
|
||
import {
|
||
computed,
|
||
ref
|
||
} from "vue";
|
||
|
||
import {
|
||
socketManager
|
||
} from '@/utils/socket.js'
|
||
import {
|
||
getToken
|
||
} from "../utils/user-info";
|
||
|
||
export const useSocketStore = defineStore('socket', () => {
|
||
// ==================== 状态定义 ====================
|
||
const connectionStatus = ref('disconnected') //connecting | connected | disconnected | error
|
||
const messages = ref([])
|
||
const messageString = ref('')
|
||
const isThinking = ref(false)
|
||
const config = ref({
|
||
token: '',
|
||
conversationId: ''
|
||
})
|
||
const taskCallId = ref('')
|
||
|
||
// 日志数组和最大值
|
||
const logs = ref([])
|
||
const maxLogCount = ref(20)
|
||
|
||
const authFailReason = ref('')
|
||
|
||
// ==================== 计算属性 ====================
|
||
const isConnected = computed(() => connectionStatus.value === 'connected')
|
||
const isConnecting = computed(() => connectionStatus.value === 'connecting')
|
||
const isDisconnected = computed(() => connectionStatus.value === 'disconnected')
|
||
const isError = computed(() => connectionStatus.value === 'error')
|
||
const lastMessage = computed(() => messages.value[messages.value.length - 1])
|
||
|
||
// 日志方法
|
||
function addLog(type, connect) {
|
||
const now = new Date()
|
||
const time =
|
||
`${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}:${now.getSeconds().toString().padStart(2,'0')}`
|
||
logs.value.push({
|
||
type,
|
||
connect,
|
||
time
|
||
})
|
||
if (logs.value.length > maxLogCount.value) {
|
||
logs.value = logs.value.slice(-maxLogCount.value)
|
||
}
|
||
console.log(`[${type.toUpperCase()}] ${connect}`);
|
||
}
|
||
|
||
function clearLogs() {
|
||
logs.value = []
|
||
}
|
||
|
||
// ==================== 连接相关方法 ====================
|
||
function connect(options = {}) {
|
||
messages.value = []
|
||
messageString.value = ''
|
||
config.value = {
|
||
...config.value,
|
||
...options
|
||
}
|
||
addLog('info',
|
||
`准备连接WebSocket。Token:${config.value.token || '未设置'}。ConversationId: ${config.value.conversationId || '未设置'}`
|
||
)
|
||
if (!config.value.token || !config.value.conversationId) {
|
||
addLog('error', 'Token和ConversationId都不能为空')
|
||
return
|
||
}
|
||
connectionStatus.value = 'connecting'
|
||
|
||
socketManager.connect({
|
||
token: config.value.token,
|
||
conversationId: config.value.conversationId,
|
||
onMessage: handleMessage,
|
||
onError: handleError,
|
||
onOpen: handleOpen,
|
||
onClose: handleClose,
|
||
onReconnect: handleReconnecting
|
||
})
|
||
|
||
setConnectionTimeout()
|
||
}
|
||
|
||
function disconnect() {
|
||
socketManager.close()
|
||
connectionStatus.value = 'disconnected'
|
||
addLog('warn', '已主动断开连接')
|
||
}
|
||
|
||
async function send(message) {
|
||
console.log("检查到要发生消息为:",message);
|
||
if (!isConnected.value) {
|
||
addLog('error', '发送失败: 连接未建立')
|
||
throw new Error('连接未建立')
|
||
}
|
||
try {
|
||
await socketManager.send(message)
|
||
addLog('send', typeof message === 'string' ? message : JSON.stringify(message))
|
||
return true
|
||
} catch (error) {
|
||
addLog('error', `发送失败: ${error.message || error}`)
|
||
throw error
|
||
}
|
||
}
|
||
|
||
// ==================== 事件回调 ====================
|
||
function handleOpen() {
|
||
connectionStatus.value = 'connected'
|
||
addLog('success', '连接成功!发送auth')
|
||
console.log('连接成功!发送auth');
|
||
sendAuth()
|
||
|
||
}
|
||
|
||
function handleClose() {
|
||
if (connectionStatus.value !== 'disconnected') {
|
||
connectionStatus.value = 'disconnected'
|
||
addLog('warn', '连接已断开')
|
||
}
|
||
}
|
||
|
||
function handleError(error) {
|
||
addLog('error', `Socket错误: ${JSON.stringify(error)}`)
|
||
connectionStatus.value = 'error'
|
||
}
|
||
|
||
function handleReconnecting(attempts) {
|
||
connectionStatus.value = 'connecting'
|
||
addLog('warn', `重连中... (${attempts}/5)`)
|
||
}
|
||
|
||
function setConnectionTimeout() {
|
||
const timer = setTimeout(() => {
|
||
if (connectionStatus.value === 'connecting') {
|
||
connectionStatus.value = 'error'
|
||
addLog('error', '连接超时(10s)')
|
||
}
|
||
}, 10000)
|
||
|
||
// 返回 timer 以便必要时清除,但目前用不到
|
||
return timer
|
||
}
|
||
|
||
function handleMessage(messageData) {
|
||
addLog('receive', `收到: ${JSON.stringify(messageData)}`)
|
||
|
||
const {
|
||
type,
|
||
conversation_id,
|
||
message_id,
|
||
data
|
||
} = messageData || {}
|
||
|
||
// 调试日志:明确输出 type 和 data
|
||
console.log(`[handleMessage] type = ${type}, data = ${JSON.stringify(data)}`);
|
||
|
||
const eventConversationId =
|
||
conversation_id ||
|
||
data?.conversation_id ||
|
||
data?.page?.conversation?.conversation_id ||
|
||
data?.page?.conversation?.id ||
|
||
''
|
||
if (
|
||
eventConversationId &&
|
||
config.value.conversationId &&
|
||
String(eventConversationId) !== String(config.value.conversationId)
|
||
) {
|
||
addLog('info', `忽略非当前会话消息: ${eventConversationId}`)
|
||
return
|
||
}
|
||
|
||
switch (type) {
|
||
case 'auth_ok':
|
||
addLog('success', '连接成功')
|
||
break
|
||
|
||
case 'auth_fail':
|
||
isThinking.value = false
|
||
authFailReason.value = data?.reason || messageData?.reason || 'unknown'
|
||
addLog('error', `认证失败: ${authFailReason.value}`)
|
||
break
|
||
|
||
case 'chat_ack':
|
||
addLog('warn', 'AI成功收到用户发送的消息')
|
||
break
|
||
|
||
case 'forbid_input':
|
||
addLog('warn', '收到封框指令')
|
||
isThinking.value = true
|
||
break
|
||
|
||
case 'relieve_input':
|
||
addLog('warn', '收到解框指令')
|
||
isThinking.value = false
|
||
break
|
||
|
||
case 'turn_status': {
|
||
const status = String(messageData.status || data?.status || '').toLowerCase()
|
||
if (status === 'locked') {
|
||
addLog('warn', '后端确认程序封框')
|
||
isThinking.value = true
|
||
} else if (status === 'finished' || status === 'cancelled' || status === 'unlocked' || status === 'relieved') {
|
||
addLog('warn', `后端确认当前轮结束/解框: ${status}`)
|
||
isThinking.value = false
|
||
}
|
||
break
|
||
}
|
||
|
||
case 'prompt':
|
||
if ((messageData.info || data?.info) === '结束等待') {
|
||
addLog('warn', '收到结束等待信号')
|
||
isThinking.value = false
|
||
}
|
||
break
|
||
|
||
case 'refresh':
|
||
if (messageData.page?.waiting === false || data?.page?.waiting === false) {
|
||
addLog('warn', 'refresh 确认 waiting=false,释放输入框')
|
||
isThinking.value = false
|
||
}
|
||
break
|
||
|
||
case 'render':
|
||
addLog('warn', 'AI正在回复信息')
|
||
handleBusinessMessage(data)
|
||
break
|
||
|
||
case 'pc_offline':
|
||
addLog('warn', 'PC 端已离线')
|
||
isThinking.value = false
|
||
authFailReason.value = 'pc offline'
|
||
break
|
||
|
||
default:
|
||
addLog('info', `未知事件类型: ${type}`)
|
||
}
|
||
}
|
||
|
||
// ==================== 消息处理 ====================
|
||
function sendAuth() {
|
||
config.value.token
|
||
socketManager.send({
|
||
"type": "auth",
|
||
"access_token": config.value.token
|
||
})
|
||
}
|
||
|
||
function handleBindConversation(messageData) {
|
||
// 管理绑定对话,// 发送"task_call_id","token""conversation_id"
|
||
const {
|
||
ws_event,
|
||
queue,
|
||
data
|
||
} = messageData || {}
|
||
if (data?.task_call_id) {
|
||
socketManager.send({
|
||
"ws_event": "message",
|
||
"data": {
|
||
"task_call_id": taskCallId.value,
|
||
"token": config.value.token,
|
||
"conversation_id": config.value.conversationId,
|
||
},
|
||
})
|
||
}
|
||
}
|
||
|
||
function handleBusinessMessage(data) {
|
||
// 处理 data 为字符串 "exit" 的情况
|
||
if (data === "exit") {
|
||
console.log("ai结束思考");
|
||
isThinking.value = false
|
||
return
|
||
}
|
||
|
||
// 处理 data 为对象的情况
|
||
if (!data || typeof data !== 'object') return
|
||
|
||
const msgType = String(data.type || '').trim()
|
||
if (msgType === 'forbid_input') {
|
||
console.log("收到后端封框指令");
|
||
isThinking.value = true
|
||
return
|
||
}
|
||
if (msgType === 'relieve_input') {
|
||
console.log("收到后端解框指令");
|
||
isThinking.value = false
|
||
return
|
||
}
|
||
|
||
if (data.cmd === 'turn_status') {
|
||
const status = String(data.status || '').toLowerCase()
|
||
if (status === 'locked') {
|
||
console.log("后端确认程序封框");
|
||
isThinking.value = true
|
||
return
|
||
}
|
||
if (status === 'finished' || status === 'cancelled' || status === 'unlocked' || status === 'relieved') {
|
||
console.log("后端确认当前轮结束/解框:", status);
|
||
isThinking.value = false
|
||
return
|
||
}
|
||
}
|
||
|
||
if (data.cmd === 'prompt' && data.info === '结束等待') {
|
||
console.log("收到结束等待信号");
|
||
isThinking.value = false
|
||
return
|
||
}
|
||
|
||
// refresh 也会在流式输出和工具中间消息落盘时出现,不能直接当成最终完成。
|
||
// 只有后端明确 page.waiting=false 时,才作为漏发结束信号的兜底释放。
|
||
if (data.cmd === 'refresh' && data.page?.waiting === false) {
|
||
console.log("refresh 确认 waiting=false,释放输入框");
|
||
isThinking.value = false
|
||
return
|
||
}
|
||
|
||
// 中断确认:后端返回 cmd=event_callback 表示中断成功
|
||
if (data.cmd === 'event_callback' && data.info === '中断成功') {
|
||
console.log("后端确认中断成功");
|
||
addLog('warn', `中断确认: ${data.info}`)
|
||
isThinking.value = false
|
||
return
|
||
}
|
||
|
||
const {
|
||
role,
|
||
chunk,
|
||
message_role,
|
||
meaasge_uuid,
|
||
message_type,
|
||
task_call_id
|
||
} = data
|
||
|
||
// ai 开始回复:首次收到 chunk 或 message_role 为 assistant 时
|
||
if (!isThinking.value && (chunk || message_role === 'assistant')) {
|
||
console.log("ai开始思考");
|
||
messageString.value = ''
|
||
isThinking.value = true
|
||
}
|
||
|
||
if (chunk) {
|
||
// 拼接 ai 回复的内容
|
||
messageString.value += chunk
|
||
if (!isThinking.value) {
|
||
console.log("修改状态为开始思考");
|
||
isThinking.value = true
|
||
}
|
||
}
|
||
}
|
||
|
||
// ==================== 状态查询 ====================
|
||
function getStatusText() {
|
||
const statusMap = {
|
||
connecting: '连接中',
|
||
connected: '已连接',
|
||
disconnected: '未连接',
|
||
error: '连接错误'
|
||
}
|
||
return statusMap[connectionStatus.value] || '未知状态'
|
||
}
|
||
|
||
function getReadyState() {
|
||
return socketManager.getReadyState()
|
||
}
|
||
|
||
// ==================== 导出 ====================
|
||
return {
|
||
// 状态
|
||
connectionStatus,
|
||
messageString,
|
||
isThinking,
|
||
logs,
|
||
config,
|
||
authFailReason,
|
||
|
||
// 计算属性
|
||
isConnected,
|
||
isConnecting,
|
||
isDisconnected,
|
||
isError,
|
||
lastMessage,
|
||
|
||
// 方法
|
||
connect,
|
||
disconnect,
|
||
send,
|
||
addLog,
|
||
clearLogs,
|
||
getStatusText
|
||
}
|
||
})
|