Merge branch 'main' into main

This commit is contained in:
glay
2025-01-22 14:28:55 +08:00
committed by GitHub
31 changed files with 2863 additions and 301 deletions

View File

@@ -1,4 +1,9 @@
import { getMessageTextContent, trimTopic } from "../utils";
import {
getMessageTextContent,
isDalle3,
safeLocalStorage,
trimTopic,
} from "../utils";
import { indexedDBStorage } from "@/app/utils/indexedDB-storage";
import { nanoid } from "nanoid";
@@ -14,14 +19,15 @@ import {
DEFAULT_INPUT_TEMPLATE,
DEFAULT_MODELS,
DEFAULT_SYSTEM_TEMPLATE,
GEMINI_SUMMARIZE_MODEL,
KnowledgeCutOffDate,
MCP_SYSTEM_TEMPLATE,
MCP_TOOLS_TEMPLATE,
ServiceProvider,
StoreKey,
SUMMARIZE_MODEL,
GEMINI_SUMMARIZE_MODEL,
ServiceProvider,
} from "../constant";
import Locale, { getLang } from "../locales";
import { isDalle3, safeLocalStorage } from "../utils";
import { prettyObject } from "../utils/format";
import { createPersistStore } from "../utils/store";
import { estimateTokenLength } from "../utils/token";
@@ -29,6 +35,8 @@ import { ModelConfig, ModelType, useAppConfig } from "./config";
import { useAccessStore } from "./access";
import { collectModelsWithDefaultModel } from "../utils/model";
import { createEmptyMask, Mask } from "./mask";
import { executeMcpAction, getAllTools } from "../mcp/actions";
import { extractMcpJson, isMcpJson } from "../mcp/utils";
const localStorage = safeLocalStorage();
@@ -53,6 +61,7 @@ export type ChatMessage = RequestMessage & {
model?: ModelType;
tools?: ChatMessageTool[];
audio_url?: string;
isMcpResponse?: boolean;
};
export function createMessage(override: Partial<ChatMessage>): ChatMessage {
@@ -189,6 +198,27 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) {
return output;
}
async function getMcpSystemPrompt(): Promise<string> {
const tools = await getAllTools();
let toolsStr = "";
tools.forEach((i) => {
// error client has no tools
if (!i.tools) return;
toolsStr += MCP_TOOLS_TEMPLATE.replace(
"{{ clientId }}",
i.clientId,
).replace(
"{{ tools }}",
i.tools.tools.map((p: object) => JSON.stringify(p, null, 2)).join("\n"),
);
});
return MCP_SYSTEM_TEMPLATE.replace("{{ MCP_TOOLS }}", toolsStr);
}
const DEFAULT_CHAT_STATE = {
sessions: [createEmptySession()],
currentSessionIndex: 0,
@@ -362,24 +392,30 @@ export const useChatStore = createPersistStore(
session.messages = session.messages.concat();
session.lastUpdate = Date.now();
});
get().updateStat(message, targetSession);
get().checkMcpJson(message);
get().summarizeSession(false, targetSession);
},
async onUserInput(content: string, attachImages?: string[]) {
async onUserInput(
content: string,
attachImages?: string[],
isMcpResponse?: boolean,
) {
const session = get().currentSession();
const modelConfig = session.mask.modelConfig;
const userContent = fillTemplateWith(content, modelConfig);
console.log("[User Input] after template: ", userContent);
// MCP Response no need to fill template
let mContent: string | MultimodalContent[] = isMcpResponse
? content
: fillTemplateWith(content, modelConfig);
let mContent: string | MultimodalContent[] = userContent;
if (attachImages && attachImages.length > 0) {
if (!isMcpResponse && attachImages && attachImages.length > 0) {
mContent = [
...(userContent
? [{ type: "text" as const, text: userContent }]
: []),
...(content ? [{ type: "text" as const, text: content }] : []),
...attachImages.map((url) => ({
type: "image_url" as const,
image_url: { url },
@@ -390,6 +426,7 @@ export const useChatStore = createPersistStore(
let userMessage: ChatMessage = createMessage({
role: "user",
content: mContent,
isMcpResponse,
});
const botMessage: ChatMessage = createMessage({
@@ -399,7 +436,7 @@ export const useChatStore = createPersistStore(
});
// get recent messages
const recentMessages = get().getMessagesWithMemory();
const recentMessages = await get().getMessagesWithMemory();
const sendMessages = recentMessages.concat(userMessage);
const messageIndex = session.messages.length + 1;
@@ -429,7 +466,7 @@ export const useChatStore = createPersistStore(
session.messages = session.messages.concat();
});
},
onFinish(message) {
async onFinish(message) {
botMessage.streaming = false;
if (message) {
botMessage.content = message;
@@ -498,7 +535,7 @@ export const useChatStore = createPersistStore(
}
},
getMessagesWithMemory() {
async getMessagesWithMemory() {
const session = get().currentSession();
const modelConfig = session.mask.modelConfig;
const clearContextIndex = session.clearContextIndex ?? 0;
@@ -514,18 +551,26 @@ export const useChatStore = createPersistStore(
(session.mask.modelConfig.model.startsWith("gpt-") ||
session.mask.modelConfig.model.startsWith("chatgpt-"));
const mcpSystemPrompt = await getMcpSystemPrompt();
var systemPrompts: ChatMessage[] = [];
systemPrompts = shouldInjectSystemPrompts
? [
createMessage({
role: "system",
content: fillTemplateWith("", {
...modelConfig,
template: DEFAULT_SYSTEM_TEMPLATE,
}),
content:
fillTemplateWith("", {
...modelConfig,
template: DEFAULT_SYSTEM_TEMPLATE,
}) + mcpSystemPrompt,
}),
]
: [];
: [
createMessage({
role: "system",
content: mcpSystemPrompt,
}),
];
if (shouldInjectSystemPrompts) {
console.log(
"[Global System Prompt] ",
@@ -768,6 +813,36 @@ export const useChatStore = createPersistStore(
lastInput,
});
},
/** check if the message contains MCP JSON and execute the MCP action */
checkMcpJson(message: ChatMessage) {
const content = getMessageTextContent(message);
if (isMcpJson(content)) {
try {
const mcpRequest = extractMcpJson(content);
if (mcpRequest) {
console.debug("[MCP Request]", mcpRequest);
executeMcpAction(mcpRequest.clientId, mcpRequest.mcp)
.then((result) => {
console.log("[MCP Response]", result);
const mcpResponse =
typeof result === "object"
? JSON.stringify(result)
: String(result);
get().onUserInput(
`\`\`\`json:mcp-response:${mcpRequest.clientId}\n${mcpResponse}\n\`\`\``,
[],
true,
);
})
.catch((error) => showToast("MCP execution failed", error));
}
} catch (error) {
console.error("[Check MCP JSON]", error);
}
}
},
};
return methods;