This commit is contained in:
董宏凯 committed 2026-05-16 22:03:41 +08:00
commit 4533755704
16 files changed
+448 -65

No files matched your search

@@ -21,6 +21,13 @@ import {
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import {
useCuratorConfig,
@@ -31,6 +38,7 @@ import {
} from "@/core/curator/hooks";
import type { CuratorConfig } from "@/core/curator/types";
import { useI18n } from "@/core/i18n/hooks";
import { useModels } from "@/core/models/hooks";
import { cn } from "@/lib/utils";
export function SkillEvolutionPanel() {
@@ -68,6 +76,7 @@ function StatusCard() {
const { t } = useI18n();
const tt = t.settings.skillEvolution.status;
const { status, isLoading } = useCuratorStatus();
const { config } = useCuratorConfig();
const runCurator = useRunCurator();
const lastRunDisplay = useMemo(() => {
@@ -79,10 +88,25 @@ function StatusCard() {
}
}, [status?.last_run_at, tt.lastRunNever]);
// 总开关或 curator 子开关任一关闭时,手动触发跑不起来 —— 直接禁用按钮。
const runDisabled = !config || !config.enabled || !config.curator.enabled;
const handleRun = async () => {
try {
await runCurator.mutateAsync();
toast.success(tt.runStarted);
const res = await runCurator.mutateAsync();
if (res.triggered) {
toast.success(tt.runStarted);
} else {
// 后端预检拦下了(开关未开 / 已有任务在跑)—— 给出对应原因,而不是误报"已开始"。
const reasonMsg: Record<string, string> = {
evolution_disabled: tt.runBlockedEvolutionDisabled,
curator_disabled: tt.runBlockedCuratorDisabled,
already_running: tt.runBlockedAlreadyRunning,
};
toast.warning(
reasonMsg[res.reason ?? ""] ?? tt.runBlockedEvolutionDisabled,
);
}
} catch (err) {
toast.error(err instanceof Error ? err.message : String(err));
}
@@ -107,7 +131,8 @@ function StatusCard() {
<Button
size="sm"
onClick={handleRun}
disabled={runCurator.isPending}
disabled={runCurator.isPending || runDisabled}
title={runDisabled ? tt.runDisabledHint : undefined}
>
{runCurator.isPending ? (
<Loader2Icon className="size-4 animate-spin" />
@@ -245,11 +270,15 @@ function StatRow({
);
}
/** Select 不允许空字符串 value,用此哨兵代表"用主模型"(对应 model_name=null)。 */
const DEFAULT_MODEL_VALUE = "__default__";
function ConfigCard() {
const { t } = useI18n();
const tt = t.settings.skillEvolution.config;
const { config, isLoading } = useCuratorConfig();
const updateConfig = useUpdateCuratorConfig();
const { models } = useModels();
const [draft, setDraft] = useState<CuratorConfig | null>(null);
useEffect(() => {
@@ -378,22 +407,35 @@ function ConfigCard() {
/>
</FieldRow>
<FieldRow
label={tt.moderationModelLabel}
helper={tt.moderationModelHelper}
label={tt.curatorModelLabel}
helper={tt.curatorModelHelper}
>
<Input
type="text"
className="max-w-[20rem]"
value={draft.moderation_model_name ?? ""}
placeholder=""
onChange={(e) =>
<Select
value={draft.curator.model_name ?? DEFAULT_MODEL_VALUE}
onValueChange={(v) =>
setDraft({
...draft,
moderation_model_name:
e.target.value.trim() === "" ? null : e.target.value,
curator: {
...draft.curator,
model_name: v === DEFAULT_MODEL_VALUE ? null : v,
},
})
}
/>
>
<SelectTrigger className="max-w-[20rem]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={DEFAULT_MODEL_VALUE}>
{tt.curatorModelDefault}
</SelectItem>
{models.map((m) => (
<SelectItem key={m.name} value={m.name}>
{m.display_name || m.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FieldRow>
<div className="flex justify-end gap-2 pt-2">