- 作品库卡片重设计:书脊封面字块 + 衬线书名 + 一句话故事(缺失给占位) + 元信息行(题材徽章/StatusDot 待审/mono 时间),Card tone=card 浮起消塌陷 - EmptyState 中性化 + inline/card/bare 变体 + eyebrow(向后兼容) - loading 骨架化(Skeleton 拼作品库结构,motion-safe) - 设置页拆分 ProvidersSettings 489→198 + 5 个子组件,凭据点用 StatusDot - 次级页(向导/模板/codex/大纲)排版字阶与圆角 token 收敛
127 lines
4.1 KiB
TypeScript
127 lines
4.1 KiB
TypeScript
"use client";
|
||
|
||
import { Save } from "lucide-react";
|
||
|
||
import { Button } from "@/components/ui/Button";
|
||
import { SectionHeader } from "@/components/ui/SectionHeader";
|
||
import { Select } from "@/components/ui/Select";
|
||
import { StatusNote } from "@/components/ui/StatusNote";
|
||
import { TextInput } from "@/components/ui/TextInput";
|
||
import {
|
||
KNOWN_PROVIDERS,
|
||
TIER_LABELS,
|
||
type RoutingDraft,
|
||
} from "@/lib/settings/providers";
|
||
|
||
// 档位路由「模型」输入框的候选 model id(datalist 建议,仍可自由输入)。按 provider 分组。
|
||
const MODEL_SUGGESTIONS: Record<string, readonly string[]> = {
|
||
anthropic: ["claude-opus-4", "claude-sonnet-4", "claude-3-5-haiku"],
|
||
deepseek: ["deepseek-chat", "deepseek-reasoner"],
|
||
kimi: ["moonshot-v1-128k", "moonshot-v1-32k", "kimi-k2"],
|
||
openai: ["gpt-4o", "gpt-4o-mini", "o3-mini"],
|
||
qwen: ["qwen-max", "qwen-plus", "qwen-turbo"],
|
||
glm: ["glm-4-plus", "glm-4-air", "glm-4-flash"],
|
||
gemini: ["gemini-2.0-flash", "gemini-1.5-pro"],
|
||
"kimi-code-key": ["kimi-for-coding"],
|
||
"kimi-code": ["kimi-for-coding"],
|
||
};
|
||
|
||
interface RoutingPanelProps {
|
||
routing: RoutingDraft[];
|
||
saving: boolean;
|
||
onProviderChange: (tier: string, providerId: string) => void;
|
||
onModelChange: (tier: string, model: string) => void;
|
||
onSave: () => void;
|
||
}
|
||
|
||
// 能力档位路由面板:写手/分析/轻量三档各指向一个 provider+model(UX §6.10)。
|
||
export function RoutingPanel({
|
||
routing,
|
||
saving,
|
||
onProviderChange,
|
||
onModelChange,
|
||
onSave,
|
||
}: RoutingPanelProps) {
|
||
return (
|
||
<section className="min-w-0">
|
||
<SectionHeader
|
||
eyebrow="路由"
|
||
title="能力档位路由"
|
||
description="写手、分析、轻量三类能力可分别指向不同模型。保存时只提交完整填写的行。"
|
||
/>
|
||
<StatusNote className="mt-3" variant="info">
|
||
写章、审稿和摘要提炼会分别读取对应档位;未完整填写的行不会覆盖现有路由。
|
||
</StatusNote>
|
||
<ul className="mt-4 divide-y divide-line rounded border border-line bg-panel">
|
||
{routing.map((row) => (
|
||
<RoutingRow
|
||
key={row.tier}
|
||
row={row}
|
||
onProviderChange={onProviderChange}
|
||
onModelChange={onModelChange}
|
||
/>
|
||
))}
|
||
</ul>
|
||
<div className="mt-3 flex justify-end">
|
||
<Button
|
||
onClick={onSave}
|
||
disabled={saving}
|
||
variant="primary"
|
||
size="sm"
|
||
>
|
||
<Save className="h-4 w-4" aria-hidden="true" />
|
||
{saving ? "保存中…" : "保存档位路由"}
|
||
</Button>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|
||
|
||
interface RoutingRowProps {
|
||
row: RoutingDraft;
|
||
onProviderChange: (tier: string, providerId: string) => void;
|
||
onModelChange: (tier: string, model: string) => void;
|
||
}
|
||
|
||
function RoutingRow({ row, onProviderChange, onModelChange }: RoutingRowProps) {
|
||
const tierLabel = TIER_LABELS[row.tier] ?? row.tier;
|
||
return (
|
||
<li className="grid gap-3 px-4 py-3 text-sm md:grid-cols-[6rem_minmax(10rem,14rem)_1fr]">
|
||
<span className="self-center text-ink">{tierLabel}</span>
|
||
<label className="sr-only" htmlFor={`route-provider-${row.tier}`}>
|
||
{tierLabel} 提供商
|
||
</label>
|
||
<Select
|
||
id={`route-provider-${row.tier}`}
|
||
value={row.provider}
|
||
onChange={(e) => onProviderChange(row.tier, e.target.value)}
|
||
>
|
||
<option value="">(未配置)</option>
|
||
{KNOWN_PROVIDERS.map((p) => (
|
||
<option key={p.id} value={p.id}>
|
||
{p.label}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
<label className="sr-only" htmlFor={`route-model-${row.tier}`}>
|
||
{tierLabel} 模型
|
||
</label>
|
||
<div className="min-w-0">
|
||
<TextInput
|
||
id={`route-model-${row.tier}`}
|
||
value={row.model}
|
||
onChange={(e) => onModelChange(row.tier, e.target.value)}
|
||
placeholder="model"
|
||
className="w-full font-mono"
|
||
list={`route-models-${row.tier}`}
|
||
/>
|
||
<datalist id={`route-models-${row.tier}`}>
|
||
{(MODEL_SUGGESTIONS[row.provider] ?? []).map((m) => (
|
||
<option key={m} value={m} />
|
||
))}
|
||
</datalist>
|
||
</div>
|
||
</li>
|
||
);
|
||
}
|