diff --git a/README.md b/README.md index 2f6aba65..5f356da2 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,22 @@ + + +赞助支持 Sponsor + +
+ + + RunningHub API — 单一接口直连 400+ 主流大模型 + + + + About RunningHub API — 产品介绍 + + +**本项目由 [RunningHub](https://www.runninghub.ai/call-api?source=github&inviteCode=edt5wh7c) 提供支持** · 单一接口直连 400+ 主流大模型 · 免费测试 +
--- diff --git a/backend/app/api/settings.py b/backend/app/api/settings.py index 8295e1d4..320cae00 100644 --- a/backend/app/api/settings.py +++ b/backend/app/api/settings.py @@ -372,12 +372,35 @@ def clear_ai_settings() -> dict: settings.ai_model = "" settings.ai_codex_command = "codex" settings.ai_codex_reasoning_effort = "" - settings.ai_max_output_tokens = 8192 - settings.ai_context_window = 64000 + settings.ai_max_output_tokens = 16384 + settings.ai_context_window = 128000 return {"ok": True} +@router.get("/ai/sponsor-models") +async def list_sponsor_models() -> dict: + """代理获取赞助商(RunningHub)的模型列表。 + + 其网关按 Origin 头过滤: 浏览器跨域请求只会拿到国产模型子集, + 服务端请求无 Origin 头可取全量, 故由后端代理转发。 + """ + import httpx + + try: + async with httpx.AsyncClient(timeout=15) as client: + res = await client.get("https://llm.runninghub.ai/v1/models") + res.raise_for_status() + data = res.json() + except Exception as exc: + raise HTTPException(status_code=502, detail=f"获取模型列表失败: {exc}") from exc + models = sorted({ + item.get("id") for item in data.get("data", []) + if isinstance(item, dict) and isinstance(item.get("id"), str) and item["id"] + }) + return {"models": models} + + # ===== 偏好设置 ===== def _realtime_allowed() -> bool: diff --git a/backend/app/config.py b/backend/app/config.py index 4942c8a4..705053a7 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -84,9 +84,9 @@ class Settings(BaseSettings): # AI ai_provider: str = "openai_compat" - ai_base_url: str = "https://api.zhaji.dev/v1" + ai_base_url: str = "https://llm.runninghub.ai/v1" ai_api_key: str = "" - ai_model: str = "gpt-5.5" + ai_model: str = "openai/gpt-6-astra-saver" ai_codex_command: str = "codex" ai_codex_reasoning_effort: str = "" # 默认浏览器风格 UA,绕过 Cloudflare 等 CDN/WAF 的 Bot 拦截(Issue #8)。 @@ -98,9 +98,10 @@ class Settings(BaseSettings): ) # AI 输出上限 (max_tokens) 与输入上下文窗口上限 (约 token)。 # 任务级 max_tokens 会被钳制到 ai_max_output_tokens; 输入估算超出上下文窗口时给出明确报错。 - # 默认 8192 高于所有现有任务 (最多 4500), 避免默认配置反而截断长报告; 可在 AI 设置里调整。 - ai_max_output_tokens: int = 8192 - ai_context_window: int = 64000 + # 钳制仅封顶不放大: 现有任务最多请求 4500, 上调默认值只影响用户自行调高任务上限的场景。 + # 上下文 128000 对齐当前主流模型底线 (GPT/Claude/GLM/DeepSeek/Kimi 均 ≥128k); 可在 AI 设置里调整。 + ai_max_output_tokens: int = 16384 + ai_context_window: int = 128000 # Server host: str = "0.0.0.0" diff --git a/docs/sponsors/runninghub-banner-v2.svg b/docs/sponsors/runninghub-banner-v2.svg new file mode 100644 index 00000000..487bca97 --- /dev/null +++ b/docs/sponsors/runninghub-banner-v2.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BUILT FOR PRODUCTION · 为生产环境而设计 + + 单一接口直连 400+ 主流大模型,图片 / 视频 / AI 内容一秒生成 + + + + ComfyUI 免运维托管 + + 按需弹性计量 + + 细粒度权限 · 全链路加密 + + + + 立即接入 RunningHub API → + + diff --git a/docs/sponsors/runninghub-banner-v3.svg b/docs/sponsors/runninghub-banner-v3.svg new file mode 100644 index 00000000..7781f344 --- /dev/null +++ b/docs/sponsors/runninghub-banner-v3.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + API + + Access 400+ leading AI models via API — Seedance, Kling, MiniMax, Nano Banana, Veo, and more + at highly competitive prices + + + + High-Concurrency Support + + Free API Testing + + End-to-End Encryption + + + + Test the API for Free + runninghub.ai + + diff --git a/docs/sponsors/runninghub-banner.svg b/docs/sponsors/runninghub-banner.svg new file mode 100644 index 00000000..89c7d021 --- /dev/null +++ b/docs/sponsors/runninghub-banner.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + R + + + + BUILT FOR PRODUCTION · 为生产环境而设计 + + RunningHub API + + + 400+ 主流大模型 + + 单一接口直连 400+ 主流大模型,图片 / 视频 / AI 内容一秒生成 + + + + ComfyUI 免运维托管 + + 按需弹性计量 + + 细粒度权限 · 全链路加密 + + + + 立即接入 API → + runninghub.ai + + diff --git a/docs/sponsors/runninghub-intro-card.svg b/docs/sponsors/runninghub-intro-card.svg new file mode 100644 index 00000000..0da53eeb --- /dev/null +++ b/docs/sponsors/runninghub-intro-card.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + RunningHub API + + + Seedance 满血版 API 接入全网骨折价,在线工具低至 0.21元/秒,节省 60%-80%成本! + + 超稳定高并发高折扣,随用随充按需弹性付费,不捆绑不排队不抽卡 + + 无限画布、ComfyUI、Agent 等在线工具从剧本到成片全链路制作 + + 一站式接入 LLM API、多模态 API、工作流 API、AI 应用 + + 400+ 主流模型(支持国际版权限)8000+ 版本任选 + + 数千家企业信赖,500万 C 端月活验证 + + RunningHub 折扣获取与免费测试联系 · zhenyuedong@haima.me + runninghub.ai + + diff --git a/docs/sponsors/sponsor-header.svg b/docs/sponsors/sponsor-header.svg new file mode 100644 index 00000000..488cb3fd --- /dev/null +++ b/docs/sponsors/sponsor-header.svg @@ -0,0 +1,4 @@ + + + 赞助支持 · SPONSOR + diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 0830a9a6..89745282 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -2035,6 +2035,10 @@ export const api = { clearAiSettings: () => request<{ ok: boolean }>('/api/settings/ai', { method: 'DELETE' }), + /** 赞助商(RunningHub)模型列表(后端代理, 规避其网关按 Origin 过滤) */ + sponsorModels: () => + request<{ models: string[] }>('/api/settings/ai/sponsor-models'), + preferences: () => request('/api/settings/preferences'), dataSources: () => request('/api/settings/data-sources'), capabilityMatrix: () => request('/api/settings/capability-matrix'), diff --git a/frontend/src/pages/settings/AI.tsx b/frontend/src/pages/settings/AI.tsx index 727cc19f..80028420 100644 --- a/frontend/src/pages/settings/AI.tsx +++ b/frontend/src/pages/settings/AI.tsx @@ -1,9 +1,9 @@ -import { useState, useEffect, useRef, createContext, useContext } from 'react' +import { useState, useEffect, useRef, createContext, useContext, type ReactNode } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' import { Save, Loader2, Check, Wifi, WifiOff, Eye, EyeOff, Shield, Shuffle, Plug, Zap, Settings2, ExternalLink, Trash2, - Terminal, + Terminal, ChevronDown, } from 'lucide-react' import { useSettings } from '@/lib/useSharedQueries' import { api, type SettingsState } from '@/lib/api' @@ -52,17 +52,17 @@ const codexModelLabel = (model?: string, effort?: string) => { return effortLabel ? `${modelLabel} · ${effortLabel}` : modelLabel } -type AiPreset = { label: string; provider?: string; url: string; model: string; codexCommand?: string; website: string; websiteLabel: string; description: string; custom?: boolean } +type AiPreset = { label: string; provider?: string; url: string; model: string; codexCommand?: string; website: string; websiteLabel: string; description: ReactNode; custom?: boolean; sponsor?: boolean } const PRESETS: AiPreset[] = [ { label: '自定义', url: '', model: '', website: '', websiteLabel: '', description: '不自动填充任何配置,完全手动填写 API 地址、模型和密钥。', custom: true }, + { label: 'RunningHub', url: 'https://llm.runninghub.ai/v1', model: 'openai/gpt-6-astra-saver', website: 'https://www.runninghub.ai/zh-cn/call-api/llm/models?source=github&inviteCode=edt5wh7c', websiteLabel: 'www.runninghub.ai · 赞助', sponsor: true, description: <>本项目赞助商 · OpenAI 兼容中转,单一接口直连 400+ 主流大模型,Claude、ChatGPT、Gemini 等国际模型直连稳定不掉线,注册即享折扣优惠。通过下方链接注册即为项目提供赞助支持。 }, { label: 'OpenAI', provider: OPENAI_PROVIDER, url: 'https://api.openai.com/v1', model: DEFAULT_OPENAI_MODEL, website: 'https://platform.openai.com/', websiteLabel: 'platform.openai.com', description: 'OpenAI 官方接口,可单独配置模型支持的推理强度。' }, { 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.7-code', website: 'https://platform.moonshot.cn/', websiteLabel: 'platform.moonshot.cn', description: '月之暗面 Moonshot 官方 OpenAI 兼容接口,支持超长上下文。' }, { label: 'Codex CLI', provider: CODEX_PROVIDER, url: '', model: DEFAULT_CODEX_MODEL, codexCommand: CODEX_COMMAND, website: 'https://developers.openai.com/codex/noninteractive', websiteLabel: 'codex exec', description: '调用本机 Codex CLI 的 codex exec, 适合已登录 ChatGPT/Codex 的本地环境。' }, - { label: '炸鸡中转站', url: 'https://api.zhaji.dev/v1', model: 'gpt-5.5', website: 'https://api.zhaji.dev', websiteLabel: 'api.zhaji.dev', description: 'OpenAI 兼容中转服务,适合直接使用国际模型。' }, ] const findPreset = (provider: string, baseUrl: string, codexCommand: string) => PRESETS.find(p => { @@ -91,6 +91,13 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { const [showKey, setShowKey] = useState(false) const [saved, setSaved] = useState(false) const [confirmClear, setConfirmClear] = useState(false) + // 赞助商预设的模型列表下拉(/models 接口 + 关键词过滤) + const [modelsOpen, setModelsOpen] = useState(false) + const [modelsLoading, setModelsLoading] = useState(false) + const [modelsError, setModelsError] = useState('') + const [modelOptions, setModelOptions] = useState([]) + const [modelFilter, setModelFilter] = useState('') + const modelBoxRef = useRef(null) const [testing, setTesting] = useState(false) const [testResult, setTestResult] = useState<{ ok: boolean; msg: string } | null>(null) const [selectedPresetLabel, setSelectedPresetLabel] = useState(PRESETS[0].label) @@ -158,8 +165,8 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { const ua = s.ai_user_agent ?? '' setCustomUa(!!ua) setUserAgent(ua) - setMaxOutputTokens(String(s?.ai_max_output_tokens ?? 8192)) - setContextWindow(String(s?.ai_context_window ?? 64000)) + setMaxOutputTokens(String(s?.ai_max_output_tokens ?? 16384)) + setContextWindow(String(s?.ai_context_window ?? 128000)) }, [s]) const payload = () => ({ @@ -220,8 +227,8 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { custom: { baseUrl: '', model: '' }, openai: { baseUrl: 'https://api.openai.com/v1', model: DEFAULT_OPENAI_MODEL }, } - setMaxOutputTokens('8192') - setContextWindow('64000') + setMaxOutputTokens('16384') + setContextWindow('128000') setTestResult(null) qc.setQueryData(QK.settings, prev => prev ? { ...prev, @@ -233,8 +240,8 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { ai_codex_model: '', ai_codex_command: CODEX_COMMAND, ai_codex_reasoning_effort: '', - ai_max_output_tokens: 8192, - ai_context_window: 64000, + ai_max_output_tokens: 16384, + ai_context_window: 128000, has_ai_key: false, ai_configured: false, ai_api_key_masked: '', @@ -292,6 +299,38 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { if (isOpenAIProvider) directDrafts.current.openai.model = value } + // 赞助商预设: 经后端代理拉取 /models 全量模型列表供下拉选择 + // (浏览器直连会被 RunningHub 网关按 Origin 过滤, 只返回国产模型) + const fetchModelOptions = async () => { + setModelsLoading(true) + setModelsError('') + setModelFilter('') + setModelsOpen(true) + try { + const data = await api.sponsorModels() + setModelOptions(data.models) + } catch (error) { + setModelOptions([]) + setModelsError(error instanceof Error ? error.message : '获取模型列表失败') + } finally { + setModelsLoading(false) + } + } + + const filteredModelOptions = modelFilter.trim() + ? modelOptions.filter(id => id.toLowerCase().includes(modelFilter.trim().toLowerCase())) + : modelOptions + + // 下拉打开时点击外部关闭 + useEffect(() => { + if (!modelsOpen) return + const onDocMouseDown = (e: MouseEvent) => { + if (modelBoxRef.current && !modelBoxRef.current.contains(e.target as Node)) setModelsOpen(false) + } + document.addEventListener('mousedown', onDocMouseDown) + return () => document.removeEventListener('mousedown', onDocMouseDown) + }, [modelsOpen]) + const handleTest = async () => { setTesting(true) setTestResult(null) @@ -348,6 +387,7 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { className={`rounded-lg border px-3 py-2 text-left transition-all ${selectedPreset?.label === p.label ? 'border-accent/40 bg-accent/10 text-accent' : 'border-border bg-base text-secondary hover:border-accent/30'}`}>
{p.label} + {p.sponsor && 优惠} {p.provider === CODEX_PROVIDER && }
@@ -360,7 +400,7 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { {selectedPreset.website && ( + className={`mt-1 inline-flex items-center gap-1 transition-colors ${selectedPreset.sponsor ? 'font-medium text-amber-600 hover:text-amber-700 dark:text-amber-400 dark:hover:text-amber-300' : 'text-muted hover:text-accent'}`}> {selectedPreset.websiteLabel} @@ -411,10 +451,43 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) { <>
- handleBaseUrlChange(e.target.value)} placeholder="https://api.zhaji.dev/v1" className={INPUT_CLS} /> + handleBaseUrlChange(e.target.value)} placeholder="https://llm.runninghub.ai/v1" className={INPUT_CLS} /> - handleModelChange(e.target.value)} placeholder="gpt-5.6-sol" className={INPUT_CLS} /> +
+ handleModelChange(e.target.value)} placeholder="gpt-5.6-sol" className={`${INPUT_CLS} ${selectedPreset?.sponsor ? 'pr-9' : ''}`} /> + {selectedPreset?.sponsor && ( + + )} + {selectedPreset?.sponsor && modelsOpen && ( +
+
+ setModelFilter(e.target.value)} + placeholder="搜索模型..." autoFocus + className="h-7 w-full rounded-md bg-base px-2 text-xs ring-1 ring-border/30 focus:outline-none focus:ring-accent/40" /> +
+
+ {modelsLoading ? ( +
+ 加载中... +
+ ) : modelsError ? ( +
获取失败: {modelsError}
+ ) : filteredModelOptions.length === 0 ? ( +
{modelOptions.length ? '无匹配模型' : '未获取到模型'}
+ ) : filteredModelOptions.map(id => ( + + ))} +
+
+ )} +
@@ -468,11 +541,11 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) {
- - setMaxOutputTokens(e.target.value)} placeholder="8192" className={INPUT_CLS} /> + + setMaxOutputTokens(e.target.value)} placeholder="16384" className={INPUT_CLS} /> - - setContextWindow(e.target.value)} placeholder="64000" className={INPUT_CLS} /> + + setContextWindow(e.target.value)} placeholder="128000" className={INPUT_CLS} />