feat: 新站点创建后显示选择对话框 (#302)

* feat: 实现全局模型白名单功能

## 功能概述
实现全局模型白名单,允许管理员配置只启用特定模型的路由,实现精细化的模型访问控制。

## 主要变更

### 后端实现
- config.ts: 新增 globalAllowedModels 配置项
- settings.ts:
  - 新增 RuntimeSettingsBody 类型支持
  - 实现数据库加载和持久化
  - 配置变更自动触发路由重建
- modelService.ts:
  - 在路由重建中添加白名单过滤逻辑
  - 大小写不敏感匹配,自动trim空格
  - 空白名单时允许所有模型(向后兼容)
- stats.ts:
  - 候选API过滤 models/modelsWithoutToken/modelsMissingTokenGroups

### 前端实现
- Settings.tsx:
  - 新增"全局模型白名单"配置卡片
  - 支持手动输入和点击选择两种添加方式
  - 绿色徽章显示已选模型,支持删除
  - 保存后自动触发路由重建

## 技术特性
- ✅ 向后兼容(默认为空数组,允许所有模型)
- ✅ 大小写不敏感匹配
- ✅ 自动trim和去重
- ✅ 输入验证(类型检查、空值过滤)
- ✅ 配置变更自动重建路由

## 测试验证
- 白名单为空时所有模型可见
- 白名单有值时只显示白名单模型
- 路由重建正确过滤模型
- 候选API正确返回过滤结果
- 前端UI交互正常

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: 站点创建后显示选择对话框

新站点创建后不再根据平台类型自动跳转,而是显示一个选择对话框让用户决定下一步操作:
- 添加账号(用户名密码登录)
- 添加 API Key
- 稍后配置

对于 codex 平台,选择"添加账号"时会跳转到 OAuth 流程,其他平台则跳转到账号管理页面。

这样可以避免用户被强制跳转,提供更灵活的配置选择。

* fix: add getModelTokenCandidates mock to Settings tests

* fix: add getModelTokenCandidates mock to proxy-transport test

---------

Co-authored-by: Ding <ding@local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Delicious233
2026-03-30 18:51:23 +08:00
committed by GitHub
parent fa25a501a8
commit 9212b765cd
3 changed files with 239 additions and 21 deletions
+83
View File
@@ -0,0 +1,83 @@
import { useEffect, useRef } from 'react';
type NextStepChoice = 'session' | 'apikey' | 'later';
type Props = {
siteName: string;
platform?: string | null;
onChoice: (choice: NextStepChoice) => void;
onClose: () => void;
};
export default function SiteCreatedModal({ siteName, platform, onChoice, onClose }: Props) {
const dialogRef = useRef<HTMLDialogElement>(null);
useEffect(() => {
const dialog = dialogRef.current;
if (!dialog) return;
if (!dialog.open) {
dialog.showModal();
}
return () => {
if (dialog.open) {
dialog.close();
}
};
}, []);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault();
onChoice('later');
}
};
return (
<dialog
ref={dialogRef}
className="modal"
onKeyDown={handleKeyDown}
onClick={(e) => {
if (e.target === dialogRef.current) {
onChoice('later');
}
}}
>
<div className="modal-box" style={{ maxWidth: 480 }}>
<h3 className="font-bold text-lg mb-2">
站点创建成功
</h3>
<p className="py-2 text-sm" style={{ color: 'var(--color-text-muted)' }}>
站点 <strong>"{siteName}"</strong> 已添加成功。接下来您想做什么?
</p>
<div className="modal-action" style={{ flexDirection: 'column', gap: 12 }}>
<button
className="btn btn-primary btn-block"
onClick={() => onChoice('session')}
>
添加账号(用户名密码登录)
</button>
<button
className="btn btn-outline btn-block"
onClick={() => onChoice('apikey')}
>
添加 API Key
</button>
<button
className="btn btn-ghost btn-block"
onClick={() => onChoice('later')}
>
稍后配置
</button>
</div>
<p className="text-xs mt-3" style={{ color: 'var(--color-text-muted)' }}>
提示:您可以随时在"站点管理"页面配置账号信息
</p>
</div>
</dialog>
);
}