forked from boos/BoosAPI
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user