Skip to content

Commit b9508fd

Browse files
committed
feat: /api/chat 转发到后端、fix:替换过期的key
1 parent b020094 commit b9508fd

8 files changed

Lines changed: 100 additions & 32 deletions

File tree

app/api/chat/route.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,59 @@ interface ChatRequest {
2929
import { resolveUserId } from "@/lib/server-auth";
3030

3131
export async function POST(req: Request) {
32+
// 1. 克隆请求,因为如果代理失败,后面的代码还需要读取 req.json()
33+
const proxyReq = req.clone();
34+
35+
// ====== 尝试优雅降级代理到 Java 后端 ======
36+
try {
37+
const backendUrl = process.env.BACKEND_URL ?? "http://localhost:8080";
38+
const controller = new AbortController();
39+
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒超时
40+
41+
// 原封不动把前端的参数丢给 Java
42+
const proxyRes = await fetch(`${backendUrl}/openai/responses/stream`, {
43+
method: "POST",
44+
headers: {
45+
"Content-Type": "application/json",
46+
"x-satoken": req.headers.get("x-satoken") || "",
47+
},
48+
body: await proxyReq.text(),
49+
signal: controller.signal,
50+
});
51+
52+
clearTimeout(timeoutId);
53+
54+
// 如果 Java 后端返回成功,则直接把它的流传回浏览器,提前结束
55+
if (proxyRes.ok && proxyRes.body) {
56+
console.log(
57+
"[Chat Fallback Proxy] 🚀 Java Backend responded successfully. Piping stream...",
58+
);
59+
return new Response(proxyRes.body, {
60+
headers: {
61+
"Content-Type":
62+
proxyRes.headers.get("Content-Type") || "text/plain; charset=utf-8",
63+
},
64+
});
65+
} else {
66+
console.warn(
67+
`[Chat Fallback Proxy] ⚠️ Java Backend returned status: ${proxyRes.status}, fallback to local Next.js inference.`,
68+
);
69+
}
70+
} catch (error) {
71+
console.warn(
72+
`[Chat Fallback Proxy] ❌ Java Backend unavailable or timed out, fallback to local Next.js inference. Error:`,
73+
error,
74+
);
75+
}
76+
// ====== 代理失败,继续往下走,启用备选方案(本地直连 AI)======
77+
3278
try {
3379
// 先把 body 消费掉,再并行验证用户身份
3480
const {
3581
messages,
3682
system,
3783
pageContext,
38-
provider = "intern", // 默认使用书生模型
84+
provider = "deepseek", // 默认使用deepseek模型
3985
apiKey,
4086
chatId,
4187
}: ChatRequest = await req.json();

app/api/suggestions/route.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function POST(req: Request) {
2424
const {
2525
messages,
2626
pageContext,
27-
provider = "intern",
27+
provider = "deepseek",
2828
apiKey,
2929
}: SuggestionsRequest = await req.json();
3030

@@ -38,17 +38,17 @@ export async function POST(req: Request) {
3838

3939
// 模型选择策略:
4040
// - 若用户选了自己的 Provider(openai/gemini),用用户的模型
41-
// - 否则(默认 intern)优先用 GLM-4-Flash(免费且快速),若 ZHIPU_API_KEY 未配置则回退到 intern
41+
// - 否则(默认 deepseek)优先用 GLM-4-Flash(免费且快速),若 ZHIPU_API_KEY 未配置则回退到 deepseek
4242
let model;
43-
if (provider !== "intern") {
43+
if (provider !== "deepseek") {
4444
// 用户自选模型(openai / gemini)
4545
model = getModel(provider, apiKey);
4646
} else if (process.env.ZHIPU_API_KEY) {
4747
// 默认使用智谱 GLM-4-Flash(免费轻量)
4848
model = createGlmFlashModel();
4949
} else {
50-
// 兜底:仍使用 intern
51-
model = getModel("intern");
50+
// 兜底:仍使用 deepseek
51+
model = getModel("deepseek");
5252
}
5353

5454
const isWelcomeRequest = messages.length === 0;
@@ -92,12 +92,12 @@ export async function POST(req: Request) {
9292
async (cPrompt: string, cModelId: string) => {
9393
// 由于 model 不能序列化传入,在缓存函数内侧由于无法动态构建,所以此处传递标识再创建
9494
const m =
95-
cModelId === "intern"
96-
? getModel("intern")
95+
cModelId === "deepseek"
96+
? getModel("deepseek")
9797
: cModelId === "glm"
9898
? createGlmFlashModel()
9999
: // 无法传递用户动态 key,对于配置了 key 的情况不走此函数
100-
getModel("intern");
100+
getModel("deepseek");
101101

102102
const { text } = await generateText({
103103
model: m,
@@ -115,7 +115,7 @@ export async function POST(req: Request) {
115115

116116
let text = "";
117117
// 判断是否可以使用缓存(如果使用了自定义 apiKey,为了防止数据交叉,不采用公用缓存)
118-
if (provider !== "intern" && !process.env.ZHIPU_API_KEY) {
118+
if (provider !== "deepseek" && !process.env.ZHIPU_API_KEY) {
119119
const { text: directText } = await generateText({
120120
model,
121121
prompt,
@@ -124,11 +124,11 @@ export async function POST(req: Request) {
124124
} else {
125125
// 确定内部用于缓存匹配的模型标识
126126
const internalModelId =
127-
provider === "intern"
127+
provider === "deepseek"
128128
? process.env.ZHIPU_API_KEY
129129
? "glm"
130-
: "intern"
131-
: "intern";
130+
: "deepseek"
131+
: "deepseek";
132132
// 将缓存粒度关联到请求本身的内容上
133133
const suggestionKey = isWelcomeRequest
134134
? `welcome-${pageContext?.slug || "default"}`

app/components/DocsAssistant.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ interface AssistantErrorState {
365365
showSettingsCTA: boolean;
366366
}
367367

368-
type AssistantProvider = "openai" | "gemini" | "intern";
368+
type AssistantProvider = "openai" | "gemini" | "deepseek";
369369

370370
type AssistantErrorData = {
371371
error?: string;
@@ -392,8 +392,8 @@ function deriveAssistantError(
392392
const providerLabel =
393393
provider === "gemini"
394394
? "Google Gemini"
395-
: provider === "intern"
396-
? "Intern-AI"
395+
: provider === "deepseek"
396+
? "Deepseek"
397397
: "OpenAI";
398398
const fallback: AssistantErrorState = {
399399
message:
@@ -441,7 +441,7 @@ function deriveAssistantError(
441441
// For intern provider, don't show settings CTA for API key related errors
442442
// 对于书生,不要显示 API 密钥相关的错误
443443
if (
444-
provider !== "intern" &&
444+
provider !== "deepseek" &&
445445
(statusCode === 400 ||
446446
statusCode === 401 ||
447447
statusCode === 403 ||

app/components/assistant-ui/SettingsDialog.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ export const SettingsDialog = ({
5353
<RadioGroup
5454
value={provider}
5555
onValueChange={(value) =>
56-
setProvider(value as "openai" | "gemini" | "intern")
56+
setProvider(value as "openai" | "gemini" | "deepseek")
5757
}
5858
>
5959
<div className="flex items-center space-x-2">
60-
<RadioGroupItem value="intern" id="intern" />
61-
<Label htmlFor="intern">InternS1 (Free)</Label>
60+
<RadioGroupItem value="deepseek" id="deepseek" />
61+
<Label htmlFor="deepseek">Deepseek (Free)</Label>
6262
</div>
6363
<div className="flex items-center space-x-2">
6464
<RadioGroupItem value="openai" id="openai" />
@@ -175,7 +175,7 @@ export const SettingsDialog = ({
175175
</div>
176176
)}
177177

178-
{provider === "intern" && (
178+
{provider === "deepseek" && (
179179
<div className="space-y-2">
180180
<div className="text-sm text-muted-foreground">
181181
感谢上海AILab的书生大模型对本项目的算力支持,Intern-AI

app/components/assistant-ui/thread.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,12 @@ const Composer: FC<ComposerProps> = ({
350350
: provider === "gemini"
351351
? geminiApiKey
352352
: "";
353-
const hasActiveKey = provider === "intern" || activeKey.trim().length > 0;
353+
const hasActiveKey = provider === "deepseek" || activeKey.trim().length > 0;
354354
const providerLabel =
355355
provider === "gemini"
356356
? "Google Gemini"
357-
: provider === "intern"
358-
? "Intern-AI"
357+
: provider === "deepseek"
358+
? "Deepseek"
359359
: "OpenAI";
360360

361361
const handleOpenSettings = useCallback(() => {

app/hooks/useAssistantSettings.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from "react";
1111
import type { ReactNode } from "react";
1212

13-
type Provider = "openai" | "gemini" | "intern";
13+
type Provider = "openai" | "gemini" | "deepseek";
1414

1515
interface AssistantSettingsState {
1616
provider: Provider;
@@ -30,7 +30,7 @@ interface AssistantSettingsContextValue extends AssistantSettingsState {
3030
const SETTINGS_KEY = "assistant-settings-storage";
3131

3232
const defaultSettings: AssistantSettingsState = {
33-
provider: "intern",
33+
provider: "deepseek",
3434
openaiApiKey: "",
3535
geminiApiKey: "",
3636
saveToLocalStorage: false,
@@ -52,8 +52,8 @@ const parseStoredSettings = (raw: string | null): AssistantSettingsState => {
5252
let provider =
5353
parsed.provider === "gemini"
5454
? "gemini"
55-
: parsed.provider === "intern"
56-
? "intern"
55+
: parsed.provider === "deepseek"
56+
? "deepseek"
5757
: "openai";
5858

5959
const openaiApiKey =
@@ -67,10 +67,10 @@ const parseStoredSettings = (raw: string | null): AssistantSettingsState => {
6767

6868
// 如果用户之前默认在 openai/gemini 但现在没有对应的 API Key,则回退到免配置的 intern 模型
6969
if (provider === "openai" && !openaiApiKey) {
70-
provider = "intern";
70+
provider = "deepseek";
7171
}
7272
if (provider === "gemini" && !geminiApiKey) {
73-
provider = "intern";
73+
provider = "deepseek";
7474
}
7575

7676
return {

lib/ai/models.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { createOpenAIModel } from "./providers/openai";
22
import { createGeminiModel } from "./providers/gemini";
33
import { createInternModel } from "./providers/intern";
4+
import { createDeepseekModel } from "./providers/deepseek";
45

5-
export type AIProvider = "openai" | "gemini" | "intern";
6+
export type AIProvider = "openai" | "gemini" | "intern" | "deepseek";
67

78
/**
89
* Model工厂 用于返回对应的 AI 模型实例
@@ -28,6 +29,9 @@ export function getModel(provider: AIProvider, apiKey?: string) {
2829
// Intern 书生模型不需要用户提供 API key
2930
return createInternModel();
3031

32+
case "deepseek":
33+
return createDeepseekModel();
34+
3135
default:
3236
throw new Error(`Unsupported AI provider: ${provider}`);
3337
}
@@ -39,5 +43,5 @@ export function getModel(provider: AIProvider, apiKey?: string) {
3943
* @returns 如果需要 API key,返回 true,否则返回 false
4044
*/
4145
export function requiresApiKey(provider: AIProvider): boolean {
42-
return provider !== "intern";
46+
return provider !== "intern" && provider !== "deepseek";
4347
}

lib/ai/providers/deepseek.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
2+
3+
/**
4+
* Create Deepseek AI model instance
5+
* Uses environment variable DEEPSEEK_API_KEY for API key
6+
* @returns Configured Deepseek model instance
7+
*/
8+
export function createDeepseekModel() {
9+
const deepseek = createOpenAICompatible({
10+
name: "deepseek",
11+
baseURL: "https://api.deepseek.com/v1", // Some compatible SDKs expect /v1
12+
headers: {
13+
Authorization: `Bearer ${process.env.DEEPSEEK_API_KEY}`,
14+
},
15+
});
16+
17+
return deepseek("deepseek-chat");
18+
}

0 commit comments

Comments
 (0)