diff --git a/frontend-web/src/components/workspace/skills/skill-evolution-panel.tsx b/frontend-web/src/components/workspace/skills/skill-evolution-panel.tsx index 7371bc92..82fc7b08 100644 --- a/frontend-web/src/components/workspace/skills/skill-evolution-panel.tsx +++ b/frontend-web/src/components/workspace/skills/skill-evolution-panel.tsx @@ -21,13 +21,6 @@ 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, @@ -38,7 +31,6 @@ 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() { @@ -273,20 +265,30 @@ function StatRow({ ); } -/** Select 不允许空字符串 value,用此哨兵代表"用主模型"(对应 model_name=null)。 */ -const DEFAULT_MODEL_VALUE = "__default__"; +interface CuratorDraft { + interval_hours: number; + stale_after_days: number; + archive_after_days: number; +} + +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, + }; +} 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(null); + const [draft, setDraft] = useState(null); useEffect(() => { if (config && !draft) { - setDraft(config); + setDraft(toDraft(config)); } }, [config, draft]); @@ -305,14 +307,12 @@ function ConfigCard() { ); } - const isDirty = - !!config && - JSON.stringify(draft) !== JSON.stringify(config); + const original = config ? toDraft(config) : null; + const isDirty = !!original && JSON.stringify(draft) !== JSON.stringify(original); const handleSave = async () => { try { - const updated = await updateConfig.mutateAsync(draft); - setDraft(updated); + await updateConfig.mutateAsync(draft); toast.success(tt.saveSuccess); } catch (err) { toast.error(err instanceof Error ? err.message : String(err)); @@ -326,29 +326,6 @@ function ConfigCard() { {tt.description} - - setDraft({ ...draft, enabled: v })} - /> - - - - setDraft({ - ...draft, - curator: { ...draft.curator, enabled: v }, - }) - } - /> - - setDraft({ - ...draft, - curator: { - ...draft.curator, - interval_hours: Math.max(1, Number(e.target.value) || 1), - }, - }) + setDraft({ ...draft, interval_hours: Math.max(1, Number(e.target.value) || 1) }) } /> @@ -377,15 +348,9 @@ function ConfigCard() { type="number" min={1} className="max-w-[10rem]" - value={draft.curator.stale_after_days} + value={draft.stale_after_days} onChange={(e) => - setDraft({ - ...draft, - curator: { - ...draft.curator, - stale_after_days: Math.max(1, Number(e.target.value) || 1), - }, - }) + setDraft({ ...draft, stale_after_days: Math.max(1, Number(e.target.value) || 1) }) } /> @@ -397,61 +362,19 @@ function ConfigCard() { type="number" min={1} className="max-w-[10rem]" - value={draft.curator.archive_after_days} + value={draft.archive_after_days} onChange={(e) => - setDraft({ - ...draft, - curator: { - ...draft.curator, - archive_after_days: Math.max(1, Number(e.target.value) || 1), - }, - }) + setDraft({ ...draft, archive_after_days: Math.max(1, Number(e.target.value) || 1) }) } /> - - -
diff --git a/frontend-web/src/core/curator/api.ts b/frontend-web/src/core/curator/api.ts index da6256bb..d9bfc193 100644 --- a/frontend-web/src/core/curator/api.ts +++ b/frontend-web/src/core/curator/api.ts @@ -17,8 +17,14 @@ export async function getCuratorConfig(): Promise { return response.json() as Promise; } +export interface CuratorConfigUpdatePayload { + interval_hours: number; + stale_after_days: number; + archive_after_days: number; +} + export async function updateCuratorConfig( - payload: CuratorConfig, + payload: CuratorConfigUpdatePayload, ): Promise { const response = await apiFetch(`${getBackendBaseURL()}/api/curator/config`, { method: "PUT", diff --git a/frontend-web/src/core/curator/hooks.ts b/frontend-web/src/core/curator/hooks.ts index 35df0d9f..58692761 100644 --- a/frontend-web/src/core/curator/hooks.ts +++ b/frontend-web/src/core/curator/hooks.ts @@ -10,6 +10,7 @@ import { runCurator, updateCuratorConfig, } from "./api"; +import type { CuratorConfigUpdatePayload } from "./api"; import type { CuratorConfig } from "./types"; // All curator-related queries are per-user data (status / config / logs all @@ -51,7 +52,7 @@ export function useUpdateCuratorConfig() { const queryClient = useQueryClient(); const keys = useCuratorKeys(); return useMutation({ - mutationFn: (payload: CuratorConfig) => updateCuratorConfig(payload), + mutationFn: (payload: CuratorConfigUpdatePayload) => updateCuratorConfig(payload), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: keys.config }); void queryClient.invalidateQueries({ queryKey: keys.status }); diff --git a/offline-backend-20260512/backend/app/gateway/routers/skills.py b/offline-backend-20260512/backend/app/gateway/routers/skills.py index 0e48b222..cd6ddf70 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/skills.py +++ b/offline-backend-20260512/backend/app/gateway/routers/skills.py @@ -2329,12 +2329,12 @@ class CuratorConfigResponse(BaseModel): class CuratorConfigRequest(BaseModel): - """Payload for updating per-user skill evolution overrides. + """Payload for updating the three user-facing curator thresholds in skill_evolution.json. - Users may only adjust archiving thresholds. All other settings (enabled, - interval, model, timeouts) are global and controlled via config.yaml. + All other settings (enabled, model, timeouts) are 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.") @@ -2352,11 +2352,7 @@ class CuratorLogsResponse(BaseModel): def _build_curator_config_response(evo) -> CuratorConfigResponse: - """Pure helper so both the GET route and the post-PUT refresh share one shape. - - ``evo`` is a ``SkillEvolutionConfig`` instance (already merged with per-user - overrides if any). - """ + """Pure helper so both the GET route and the post-PUT refresh share one shape.""" return CuratorConfigResponse( enabled=evo.enabled, curator=CuratorSubConfig( @@ -2372,8 +2368,7 @@ def _build_curator_config_response(evo) -> CuratorConfigResponse: @router.get("/curator/config", response_model=CuratorConfigResponse, summary="Get Skill Evolution Config") async def get_curator_config(request: Request) -> CuratorConfigResponse: - """Return the current curator config for the authenticated user (yaml - defaults + that user's overrides).""" + """Return the current curator config for the authenticated user (yaml defaults + per-user overrides).""" try: from deerflow.config.skill_evolution_runtime import load_skill_evolution_config_for_user @@ -2386,11 +2381,9 @@ async def get_curator_config(request: Request) -> CuratorConfigResponse: @router.put("/curator/config", response_model=CuratorConfigResponse, summary="Update Skill Evolution Config") async def update_curator_config(body: CuratorConfigRequest, request: Request) -> CuratorConfigResponse: - """Persist per-user archiving thresholds to ``users/{user_id}/skill_evolution.json``. + """Persist the three user-facing curator thresholds to the per-user ``skill_evolution.json``. - Only ``stale_after_days`` and ``archive_after_days`` are user-editable. - All other settings (enabled, interval, model, timeouts) are global and - read from config.yaml; users cannot override them here. + Other settings (enabled, model, timeouts) are global and controlled via config.yaml. """ try: from deerflow.config.skill_evolution_runtime import load_skill_evolution_config_for_user, save_user_overrides @@ -2398,9 +2391,10 @@ async def update_curator_config(body: CuratorConfigRequest, request: Request) -> user_id = _current_user_id(request) save_user_overrides(user_id, { "curator": { + "interval_hours": body.interval_hours, "stale_after_days": body.stale_after_days, "archive_after_days": body.archive_after_days, - } + }, }) return _build_curator_config_response(load_skill_evolution_config_for_user(user_id)) except HTTPException: