917 lines
30 KiB
TypeScript
917 lines
30 KiB
TypeScript
import { defineStore } from 'pinia';
|
||
import { ref, reactive } from 'vue';
|
||
|
||
// 定义消息类型
|
||
export interface ChatMessage {
|
||
id: string;
|
||
content: string;
|
||
role: 'user' | 'assistant';
|
||
createAt: Date;
|
||
}
|
||
|
||
// WebSocket事件类型
|
||
export enum WebSocketEvent {
|
||
CONNECTED = 'connected',
|
||
DISCONNECTED = 'disconnected',
|
||
MESSAGE = 'message',
|
||
ERROR = 'error',
|
||
MODEL_LIST = 'model_list'
|
||
}
|
||
|
||
// 定义VSCode API接口
|
||
let vscode: any = undefined;
|
||
|
||
try {
|
||
vscode = acquireVsCodeApi();
|
||
} catch (error) {
|
||
// 在开发环境下,可能没有VSCode API
|
||
console.warn('VSCode API不可用,可能是在浏览器开发环境中运行');
|
||
}
|
||
|
||
// WebSocket服务类 - 浏览器环境下实现
|
||
export class WebSocketService {
|
||
private socket: WebSocket | null = null;
|
||
private requestMap = new Map<number, (response: any) => void>();
|
||
private nextRequestId = 1;
|
||
private reconnectAttempts = 0;
|
||
private reconnectTimer: number | null = null;
|
||
private readonly MAX_RECONNECT_ATTEMPTS = 5;
|
||
private readonly RECONNECT_DELAY = 2000;
|
||
private isReconnecting = false;
|
||
private pingInterval: number | null = null;
|
||
|
||
public isConnected = false;
|
||
private eventListeners: Map<string, Array<(data?: any) => void>> = new Map();
|
||
|
||
constructor(private apiUrl: string, private apiKey: string, private logger: (message: string) => void = console.log) {}
|
||
|
||
/**
|
||
* 连接到WebSocket服务器
|
||
*/
|
||
public connect(): void {
|
||
if (this.socket?.readyState === WebSocket.OPEN) {
|
||
this.logger('[WebSocketService] 已连接');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
this.logger(`[WebSocketService] 尝试连接到: ${this.apiUrl}`);
|
||
|
||
// 检查URL格式
|
||
let wsUrl = this.apiUrl;
|
||
|
||
// 确保URL以/ws结尾,但避免重复
|
||
if (!wsUrl.endsWith('/ws')) {
|
||
if (wsUrl.endsWith('/')) {
|
||
wsUrl += 'ws';
|
||
} else {
|
||
wsUrl += '/ws';
|
||
}
|
||
}
|
||
|
||
// 检查是否有API密钥,如果有则添加为查询参数
|
||
if (this.apiKey) {
|
||
const separator = wsUrl.includes('?') ? '&' : '?';
|
||
wsUrl += `${separator}apiKey=${encodeURIComponent(this.apiKey)}`;
|
||
}
|
||
|
||
// 检查环境和协议
|
||
// const isVsCodeWebview = typeof window !== 'undefined' && 'acquireVsCodeApi' in window;
|
||
const isLocalhost = wsUrl.includes('localhost') || wsUrl.includes('127.0.0.1');
|
||
|
||
// VS Code WebView环境下可能需要wss
|
||
// if (isVsCodeWebview && wsUrl.startsWith('ws://')) {
|
||
// this.logger('[WebSocketService] 在VS Code WebView中,尝试使用安全WebSocket连接');
|
||
// wsUrl = wsUrl.replace('ws://', 'wss://');
|
||
// }
|
||
|
||
this.logger(`[WebSocketService] 最终WebSocket URL: ${wsUrl}`);
|
||
this.socket = new WebSocket(wsUrl);
|
||
this.setupEventHandlers();
|
||
|
||
// 处理连接失败的情况
|
||
if (isLocalhost) {
|
||
// 如果使用localhost失败,尝试使用127.0.0.1
|
||
const fallbackTimeout = setTimeout(() => {
|
||
if (this.socket?.readyState !== WebSocket.OPEN) {
|
||
this.logger('[WebSocketService] localhost连接超时,尝试使用127.0.0.1');
|
||
|
||
let fallbackUrl;
|
||
if (wsUrl.includes('localhost')) {
|
||
fallbackUrl = wsUrl.replace('localhost', '127.0.0.1');
|
||
} else if (wsUrl.includes('127.0.0.1')) {
|
||
fallbackUrl = wsUrl.replace('127.0.0.1', 'localhost');
|
||
}
|
||
|
||
if (fallbackUrl && fallbackUrl !== wsUrl) {
|
||
this.logger(`[WebSocketService] 尝试备用连接: ${fallbackUrl}`);
|
||
|
||
// 关闭旧连接
|
||
if (this.socket) {
|
||
this.socket.close();
|
||
}
|
||
|
||
// 尝试新连接
|
||
this.socket = new WebSocket(fallbackUrl);
|
||
this.setupEventHandlers();
|
||
}
|
||
}
|
||
clearTimeout(fallbackTimeout);
|
||
}, 3000); // 3秒后如果未连接则尝试备用地址
|
||
}
|
||
} catch (error) {
|
||
this.logger(`[WebSocketService] 连接错误: ${error instanceof Error ? error.message : String(error)}`);
|
||
this.emit(WebSocketEvent.ERROR, `连接错误: ${error instanceof Error ? error.message : String(error)}`);
|
||
this.scheduleReconnect();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 断开WebSocket连接
|
||
*/
|
||
public disconnect(): void {
|
||
this.clearTimers();
|
||
|
||
if (this.socket) {
|
||
try {
|
||
this.socket.close();
|
||
} catch (err) {
|
||
this.logger(`关闭WebSocket时出错: ${err}`);
|
||
}
|
||
this.socket = null;
|
||
}
|
||
|
||
this.isConnected = false;
|
||
this.emit(WebSocketEvent.DISCONNECTED);
|
||
}
|
||
|
||
/**
|
||
* 监听事件
|
||
*/
|
||
public on(event: WebSocketEvent, callback: (data?: any) => void): void {
|
||
if (!this.eventListeners.has(event)) {
|
||
this.eventListeners.set(event, []);
|
||
}
|
||
|
||
this.eventListeners.get(event)?.push(callback);
|
||
}
|
||
|
||
/**
|
||
* 移除事件监听
|
||
*/
|
||
public off(event: WebSocketEvent, callback: (data?: any) => void): void {
|
||
const listeners = this.eventListeners.get(event);
|
||
if (listeners) {
|
||
const index = listeners.indexOf(callback);
|
||
if (index !== -1) {
|
||
listeners.splice(index, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 触发事件
|
||
*/
|
||
private emit(event: WebSocketEvent, data?: any): void {
|
||
const listeners = this.eventListeners.get(event);
|
||
if (listeners) {
|
||
listeners.forEach(callback => callback(data));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置WebSocket事件处理器
|
||
*/
|
||
private setupEventHandlers(): void {
|
||
if (!this.socket) {
|
||
this.logger('[WebSocketService] 无法设置事件处理器: socket为null');
|
||
return;
|
||
}
|
||
|
||
// 记录连接建立时间
|
||
let connectionEstablishedTime = 0;
|
||
|
||
// 使用浏览器风格的事件处理
|
||
this.socket.onopen = () => {
|
||
this.logger('[WebSocketService] WebSocket连接已建立成功');
|
||
this.isConnected = true;
|
||
connectionEstablishedTime = Date.now();
|
||
|
||
// 延迟重置重连计数器,确保连接稳定
|
||
setTimeout(() => {
|
||
if (this.isConnected) {
|
||
this.reconnectAttempts = 0;
|
||
this.logger('[WebSocketService] 连接保持稳定,重置重连计数器');
|
||
}
|
||
}, 5000);
|
||
|
||
// 设置定时ping保持连接
|
||
this.setupPingInterval();
|
||
|
||
this.emit(WebSocketEvent.CONNECTED);
|
||
this.logger('[WebSocketService] 已触发CONNECTED事件');
|
||
|
||
// 连接后立即获取模型列表
|
||
this.logger('[WebSocketService] 开始获取初始模型列表');
|
||
this.getModelList()
|
||
.then(models => {
|
||
this.logger(`[WebSocketService] 获取初始模型列表成功: ${JSON.stringify(models)}`);
|
||
this.emit(WebSocketEvent.MODEL_LIST, models);
|
||
})
|
||
.catch(error => {
|
||
this.logger(`[WebSocketService] 获取初始模型列表失败: ${error}`);
|
||
});
|
||
};
|
||
|
||
this.socket.onmessage = (event: MessageEvent) => {
|
||
try {
|
||
const data = event.data;
|
||
// 检查是否是HTTP错误响应
|
||
const stringData = typeof data === 'string' ? data : data.toString();
|
||
|
||
// 尝试解析JSON
|
||
let response;
|
||
try {
|
||
response = JSON.parse(stringData);
|
||
} catch (jsonError) {
|
||
this.logger(`无法解析JSON响应: ${jsonError}, 原始消息: ${stringData.substring(0, 100)}`);
|
||
this.emit(WebSocketEvent.ERROR, `服务器返回的不是有效JSON: ${stringData.substring(0, 50)}...`);
|
||
// 自动发送默认模型列表,避免前端卡住
|
||
this.emit(WebSocketEvent.MODEL_LIST, this.getDefaultModels());
|
||
return;
|
||
}
|
||
|
||
this.logger(`收到WebSocket消息: ${JSON.stringify(response)}`);
|
||
|
||
// 处理模型列表响应(特殊处理)
|
||
if (response.models && Array.isArray(response.models)) {
|
||
// 查找对应的模型列表请求
|
||
let modelRequestId = -1;
|
||
for (const [reqId, _] of this.requestMap.entries()) {
|
||
if (reqId < 100) { // 假设小ID是模型列表请求
|
||
modelRequestId = reqId;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (modelRequestId !== -1) {
|
||
const callback = this.requestMap.get(modelRequestId);
|
||
if (callback) {
|
||
callback(response);
|
||
this.requestMap.delete(modelRequestId);
|
||
}
|
||
}
|
||
|
||
// 无论找到对应请求与否,都通知模型列表更新
|
||
this.emit(WebSocketEvent.MODEL_LIST, response.models);
|
||
}
|
||
// 处理常规请求响应
|
||
else if (response.request_id && this.requestMap.has(response.request_id)) {
|
||
const callback = this.requestMap.get(response.request_id);
|
||
if (callback) {
|
||
callback(response);
|
||
|
||
// 如果不是流式响应或是最后一个包,清除请求
|
||
if (!response.stream_seq_id || response.stream_finsh) {
|
||
this.requestMap.delete(response.request_id);
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
this.logger(`解析WebSocket消息失败: ${error}, 原始消息: ${typeof event.data === 'string' ? event.data.substring(0, 100) : '非文本数据'}`);
|
||
// 自动发送默认模型列表,避免前端卡住
|
||
this.emit(WebSocketEvent.MODEL_LIST, this.getDefaultModels());
|
||
}
|
||
};
|
||
|
||
this.socket.onerror = (event: Event) => {
|
||
const errorDetails = JSON.stringify(event);
|
||
this.logger(`[WebSocketService] WebSocket错误事件: ${errorDetails}`);
|
||
|
||
// 检查是否包含isTrusted属性
|
||
if (event && 'isTrusted' in event && event.isTrusted) {
|
||
this.logger('[WebSocketService] 这是一个受信任的错误事件,可能是证书或安全设置导致的');
|
||
|
||
// 获取更多诊断信息
|
||
let diagInfo = '';
|
||
|
||
// 检查是否是localhost
|
||
if (this.apiUrl.includes('localhost') || this.apiUrl.includes('127.0.0.1')) {
|
||
diagInfo += '本地连接(localhost)可能需要特殊权限; ';
|
||
|
||
// 建议使用127.0.0.1而不是localhost
|
||
if (this.apiUrl.includes('localhost')) {
|
||
diagInfo += '建议尝试使用127.0.0.1替代localhost; ';
|
||
}
|
||
}
|
||
|
||
// 检查是否使用了wss安全连接
|
||
if (this.apiUrl.startsWith('wss://')) {
|
||
diagInfo += '使用了安全WebSocket连接,请确认服务器证书有效; ';
|
||
} else {
|
||
// 如果使用ws不安全连接,可能是混合内容问题
|
||
diagInfo += 'VS Code中使用不安全WebSocket(ws://)可能受到限制,建议使用安全连接(wss://); ';
|
||
}
|
||
|
||
// 检查WebView上下文
|
||
if (typeof window !== 'undefined' && 'acquireVsCodeApi' in window) {
|
||
diagInfo += 'WebView环境中可能有额外的安全限制; ';
|
||
}
|
||
|
||
const errorMessage = `连接错误(isTrusted): ${diagInfo}请检查服务器配置和网络连接`;
|
||
this.emit(WebSocketEvent.ERROR, errorMessage);
|
||
} else {
|
||
this.emit(WebSocketEvent.ERROR, `连接错误: ${errorDetails}`);
|
||
}
|
||
|
||
// 重连
|
||
if (this.socket?.readyState !== WebSocket.OPEN) {
|
||
this.logger('[WebSocketService] Socket未处于OPEN状态,安排重连');
|
||
this.scheduleReconnect();
|
||
}
|
||
};
|
||
|
||
this.socket.onclose = (event: CloseEvent) => {
|
||
this.logger(`[WebSocketService] WebSocket连接关闭: 代码=${event.code} (${this.getCloseEventReason(event.code)}), 原因=${event.reason || '未提供'}`);
|
||
this.isConnected = false;
|
||
|
||
// 计算连接持续时间
|
||
if (connectionEstablishedTime > 0) {
|
||
const duration = Date.now() - connectionEstablishedTime;
|
||
this.logger(`[WebSocketService] 连接持续时间: ${duration}ms`);
|
||
}
|
||
|
||
this.clearTimers();
|
||
this.emit(WebSocketEvent.DISCONNECTED);
|
||
this.logger('[WebSocketService] 已触发DISCONNECTED事件');
|
||
|
||
// 如果不是正常关闭,尝试重连
|
||
if (event.code !== 1000) {
|
||
this.logger('[WebSocketService] 非正常关闭,安排重连');
|
||
this.scheduleReconnect();
|
||
}
|
||
};
|
||
|
||
this.logger('[WebSocketService] 所有WebSocket事件处理器已设置完成');
|
||
}
|
||
|
||
/**
|
||
* 获取模型列表
|
||
*/
|
||
public async getModelList(): Promise<string[]> {
|
||
try {
|
||
const requestId = this.nextRequestId++;
|
||
|
||
// 按照API文档使用list_model命令
|
||
const request = {
|
||
request_id: requestId,
|
||
cmd: 'list_model' // 使用文档中指定的命令
|
||
};
|
||
|
||
this.logger(`请求模型列表 (ID: ${requestId})`);
|
||
|
||
return new Promise<string[]>((resolve) => {
|
||
// 设置5秒超时,超时后返回默认模型
|
||
const timeout = setTimeout(() => {
|
||
if (this.requestMap.has(requestId)) {
|
||
this.logger(`模型列表请求超时 (ID: ${requestId})`);
|
||
this.requestMap.delete(requestId);
|
||
resolve(this.getDefaultModels());
|
||
}
|
||
}, 5000);
|
||
|
||
// 设置响应处理器
|
||
this.requestMap.set(requestId, (response) => {
|
||
clearTimeout(timeout);
|
||
|
||
if (response.error) {
|
||
this.logger(`获取模型列表错误: ${response.error}`);
|
||
resolve(this.getDefaultModels());
|
||
} else {
|
||
// 根据文档,应该使用models字段
|
||
const models = response.models || [];
|
||
this.logger(`获取到模型列表: ${JSON.stringify(models)}`);
|
||
|
||
if (models.length === 0) {
|
||
resolve(this.getDefaultModels());
|
||
} else {
|
||
resolve(models);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 发送请求
|
||
this.sendRequest(request);
|
||
});
|
||
} catch (error) {
|
||
this.logger(`获取模型列表异常: ${error}`);
|
||
return this.getDefaultModels();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发送聊天消息
|
||
*/
|
||
public async sendChatMessage(message: string, model: string): Promise<string> {
|
||
if (!this.isConnected) {
|
||
this.logger('无法发送消息: WebSocket未连接');
|
||
throw new Error('WebSocket未连接');
|
||
}
|
||
|
||
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
||
this.logger(`WebSocket状态异常: ${this.socket ? this.getReadyStateDescription(this.socket.readyState) : 'socket为null'}`);
|
||
throw new Error(`WebSocket未准备好: ${this.socket ? this.getReadyStateDescription(this.socket.readyState) : 'socket为null'}`);
|
||
}
|
||
|
||
const requestId = this.nextRequestId++;
|
||
|
||
// 按照API文档中的格式
|
||
const request = {
|
||
cmd: 'exec_chat', // 使用exec_chat命令
|
||
request_id: requestId,
|
||
msg: message, // 使用msg字段而不是content
|
||
model: model,
|
||
stream: true
|
||
};
|
||
|
||
this.logger(`发送聊天消息 (ID: ${requestId}): ${message.substring(0, 50)}${message.length > 50 ? '...' : ''}`);
|
||
this.logger(`使用模型: ${model}`);
|
||
|
||
// 创建用户消息
|
||
const userMessage: ChatMessage = {
|
||
id: `request-${requestId}`,
|
||
content: message,
|
||
role: 'user',
|
||
createAt: new Date()
|
||
};
|
||
|
||
// 通知消息已创建
|
||
this.emit(WebSocketEvent.MESSAGE, userMessage);
|
||
|
||
return new Promise<string>((resolve, reject) => {
|
||
let responseContent = '';
|
||
let responseStartTime: number | null = null;
|
||
let lastResponseTime: number | null = null;
|
||
|
||
// 设置超时处理
|
||
const timeoutId = setTimeout(() => {
|
||
this.logger(`聊天消息响应超时 (ID: ${requestId})`);
|
||
this.requestMap.delete(requestId);
|
||
reject(new Error('服务器响应超时,请稍后重试'));
|
||
}, 60000); // 60秒超时
|
||
|
||
this.requestMap.set(requestId, (response) => {
|
||
// 记录第一次收到响应的时间
|
||
if (responseStartTime === null) {
|
||
responseStartTime = Date.now();
|
||
this.logger(`收到第一个响应包 (ID: ${requestId}), 延迟: ${Date.now() - lastResponseTime!}ms`);
|
||
}
|
||
|
||
// 更新最后响应时间
|
||
lastResponseTime = Date.now();
|
||
|
||
if (response.error) {
|
||
clearTimeout(timeoutId);
|
||
this.logger(`聊天消息响应错误 (ID: ${requestId}): ${response.error}`);
|
||
reject(new Error(response.error));
|
||
return;
|
||
}
|
||
|
||
// 处理消息内容 - 适配不同的响应字段名
|
||
if (response.msg) {
|
||
responseContent += response.msg;
|
||
|
||
// 创建助手消息
|
||
const assistantMessage: ChatMessage = {
|
||
id: `response-${requestId}-${response.stream_seq_id || 0}`,
|
||
content: responseContent,
|
||
role: 'assistant',
|
||
createAt: new Date()
|
||
};
|
||
|
||
// 通知有新消息
|
||
this.emit(WebSocketEvent.MESSAGE, assistantMessage);
|
||
}
|
||
|
||
// 检查是否是最后一个响应包
|
||
if (!response.stream_seq_id || response.stream_finsh) {
|
||
clearTimeout(timeoutId);
|
||
const totalTime = Date.now() - responseStartTime!;
|
||
this.logger(`聊天消息完成 (ID: ${requestId}), 总耗时: ${totalTime}ms`);
|
||
resolve(responseContent);
|
||
}
|
||
});
|
||
|
||
// 记录发送时间
|
||
lastResponseTime = Date.now();
|
||
|
||
try {
|
||
this.sendRequest(request);
|
||
} catch (error) {
|
||
clearTimeout(timeoutId);
|
||
this.logger(`发送请求失败 (ID: ${requestId}): ${error}`);
|
||
this.requestMap.delete(requestId);
|
||
reject(error);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取WebSocket ReadyState的描述
|
||
*/
|
||
private getReadyStateDescription(state: number): string {
|
||
switch (state) {
|
||
case WebSocket.CONNECTING:
|
||
return '正在连接 (CONNECTING: 0)';
|
||
case WebSocket.OPEN:
|
||
return '已连接 (OPEN: 1)';
|
||
case WebSocket.CLOSING:
|
||
return '正在关闭 (CLOSING: 2)';
|
||
case WebSocket.CLOSED:
|
||
return '已关闭 (CLOSED: 3)';
|
||
default:
|
||
return `未知状态: ${state}`;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置心跳检测
|
||
*/
|
||
private setupPingInterval(): void {
|
||
this.clearTimers();
|
||
|
||
// 每30秒发送一次空消息以保持连接
|
||
this.pingInterval = window.setInterval(() => {
|
||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||
try {
|
||
// 发送空心跳消息
|
||
this.socket.send(JSON.stringify({ type: 'ping' }));
|
||
} catch (error) {
|
||
this.logger(`发送心跳包失败: ${error}`);
|
||
}
|
||
}
|
||
}, 30000);
|
||
}
|
||
|
||
/**
|
||
* 安排重连
|
||
*/
|
||
private scheduleReconnect(): void {
|
||
if (this.isReconnecting || this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
|
||
if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
|
||
this.logger(`达到最大重连尝试次数 (${this.MAX_RECONNECT_ATTEMPTS}),停止重连`);
|
||
this.emit(WebSocketEvent.ERROR, `连接失败:已尝试 ${this.MAX_RECONNECT_ATTEMPTS} 次重连,请检查网络或服务器状态后手动重连`);
|
||
}
|
||
return;
|
||
}
|
||
|
||
this.isReconnecting = true;
|
||
this.reconnectAttempts++;
|
||
|
||
// 使用指数退避策略和随机抖动
|
||
const baseDelay = this.RECONNECT_DELAY * Math.pow(1.5, this.reconnectAttempts - 1);
|
||
const jitter = Math.random() * 1000; // 添加最多1秒的随机抖动
|
||
const delay = Math.min(baseDelay + jitter, 30000); // 上限为30秒
|
||
|
||
this.logger(`尝试重新连接 (${this.reconnectAttempts}/${this.MAX_RECONNECT_ATTEMPTS}), 延迟 ${delay}ms`);
|
||
|
||
this.reconnectTimer = window.setTimeout(() => {
|
||
this.isReconnecting = false;
|
||
this.connect();
|
||
}, delay);
|
||
}
|
||
|
||
/**
|
||
* 清除所有定时器
|
||
*/
|
||
private clearTimers(): void {
|
||
if (this.pingInterval !== null) {
|
||
window.clearInterval(this.pingInterval);
|
||
this.pingInterval = null;
|
||
}
|
||
|
||
if (this.reconnectTimer !== null) {
|
||
window.clearTimeout(this.reconnectTimer);
|
||
this.reconnectTimer = null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 发送WebSocket请求
|
||
*/
|
||
private sendRequest(request: any): void {
|
||
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
|
||
this.logger('无法发送请求: WebSocket未连接');
|
||
throw new Error('WebSocket未连接');
|
||
}
|
||
|
||
try {
|
||
const requestStr = JSON.stringify(request);
|
||
this.socket.send(requestStr);
|
||
} catch (error) {
|
||
this.logger(`发送WebSocket请求失败: ${error}`);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取默认模型列表
|
||
*/
|
||
private getDefaultModels(): string[] {
|
||
return ['qwq:latest', 'deepseek-r1:32b', 'qwen2.5-coder:7b'];
|
||
}
|
||
|
||
/**
|
||
* 获取WebSocket关闭代码解释
|
||
*/
|
||
private getCloseEventReason(code: number): string {
|
||
const explanations: Record<number, string> = {
|
||
1000: '正常关闭',
|
||
1001: '端点离开',
|
||
1002: '协议错误',
|
||
1003: '无法接受数据',
|
||
1004: '保留',
|
||
1005: '未提供状态码',
|
||
1006: '异常关闭',
|
||
1007: '数据类型不一致',
|
||
1008: '违反策略',
|
||
1009: '消息太大',
|
||
1010: '必需的扩展缺失',
|
||
1011: '内部错误',
|
||
1012: '服务重启',
|
||
1013: '临时错误',
|
||
1014: '服务器超载',
|
||
1015: 'TLS握手失败'
|
||
};
|
||
|
||
return explanations[code] || `未知代码: ${code}`;
|
||
}
|
||
}
|
||
|
||
// 创建聊天状态管理
|
||
export const useChatStore = defineStore('chat', () => {
|
||
// WebSocket服务实例
|
||
const wsService = ref<WebSocketService | null>(null);
|
||
|
||
// 聊天状态
|
||
const chatState = reactive({
|
||
messages: [] as ChatMessage[],
|
||
loading: false,
|
||
connectionStatus: 'disconnected' as 'connecting' | 'connected' | 'disconnected' | 'error',
|
||
errorMessage: '',
|
||
availableModels: [] as string[],
|
||
currentModel: '',
|
||
loadingModels: false
|
||
});
|
||
|
||
// 初始化WebSocket服务
|
||
function initWebSocketService(apiUrl: string, apiKey: string) {
|
||
// 记录函数调用到VSCode日志
|
||
const logToVSCode = (message: string) => {
|
||
try {
|
||
if (window.vscodeApi) {
|
||
window.vscodeApi.postMessage({
|
||
type: 'log',
|
||
message: `[WebSocketInit] ${message}`
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('向VSCode发送日志失败:', error);
|
||
}
|
||
};
|
||
|
||
logToVSCode(`初始化WebSocket服务开始: ${apiUrl}`);
|
||
console.log(`[ChatStore] 初始化WebSocket服务开始: ${apiUrl}`);
|
||
|
||
try {
|
||
if (!apiUrl || !apiKey) {
|
||
const errorMsg = 'API URL或API Key为空,无法初始化WebSocket服务';
|
||
console.error('[ChatStore] ' + errorMsg);
|
||
logToVSCode(errorMsg);
|
||
chatState.errorMessage = 'API配置不完整,请先完成配置';
|
||
chatState.connectionStatus = 'error';
|
||
return;
|
||
}
|
||
|
||
// 如果已存在WebSocket服务,先断开连接
|
||
if (wsService.value) {
|
||
console.log('[ChatStore] 断开现有WebSocket连接');
|
||
logToVSCode('断开现有WebSocket连接');
|
||
wsService.value.disconnect();
|
||
}
|
||
|
||
// 创建新的WebSocket服务实例
|
||
console.log('[ChatStore] 创建新的WebSocket服务实例');
|
||
logToVSCode('创建新的WebSocket服务实例');
|
||
wsService.value = new WebSocketService(apiUrl, apiKey, (message) => {
|
||
// 确保日志同时发送到控制台和VSCode
|
||
console.log(`[WebSocketService] ${message}`);
|
||
|
||
if (window.vscodeApi) {
|
||
window.vscodeApi.postMessage({
|
||
type: 'log',
|
||
message: `[WebSocketService] ${message}`
|
||
});
|
||
}
|
||
});
|
||
|
||
// 设置事件监听
|
||
console.log('[ChatStore] 设置WebSocket事件监听');
|
||
logToVSCode('设置WebSocket事件监听');
|
||
setupWebSocketEvents();
|
||
|
||
// 连接WebSocket
|
||
console.log('[ChatStore] 开始连接WebSocket');
|
||
logToVSCode('开始连接WebSocket');
|
||
chatState.connectionStatus = 'connecting';
|
||
wsService.value.connect();
|
||
|
||
// 5秒后检查连接状态
|
||
setTimeout(() => {
|
||
if (!wsService.value) {
|
||
console.error('[ChatStore] WebSocket服务实例为null');
|
||
logToVSCode('WebSocket服务实例为null');
|
||
return;
|
||
}
|
||
|
||
if (!wsService.value.isConnected) {
|
||
console.error('[ChatStore] WebSocket连接超时');
|
||
logToVSCode('WebSocket连接超时');
|
||
chatState.errorMessage = '连接超时,请检查API配置是否正确';
|
||
chatState.connectionStatus = 'error';
|
||
} else {
|
||
console.log('[ChatStore] WebSocket连接成功');
|
||
logToVSCode('WebSocket连接成功');
|
||
}
|
||
}, 5000);
|
||
|
||
console.log('[ChatStore] 初始化WebSocket服务完成');
|
||
logToVSCode('初始化WebSocket服务完成');
|
||
} catch (error) {
|
||
console.error('[ChatStore] 初始化WebSocket服务异常:', error);
|
||
logToVSCode(`初始化WebSocket服务异常: ${error instanceof Error ? error.message : String(error)}`);
|
||
chatState.errorMessage = `初始化WebSocket失败: ${error instanceof Error ? error.message : String(error)}`;
|
||
chatState.connectionStatus = 'error';
|
||
}
|
||
}
|
||
|
||
// 设置WebSocket事件监听
|
||
function setupWebSocketEvents() {
|
||
if (!wsService.value) {
|
||
console.error('[ChatStore] setupWebSocketEvents失败: wsService为null');
|
||
return;
|
||
}
|
||
|
||
console.log('[ChatStore] 开始设置WebSocket事件监听...');
|
||
|
||
wsService.value.on(WebSocketEvent.CONNECTED, () => {
|
||
console.log('[ChatStore] 收到WebSocket CONNECTED事件');
|
||
chatState.connectionStatus = 'connected';
|
||
chatState.errorMessage = '';
|
||
});
|
||
|
||
wsService.value.on(WebSocketEvent.DISCONNECTED, () => {
|
||
console.log('[ChatStore] 收到WebSocket DISCONNECTED事件');
|
||
chatState.connectionStatus = 'disconnected';
|
||
});
|
||
|
||
wsService.value.on(WebSocketEvent.ERROR, (error: string) => {
|
||
console.error('[ChatStore] 收到WebSocket ERROR事件:', error);
|
||
chatState.errorMessage = error;
|
||
chatState.connectionStatus = 'error';
|
||
chatState.loading = false;
|
||
chatState.loadingModels = false;
|
||
});
|
||
|
||
wsService.value.on(WebSocketEvent.MESSAGE, (message: ChatMessage) => {
|
||
console.log('[ChatStore] 收到WebSocket MESSAGE事件');
|
||
// 查找是否已存在具有相同ID的消息
|
||
const existingIndex = chatState.messages.findIndex(m => m.id === message.id);
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有消息
|
||
chatState.messages[existingIndex] = message;
|
||
} else {
|
||
// 添加新消息
|
||
chatState.messages.push(message);
|
||
}
|
||
|
||
chatState.loading = false;
|
||
});
|
||
|
||
wsService.value.on(WebSocketEvent.MODEL_LIST, (models: string[]) => {
|
||
console.log('[ChatStore] 收到WebSocket MODEL_LIST事件:', models);
|
||
chatState.loadingModels = false;
|
||
|
||
if (Array.isArray(models) && models.length > 0) {
|
||
chatState.availableModels = models;
|
||
|
||
// 如果没有当前模型,设置第一个为当前模型
|
||
if (!chatState.currentModel && models.length > 0) {
|
||
chatState.currentModel = models[0];
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log('[ChatStore] WebSocket事件监听器设置完成');
|
||
}
|
||
|
||
// 发送消息
|
||
async function sendMessage(content: string) {
|
||
console.log('Store: 开始发送消息', { content, connectionStatus: chatState.connectionStatus });
|
||
|
||
if (!wsService.value || !wsService.value.isConnected) {
|
||
console.error('Store: WebSocket未连接,无法发送消息');
|
||
chatState.errorMessage = '未连接到AI服务,请先连接';
|
||
chatState.connectionStatus = 'error';
|
||
throw new Error('WebSocket未连接');
|
||
}
|
||
|
||
if (!content.trim() || chatState.loading) {
|
||
console.log('Store: 消息为空或正在加载中,不发送');
|
||
return;
|
||
}
|
||
|
||
// 确保有当前模型
|
||
if (!chatState.currentModel && chatState.availableModels.length > 0) {
|
||
console.log('Store: 自动选择模型', chatState.availableModels[0]);
|
||
chatState.currentModel = chatState.availableModels[0];
|
||
} else if (!chatState.currentModel) {
|
||
console.error('Store: 没有可用模型');
|
||
throw new Error('没有可用的AI模型');
|
||
}
|
||
|
||
console.log(`Store: 准备发送消息到WebSocket,使用模型: ${chatState.currentModel}`);
|
||
chatState.loading = true;
|
||
|
||
try {
|
||
// 记录开始时间,用于计算响应时间
|
||
const startTime = Date.now();
|
||
|
||
// 直接发送消息到WebSocket服务
|
||
const response = await wsService.value.sendChatMessage(content, chatState.currentModel);
|
||
|
||
// 计算响应时间
|
||
const responseTime = Date.now() - startTime;
|
||
console.log(`Store: 收到WebSocket响应,耗时: ${responseTime}ms`);
|
||
|
||
return response;
|
||
} catch (error) {
|
||
console.error('Store: 发送消息失败:', error);
|
||
chatState.errorMessage = `发送消息失败: ${error instanceof Error ? error.message : '未知错误'}`;
|
||
chatState.connectionStatus = 'error';
|
||
chatState.loading = false;
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 刷新模型列表
|
||
async function refreshModels() {
|
||
if (!wsService.value || !wsService.value.isConnected) {
|
||
return;
|
||
}
|
||
|
||
chatState.loadingModels = true;
|
||
|
||
try {
|
||
const models = await wsService.value.getModelList();
|
||
|
||
if (Array.isArray(models) && models.length > 0) {
|
||
chatState.availableModels = models;
|
||
}
|
||
|
||
chatState.loadingModels = false;
|
||
} catch (error) {
|
||
chatState.loadingModels = false;
|
||
}
|
||
}
|
||
|
||
// 设置当前模型
|
||
function setCurrentModel(model: string) {
|
||
chatState.currentModel = model;
|
||
}
|
||
|
||
// 清空消息
|
||
function clearMessages() {
|
||
chatState.messages = [];
|
||
}
|
||
|
||
// 手动重连
|
||
function reconnect() {
|
||
if (wsService.value) {
|
||
wsService.value.disconnect();
|
||
chatState.connectionStatus = 'connecting';
|
||
wsService.value.connect();
|
||
}
|
||
}
|
||
|
||
return {
|
||
chatState,
|
||
initWebSocketService,
|
||
sendMessage,
|
||
refreshModels,
|
||
setCurrentModel,
|
||
clearMessages,
|
||
reconnect
|
||
};
|
||
});
|