feat: 更新 README, 新增快速预设, 调整 AI 默认上下文/输出限制

README:
- 更新顶部说明区块, 新增静态展示资源

快速预设 (设置页):
- 新增预设项, 调整官网链接与介绍文案, 高亮样式适配明暗主题
- 模型输入框支持下拉选择: 经后端代理拉取模型列表, 支持关键词过滤

AI 默认配置:
- 输出上限 8192 → 16384, 上下文窗口 64000 → 128000, 对齐当前主流模型能力
- 后端新增模型列表代理接口, 前端 api 同步封装
This commit is contained in:
shy3130
2026-09-24 11:39:54 +08:00
parent 9c1abd7fe5
commit c37f450e28
10 changed files with 338 additions and 24 deletions
+16
View File
@@ -32,6 +32,22 @@
</div>
<img src="docs/sponsors/sponsor-header.svg" width="240" alt="赞助支持 Sponsor">
<div align="center">
<a href="https://www.runninghub.ai/call-api?source=github&inviteCode=edt5wh7c" target="_blank" rel="noopener noreferrer">
<img src="docs/sponsors/runninghub-banner-v3.svg?v=3" alt="RunningHub API — 单一接口直连 400+ 主流大模型" width="100%">
</a>
<a href="https://www.runninghub.ai/call-api?source=github&inviteCode=edt5wh7c" target="_blank" rel="noopener noreferrer">
<img src="docs/sponsors/runninghub-intro-card.svg?v=4" alt="About RunningHub API — 产品介绍" width="100%">
</a>
**本项目由 [RunningHub](https://www.runninghub.ai/call-api?source=github&inviteCode=edt5wh7c) 提供支持** · 单一接口直连 400+ 主流大模型 · 免费测试
</div>
---
+25 -2
View File
@@ -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:
+6 -5
View File
@@ -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"
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.6 KiB

+54
View File
@@ -0,0 +1,54 @@
<svg width="1200" height="300" viewBox="0 0 1200 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="RunningHub API sponsor banner">
<defs>
<linearGradient id="rGrad" x1="0" y1="0" x2="1" y2="0.6">
<stop offset="0" stop-color="#b8f51e"/>
<stop offset="1" stop-color="#f6ffc4"/>
</linearGradient>
<linearGradient id="ctaGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#b8f51e"/>
<stop offset="1" stop-color="#e9ff9e"/>
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#b8f51e" stop-opacity="0.14"/>
<stop offset="1" stop-color="#b8f51e" stop-opacity="0"/>
</radialGradient>
<linearGradient id="bgGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#0c1105"/>
<stop offset="1" stop-color="#000000"/>
</linearGradient>
<clipPath id="cardClip">
<rect x="0" y="0" width="1200" height="300" rx="24"/>
</clipPath>
</defs>
<rect x="0" y="0" width="1200" height="300" rx="24" fill="url(#bgGrad)" stroke="#2a3512" stroke-width="1"/>
<g clip-path="url(#cardClip)">
<ellipse cx="150" cy="150" rx="240" ry="200" fill="url(#glow)"/>
<text x="58" y="240" font-family="'Arial Black','Arial Bold',Arial,sans-serif" font-weight="900" font-size="230" fill="url(#rGrad)">R</text>
<line x1="272" y1="72" x2="272" y2="228" stroke="#3a4a14" stroke-width="1.5"/>
<text x="310" y="88" font-family="Arial,sans-serif" font-size="14" letter-spacing="2" fill="#8a9a72">BUILT FOR PRODUCTION · 为生产环境而设计</text>
<text x="310" y="136" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="42" font-weight="700" fill="#ffffff">RunningHub <tspan fill="url(#rGrad)">API</tspan></text>
<rect x="740" y="100" width="196" height="44" rx="22" fill="none" stroke="#b8f51e" stroke-opacity="0.55"/>
<text x="838" y="129" text-anchor="middle" font-family="'PingFang SC','Microsoft YaHei',sans-serif" font-size="19" font-weight="600" fill="#d7f75a">400+ 主流大模型</text>
<text x="310" y="182" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',sans-serif" font-size="20" fill="#c9d1bd">单一接口直连 400+ 主流大模型,图片 / 视频 / AI 内容一秒生成</text>
<g font-family="'PingFang SC','Microsoft YaHei',sans-serif" font-size="15" fill="#a8b894">
<rect x="310" y="206" width="176" height="34" rx="17" fill="#11170a" stroke="#2c3a13"/>
<text x="398" y="228" text-anchor="middle">ComfyUI 免运维托管</text>
<rect x="502" y="206" width="128" height="34" rx="17" fill="#11170a" stroke="#2c3a13"/>
<text x="566" y="228" text-anchor="middle">按需弹性计量</text>
<rect x="646" y="206" width="216" height="34" rx="17" fill="#11170a" stroke="#2c3a13"/>
<text x="754" y="228" text-anchor="middle">细粒度权限 · 全链路加密</text>
</g>
<rect x="948" y="121" width="212" height="58" rx="29" fill="url(#ctaGrad)"/>
<text x="1054" y="158" text-anchor="middle" font-family="'PingFang SC','Microsoft YaHei',sans-serif" font-size="21" font-weight="700" fill="#0a0f02">立即接入 API →</text>
<text x="1054" y="228" text-anchor="middle" font-family="Arial,sans-serif" font-size="15" fill="#7d8a6a">runninghub.ai</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

+43
View File
@@ -0,0 +1,43 @@
<svg width="1200" height="294" viewBox="0 0 1200 294" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="RunningHub API 特性与优惠">
<defs>
<linearGradient id="bgGrad" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#0c1105"/>
<stop offset="1" stop-color="#000000"/>
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#CCFF00" stop-opacity="0.13"/>
<stop offset="1" stop-color="#CCFF00" stop-opacity="0"/>
</radialGradient>
<linearGradient id="ctaGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0" stop-color="#CCFF00"/>
<stop offset="1" stop-color="#F5FFCD"/>
</linearGradient>
<clipPath id="cardClip">
<rect x="0" y="0" width="1200" height="294" rx="24"/>
</clipPath>
<linearGradient id="gradOfficial" x1="114.561" y1="307.02" x2="503.424" y2="307.02" gradientUnits="userSpaceOnUse">
<stop stop-color="#CCFF00"/>
<stop offset="1" stop-color="#F5FFCD"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="1200" height="294" rx="24" fill="url(#bgGrad)" stroke="#2a3512" stroke-width="1"/>
<g clip-path="url(#cardClip)">
<text x="80" y="42" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="18" font-weight="700" fill="#ffffff">RunningHub API</text>
<line x1="80" y1="56" x2="1120" y2="56" stroke="#232c11" stroke-width="1"/>
<circle cx="88" cy="83" r="3.5" fill="#CCFF00"/>
<text x="104" y="88" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="14" fill="#d6ddcc"><tspan font-weight="700" fill="#CCFF00">Seedance</tspan> 满血版 API 接入<tspan font-weight="700" fill="#CCFF00">全网骨折价</tspan>,在线工具低至 <tspan font-weight="700" fill="#CCFF00">0.21元/秒</tspan>,节省 <tspan font-weight="700" fill="#CCFF00">60%-80%成本</tspan>!</text>
<circle cx="88" cy="108" r="3.5" fill="#CCFF00"/>
<text x="104" y="113" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="14" fill="#d6ddcc">超稳定高并发高<tspan font-weight="700" fill="#CCFF00">折扣</tspan>,随用随充按需弹性付费,不捆绑不排队不抽卡</text>
<circle cx="88" cy="133" r="3.5" fill="#CCFF00"/>
<text x="104" y="138" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="14" fill="#d6ddcc">无限画布、ComfyUI、Agent 等在线工具从剧本到成片全链路制作</text>
<circle cx="88" cy="158" r="3.5" fill="#CCFF00"/>
<text x="104" y="163" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="14" fill="#d6ddcc">一站式接入 LLM API、多模态 API、工作流 API、AI 应用</text>
<circle cx="88" cy="183" r="3.5" fill="#CCFF00"/>
<text x="104" y="188" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="14" fill="#d6ddcc">400+ 主流模型<tspan font-weight="700" fill="#CCFF00">(支持国际版权限)</tspan>8000+ 版本<tspan font-weight="700" fill="#CCFF00">任选</tspan></text>
<circle cx="88" cy="208" r="3.5" fill="#CCFF00"/>
<text x="104" y="213" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="14" fill="#d6ddcc">数千家企业信赖,<tspan font-weight="700" fill="#CCFF00">500万 C 端月活</tspan>验证</text>
<line x1="80" y1="238" x2="1120" y2="238" stroke="#232c11" stroke-width="1"/>
<text x="80" y="266" font-family="'PingFang SC','Microsoft YaHei','Noto Sans SC',Arial,sans-serif" font-size="14" fill="#a8b894">RunningHub 折扣获取与免费测试联系 · <tspan font-family="Arial,sans-serif" font-weight="600" fill="#CCFF00">zhenyuedong@haima.me</tspan></text>
<text x="1120" y="266" text-anchor="end" font-family="Arial,sans-serif" font-size="13" fill="#7d8a6a">runninghub.ai</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg width="240" height="56" viewBox="0 0 240 56" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="赞助支持 Sponsor">
<rect x="0" y="4" width="240" height="48" rx="24" fill="#0c1105" stroke="#2c3a13"/>
<text x="22" y="34" font-family="'PingFang SC','Microsoft YaHei',Arial,sans-serif" font-size="19" font-weight="700" fill="#f2f5ec">赞助支持 <tspan fill="#CCFF00" font-size="14">· SPONSOR</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 433 B

+4
View File
@@ -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<Preferences>('/api/settings/preferences'),
dataSources: () => request<DataSourcesResponse>('/api/settings/data-sources'),
capabilityMatrix: () => request<CapabilityMatrix>('/api/settings/capability-matrix'),
+90 -17
View File
@@ -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+ 主流大模型,<span className="font-medium text-amber-600 dark:text-amber-400">Claude、ChatGPT、Gemini</span> 等国际模型直连稳定不掉线,注册即享折扣优惠。通过下方链接注册即为项目提供赞助支持。</> },
{ 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<string[]>([])
const [modelFilter, setModelFilter] = useState('')
const modelBoxRef = useRef<HTMLDivElement | null>(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<SettingsState>(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'}`}>
<div className="flex items-center gap-1.5 text-xs font-medium">
<span>{p.label}</span>
{p.sponsor && <span className="rounded-full border border-amber-500/40 bg-amber-400/15 px-1.5 py-px text-[9px] font-semibold leading-none text-amber-600 dark:border-amber-400/40 dark:text-amber-400">优惠</span>}
{p.provider === CODEX_PROVIDER && <Terminal className="h-3 w-3" />}
</div>
</button>
@@ -360,7 +400,7 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) {
</div>
{selectedPreset.website && (
<a href={selectedPreset.website} target="_blank" rel="noreferrer"
className="mt-1 inline-flex items-center gap-1 text-muted hover:text-accent transition-colors">
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}
<ExternalLink className="h-3 w-3" />
</a>
@@ -411,10 +451,43 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) {
<>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Field label="API 地址">
<input type="text" value={baseUrl} onChange={e => handleBaseUrlChange(e.target.value)} placeholder="https://api.zhaji.dev/v1" className={INPUT_CLS} />
<input type="text" value={baseUrl} onChange={e => handleBaseUrlChange(e.target.value)} placeholder="https://llm.runninghub.ai/v1" className={INPUT_CLS} />
</Field>
<Field label="模型">
<input type="text" value={model} onChange={e => handleModelChange(e.target.value)} placeholder="gpt-5.6-sol" className={INPUT_CLS} />
<div ref={modelBoxRef} className="relative">
<input type="text" value={model} onChange={e => handleModelChange(e.target.value)} placeholder="gpt-5.6-sol" className={`${INPUT_CLS} ${selectedPreset?.sponsor ? 'pr-9' : ''}`} />
{selectedPreset?.sponsor && (
<button type="button" onClick={fetchModelOptions} aria-label="获取模型列表"
className="absolute right-1.5 top-1/2 flex h-6 w-7 -translate-y-1/2 items-center justify-center rounded-md border border-border/40 bg-base text-muted transition-colors hover:border-accent/40 hover:text-accent">
{modelsLoading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <ChevronDown className="h-3.5 w-3.5" />}
</button>
)}
{selectedPreset?.sponsor && modelsOpen && (
<div className="absolute z-20 mt-1 w-full rounded-lg border border-border/40 bg-base shadow-lg">
<div className="border-b border-border/30 p-2">
<input type="text" value={modelFilter} onChange={e => 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" />
</div>
<div className="max-h-56 overflow-y-auto py-1">
{modelsLoading ? (
<div className="flex items-center justify-center gap-1.5 py-6 text-xs text-muted">
<Loader2 className="h-3.5 w-3.5 animate-spin" />加载中...
</div>
) : modelsError ? (
<div className="px-3 py-4 text-center text-xs text-muted">获取失败: {modelsError}</div>
) : filteredModelOptions.length === 0 ? (
<div className="px-3 py-4 text-center text-xs text-muted">{modelOptions.length ? '无匹配模型' : '未获取到模型'}</div>
) : filteredModelOptions.map(id => (
<button key={id} type="button" onClick={() => { handleModelChange(id); setModelsOpen(false) }}
className={`block w-full truncate px-3 py-1.5 text-left font-mono text-xs transition-colors hover:bg-accent/10 ${model === id ? 'text-accent' : 'text-secondary'}`}>
{id}
</button>
))}
</div>
</div>
)}
</div>
</Field>
</div>
@@ -468,11 +541,11 @@ export function SettingsAIPanel({ highlight }: { highlight?: string } = {}) {
<div className="border-t border-border/20 pt-4">
<div className="grid grid-cols-2 gap-4">
<Field label="输出上限 max_tokens" hint="所有 AI 任务的输出 token 上限, 任务请求会被钳制到此值; 默认 8192">
<input type="number" min={1} value={maxOutputTokens} onChange={e => setMaxOutputTokens(e.target.value)} placeholder="8192" className={INPUT_CLS} />
<Field label="输出上限 max_tokens" hint="所有 AI 任务的输出 token 上限, 任务请求会被钳制到此值; 默认 16384">
<input type="number" min={1} value={maxOutputTokens} onChange={e => setMaxOutputTokens(e.target.value)} placeholder="16384" className={INPUT_CLS} />
</Field>
<Field label="上下文窗口 (输入上限)" hint="输入估算超出此窗口时会报错并提示调大; 默认 64000">
<input type="number" min={1} value={contextWindow} onChange={e => setContextWindow(e.target.value)} placeholder="64000" className={INPUT_CLS} />
<Field label="上下文窗口 (输入上限)" hint="输入估算超出此窗口时会报错并提示调大; 默认 128000">
<input type="number" min={1} value={contextWindow} onChange={e => setContextWindow(e.target.value)} placeholder="128000" className={INPUT_CLS} />
</Field>
</div>
</div>