Skip to content

Commit d2e764a

Browse files
committed
refactor: 解耦AI模块代码,为后续拓展AI功能,复用代码提供基础
1 parent df44a20 commit d2e764a

6 files changed

Lines changed: 180 additions & 75 deletions

File tree

app/api/chat/route.ts

Lines changed: 44 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,52 @@
1-
import { createOpenAI } from "@ai-sdk/openai";
2-
import { createGoogleGenerativeAI } from "@ai-sdk/google";
3-
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
41
import { streamText, UIMessage, convertToModelMessages } from "ai";
2+
import { getModel, requiresApiKey, type AIProvider } from "@/lib/ai/models";
3+
import { buildSystemMessage } from "@/lib/ai/prompt";
54

65
// Allow streaming responses up to 30 seconds
76
export const maxDuration = 30;
87

9-
export async function POST(req: Request) {
10-
const {
11-
messages,
12-
system,
13-
pageContext,
14-
provider,
15-
apiKey,
16-
}: {
17-
messages: UIMessage[];
18-
system?: string; // System message forwarded from AssistantChatTransport
19-
tools?: unknown; // Frontend tools forwarded from AssistantChatTransport
20-
pageContext?: {
21-
title?: string;
22-
description?: string;
23-
content?: string;
24-
slug?: string;
25-
};
26-
provider?: "openai" | "gemini" | "intern";
27-
apiKey?: string;
28-
} = await req.json();
29-
30-
// Check if API key is provided (not required for intern provider)
31-
if (provider !== "intern" && (!apiKey || apiKey.trim() === "")) {
32-
return Response.json(
33-
{
34-
error:
35-
"API key is required. Please configure your API key in the settings.",
36-
},
37-
{ status: 400 },
38-
);
39-
}
8+
interface ChatRequest {
9+
messages: UIMessage[];
10+
system?: string; // System message forwarded from AssistantChatTransport
11+
tools?: unknown; // Frontend tools forwarded from AssistantChatTransport
12+
pageContext?: {
13+
title?: string;
14+
description?: string;
15+
content?: string;
16+
slug?: string;
17+
};
18+
provider?: AIProvider;
19+
apiKey?: string;
20+
}
4021

22+
export async function POST(req: Request) {
4123
try {
42-
// Build system message with page context
43-
let systemMessage =
44-
system ||
45-
`You are a helpful AI assistant for a documentation website.
46-
You can help users understand the documentation, answer questions about the content,
47-
and provide guidance on the topics covered in the docs. Be concise and helpful.`;
24+
const {
25+
messages,
26+
system,
27+
pageContext,
28+
provider = "openai", // Default to OpenAI
29+
apiKey,
30+
}: ChatRequest = await req.json();
4831

49-
// Add current page context if available
50-
if (pageContext?.content) {
51-
systemMessage += `\n\n--- CURRENT PAGE CONTEXT ---\n`;
52-
if (pageContext.title) {
53-
systemMessage += `Page Title: ${pageContext.title}\n`;
54-
}
55-
if (pageContext.description) {
56-
systemMessage += `Page Description: ${pageContext.description}\n`;
57-
}
58-
if (pageContext.slug) {
59-
systemMessage += `Page URL: /docs/${pageContext.slug}\n`;
60-
}
61-
systemMessage += `Page Content:\n${pageContext.content}`;
62-
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.`;
32+
// Validate API key for providers that require it
33+
if (requiresApiKey(provider) && (!apiKey || apiKey.trim() === "")) {
34+
return Response.json(
35+
{
36+
error:
37+
"API key is required. Please configure your API key in the settings.",
38+
},
39+
{ status: 400 },
40+
);
6341
}
6442

65-
// Select model based on provider
66-
let model;
67-
if (provider === "gemini") {
68-
const customGoogle = createGoogleGenerativeAI({
69-
apiKey: apiKey,
70-
});
71-
model = customGoogle("models/gemini-2.0-flash");
72-
} else if (provider === "intern") {
73-
const intern = createOpenAICompatible({
74-
name: "intern",
75-
baseURL: "https://chat.intern-ai.org.cn/api/v1/",
76-
apiKey: process.env.INTERN_KEY,
77-
});
78-
model = intern("intern-s1");
79-
} else {
80-
// Default to OpenAI
81-
const customOpenAI = createOpenAI({
82-
apiKey: apiKey,
83-
});
84-
model = customOpenAI("gpt-4.1-nano");
85-
}
43+
// Build system message with page context
44+
const systemMessage = buildSystemMessage(system, pageContext);
45+
46+
// Get AI model instance based on provider
47+
const model = getModel(provider, apiKey);
8648

49+
// Generate streaming response
8750
const result = streamText({
8851
model: model,
8952
system: systemMessage,
@@ -93,6 +56,12 @@ export async function POST(req: Request) {
9356
return result.toUIMessageStreamResponse();
9457
} catch (error) {
9558
console.error("Chat API error:", error);
59+
60+
// Handle specific model creation errors
61+
if (error instanceof Error && error.message.includes("API key")) {
62+
return Response.json({ error: error.message }, { status: 400 });
63+
}
64+
9665
return Response.json(
9766
{ error: "Failed to process chat request" },
9867
{ status: 500 },

lib/ai/models.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { createOpenAIModel } from "./providers/openai";
2+
import { createGeminiModel } from "./providers/gemini";
3+
import { createInternModel } from "./providers/intern";
4+
5+
export type AIProvider = "openai" | "gemini" | "intern";
6+
7+
/**
8+
* Model factory that returns the appropriate AI model based on provider
9+
* @param provider - The AI provider to use
10+
* @param apiKey - API key (not required for intern provider)
11+
* @returns Configured AI model instance
12+
*/
13+
export function getModel(provider: AIProvider, apiKey?: string) {
14+
switch (provider) {
15+
case "openai":
16+
if (!apiKey || apiKey.trim() === "") {
17+
throw new Error("OpenAI API key is required");
18+
}
19+
return createOpenAIModel(apiKey);
20+
21+
case "gemini":
22+
if (!apiKey || apiKey.trim() === "") {
23+
throw new Error("Gemini API key is required");
24+
}
25+
return createGeminiModel(apiKey);
26+
27+
case "intern":
28+
// Intern provider doesn't need API key from user
29+
return createInternModel();
30+
31+
default:
32+
throw new Error(`Unsupported AI provider: ${provider}`);
33+
}
34+
}
35+
36+
/**
37+
* Check if the given provider requires an API key from the user
38+
* @param provider - The AI provider to check
39+
* @returns True if API key is required, false otherwise
40+
*/
41+
export function requiresApiKey(provider: AIProvider): boolean {
42+
return provider !== "intern";
43+
}

lib/ai/prompt.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
interface PageContext {
2+
title?: string;
3+
description?: string;
4+
content?: string;
5+
slug?: string;
6+
}
7+
8+
/**
9+
* Build system message with page context for AI assistant
10+
* @param customSystem - Custom system message (optional)
11+
* @param pageContext - Current page context (optional)
12+
* @returns Complete system message string
13+
*/
14+
export function buildSystemMessage(
15+
customSystem?: string,
16+
pageContext?: PageContext,
17+
): string {
18+
// Default system message for documentation assistant
19+
let systemMessage =
20+
customSystem ||
21+
`You are a helpful AI assistant for a documentation website.
22+
You can help users understand the documentation, answer questions about the content,
23+
and provide guidance on the topics covered in the docs. Be concise and helpful.`;
24+
25+
// Add current page context if available
26+
if (pageContext?.content) {
27+
systemMessage += `\n\n--- CURRENT PAGE CONTEXT ---\n`;
28+
29+
if (pageContext.title) {
30+
systemMessage += `Page Title: ${pageContext.title}\n`;
31+
}
32+
33+
if (pageContext.description) {
34+
systemMessage += `Page Description: ${pageContext.description}\n`;
35+
}
36+
37+
if (pageContext.slug) {
38+
systemMessage += `Page URL: /docs/${pageContext.slug}\n`;
39+
}
40+
41+
systemMessage += `Page Content:\n${pageContext.content}`;
42+
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.`;
43+
}
44+
45+
return systemMessage;
46+
}

lib/ai/providers/gemini.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
2+
3+
/**
4+
* Create Google Gemini model instance
5+
* @param apiKey - Google Gemini API key provided by user
6+
* @returns Configured Gemini model instance
7+
*/
8+
export function createGeminiModel(apiKey: string) {
9+
const customGoogle = createGoogleGenerativeAI({
10+
apiKey: apiKey,
11+
});
12+
13+
// Use the specific model configured for this project
14+
return customGoogle("models/gemini-2.0-flash");
15+
}

lib/ai/providers/intern.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
2+
3+
/**
4+
* Create Intern-AI model instance
5+
* Uses environment variable INTERN_KEY for API key
6+
* @returns Configured Intern-AI model instance
7+
*/
8+
export function createInternModel() {
9+
const intern = createOpenAICompatible({
10+
name: "intern",
11+
baseURL: "https://chat.intern-ai.org.cn/api/v1/",
12+
apiKey: process.env.INTERN_KEY,
13+
});
14+
15+
// Use the specific model configured for this project
16+
return intern("intern-s1");
17+
}

lib/ai/providers/openai.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createOpenAI } from "@ai-sdk/openai";
2+
3+
/**
4+
* Create OpenAI model instance
5+
* @param apiKey - OpenAI API key provided by user
6+
* @returns Configured OpenAI model instance
7+
*/
8+
export function createOpenAIModel(apiKey: string) {
9+
const customOpenAI = createOpenAI({
10+
apiKey: apiKey,
11+
});
12+
13+
// Use the specific model configured for this project
14+
return customOpenAI("gpt-4.1-nano");
15+
}

0 commit comments

Comments
 (0)