Files
phone-app------test1-/stores/socket.js
2026-08-03 17:09:22 +08:00

400 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}
})