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,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import {
|
import {
|
||||||
useCuratorConfig,
|
useCuratorConfig,
|
||||||
@@ -31,6 +38,7 @@ import {
|
|||||||
} from "@/core/curator/hooks";
|
} from "@/core/curator/hooks";
|
||||||
import type { CuratorConfig } from "@/core/curator/types";
|
import type { CuratorConfig } from "@/core/curator/types";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
import { useModels } from "@/core/models/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export function SkillEvolutionPanel() {
|
export function SkillEvolutionPanel() {
|
||||||
@@ -269,13 +277,17 @@ interface CuratorDraft {
|
|||||||
interval_hours: number;
|
interval_hours: number;
|
||||||
stale_after_days: number;
|
stale_after_days: number;
|
||||||
archive_after_days: number;
|
archive_after_days: number;
|
||||||
|
model_name: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_MODEL_VALUE = "__default__";
|
||||||
|
|
||||||
function toDraft(config: CuratorConfig): CuratorDraft {
|
function toDraft(config: CuratorConfig): CuratorDraft {
|
||||||
return {
|
return {
|
||||||
interval_hours: config.curator.interval_hours,
|
interval_hours: config.curator.interval_hours,
|
||||||
stale_after_days: config.curator.stale_after_days,
|
stale_after_days: config.curator.stale_after_days,
|
||||||
archive_after_days: config.curator.archive_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 tt = t.settings.skillEvolution.config;
|
||||||
const { config, isLoading } = useCuratorConfig();
|
const { config, isLoading } = useCuratorConfig();
|
||||||
const updateConfig = useUpdateCuratorConfig();
|
const updateConfig = useUpdateCuratorConfig();
|
||||||
|
const { models } = useModels();
|
||||||
|
|
||||||
const [draft, setDraft] = useState<CuratorDraft | null>(null);
|
const [draft, setDraft] = useState<CuratorDraft | null>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -368,6 +381,36 @@ function ConfigCard() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FieldRow>
|
</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">
|
<div className="flex justify-end gap-2 pt-2">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface CuratorConfigUpdatePayload {
|
|||||||
interval_hours: number;
|
interval_hours: number;
|
||||||
stale_after_days: number;
|
stale_after_days: number;
|
||||||
archive_after_days: number;
|
archive_after_days: number;
|
||||||
|
model_name: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCuratorConfig(
|
export async function updateCuratorConfig(
|
||||||
|
|||||||
@@ -2329,14 +2329,15 @@ class CuratorConfigResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class CuratorConfigRequest(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.")
|
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.")
|
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.")
|
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):
|
class CuratorLogRecord(BaseModel):
|
||||||
@@ -2394,6 +2395,7 @@ async def update_curator_config(body: CuratorConfigRequest, request: Request) ->
|
|||||||
"interval_hours": body.interval_hours,
|
"interval_hours": body.interval_hours,
|
||||||
"stale_after_days": body.stale_after_days,
|
"stale_after_days": body.stale_after_days,
|
||||||
"archive_after_days": body.archive_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))
|
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.
|
# Only these curator sub-fields may be overridden per user.
|
||||||
# All other fields (enabled, model_name, timeouts, etc.) are global-only.
|
# 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:
|
def load_user_overrides(user_id: str) -> dict[str, Any] | None:
|
||||||
|
|||||||
Reference in new issue
Block a user