Skip to content

Commit 0835912

Browse files
committed
fix(assistant): 切换免费模型 Intern-S1 → GLM-4.6V-Flash + 加关闭按钮 (#285)
- 免费模型 Intern-S1 key 过期导致匿名用户发消息报 unauthorized。 改接智谱 GLM-4.6V-Flash(128K 上下文、多模态、免费),用 ZHIPU_API_KEY 走 OpenAI-compatible 接口。保留 provider 名 `intern` 避免 localStorage 迁移。 - 匿名请求跳过 Java 后端代理:/openai/responses/stream 有 @SaCheckLogin, 匿名必 401,短路省 5s 超时、避免错误文案透传。 - 模态框右上角加 X 关闭按钮(改用受控 open state),解决 "边框悬浮但找不到关闭入口" 的 UX 问题。 - SettingsDialog 文案更新:标注 GLM-4.6V-Flash,保留对书生 Intern-S1 首发算力赞助的致谢。 - .env.sample 新增 ZHIPU_API_KEY 占位。 Closes #285
1 parent fd636b8 commit 0835912

5 files changed

Lines changed: 65 additions & 23 deletions

File tree

.env.sample

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ INDEXNOW_API_TOKEN=
2424
#Open的Key
2525
INDEXNOW_KEY=5b6ef14a7406496b8a2ce8ab17820b34
2626
NEXT_PUBLIC_SITE_URL=https://involutionhell.com
27-
# 内部识别/认证 Key
27+
# 书生 Intern-S1 Key(已弃用:默认免费模型已切换至 GLM-4.6V-Flash)
2828
INTERN_KEY=
29+
# 智谱 AI 开放平台 API Key,聊天与建议接口的默认免费模型 GLM-4.6V-Flash
30+
# 在 https://open.bigmodel.cn/ 注册后获取
31+
ZHIPU_API_KEY=
2932
# Neon 项目 ID
3033
NEON_PROJECT_ID=
3134
# Neon 提供的 Postgres 连接。

app/api/chat/route.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,16 @@ export async function POST(req: Request) {
3333
const proxyReq = req.clone();
3434

3535
// ====== 尝试优雅降级代理到 Java 后端 ======
36+
// Java 后端 /openai/responses/stream 带 @SaCheckLogin,匿名请求必 401;
37+
// 直接跳过代理省掉 5s 超时,也避免 401 文案被上游误显示为"unauthorized"。
38+
const hasAuthToken = Boolean(req.headers.get("x-satoken"));
3639
try {
40+
if (!hasAuthToken) {
41+
throw new Error("Anonymous request, skip backend proxy.");
42+
}
3743
const backendUrl = process.env.BACKEND_URL;
3844
if (!backendUrl) throw new Error("BACKEND_URL is not configured.");
45+
3946
const controller = new AbortController();
4047
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5秒超时
4148

app/components/assistant-ui/SettingsDialog.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export const SettingsDialog = ({
5858
>
5959
<div className="flex items-center space-x-2">
6060
<RadioGroupItem value="intern" id="intern" />
61-
<Label htmlFor="intern">InternS1 (Free)</Label>
61+
<Label htmlFor="intern">GLM-4.6V-Flash (Free)</Label>
6262
</div>
6363
<div className="flex items-center space-x-2">
6464
<RadioGroupItem value="openai" id="openai" />
@@ -176,11 +176,15 @@ export const SettingsDialog = ({
176176
)}
177177

178178
{provider === "intern" && (
179-
<div className="space-y-2">
180-
<div className="text-sm text-muted-foreground">
181-
感谢上海AILab的书生大模型对本项目的算力支持,Intern-AI
182-
模型已预配置,无需提供 API Key。
183-
</div>
179+
<div className="space-y-2 text-sm text-muted-foreground">
180+
<p>
181+
当前免费模型为智谱 GLM-4.6V-Flash(128K
182+
上下文,支持多模态),由站点统一承担费用,无需提供 API Key。
183+
</p>
184+
<p className="text-xs">
185+
🙏 特别鸣谢上海 AI Lab 书生 Intern-S1
186+
为本项目提供首发算力支持——第一个金主,永远铭记。
187+
</p>
184188
</div>
185189
)}
186190
</div>

app/components/assistant-ui/assistant-modal.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use client";
22

3-
import { BotIcon, ChevronDownIcon } from "lucide-react";
3+
import { BotIcon, ChevronDownIcon, XIcon } from "lucide-react";
44

5-
import { type FC, forwardRef, useState, useEffect } from "react";
5+
import { type FC, forwardRef, useState, useEffect, useCallback } from "react";
66
import { AssistantModalPrimitive } from "@assistant-ui/react";
77

88
import { Thread } from "@/app/components/assistant-ui/thread";
@@ -30,6 +30,13 @@ export const AssistantModal: FC<AssistantModalProps> = ({
3030
isLoadingWelcome,
3131
}) => {
3232
const [showBubble, setShowBubble] = useState(false);
33+
// 受控状态:允许模态框内部的 X 按钮主动关闭窗口
34+
// issue #285: 原先只能靠 Trigger/点击外部/Esc 关闭,用户反馈不知道怎么关窗
35+
const [open, setOpen] = useState(false);
36+
37+
const handleCloseModal = useCallback(() => {
38+
setOpen(false);
39+
}, []);
3340

3441
useEffect(() => {
3542
// 检查本次访问是否已关闭过气泡
@@ -61,7 +68,7 @@ export const AssistantModal: FC<AssistantModalProps> = ({
6168
};
6269

6370
return (
64-
<AssistantModalPrimitive.Root>
71+
<AssistantModalPrimitive.Root open={open} onOpenChange={setOpen}>
6572
<AssistantModalPrimitive.Anchor className="aui-root aui-modal-anchor fixed right-4 bottom-4 size-14">
6673
{/* 自定义气泡组件 */}
6774
{showBubble && (
@@ -88,8 +95,18 @@ export const AssistantModal: FC<AssistantModalProps> = ({
8895
</AssistantModalPrimitive.Anchor>
8996
<AssistantModalPrimitive.Content
9097
sideOffset={16}
91-
className="aui-root aui-modal-content z-50 h-[500px] w-[400px] overflow-clip rounded-xl border bg-popover p-0 text-popover-foreground shadow-md outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom-1/2 data-[state=closed]:slide-out-to-right-1/2 data-[state=closed]:zoom-out data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-1/2 data-[state=open]:slide-in-from-right-1/2 data-[state=open]:zoom-in [&>.aui-thread-root]:bg-inherit"
98+
className="aui-root aui-modal-content relative z-50 h-[500px] w-[400px] overflow-clip rounded-xl border bg-popover p-0 text-popover-foreground shadow-md outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:slide-out-to-bottom-1/2 data-[state=closed]:slide-out-to-right-1/2 data-[state=closed]:zoom-out data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-bottom-1/2 data-[state=open]:slide-in-from-right-1/2 data-[state=open]:zoom-in [&>.aui-thread-root]:bg-inherit"
9299
>
100+
{/* 右上角关闭按钮,issue #285:用户找不到关闭模态框的显式入口 */}
101+
<button
102+
type="button"
103+
onClick={handleCloseModal}
104+
aria-label="Close assistant"
105+
className="aui-modal-close absolute top-2 right-2 z-10 inline-flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
106+
>
107+
<XIcon className="size-4" />
108+
<span className="sr-only">Close assistant</span>
109+
</button>
93110
<Thread
94111
errorMessage={errorMessage}
95112
showSettingsAction={showSettingsAction}

lib/ai/providers/intern.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,33 @@
11
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
22

33
/**
4-
* Create Intern-AI model instance
5-
* Uses environment variable INTERN_KEY for API key
6-
* 在开发环境下,临时映射到 Deepseek 进行测试
7-
* @returns Configured Intern-AI model instance
4+
* 免费聊天模型:智谱 GLM-4.6V-Flash
5+
*
6+
* 历史原因:此前默认走上海 AI Lab 的书生 Intern-S1,现因 key 过期改接智谱
7+
* GLM-4.6V-Flash(同样免费、128K 上下文、支持多模态)。为避免破坏用户
8+
* localStorage 里已有的 provider="intern" 设置,函数/Provider 名保留 `intern`
9+
* 作为语义上的"免费兜底模型"标识。
10+
*
11+
* 开发环境仍可通过 DEEPSEEK_API_KEY 快速切换到 Deepseek 接口做调试。
12+
* 生产环境需要在服务端配置 ZHIPU_API_KEY(智谱 AI 开放平台)。
813
*/
914
export function createInternModel() {
1015
const isDev = process.env.NODE_ENV === "development";
1116

12-
const intern = createOpenAICompatible({
13-
name: "intern",
14-
baseURL: isDev
15-
? "https://api.deepseek.com" // 开发环境临时使用 Deepseek 接口测试
16-
: "https://chat.intern-ai.org.cn/api/v1/",
17-
apiKey: isDev ? process.env.DEEPSEEK_API_KEY : process.env.INTERN_KEY,
17+
if (isDev && process.env.DEEPSEEK_API_KEY) {
18+
const deepseek = createOpenAICompatible({
19+
name: "deepseek",
20+
baseURL: "https://api.deepseek.com",
21+
apiKey: process.env.DEEPSEEK_API_KEY,
22+
});
23+
return deepseek("deepseek-chat");
24+
}
25+
26+
const glm = createOpenAICompatible({
27+
name: "zhipu",
28+
baseURL: "https://open.bigmodel.cn/api/paas/v4/",
29+
apiKey: process.env.ZHIPU_API_KEY,
1830
});
1931

20-
// Use the specific model configured for this project
21-
return intern(isDev ? "deepseek-chat" : "intern-s1");
32+
return glm("glm-4.6v-flash");
2233
}

0 commit comments

Comments
 (0)