From 4c3d3fd9ca06b16f26ed846418373792da18d21c Mon Sep 17 00:00:00 2001 From: Crokily Date: Fri, 26 Sep 2025 15:13:39 +1000 Subject: [PATCH 1/8] chore: add @ai-sdk/openai-compatible dependency and update pnpm-lock.yaml --- package.json | 1 + pnpm-lock.yaml | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/package.json b/package.json index 65079af..b5e69d9 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "@ai-sdk/google": "^2.0.14", "@ai-sdk/openai": "^2.0.32", + "@ai-sdk/openai-compatible": "^1.0.19", "@ai-sdk/react": "^2.0.48", "@assistant-ui/react": "^0.11.14", "@assistant-ui/react-ai-sdk": "^1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d873bc..30619f4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,9 @@ importers: "@ai-sdk/openai": specifier: ^2.0.32 version: 2.0.32(zod@4.1.11) + "@ai-sdk/openai-compatible": + specifier: ^1.0.19 + version: 1.0.19(zod@4.1.11) "@ai-sdk/react": specifier: ^2.0.48 version: 2.0.48(react@19.1.1)(zod@4.1.11) @@ -208,6 +211,15 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 + "@ai-sdk/openai-compatible@1.0.19": + resolution: + { + integrity: sha512-hnsqPCCSNKgpZRNDOAIXZs7OcUDM4ut5ggWxj2sjB4tNL/aBn/xrM7pJkqu+WuPowyrE60wPVSlw0LvtXAlMXQ==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + "@ai-sdk/openai@2.0.32": resolution: { @@ -217,6 +229,15 @@ packages: peerDependencies: zod: ^3.25.76 || ^4 + "@ai-sdk/provider-utils@3.0.10": + resolution: + { + integrity: sha512-T1gZ76gEIwffep6MWI0QNy9jgoybUHE7TRaHB5k54K8mF91ciGFlbtCGxDYhMH3nCRergKwYFIDeFF0hJSIQHQ==, + } + engines: { node: ">=18" } + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + "@ai-sdk/provider-utils@3.0.9": resolution: { @@ -8409,12 +8430,25 @@ snapshots: "@ai-sdk/provider-utils": 3.0.9(zod@4.1.11) zod: 4.1.11 + "@ai-sdk/openai-compatible@1.0.19(zod@4.1.11)": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@ai-sdk/provider-utils": 3.0.10(zod@4.1.11) + zod: 4.1.11 + "@ai-sdk/openai@2.0.32(zod@4.1.11)": dependencies: "@ai-sdk/provider": 2.0.0 "@ai-sdk/provider-utils": 3.0.9(zod@4.1.11) zod: 4.1.11 + "@ai-sdk/provider-utils@3.0.10(zod@4.1.11)": + dependencies: + "@ai-sdk/provider": 2.0.0 + "@standard-schema/spec": 1.0.0 + eventsource-parser: 3.0.6 + zod: 4.1.11 + "@ai-sdk/provider-utils@3.0.9(zod@4.1.11)": dependencies: "@ai-sdk/provider": 2.0.0 From a5ed1a892bef293e7dc9842d4a126271fde1ab40 Mon Sep 17 00:00:00 2001 From: Crokily Date: Fri, 26 Sep 2025 15:53:20 +1000 Subject: [PATCH 2/8] feat: add support for 'intern' provider in chat API and integrate OpenAI-compatible model --- app/api/chat/route.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 820c88f..620e97d 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,5 +1,6 @@ import { createOpenAI } from "@ai-sdk/openai"; import { createGoogleGenerativeAI } from "@ai-sdk/google"; +import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { streamText, UIMessage, convertToModelMessages } from "ai"; // Allow streaming responses up to 30 seconds @@ -22,12 +23,12 @@ export async function POST(req: Request) { content?: string; slug?: string; }; - provider?: "openai" | "gemini"; + provider?: "openai" | "gemini" | "intern"; apiKey?: string; } = await req.json(); - // Check if API key is provided - if (!apiKey || apiKey.trim() === "") { + // Check if API key is provided (not required for intern provider) + if (provider !== "intern" && (!apiKey || apiKey.trim() === "")) { return Response.json( { error: @@ -68,6 +69,13 @@ export async function POST(req: Request) { apiKey: apiKey, }); model = customGoogle("models/gemini-2.0-flash"); + } else if (provider === "intern") { + const intern = createOpenAICompatible({ + name: "intern", + baseURL: "https://chat.intern-ai.org.cn/api/v1/", + apiKey: process.env.INTERN_KEY, + }); + model = intern("intern-s1"); } else { // Default to OpenAI const customOpenAI = createOpenAI({ From df44a20f11da9b99cf02e318a94f60df3a4bfa33 Mon Sep 17 00:00:00 2001 From: Crokily Date: Fri, 26 Sep 2025 16:20:40 +1000 Subject: [PATCH 3/8] feat: enhance support for 'intern' provider in settings and error handling --- app/components/DocsAssistant.tsx | 29 ++++++++++++------- .../assistant-ui/SettingsDialog.tsx | 15 +++++++++- app/components/assistant-ui/thread.tsx | 16 ++++++++-- app/hooks/useAssistantSettings.tsx | 9 ++++-- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/app/components/DocsAssistant.tsx b/app/components/DocsAssistant.tsx index 8e25a62..92b76fb 100644 --- a/app/components/DocsAssistant.tsx +++ b/app/components/DocsAssistant.tsx @@ -53,7 +53,9 @@ function DocsAssistantInner({ pageContext }: DocsAssistantProps) { const currentApiKey = currentProvider === "openai" ? openaiApiKeyRef.current - : geminiApiKeyRef.current; + : currentProvider === "gemini" + ? geminiApiKeyRef.current + : ""; // intern provider doesn't need API key console.log("[DocsAssistant] useChat body function called with:", { provider: currentProvider, @@ -118,9 +120,14 @@ interface AssistantErrorState { function deriveAssistantError( err: unknown, - provider: "openai" | "gemini", + provider: "openai" | "gemini" | "intern", ): AssistantErrorState { - const providerLabel = provider === "gemini" ? "Google Gemini" : "OpenAI"; + const providerLabel = + provider === "gemini" + ? "Google Gemini" + : provider === "intern" + ? "Intern-AI" + : "OpenAI"; const fallback: AssistantErrorState = { message: "The assistant couldn't complete that request. Please try again later.", @@ -176,14 +183,16 @@ function deriveAssistantError( let showSettingsCTA = false; + // For intern provider, don't show settings CTA for API key related errors if ( - statusCode === 400 || - statusCode === 401 || - statusCode === 403 || - normalized.includes("api key") || - normalized.includes("apikey") || - normalized.includes("missing key") || - normalized.includes("unauthorized") + provider !== "intern" && + (statusCode === 400 || + statusCode === 401 || + statusCode === 403 || + normalized.includes("api key") || + normalized.includes("apikey") || + normalized.includes("missing key") || + normalized.includes("unauthorized")) ) { showSettingsCTA = true; } diff --git a/app/components/assistant-ui/SettingsDialog.tsx b/app/components/assistant-ui/SettingsDialog.tsx index a8520cf..9929afe 100644 --- a/app/components/assistant-ui/SettingsDialog.tsx +++ b/app/components/assistant-ui/SettingsDialog.tsx @@ -51,9 +51,13 @@ export const SettingsDialog = ({ - setProvider(value as "openai" | "gemini") + setProvider(value as "openai" | "gemini" | "intern") } > +
+ + +
@@ -90,6 +94,15 @@ export const SettingsDialog = ({ />
)} + + {provider === "intern" && ( +
+
+ 感谢上海AILab的书生大模型对本项目的算力支持,Intern-AI + 模型已预配置,无需提供 API Key。 +
+
+ )} diff --git a/app/components/assistant-ui/thread.tsx b/app/components/assistant-ui/thread.tsx index c71b6cc..91645b6 100644 --- a/app/components/assistant-ui/thread.tsx +++ b/app/components/assistant-ui/thread.tsx @@ -268,9 +268,19 @@ const Composer: FC = ({ onClearError, }) => { const { provider, openaiApiKey, geminiApiKey } = useAssistantSettings(); - const activeKey = provider === "openai" ? openaiApiKey : geminiApiKey; - const hasActiveKey = activeKey.trim().length > 0; - const providerLabel = provider === "gemini" ? "Google Gemini" : "OpenAI"; + const activeKey = + provider === "openai" + ? openaiApiKey + : provider === "gemini" + ? geminiApiKey + : ""; + const hasActiveKey = provider === "intern" || activeKey.trim().length > 0; + const providerLabel = + provider === "gemini" + ? "Google Gemini" + : provider === "intern" + ? "Intern-AI" + : "OpenAI"; const handleOpenSettings = useCallback(() => { onClearError?.(); diff --git a/app/hooks/useAssistantSettings.tsx b/app/hooks/useAssistantSettings.tsx index 878d697..e8beecc 100644 --- a/app/hooks/useAssistantSettings.tsx +++ b/app/hooks/useAssistantSettings.tsx @@ -10,7 +10,7 @@ import { } from "react"; import type { ReactNode } from "react"; -type Provider = "openai" | "gemini"; +type Provider = "openai" | "gemini" | "intern"; interface AssistantSettingsState { provider: Provider; @@ -45,7 +45,12 @@ const parseStoredSettings = (raw: string | null): AssistantSettingsState => { try { const parsed = JSON.parse(raw) as Partial; return { - provider: parsed.provider === "gemini" ? "gemini" : "openai", + provider: + parsed.provider === "gemini" + ? "gemini" + : parsed.provider === "intern" + ? "intern" + : "openai", openaiApiKey: typeof parsed.openaiApiKey === "string" ? parsed.openaiApiKey : "", geminiApiKey: From d2e764af165b19f76056ea71b800c8f14c47122c Mon Sep 17 00:00:00 2001 From: Crokily Date: Fri, 26 Sep 2025 17:09:07 +1000 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20=E8=A7=A3=E8=80=A6AI=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E4=BB=A3=E7=A0=81=EF=BC=8C=E4=B8=BA=E5=90=8E=E7=BB=AD?= =?UTF-8?q?=E6=8B=93=E5=B1=95AI=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=A4=8D?= =?UTF-8?q?=E7=94=A8=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BE=9B=E5=9F=BA=E7=A1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/chat/route.ts | 119 ++++++++++++++----------------------- lib/ai/models.ts | 43 ++++++++++++++ lib/ai/prompt.ts | 46 ++++++++++++++ lib/ai/providers/gemini.ts | 15 +++++ lib/ai/providers/intern.ts | 17 ++++++ lib/ai/providers/openai.ts | 15 +++++ 6 files changed, 180 insertions(+), 75 deletions(-) create mode 100644 lib/ai/models.ts create mode 100644 lib/ai/prompt.ts create mode 100644 lib/ai/providers/gemini.ts create mode 100644 lib/ai/providers/intern.ts create mode 100644 lib/ai/providers/openai.ts diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 620e97d..96fd084 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,89 +1,52 @@ -import { createOpenAI } from "@ai-sdk/openai"; -import { createGoogleGenerativeAI } from "@ai-sdk/google"; -import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { streamText, UIMessage, convertToModelMessages } from "ai"; +import { getModel, requiresApiKey, type AIProvider } from "@/lib/ai/models"; +import { buildSystemMessage } from "@/lib/ai/prompt"; // Allow streaming responses up to 30 seconds export const maxDuration = 30; -export async function POST(req: Request) { - const { - messages, - system, - pageContext, - provider, - apiKey, - }: { - messages: UIMessage[]; - system?: string; // System message forwarded from AssistantChatTransport - tools?: unknown; // Frontend tools forwarded from AssistantChatTransport - pageContext?: { - title?: string; - description?: string; - content?: string; - slug?: string; - }; - provider?: "openai" | "gemini" | "intern"; - apiKey?: string; - } = await req.json(); - - // Check if API key is provided (not required for intern provider) - if (provider !== "intern" && (!apiKey || apiKey.trim() === "")) { - return Response.json( - { - error: - "API key is required. Please configure your API key in the settings.", - }, - { status: 400 }, - ); - } +interface ChatRequest { + messages: UIMessage[]; + system?: string; // System message forwarded from AssistantChatTransport + tools?: unknown; // Frontend tools forwarded from AssistantChatTransport + pageContext?: { + title?: string; + description?: string; + content?: string; + slug?: string; + }; + provider?: AIProvider; + apiKey?: string; +} +export async function POST(req: Request) { try { - // Build system message with page context - let systemMessage = - system || - `You are a helpful AI assistant for a documentation website. - You can help users understand the documentation, answer questions about the content, - and provide guidance on the topics covered in the docs. Be concise and helpful.`; + const { + messages, + system, + pageContext, + provider = "openai", // Default to OpenAI + apiKey, + }: ChatRequest = await req.json(); - // Add current page context if available - if (pageContext?.content) { - systemMessage += `\n\n--- CURRENT PAGE CONTEXT ---\n`; - if (pageContext.title) { - systemMessage += `Page Title: ${pageContext.title}\n`; - } - if (pageContext.description) { - systemMessage += `Page Description: ${pageContext.description}\n`; - } - if (pageContext.slug) { - systemMessage += `Page URL: /docs/${pageContext.slug}\n`; - } - systemMessage += `Page Content:\n${pageContext.content}`; - systemMessage += `\n--- END OF CONTEXT ---\n\nWhen users ask about "this page", "current page", or refer to the content they're reading, use the above context to provide accurate answers. You can summarize, explain, or answer specific questions about the current page content.`; + // Validate API key for providers that require it + if (requiresApiKey(provider) && (!apiKey || apiKey.trim() === "")) { + return Response.json( + { + error: + "API key is required. Please configure your API key in the settings.", + }, + { status: 400 }, + ); } - // Select model based on provider - let model; - if (provider === "gemini") { - const customGoogle = createGoogleGenerativeAI({ - apiKey: apiKey, - }); - model = customGoogle("models/gemini-2.0-flash"); - } else if (provider === "intern") { - const intern = createOpenAICompatible({ - name: "intern", - baseURL: "https://chat.intern-ai.org.cn/api/v1/", - apiKey: process.env.INTERN_KEY, - }); - model = intern("intern-s1"); - } else { - // Default to OpenAI - const customOpenAI = createOpenAI({ - apiKey: apiKey, - }); - model = customOpenAI("gpt-4.1-nano"); - } + // Build system message with page context + const systemMessage = buildSystemMessage(system, pageContext); + + // Get AI model instance based on provider + const model = getModel(provider, apiKey); + // Generate streaming response const result = streamText({ model: model, system: systemMessage, @@ -93,6 +56,12 @@ export async function POST(req: Request) { return result.toUIMessageStreamResponse(); } catch (error) { console.error("Chat API error:", error); + + // Handle specific model creation errors + if (error instanceof Error && error.message.includes("API key")) { + return Response.json({ error: error.message }, { status: 400 }); + } + return Response.json( { error: "Failed to process chat request" }, { status: 500 }, diff --git a/lib/ai/models.ts b/lib/ai/models.ts new file mode 100644 index 0000000..359e60e --- /dev/null +++ b/lib/ai/models.ts @@ -0,0 +1,43 @@ +import { createOpenAIModel } from "./providers/openai"; +import { createGeminiModel } from "./providers/gemini"; +import { createInternModel } from "./providers/intern"; + +export type AIProvider = "openai" | "gemini" | "intern"; + +/** + * Model factory that returns the appropriate AI model based on provider + * @param provider - The AI provider to use + * @param apiKey - API key (not required for intern provider) + * @returns Configured AI model instance + */ +export function getModel(provider: AIProvider, apiKey?: string) { + switch (provider) { + case "openai": + if (!apiKey || apiKey.trim() === "") { + throw new Error("OpenAI API key is required"); + } + return createOpenAIModel(apiKey); + + case "gemini": + if (!apiKey || apiKey.trim() === "") { + throw new Error("Gemini API key is required"); + } + return createGeminiModel(apiKey); + + case "intern": + // Intern provider doesn't need API key from user + return createInternModel(); + + default: + throw new Error(`Unsupported AI provider: ${provider}`); + } +} + +/** + * Check if the given provider requires an API key from the user + * @param provider - The AI provider to check + * @returns True if API key is required, false otherwise + */ +export function requiresApiKey(provider: AIProvider): boolean { + return provider !== "intern"; +} diff --git a/lib/ai/prompt.ts b/lib/ai/prompt.ts new file mode 100644 index 0000000..66cf96a --- /dev/null +++ b/lib/ai/prompt.ts @@ -0,0 +1,46 @@ +interface PageContext { + title?: string; + description?: string; + content?: string; + slug?: string; +} + +/** + * Build system message with page context for AI assistant + * @param customSystem - Custom system message (optional) + * @param pageContext - Current page context (optional) + * @returns Complete system message string + */ +export function buildSystemMessage( + customSystem?: string, + pageContext?: PageContext, +): string { + // Default system message for documentation assistant + let systemMessage = + customSystem || + `You are a helpful AI assistant for a documentation website. + You can help users understand the documentation, answer questions about the content, + and provide guidance on the topics covered in the docs. Be concise and helpful.`; + + // Add current page context if available + if (pageContext?.content) { + systemMessage += `\n\n--- CURRENT PAGE CONTEXT ---\n`; + + if (pageContext.title) { + systemMessage += `Page Title: ${pageContext.title}\n`; + } + + if (pageContext.description) { + systemMessage += `Page Description: ${pageContext.description}\n`; + } + + if (pageContext.slug) { + systemMessage += `Page URL: /docs/${pageContext.slug}\n`; + } + + systemMessage += `Page Content:\n${pageContext.content}`; + systemMessage += `\n--- END OF CONTEXT ---\n\nWhen users ask about "this page", "current page", or refer to the content they're reading, use the above context to provide accurate answers. You can summarize, explain, or answer specific questions about the current page content.`; + } + + return systemMessage; +} diff --git a/lib/ai/providers/gemini.ts b/lib/ai/providers/gemini.ts new file mode 100644 index 0000000..9c6249d --- /dev/null +++ b/lib/ai/providers/gemini.ts @@ -0,0 +1,15 @@ +import { createGoogleGenerativeAI } from "@ai-sdk/google"; + +/** + * Create Google Gemini model instance + * @param apiKey - Google Gemini API key provided by user + * @returns Configured Gemini model instance + */ +export function createGeminiModel(apiKey: string) { + const customGoogle = createGoogleGenerativeAI({ + apiKey: apiKey, + }); + + // Use the specific model configured for this project + return customGoogle("models/gemini-2.0-flash"); +} diff --git a/lib/ai/providers/intern.ts b/lib/ai/providers/intern.ts new file mode 100644 index 0000000..71d06a5 --- /dev/null +++ b/lib/ai/providers/intern.ts @@ -0,0 +1,17 @@ +import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; + +/** + * Create Intern-AI model instance + * Uses environment variable INTERN_KEY for API key + * @returns Configured Intern-AI model instance + */ +export function createInternModel() { + const intern = createOpenAICompatible({ + name: "intern", + baseURL: "https://chat.intern-ai.org.cn/api/v1/", + apiKey: process.env.INTERN_KEY, + }); + + // Use the specific model configured for this project + return intern("intern-s1"); +} diff --git a/lib/ai/providers/openai.ts b/lib/ai/providers/openai.ts new file mode 100644 index 0000000..88f2724 --- /dev/null +++ b/lib/ai/providers/openai.ts @@ -0,0 +1,15 @@ +import { createOpenAI } from "@ai-sdk/openai"; + +/** + * Create OpenAI model instance + * @param apiKey - OpenAI API key provided by user + * @returns Configured OpenAI model instance + */ +export function createOpenAIModel(apiKey: string) { + const customOpenAI = createOpenAI({ + apiKey: apiKey, + }); + + // Use the specific model configured for this project + return customOpenAI("gpt-4.1-nano"); +} From 512cac2d774c32d470d23416b04d465195e0dd52 Mon Sep 17 00:00:00 2001 From: Crokily Date: Sat, 27 Sep 2025 10:40:39 +1000 Subject: [PATCH 5/8] =?UTF-8?q?style:=20=E5=B0=86=E6=9C=ACpr=E6=B6=89?= =?UTF-8?q?=E5=8F=8A=E7=9A=84=E4=BB=A3=E7=A0=81=EF=BC=88=E4=B8=BB=E8=A6=81?= =?UTF-8?q?=E6=98=AFAI=E6=A8=A1=E5=9D=97=EF=BC=89=E5=86=85=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=94=B9=E6=88=90=E4=B8=AD=E6=96=87=EF=BC=8C?= =?UTF-8?q?=E4=BE=BF=E4=BA=8E=E5=8D=8F=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/chat/route.ts | 18 +++++++++--------- lib/ai/models.ts | 16 ++++++++-------- lib/ai/prompt.ts | 12 ++++++------ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 96fd084..5e872c6 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -2,13 +2,13 @@ import { streamText, UIMessage, convertToModelMessages } from "ai"; import { getModel, requiresApiKey, type AIProvider } from "@/lib/ai/models"; import { buildSystemMessage } from "@/lib/ai/prompt"; -// Allow streaming responses up to 30 seconds +// 流式响应最长30秒 export const maxDuration = 30; interface ChatRequest { messages: UIMessage[]; - system?: string; // System message forwarded from AssistantChatTransport - tools?: unknown; // Frontend tools forwarded from AssistantChatTransport + system?: string; + tools?: unknown; pageContext?: { title?: string; description?: string; @@ -25,11 +25,11 @@ export async function POST(req: Request) { messages, system, pageContext, - provider = "openai", // Default to OpenAI + provider = "intern", // 默认使用书生模型 apiKey, }: ChatRequest = await req.json(); - // Validate API key for providers that require it + // 对指定Provider验证key是否存在 if (requiresApiKey(provider) && (!apiKey || apiKey.trim() === "")) { return Response.json( { @@ -40,13 +40,13 @@ export async function POST(req: Request) { ); } - // Build system message with page context + // 构建系统消息,包含页面上下文 const systemMessage = buildSystemMessage(system, pageContext); - // Get AI model instance based on provider + // 根据Provider获取 AI 模型实例 const model = getModel(provider, apiKey); - // Generate streaming response + // 生成流式响应 const result = streamText({ model: model, system: systemMessage, @@ -57,7 +57,7 @@ export async function POST(req: Request) { } catch (error) { console.error("Chat API error:", error); - // Handle specific model creation errors + // 处理特定模型创建错误 if (error instanceof Error && error.message.includes("API key")) { return Response.json({ error: error.message }, { status: 400 }); } diff --git a/lib/ai/models.ts b/lib/ai/models.ts index 359e60e..b94968d 100644 --- a/lib/ai/models.ts +++ b/lib/ai/models.ts @@ -5,10 +5,10 @@ import { createInternModel } from "./providers/intern"; export type AIProvider = "openai" | "gemini" | "intern"; /** - * Model factory that returns the appropriate AI model based on provider - * @param provider - The AI provider to use - * @param apiKey - API key (not required for intern provider) - * @returns Configured AI model instance + * Model工厂 用于返回对应的 AI 模型实例 + * @param provider - 要用的provider + * @param apiKey - API key (intern provider不需要用户提供 API key) + * @returns 配置好的 AI 模型实例 */ export function getModel(provider: AIProvider, apiKey?: string) { switch (provider) { @@ -25,7 +25,7 @@ export function getModel(provider: AIProvider, apiKey?: string) { return createGeminiModel(apiKey); case "intern": - // Intern provider doesn't need API key from user + // Intern 书生模型不需要用户提供 API key return createInternModel(); default: @@ -34,9 +34,9 @@ export function getModel(provider: AIProvider, apiKey?: string) { } /** - * Check if the given provider requires an API key from the user - * @param provider - The AI provider to check - * @returns True if API key is required, false otherwise + * 检查指定的提供者是否需要用户提供 API key + * @param provider - 要检查的provider + * @returns 如果需要 API key,返回 true,否则返回 false */ export function requiresApiKey(provider: AIProvider): boolean { return provider !== "intern"; diff --git a/lib/ai/prompt.ts b/lib/ai/prompt.ts index 66cf96a..37f3c38 100644 --- a/lib/ai/prompt.ts +++ b/lib/ai/prompt.ts @@ -6,23 +6,23 @@ interface PageContext { } /** - * Build system message with page context for AI assistant - * @param customSystem - Custom system message (optional) - * @param pageContext - Current page context (optional) - * @returns Complete system message string + * 构建系统消息,包含页面上下文 + * @param customSystem - 自定义系统消息 (可选) + * @param pageContext - 当前页面上下文 (可选) + * @returns 完整的系统消息字符串 */ export function buildSystemMessage( customSystem?: string, pageContext?: PageContext, ): string { - // Default system message for documentation assistant + // 默认系统消息 let systemMessage = customSystem || `You are a helpful AI assistant for a documentation website. You can help users understand the documentation, answer questions about the content, and provide guidance on the topics covered in the docs. Be concise and helpful.`; - // Add current page context if available + // 如果当前页面上下文可用,则添加到系统消息中 if (pageContext?.content) { systemMessage += `\n\n--- CURRENT PAGE CONTEXT ---\n`; From 20e3caeca63c445869e17fb3913cc146d238acfb Mon Sep 17 00:00:00 2001 From: Crokily Date: Sat, 27 Sep 2025 12:09:10 +1000 Subject: [PATCH 6/8] feat: update default system message to support multilingual responses --- lib/ai/prompt.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ai/prompt.ts b/lib/ai/prompt.ts index 37f3c38..5dacf20 100644 --- a/lib/ai/prompt.ts +++ b/lib/ai/prompt.ts @@ -18,9 +18,9 @@ export function buildSystemMessage( // 默认系统消息 let systemMessage = customSystem || - `You are a helpful AI assistant for a documentation website. - You can help users understand the documentation, answer questions about the content, - and provide guidance on the topics covered in the docs. Be concise and helpful.`; + `You are a helpful AI assistant for a documentation website. + Always respond in the same language as the user's question: if the user asks in 中文, answer in 中文; if the user asks in English, answer in English. + You can help users understand the documentation, answer questions about the content, and provide guidance on the topics covered in the docs. Be concise and helpful.`; // 如果当前页面上下文可用,则添加到系统消息中 if (pageContext?.content) { From a327e586f63bb92fc807a8370790ba801f26dda8 Mon Sep 17 00:00:00 2001 From: Crokily Date: Sat, 27 Sep 2025 12:26:48 +1000 Subject: [PATCH 7/8] style: adjust sizes of AssistantModal button icons for improved UI consistency --- app/components/assistant-ui/assistant-modal.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/assistant-ui/assistant-modal.tsx b/app/components/assistant-ui/assistant-modal.tsx index ee46937..43e7b5e 100644 --- a/app/components/assistant-ui/assistant-modal.tsx +++ b/app/components/assistant-ui/assistant-modal.tsx @@ -21,7 +21,7 @@ export const AssistantModal: FC = ({ }) => { return ( - + @@ -59,12 +59,12 @@ const AssistantModalButton = forwardRef< > {tooltip} From 1dbb235d4d3a8e181eb737563b697ddbeba4b1c6 Mon Sep 17 00:00:00 2001 From: Crokily Date: Sat, 27 Sep 2025 14:36:38 +1000 Subject: [PATCH 8/8] style: update Composer component background color for improved visibility --- app/components/assistant-ui/thread.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/assistant-ui/thread.tsx b/app/components/assistant-ui/thread.tsx index 91645b6..4a0394f 100644 --- a/app/components/assistant-ui/thread.tsx +++ b/app/components/assistant-ui/thread.tsx @@ -288,7 +288,7 @@ const Composer: FC = ({ }, [onClearError, onOpenChange]); return ( -
+