feat(skill-evolution): expose model_name as per-user configurable field

前端恢复模型选择器,后端允许 model_name 写入 per-user skill_evolution.json。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
xuhdandClaude Sonnet 4.6 committed 2026-06-05 13:32:19 +08:00
1 parent 603d667608
commit 2c2650ff07
4 files changed
+49 -3

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() {
@@ -269,13 +277,17 @@ interface CuratorDraft {
interval_hours: number;
stale_after_days: number;
archive_after_days: number;
model_name: string | null;
}
const DEFAULT_MODEL_VALUE = "__default__";
function toDraft(config: CuratorConfig): CuratorDraft {
return {
interval_hours: config.curator.interval_hours,
stale_after_days: config.curator.stale_after_days,
archive_after_days: config.curator.archive_after_days,
model_name: config.curator.model_name,
};
}
@@ -284,6 +296,7 @@ function ConfigCard() {
const tt = t.settings.skillEvolution.config;
const { config, isLoading } = useCuratorConfig();
const updateConfig = useUpdateCuratorConfig();
const { models } = useModels();
const [draft, setDraft] = useState<CuratorDraft | null>(null);
useEffect(() => {
@@ -368,6 +381,36 @@ function ConfigCard() {
}
/>
</FieldRow>
<FieldRow
label={tt.curatorModelLabel}
helper={tt.curatorModelHelper}
>
<Select
value={draft.model_name ?? DEFAULT_MODEL_VALUE}
onValueChange={(v) =>
setDraft({ ...draft, model_name: v === DEFAULT_MODEL_VALUE ? null : v })
}
>
<SelectTrigger
className={cn(
"max-w-[20rem]",
"dark:border-[#1B477E] dark:bg-[#0D2D54] dark:text-[#AACEFF] dark:data-[placeholder]:text-[#566B7F] dark:hover:bg-[#0D2D54]",
)}
>
<SelectValue />
</SelectTrigger>
<SelectContent position="popper" side="bottom" sideOffset={4}>
<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">
<Button
+1
View File
@@ -21,6 +21,7 @@ export interface CuratorConfigUpdatePayload {
interval_hours: number;
stale_after_days: number;
archive_after_days: number;
model_name: string | null;
}
export async function updateCuratorConfig(
@@ -2329,14 +2329,15 @@ class CuratorConfigResponse(BaseModel):
class CuratorConfigRequest(BaseModel):
"""Payload for updating the three user-facing curator thresholds in skill_evolution.json.
"""Payload for updating user-facing curator settings in skill_evolution.json.
All other settings (enabled, model, timeouts) are controlled via config.yaml.
All other settings (enabled, timeouts) are global-only and controlled via config.yaml.
"""
interval_hours: int = Field(..., ge=1, description="Hours between curator runs.")
stale_after_days: int = Field(..., ge=1, description="Days of inactivity before a skill is marked stale.")
archive_after_days: int = Field(..., ge=1, description="Days of inactivity before a stale skill is archived.")
model_name: str | None = Field(default=None, description="Model for the curator LLM review; null = primary model.")
class CuratorLogRecord(BaseModel):
@@ -2394,6 +2395,7 @@ async def update_curator_config(body: CuratorConfigRequest, request: Request) ->
"interval_hours": body.interval_hours,
"stale_after_days": body.stale_after_days,
"archive_after_days": body.archive_after_days,
"model_name": body.model_name,
},
})
return _build_curator_config_response(load_skill_evolution_config_for_user(user_id))
@@ -137,7 +137,7 @@ def resolve_user_path(user_id: str) -> Path:
# Only these curator sub-fields may be overridden per user.
# All other fields (enabled, model_name, timeouts, etc.) are global-only.
_USER_ALLOWED_CURATOR_KEYS = {"interval_hours", "stale_after_days", "archive_after_days"}
_USER_ALLOWED_CURATOR_KEYS = {"interval_hours", "stale_after_days", "archive_after_days", "model_name"}
def load_user_overrides(user_id: str) -> dict[str, Any] | None: