|
|
@@ -0,0 +1,609 @@
|
|
|
+import AgentApi, { type AgentChatModel } from "@/api/agent/Agent";
|
|
|
+import AgentWorkApi from "@/api/agent/AgentWorks";
|
|
|
+import { nextTick, onBeforeUnmount, onMounted, ref, type Ref } from "vue";
|
|
|
+import { ChatUtils } from "../utils/ChatUtils";
|
|
|
+import { ChatMessage as ChatMessageModel } from "../model/Message";
|
|
|
+import { useMessages, LocalMessageIdPool, mergeSystemMessages } from "./Messages";
|
|
|
+import { useToolCalls } from "./ToolCall";
|
|
|
+import { useChatStaticMessages } from "./StaticMessages";
|
|
|
+import { useChatContext, type ContextMemoryConfig, type ContextMemorySetting } from "./Context";
|
|
|
+import { useChatTools, type ChatToolsManager } from "./Tools";
|
|
|
+import type { ChatSessionManager } from "../composables/useChatSession";
|
|
|
+import type { SSE } from "sse.js";
|
|
|
+import type OpenAI from "openai";
|
|
|
+import { requireNotNull } from "@imengyu/imengyu-utils";
|
|
|
+
|
|
|
+export type ChatModelOptionValue = {
|
|
|
+ temperature: number;
|
|
|
+ top_p: number;
|
|
|
+ top_k: number;
|
|
|
+ presence_penalty: number;
|
|
|
+};
|
|
|
+
|
|
|
+export type ChatAttachmentStatus = 'uploading' | 'success' | 'error';
|
|
|
+export type ChatAttachmentType = 'image' | 'audio' | 'video' | 'text' | 'document' | 'unknown';
|
|
|
+
|
|
|
+export interface ChatAttachmentItem {
|
|
|
+ id?: number;
|
|
|
+ localId: string;
|
|
|
+ name: string;
|
|
|
+ size: number;
|
|
|
+ status: ChatAttachmentStatus;
|
|
|
+ path?: string;
|
|
|
+ url?: string;
|
|
|
+ type: ChatAttachmentType;
|
|
|
+ errorMessage?: string;
|
|
|
+ file: File;
|
|
|
+}
|
|
|
+export type ChatInterfaceManager = {
|
|
|
+ messages: Ref<ChatMessageModel[]>;
|
|
|
+ focusInput?: () => void;
|
|
|
+ setInputValue?: (value: string) => void;
|
|
|
+ scrollToBottom?: () => void;
|
|
|
+ stopMessageEditing?: () => void;
|
|
|
+ getAttachmentList?: () => Promise<ChatAttachmentItem[]>;
|
|
|
+};
|
|
|
+export interface ChatConfig {
|
|
|
+ /**
|
|
|
+ * 默认系统提示词
|
|
|
+ * @default ''
|
|
|
+ */
|
|
|
+ defaultSystemPrompt?: string;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上下文记忆设置
|
|
|
+ * @default 'short'
|
|
|
+ */
|
|
|
+ contextMemorySetting?: ContextMemorySetting;
|
|
|
+ /**
|
|
|
+ * 上下文记忆参数配置
|
|
|
+ */
|
|
|
+ contextMemoryConfig?: ContextMemoryConfig;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建欢迎消息
|
|
|
+ */
|
|
|
+ onBuildWelcome?: () => {
|
|
|
+ /**
|
|
|
+ * 欢迎消息
|
|
|
+ * @default ''
|
|
|
+ */
|
|
|
+ welcomeMessage?: string;
|
|
|
+ /**
|
|
|
+ * 欢迎动作
|
|
|
+ * @default []
|
|
|
+ */
|
|
|
+ welcomeActions?: string[];
|
|
|
+ };
|
|
|
+ /**
|
|
|
+ * 获取发送选项
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ onGetSendOptions: () => ChatSendOptions;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 新对话
|
|
|
+ * @param isNewChat - 是否是新对话
|
|
|
+ */
|
|
|
+ onNewChat?: (isNewChat: boolean) => void;
|
|
|
+ /**
|
|
|
+ * 发送消息前
|
|
|
+ * @param message - 消息
|
|
|
+ */
|
|
|
+ onBeforeSend?: (userMessages: ChatMessageModel[], streamOptions: any) => void;
|
|
|
+ /**
|
|
|
+ * 对话消息结束
|
|
|
+ * @param message - 消息
|
|
|
+ * @param currentUserMessageIds - 当前用户消息ID列表
|
|
|
+ */
|
|
|
+ onAiMessageFinish?: (message: ChatMessageModel, currentUserMessageIds: number[], finishReason: string) => void;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否在ai回答后自动生成可能的问题
|
|
|
+ * @default false
|
|
|
+ */
|
|
|
+ autoGeneratePossibleQuestions?: boolean;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化工具
|
|
|
+ * @param toolsManager - 工具管理器
|
|
|
+ * @returns
|
|
|
+ */
|
|
|
+ onInitTools?: (toolsManager: ChatToolsManager) => void;
|
|
|
+}
|
|
|
+
|
|
|
+export type ChatSendOptions = {
|
|
|
+ enableSearch: boolean;
|
|
|
+ enableThinking: boolean;
|
|
|
+ model: string;
|
|
|
+ modelInfo: AgentChatModel ;
|
|
|
+ customSystemPrompt?: string|null;
|
|
|
+ chatOptions: ChatModelOptionValue;
|
|
|
+};
|
|
|
+
|
|
|
+export function useChat(options: {
|
|
|
+ config: ChatConfig;
|
|
|
+ interfaceManager: ChatInterfaceManager;
|
|
|
+ sessionManager: ChatSessionManager;
|
|
|
+}) {
|
|
|
+
|
|
|
+ const isLoading = ref(false);
|
|
|
+ const config = options.config;
|
|
|
+ let streamingAiMessageId: number | null = null;
|
|
|
+ let eventSource: SSE | null = null;
|
|
|
+ let startTime = 0;
|
|
|
+
|
|
|
+ const messages = options.interfaceManager.messages;
|
|
|
+ const referenceMessage = ref('');
|
|
|
+ const messagesManager = useMessages(messages);
|
|
|
+ const toolsManager = useChatTools(); options.config?.onInitTools?.(toolsManager);
|
|
|
+ const staticMessagesManager = useChatStaticMessages(config);
|
|
|
+ const sessionManager = options.sessionManager;
|
|
|
+ const interfaceManager = options.interfaceManager;
|
|
|
+ const contextManager = useChatContext({
|
|
|
+ message: messages,
|
|
|
+ contextMemorySetting: config.contextMemorySetting,
|
|
|
+ contextMemoryConfig: config.contextMemoryConfig,
|
|
|
+ sessionManager: sessionManager,
|
|
|
+ });
|
|
|
+ const toolCallsManager = useToolCalls(messagesManager, toolsManager, interfaceManager, sessionManager);
|
|
|
+
|
|
|
+ //新建会话消息构建
|
|
|
+ sessionManager.events.on('session-newed', () => {
|
|
|
+ messages.value = [staticMessagesManager.getWelcomeMessage()];
|
|
|
+ config.onNewChat?.(true);
|
|
|
+ contextManager.estimateTokenUseage(config.onGetSendOptions());
|
|
|
+ });
|
|
|
+ //加载会话消息构建
|
|
|
+ sessionManager.events.on('session-loaded', (session) => {
|
|
|
+ config.onNewChat?.(false);
|
|
|
+ contextManager.estimateTokenUseage(config.onGetSendOptions());
|
|
|
+ });
|
|
|
+
|
|
|
+ async function buildStreamOptions(
|
|
|
+ sendOptions: ChatSendOptions,
|
|
|
+ currentUserMessageIds: number[],
|
|
|
+ limitHistoryStartAtMessageId: number|undefined,
|
|
|
+ openAiTools: OpenAI.Chat.Completions.ChatCompletionTool[],
|
|
|
+ ) {
|
|
|
+ const finalMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建系统消息
|
|
|
+ */
|
|
|
+ const systemMessages = [] as OpenAI.Chat.ChatCompletionSystemMessageParam[];
|
|
|
+ if (sendOptions?.customSystemPrompt)
|
|
|
+ systemMessages.push({ role: 'system', content: sendOptions.customSystemPrompt.trim() });
|
|
|
+ else if (config.defaultSystemPrompt)
|
|
|
+ systemMessages.push({ role: 'system', content: config.defaultSystemPrompt.trim() });
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建用户消息
|
|
|
+ */
|
|
|
+ const {
|
|
|
+ userMessages,
|
|
|
+ systemMessages: contextSystemMessages
|
|
|
+ } = await contextManager.convertMessagesToAi(sendOptions, currentUserMessageIds, limitHistoryStartAtMessageId);
|
|
|
+
|
|
|
+ const referenceSystemMessages: OpenAI.Chat.ChatCompletionSystemMessageParam[] = [];
|
|
|
+ if (referenceMessage.value)
|
|
|
+ referenceSystemMessages.push({ role: 'system', content: `[用户引用消息] ${referenceMessage.value.trim()}` });
|
|
|
+
|
|
|
+ finalMessages.push(
|
|
|
+ //合并系统消息
|
|
|
+ ...mergeSystemMessages([
|
|
|
+ ...systemMessages,
|
|
|
+ ...referenceSystemMessages,
|
|
|
+ ...(contextSystemMessages) as OpenAI.Chat.ChatCompletionSystemMessageParam[],
|
|
|
+ ]),
|
|
|
+ ...userMessages,
|
|
|
+ );
|
|
|
+
|
|
|
+ if (finalMessages.length === 0)
|
|
|
+ throw new Error('消息不能为空');
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建额外选项
|
|
|
+ */
|
|
|
+ const extraOptions = {
|
|
|
+ enable_thinking: sendOptions.enableThinking ?? undefined,
|
|
|
+ enable_search: sendOptions.enableSearch ?? undefined,
|
|
|
+ };
|
|
|
+ const chartOptions: OpenAI.Chat.Completions.ChatCompletionCreateParams = {
|
|
|
+ stream: true,
|
|
|
+ model: sendOptions.model,
|
|
|
+ ...sendOptions.chatOptions || {},
|
|
|
+ messages: finalMessages,
|
|
|
+ tools: openAiTools.length ? openAiTools : undefined,
|
|
|
+ tool_choice: openAiTools.length ? 'auto' : undefined,
|
|
|
+ };
|
|
|
+ return {
|
|
|
+ ...chartOptions,
|
|
|
+ ...extraOptions,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对话逻辑
|
|
|
+ */
|
|
|
+ async function startStreamForUserMessages(
|
|
|
+ currentUserMessageIds: number[],
|
|
|
+ aiMessageId?: number,
|
|
|
+ limitHistoryStartAtMessageId?: number
|
|
|
+ ) {
|
|
|
+ const sendOptions = config.onGetSendOptions();
|
|
|
+ if (!sendOptions.model)
|
|
|
+ throw new Error('模型不能为空');
|
|
|
+
|
|
|
+ let aiMessage: ChatMessageModel;
|
|
|
+ if (!aiMessageId) {
|
|
|
+ aiMessageId = LocalMessageIdPool.getNextId();
|
|
|
+ aiMessage = messagesManager.addMessage(ChatMessageModel.createAssistant("", aiMessageId, "loading"));
|
|
|
+ } else {
|
|
|
+ aiMessage = requireNotNull(messagesManager.findMessage(aiMessageId));
|
|
|
+ }
|
|
|
+ aiMessage.parentId = currentUserMessageIds[0];
|
|
|
+ aiMessage.state = "loading";
|
|
|
+ aiMessage.name = sendOptions.modelInfo.name ?? '';
|
|
|
+
|
|
|
+ //持久化消息
|
|
|
+ if (!aiMessage.isPersisted)
|
|
|
+ await sessionManager.persistMessages([aiMessage]);
|
|
|
+ await nextTick();
|
|
|
+ await interfaceManager.scrollToBottom?.();
|
|
|
+
|
|
|
+ streamingAiMessageId = aiMessage.id;
|
|
|
+ startTime = Date.now();
|
|
|
+
|
|
|
+ const toolCallBuffers = new Map<number, { id?: string; name?: string; arguments: string }>();
|
|
|
+ let streamOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming;
|
|
|
+ try {
|
|
|
+ // 用于拼接 tool_calls 的 arguments(流式会分片)
|
|
|
+ streamOptions = await buildStreamOptions(
|
|
|
+ sendOptions,
|
|
|
+ currentUserMessageIds,
|
|
|
+ limitHistoryStartAtMessageId,
|
|
|
+ toolsManager.openAiTools.value
|
|
|
+ );
|
|
|
+
|
|
|
+ config.onBeforeSend?.(messages.value.filter(m => currentUserMessageIds.includes(m.id)), streamOptions);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("构建流式选项失败:", error);
|
|
|
+ failedAndSetInfo("处理消息失败", error);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ eventSource = AgentApi.chatStream(streamOptions as OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming);
|
|
|
+ eventSource.onmessage = async (event) => {
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(event.data);
|
|
|
+
|
|
|
+ // 后端通过 SSE 下发错误(含超时等)
|
|
|
+ if (data?.error) {
|
|
|
+ eventSource?.close();
|
|
|
+ isLoading.value = false;
|
|
|
+ streamingAiMessageId = null;
|
|
|
+
|
|
|
+ const errorType = String(data.errorType || '');
|
|
|
+ const errorText = typeof data.error === 'string' ? data.error : '请求失败';
|
|
|
+ const title =
|
|
|
+ errorType === 'timeout_connect' || errorType === 'timeout_idle'
|
|
|
+ ? '请求超时'
|
|
|
+ : '请求错误';
|
|
|
+ aiMessage.content = errorType.startsWith('timeout')
|
|
|
+ ? '请求超时,请检查模型地址/网络后重试。'
|
|
|
+ : '失败,请稍后重试。';
|
|
|
+ aiMessage.setError(title, errorText);
|
|
|
+ await sessionManager.persistMessages([aiMessage]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!data.chunk) return;
|
|
|
+ const chunk = data.chunk as OpenAI.Chat.ChatCompletionChunk;
|
|
|
+ const choice = chunk.choices[0];
|
|
|
+ if (!choice) return;
|
|
|
+
|
|
|
+ // 结束处理
|
|
|
+ if (choice.finish_reason) {
|
|
|
+ //结束状态
|
|
|
+ isLoading.value = false;
|
|
|
+ streamingAiMessageId = null;
|
|
|
+
|
|
|
+ const isAiMessageEmpty = aiMessage.content === '' && aiMessage.reasoningContent === '';
|
|
|
+ const finishReason = choice.finish_reason;
|
|
|
+ aiMessage.state = "success";
|
|
|
+ aiMessage.replyTime = Date.now() - startTime;
|
|
|
+
|
|
|
+ switch (finishReason) {
|
|
|
+ case "tool_calls": {
|
|
|
+ // 如果内容与思考内容都为空,则说明ai进行了工具调用
|
|
|
+ if (isAiMessageEmpty) {
|
|
|
+ aiMessage.content = '准备执行工具调用...';
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //持久化消息
|
|
|
+ sessionManager.persistAssistantAfterStream(aiMessage, currentUserMessageIds);
|
|
|
+ config.onAiMessageFinish?.(aiMessage, currentUserMessageIds, finishReason);
|
|
|
+ contextManager.estimateTokenUseage(sendOptions);
|
|
|
+
|
|
|
+ switch (finishReason) {
|
|
|
+ case "stop": {
|
|
|
+ //如果需要自动生成可能的问题,则生成可能的问题
|
|
|
+ if (config.autoGeneratePossibleQuestions && !isAiMessageEmpty) {
|
|
|
+ const userMessage = messages.value.find(m => currentUserMessageIds.includes(m.id))?.content;
|
|
|
+ const possibleQuestions = await AgentWorkApi.autoGeneratePossibleQuestions(userMessage ?? '', aiMessage.content);
|
|
|
+ aiMessage.actions = possibleQuestions;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case "tool_calls": {
|
|
|
+ //工具调用处理
|
|
|
+
|
|
|
+ const toolCalls: OpenAI.Chat.ChatCompletionMessageToolCall[] = [...toolCallBuffers.entries()]
|
|
|
+ .sort((a, b) => a[0] - b[0])
|
|
|
+ .map(([, b]) => ({
|
|
|
+ id: b.id || `call_${Math.random().toString(16).slice(2)}`,
|
|
|
+ type: "function",
|
|
|
+ function: {
|
|
|
+ name: b.name || "unknown",
|
|
|
+ arguments: b.arguments || "",
|
|
|
+ },
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 把 tool_calls 挂在 assistant 消息上,便于下一轮历史回放(如果后端严格校验)
|
|
|
+ aiMessage.toolCalls = toolCalls;
|
|
|
+
|
|
|
+ // 执行工具 -> 追加 tool 消息 -> 再发起一轮 stream 获取最终回答
|
|
|
+ await toolCallsManager.executeToolCalls(toolCalls, aiMessage.parentId);
|
|
|
+
|
|
|
+ // 继续一轮对话(同一批 user messages)
|
|
|
+ isLoading.value = true;
|
|
|
+ await startStreamForUserMessages(currentUserMessageIds);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 结束后关闭
|
|
|
+ if (data.finished) {
|
|
|
+ closeStream();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // tool_calls 拼接
|
|
|
+ const delta: any = choice.delta as any;
|
|
|
+ if (Array.isArray(delta?.tool_calls)) {
|
|
|
+ for (const tc of delta.tool_calls as any[]) {
|
|
|
+ const index = tc.index ?? 0;
|
|
|
+ const buf = toolCallBuffers.get(index) ?? { arguments: "" };
|
|
|
+ if (tc.id) buf.id = tc.id;
|
|
|
+ if (tc.function?.name) buf.name = tc.function.name;
|
|
|
+ if (typeof tc.function?.arguments === "string") buf.arguments += tc.function.arguments;
|
|
|
+ toolCallBuffers.set(index, buf);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 内容/思考内容
|
|
|
+ if (choice?.delta?.content) {
|
|
|
+ aiMessage.content += choice.delta.content;
|
|
|
+ //如果思考内容不为空,则计算推理时间
|
|
|
+ if (aiMessage.reasoningContent && aiMessage.reasoningTime === 0)
|
|
|
+ aiMessage.reasoningTime = Date.now() - startTime;
|
|
|
+ } else if (delta?.reasoning_content) {
|
|
|
+ aiMessage.reasoningContent += delta.reasoning_content;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ // 解析失败不应打断整个对话
|
|
|
+ console.error("解析SSE数据失败:", error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ eventSource.onerror = (error) => {
|
|
|
+ console.error("SSE连接错误:", error);
|
|
|
+ eventSource?.close();
|
|
|
+
|
|
|
+ isLoading.value = false;
|
|
|
+ streamingAiMessageId = null;
|
|
|
+ aiMessage.content = "失败,请稍后重试。";
|
|
|
+ aiMessage.setError("请求错误", ChatUtils.formatError(error));
|
|
|
+ sessionManager.persistMessages([aiMessage]);
|
|
|
+ };
|
|
|
+
|
|
|
+ eventSource.stream();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送消息
|
|
|
+ * @param inputMessage - 输入消息
|
|
|
+ */
|
|
|
+ async function send(inputMessage: string) {
|
|
|
+ if (isLoading.value) return;
|
|
|
+
|
|
|
+ const userMessage = inputMessage.trim();
|
|
|
+ if (!userMessage) {
|
|
|
+ messagesManager.addMessage(staticMessagesManager.getEmptyMessage());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ interfaceManager.stopMessageEditing?.();
|
|
|
+
|
|
|
+ const newMessages = [
|
|
|
+ messagesManager.addMessage(ChatMessageModel.createUser(userMessage, LocalMessageIdPool.getNextId()))
|
|
|
+ ];
|
|
|
+ const attachmentItems = await interfaceManager.getAttachmentList?.() || [];
|
|
|
+ for (const item of attachmentItems) {
|
|
|
+ const m = await ChatMessageModel.createAttachment(item, LocalMessageIdPool.getNextId());
|
|
|
+ m.name = '你';
|
|
|
+ newMessages.push(messagesManager.addMessage(m));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存用户消息与会话
|
|
|
+ try {
|
|
|
+ await sessionManager.saveUserMessageAndPersistSession(newMessages);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ newMessages.forEach((m) => {
|
|
|
+ m.setError("保存会话失败,请稍后重试。", ChatUtils.formatError(error));
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ await nextTick();
|
|
|
+
|
|
|
+ try {
|
|
|
+ isLoading.value = true;
|
|
|
+ await startStreamForUserMessages(newMessages.map((m) => m.id));
|
|
|
+ } catch (error) {
|
|
|
+ console.error("失败:", error);
|
|
|
+ isLoading.value = false;
|
|
|
+ streamingAiMessageId = null;
|
|
|
+ const message = messages.value[messages.value.length - 1];
|
|
|
+ if (message && !message.isUser) {
|
|
|
+ message.content = "失败,请稍后重试。";
|
|
|
+ message.setError("请求错误", ChatUtils.formatError(error));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 停止对话
|
|
|
+ */
|
|
|
+ function stop() {
|
|
|
+ failedAndSetInfo("手动停止对话", null);
|
|
|
+ closeStream();
|
|
|
+ isLoading.value = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ function failedAndSetInfo(message: string, error: any) {
|
|
|
+ isLoading.value = false;
|
|
|
+ if (streamingAiMessageId) {
|
|
|
+ const aiMessage = messagesManager.findMessage(streamingAiMessageId);
|
|
|
+ if (aiMessage) {
|
|
|
+ aiMessage.content = message;
|
|
|
+ aiMessage.setError(message, ChatUtils.formatError(error));
|
|
|
+ sessionManager.persistMessages([aiMessage]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ streamingAiMessageId = null;
|
|
|
+ }
|
|
|
+ function closeStream() {
|
|
|
+ if (eventSource) {
|
|
|
+ eventSource.close();
|
|
|
+ eventSource = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ function prefindUserMessageIds(messageId: number) {
|
|
|
+ let limitHistoryStartAtMessageId: number | undefined;
|
|
|
+ const currentUserMessageIds = [] as number[];
|
|
|
+ for (let i = messages.value.findIndex(m => m.id === messageId); i >= 0; i--) {
|
|
|
+ const message = messages.value[i];
|
|
|
+ if (message.isUser)
|
|
|
+ currentUserMessageIds.push(message.id);
|
|
|
+ }
|
|
|
+ return {
|
|
|
+ currentUserMessageIds,
|
|
|
+ limitHistoryStartAtMessageId,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户编辑消息,重新发送
|
|
|
+ * @param messageId
|
|
|
+ */
|
|
|
+ async function editMessage(messageId: number, newContent: string) {
|
|
|
+ const message = messagesManager.findMessage(messageId);
|
|
|
+ if (!message)
|
|
|
+ return;
|
|
|
+
|
|
|
+ message.content = newContent;
|
|
|
+ if (message.isPersisted)
|
|
|
+ await sessionManager.persistMessages([message]);
|
|
|
+
|
|
|
+ //向上查找,找到本轮次所有用户消息ID
|
|
|
+ const { currentUserMessageIds, limitHistoryStartAtMessageId } = prefindUserMessageIds(messageId);
|
|
|
+ if (message.replyItemId) {
|
|
|
+ //如果已有回复,则清空回复内容
|
|
|
+ const replyMessage = messagesManager.findMessage(message.replyItemId);
|
|
|
+ if (replyMessage) {
|
|
|
+ replyMessage.content = '';
|
|
|
+ replyMessage.reasoningContent = '';
|
|
|
+ replyMessage.resetError();
|
|
|
+ }
|
|
|
+ await startStreamForUserMessages(currentUserMessageIds, message.replyItemId, limitHistoryStartAtMessageId);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ await startStreamForUserMessages(currentUserMessageIds, undefined, limitHistoryStartAtMessageId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重新生成AI消息
|
|
|
+ * @param messageId
|
|
|
+ */
|
|
|
+ async function regenerateMessage(messageId: number) {
|
|
|
+ const message = messagesManager.findMessage(messageId);
|
|
|
+ if (!message)
|
|
|
+ return;
|
|
|
+ if (!message.isAssistant)
|
|
|
+ return;
|
|
|
+ message.content = '';
|
|
|
+ message.reasoningContent = '';
|
|
|
+ message.resetError();
|
|
|
+ //向上查找,找到本轮次所有用户消息ID
|
|
|
+ const { currentUserMessageIds, limitHistoryStartAtMessageId } = prefindUserMessageIds(messageId);
|
|
|
+ await startStreamForUserMessages(currentUserMessageIds, message.id, limitHistoryStartAtMessageId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置参考消息并等待用户输入
|
|
|
+ * @param message - 参考消息
|
|
|
+ */
|
|
|
+ async function setReferenceAndWaitUser(message: string) {
|
|
|
+ referenceMessage.value = message;
|
|
|
+ interfaceManager.focusInput?.();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清空参考消息
|
|
|
+ */
|
|
|
+ function clearReferenceMessage() {
|
|
|
+ referenceMessage.value = '';
|
|
|
+ }
|
|
|
+
|
|
|
+ onMounted(async () => {
|
|
|
+ if (sessionManager.enableSession) {
|
|
|
+ await sessionManager.loadSessions();
|
|
|
+ sessionManager.onSelectNew();
|
|
|
+ } else {
|
|
|
+ sessionManager.onSelectLocal();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ onBeforeUnmount(() => {
|
|
|
+ stop();
|
|
|
+ });
|
|
|
+
|
|
|
+ return {
|
|
|
+ send,
|
|
|
+ stop,
|
|
|
+ editMessage,
|
|
|
+ regenerateMessage,
|
|
|
+ setReferenceAndWaitUser,
|
|
|
+ clearReferenceMessage,
|
|
|
+ config,
|
|
|
+ isLoading,
|
|
|
+ messages,
|
|
|
+ referenceMessage,
|
|
|
+ staticMessagesManager,
|
|
|
+ toolsManager,
|
|
|
+ messagesManager,
|
|
|
+ sessionManager,
|
|
|
+ interfaceManager,
|
|
|
+ toolCallsManager,
|
|
|
+ contextManager,
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+export type ChatManager = ReturnType<typeof useChat>;
|