diff --git a/backend/app/api/settings.py b/backend/app/api/settings.py index deac8a1..1e0700a 100644 --- a/backend/app/api/settings.py +++ b/backend/app/api/settings.py @@ -57,7 +57,6 @@ def get_settings() -> dict: "ai_api_key_masked": secrets_store.mask(secrets_store.get_ai_key()), "has_ai_key": bool(secrets_store.get_ai_key()), "ai_model": secrets_store.get_ai_config("ai_model", settings.ai_model), - "ai_daily_token_budget": int(secrets_store.get_ai_config("ai_daily_token_budget", str(settings.ai_daily_token_budget)) or settings.ai_daily_token_budget), "ai_user_agent": secrets_store.get_ai_config("ai_user_agent", settings.ai_user_agent), } @@ -212,7 +211,6 @@ class AiSettingsIn(BaseModel): base_url: str = "" api_key: str | None = None model: str = "" - daily_token_budget: int = 500_000 user_agent: str = "" @@ -238,8 +236,6 @@ def save_ai_settings(req: AiSettingsIn) -> dict: if req.model: updates["ai_model"] = req.model settings.ai_model = req.model - updates["ai_daily_token_budget"] = req.daily_token_budget - settings.ai_daily_token_budget = req.daily_token_budget # user_agent 允许清空(回到默认浏览器 UA),故无条件持久化 updates["ai_user_agent"] = req.user_agent settings.ai_user_agent = req.user_agent diff --git a/backend/app/config.py b/backend/app/config.py index 922410d..16ee8f9 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -70,7 +70,6 @@ class Settings(BaseSettings): ai_base_url: str = "https://api.alysc.top" ai_api_key: str = "" ai_model: str = "gpt-5.5" - ai_daily_token_budget: int = 5_000_000 # 默认浏览器风格 UA,绕过 Cloudflare 等 CDN/WAF 的 Bot 拦截(Issue #8)。 # 用户可在 AI 设置页按需修改。 ai_user_agent: str = ( diff --git a/backend/uv.lock b/backend/uv.lock index c6b9e52..09cb06f 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -2470,7 +2470,7 @@ all = [ [[package]] name = "tickflow-stock-panel-backend" -version = "0.1.35" +version = "0.1.44" source = { editable = "." } dependencies = [ { name = "apscheduler" }, diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 72a4e00..298aa4e 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -565,7 +565,6 @@ export interface SettingsState { ai_api_key_masked: string has_ai_key: boolean ai_model: string - ai_daily_token_budget: number ai_user_agent: string } @@ -637,7 +636,7 @@ export const api = { ), /** 保存 AI 配置 */ - saveAiSettings: (ai: { provider?: string; base_url?: string; api_key?: string; model?: string; daily_token_budget?: number; user_agent?: string }) => + saveAiSettings: (ai: { provider?: string; base_url?: string; api_key?: string; model?: string; user_agent?: string }) => request<{ ok: boolean }>('/api/settings/ai', { method: 'POST', body: JSON.stringify(ai), diff --git a/frontend/src/pages/settings/AI.tsx b/frontend/src/pages/settings/AI.tsx index 407c7d6..172b53e 100644 --- a/frontend/src/pages/settings/AI.tsx +++ b/frontend/src/pages/settings/AI.tsx @@ -1,13 +1,22 @@ import { useState, useEffect } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' -import { Save, Loader2, Check, Wifi, WifiOff, Eye, EyeOff, Shield, Shuffle } from 'lucide-react' +import { + Save, Loader2, Check, Wifi, WifiOff, Eye, EyeOff, Shield, + Shuffle, Plug, Zap, Settings2, ExternalLink, +} from 'lucide-react' import { useSettings } from '@/lib/useSharedQueries' import { api } from '@/lib/api' import { QK } from '@/lib/queryKeys' +// 统一的输入框样式(与项目其他设置页一致) +const INPUT_CLS = + 'w-full h-9 px-2.5 rounded-lg bg-base border-0 ring-1 ring-border/30 text-xs font-mono text-foreground placeholder:text-muted/30 focus:outline-none focus:ring-2 focus:ring-accent/30 transition-shadow' + const PRESETS: { label: string; url: string; model: string; website: string; websiteLabel: string; description: string; partner?: boolean; promo?: string }[] = [ - { label: 'DeepSeek', url: 'https://api.deepseek.com/v1', model: 'deepseek-chat', website: 'https://www.deepseek.com/', websiteLabel: 'deepseek.com', description: 'DeepSeek 官方 OpenAI 兼容接口。' }, - { label: '通义千问', url: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-plus', website: 'https://tongyi.aliyun.com/', websiteLabel: 'tongyi.aliyun.com', description: '阿里云 DashScope 兼容模式接口。' }, + { label: 'DeepSeek', url: 'https://api.deepseek.com', model: 'deepseek-v4-pro', website: 'https://www.deepseek.com/', websiteLabel: 'deepseek.com', description: 'DeepSeek 官方 OpenAI 兼容接口。' }, + { label: '通义千问', url: 'https://dashscope.aliyuncs.com/compatible-mode/v1', model: 'qwen-3.6plus', website: 'https://tongyi.aliyun.com/', websiteLabel: 'tongyi.aliyun.com', description: '阿里云 DashScope 兼容模式接口。' }, + { label: '智谱 GLM', url: 'https://open.bigmodel.cn/api/paas/v4', model: 'glm-5.2', website: 'https://open.bigmodel.cn/', websiteLabel: 'open.bigmodel.cn', description: '智谱 AI 官方 OpenAI 兼容接口。' }, + { label: 'Kimi', url: 'https://api.moonshot.cn/v1', model: 'kimi-k2.6', website: 'https://platform.moonshot.cn/', websiteLabel: 'platform.moonshot.cn', description: '月之暗面 Moonshot 官方 OpenAI 兼容接口,支持超长上下文。' }, { label: '炸鸡中转站', url: 'https://code.alysc.top/v1', model: 'gpt-5.5', website: 'https://code.alysc.top/sign-up?aff=1afk', websiteLabel: 'code.alysc.top', description: 'OpenAI 兼容中转服务,适合直接使用国际模型。', partner: true, promo: '通过链接邀请注册赠送免费额度 · 国际模型最低0.01倍率' }, ] @@ -20,7 +29,6 @@ export function SettingsAIPanel() { const [baseUrl, setBaseUrl] = useState('') const [apiKey, setApiKey] = useState('') const [model, setModel] = useState('') - const [tokenBudget, setTokenBudget] = useState(5_000_000) // 自定义 User-Agent 开关:关闭 → 后端用内置默认浏览器 UA(开箱绕过 CDN 拦截); // 开启 → 用下方文本框的 UA,留空时随机生成。 const [customUa, setCustomUa] = useState(false) @@ -49,7 +57,6 @@ export function SettingsAIPanel() { setProvider(s.ai_provider ?? 'openai_compat') setBaseUrl(s.ai_base_url ?? '') setModel(s.ai_model ?? '') - setTokenBudget(s.ai_daily_token_budget ?? 500_000) // 有已保存的自定义 UA → 开关默认开启;否则关闭(用后端内置默认) const ua = s.ai_user_agent ?? '' setCustomUa(!!ua) @@ -58,7 +65,7 @@ export function SettingsAIPanel() { const save = useMutation({ mutationFn: () => api.saveAiSettings({ - provider, base_url: baseUrl, api_key: apiKey || undefined, model, daily_token_budget: tokenBudget, + provider, base_url: baseUrl, api_key: apiKey || undefined, model, // 关闭开关 → 提交空串,后端回退内置默认 UA user_agent: customUa ? userAgent : '', }), @@ -72,9 +79,9 @@ export function SettingsAIPanel() { setTesting(true); setTestResult(null) try { // 先保存当前配置(不保存 Key 仅用于测试时临时存) - if (apiKey) await api.saveAiSettings({ provider, base_url: baseUrl, api_key: apiKey, model, daily_token_budget: tokenBudget, user_agent: customUa ? userAgent : '' }) + if (apiKey) await api.saveAiSettings({ provider, base_url: baseUrl, api_key: apiKey, model, user_agent: customUa ? userAgent : '' }) const r = await api.strategyAiTest() - setTestResult({ ok: r.ok, msg: r.ok ? `连通成功 · 模型: ${r.model}${r.usage ? ` · 消耗 ${r.usage.prompt + r.usage.completion} tokens` : ''}` : (r.error ?? '未知错误') }) + setTestResult({ ok: r.ok, msg: r.ok ? `连通成功 · 模型: ${r.model}` : (r.error ?? '未知错误') }) } catch (e: any) { setTestResult({ ok: false, msg: String(e?.message ?? '测试失败') }) } finally { setTesting(false) } @@ -86,44 +93,49 @@ export function SettingsAIPanel() { const configured = s?.has_ai_key const selectedPreset = PRESETS.find(p => p.url === baseUrl) + const canSave = !!baseUrl && !!model return ( -