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:
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
|
||||
|
||||
@@ -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))
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user