feat(skills): add slash command activation

This commit is contained in:
Goon committed 2026-05-24 16:39:44 +07:00
1 parent 14b32bd219
commit c99f7e1fa4
24 files changed
+978 -40

No files matched your search

@@ -45,6 +45,10 @@ export interface InitState {
bgProvider: string;
bgModel: string;
skillUploadMaxSize: string;
skillSlashEnabled: boolean;
skillSlashSuggest: boolean;
skillSlashPartial: boolean;
skillSlashPrefix: string;
}
export const DEFAULTS: InitState = {
@@ -56,6 +60,10 @@ export const DEFAULTS: InitState = {
kgProvider: "", kgModel: "", kgMinConfidence: "0.75",
bgProvider: "", bgModel: "",
skillUploadMaxSize: "20",
skillSlashEnabled: true,
skillSlashSuggest: true,
skillSlashPartial: false,
skillSlashPrefix: "/",
};
export function parseBool(v: string | undefined, fallback: boolean): boolean {
@@ -62,6 +62,10 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP
const [bgProvider, setBgProvider] = useState("");
const [bgModel, setBgModel] = useState("");
const [skillUploadMaxSize, setSkillUploadMaxSize] = useState("20");
const [skillSlashEnabled, setSkillSlashEnabled] = useState(true);
const [skillSlashSuggest, setSkillSlashSuggest] = useState(true);
const [skillSlashPartial, setSkillSlashPartial] = useState(false);
const [skillSlashPrefix, setSkillSlashPrefix] = useState("/");
const applyConfigs = useCallback((
configs: Record<string, string>,
@@ -79,6 +83,10 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP
kgMinConfidence: String(kgSettings?.min_confidence ?? 0.75),
bgProvider: configs["background.provider"] ?? "", bgModel: configs["background.model"] ?? "",
skillUploadMaxSize: configs["skills.max_upload_size_mb"] ?? "20",
skillSlashEnabled: parseBool(configs["skills.slash_commands.enabled"], true),
skillSlashSuggest: parseBool(configs["skills.slash_commands.suggest_not_found"], true),
skillSlashPartial: parseBool(configs["skills.slash_commands.partial_matching"], false),
skillSlashPrefix: configs["skills.slash_commands.prefix"] ?? "/",
};
setInit(s);
setEmbProvider(s.embProvider); setEmbModel(s.embModel); setEmbMaxChunkLen(s.embMaxChunkLen); setEmbChunkOverlap(s.embChunkOverlap);
@@ -87,6 +95,10 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP
setKgProvider(s.kgProvider); setKgModel(s.kgModel); setKgMinConfidence(s.kgMinConfidence);
setBgProvider(s.bgProvider); setBgModel(s.bgModel);
setSkillUploadMaxSize(s.skillUploadMaxSize);
setSkillSlashEnabled(s.skillSlashEnabled);
setSkillSlashSuggest(s.skillSlashSuggest);
setSkillSlashPartial(s.skillSlashPartial);
setSkillSlashPrefix(s.skillSlashPrefix);
resetEmb();
}, [resetEmb]);
@@ -131,6 +143,10 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP
if (bgProvider !== init.bgProvider) updates["background.provider"] = bgProvider;
if (bgModel !== init.bgModel) updates["background.model"] = bgModel;
if (skillUploadMaxSize !== init.skillUploadMaxSize) updates["skills.max_upload_size_mb"] = skillUploadMaxSize;
if (skillSlashEnabled !== init.skillSlashEnabled) updates["skills.slash_commands.enabled"] = String(skillSlashEnabled);
if (skillSlashSuggest !== init.skillSlashSuggest) updates["skills.slash_commands.suggest_not_found"] = String(skillSlashSuggest);
if (skillSlashPartial !== init.skillSlashPartial) updates["skills.slash_commands.partial_matching"] = String(skillSlashPartial);
if (skillSlashPrefix !== init.skillSlashPrefix) updates["skills.slash_commands.prefix"] = skillSlashPrefix.trim() || "/";
for (const [key, value] of Object.entries(updates)) await http.put(`/v1/system-configs/${key}`, { value });
const kgChanged = kgProvider !== init.kgProvider || kgModel !== init.kgModel || kgMinConfidence !== init.kgMinConfidence;
if (kgChanged) {
@@ -211,6 +227,14 @@ export function SystemSettingsModal({ open, onOpenChange }: SystemSettingsModalP
<SystemSettingsSkillsCard
uploadMaxSize={skillUploadMaxSize}
setUploadMaxSize={setSkillUploadMaxSize}
slashEnabled={skillSlashEnabled}
setSlashEnabled={setSkillSlashEnabled}
slashSuggest={skillSlashSuggest}
setSlashSuggest={setSkillSlashSuggest}
slashPartial={skillSlashPartial}
setSlashPartial={setSkillSlashPartial}
slashPrefix={skillSlashPrefix}
setSlashPrefix={setSkillSlashPrefix}
/>
<SystemSettingsCompactionCard
@@ -3,15 +3,32 @@ import { useTranslation } from "react-i18next";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
interface SystemSettingsSkillsCardProps {
uploadMaxSize: string;
setUploadMaxSize: (value: string) => void;
slashEnabled: boolean;
setSlashEnabled: (value: boolean) => void;
slashSuggest: boolean;
setSlashSuggest: (value: boolean) => void;
slashPartial: boolean;
setSlashPartial: (value: boolean) => void;
slashPrefix: string;
setSlashPrefix: (value: string) => void;
}
export function SystemSettingsSkillsCard({
uploadMaxSize,
setUploadMaxSize,
slashEnabled,
setSlashEnabled,
slashSuggest,
setSlashSuggest,
slashPartial,
setSlashPartial,
slashPrefix,
setSlashPrefix,
}: SystemSettingsSkillsCardProps) {
const { t } = useTranslation("system-settings");
@@ -24,7 +41,7 @@ export function SystemSettingsSkillsCard({
</CardTitle>
<CardDescription>{t("skills.description")}</CardDescription>
</CardHeader>
<CardContent className="space-y-2 pt-0">
<CardContent className="space-y-4 pt-0">
<div className="flex items-start justify-between gap-4">
<div className="space-y-0.5">
<Label htmlFor="skillUploadMaxSize" className="text-sm font-medium">
@@ -42,7 +59,71 @@ export function SystemSettingsSkillsCard({
className="w-24 shrink-0 text-base md:text-sm"
/>
</div>
<div className="space-y-3 border-t pt-4">
<SkillSwitchRow
id="skillSlashEnabled"
label={t("skills.slashEnabled")}
hint={t("skills.slashEnabledHint")}
checked={slashEnabled}
onCheckedChange={setSlashEnabled}
/>
<SkillSwitchRow
id="skillSlashSuggest"
label={t("skills.slashSuggest")}
hint={t("skills.slashSuggestHint")}
checked={slashSuggest}
onCheckedChange={setSlashSuggest}
/>
<SkillSwitchRow
id="skillSlashPartial"
label={t("skills.slashPartial")}
hint={t("skills.slashPartialHint")}
checked={slashPartial}
onCheckedChange={setSlashPartial}
/>
<div className="flex items-start justify-between gap-4">
<div className="space-y-0.5">
<Label htmlFor="skillSlashPrefix" className="text-sm font-medium">
{t("skills.slashPrefix")}
</Label>
<p className="text-xs text-muted-foreground">{t("skills.slashPrefixHint")}</p>
</div>
<Input
id="skillSlashPrefix"
maxLength={1}
value={slashPrefix}
onChange={(e) => setSlashPrefix(e.target.value.slice(0, 1))}
className="w-16 shrink-0 text-center text-base md:text-sm"
/>
</div>
</div>
</CardContent>
</Card>
);
}
function SkillSwitchRow({
id,
label,
hint,
checked,
onCheckedChange,
}: {
id: string;
label: string;
hint: string;
checked: boolean;
onCheckedChange: (value: boolean) => void;
}) {
return (
<div className="flex items-start justify-between gap-4">
<div className="space-y-0.5">
<Label htmlFor={id} className="text-sm font-medium">
{label}
</Label>
<p className="text-xs text-muted-foreground">{hint}</p>
</div>
<Switch id={id} checked={checked} onCheckedChange={onCheckedChange} />
</div>
);
}
@@ -66,9 +66,17 @@
},
"skills": {
"title": "Skills",
"description": "Tenant skill package upload limits.",
"description": "Tenant skill package and slash command settings.",
"maxUploadSize": "Max upload size",
"maxUploadSizeHint": "Per-file ZIP limit in MB. Allowed range: 1-500."
"maxUploadSizeHint": "Per-file ZIP limit in MB. Allowed range: 1-500.",
"slashEnabled": "Enable slash commands",
"slashEnabledHint": "Detect /skill-name and /use skill-name at the start of user prompts.",
"slashSuggest": "Suggest similar skills",
"slashSuggestHint": "Show close matches when a requested skill is not found.",
"slashPartial": "Allow partial matching",
"slashPartialHint": "Let unique prefixes like /frontend activate matching skills.",
"slashPrefix": "Command prefix",
"slashPrefixHint": "Single character used before skill commands."
},
"compaction": {
"title": "Pending Message Compaction",
@@ -66,9 +66,17 @@
},
"skills": {
"title": "Skills",
"description": "Giới hạn tải lên gói skill theo tenant.",
"description": "Cài đặt gói skill và slash command theo tenant.",
"maxUploadSize": "Dung lượng tải lên tối đa",
"maxUploadSizeHint": "Giới hạn ZIP theo từng tệp, tính bằng MB. Khoảng cho phép: 1-500."
"maxUploadSizeHint": "Giới hạn ZIP theo từng tệp, tính bằng MB. Khoảng cho phép: 1-500.",
"slashEnabled": "Bật slash command",
"slashEnabledHint": "Nhận diện /skill-name và /use skill-name ở đầu prompt.",
"slashSuggest": "Gợi ý skill gần đúng",
"slashSuggestHint": "Hiển thị các skill gần giống khi không tìm thấy skill được gọi.",
"slashPartial": "Cho phép khớp một phần",
"slashPartialHint": "Cho phép prefix duy nhất như /frontend kích hoạt skill tương ứng.",
"slashPrefix": "Prefix command",
"slashPrefixHint": "Một ký tự dùng trước skill command."
},
"compaction": {
"title": "Nén tin nhắn chờ",
@@ -66,9 +66,17 @@
},
"skills": {
"title": "技能",
"description": "租户技能包上传限制。",
"description": "租户技能包和斜杠命令设置。",
"maxUploadSize": "最大上传大小",
"maxUploadSizeHint": "单个 ZIP 文件限制,单位 MB。允许范围:1-500。"
"maxUploadSizeHint": "单个 ZIP 文件限制,单位 MB。允许范围:1-500。",
"slashEnabled": "启用斜杠命令",
"slashEnabledHint": "在用户提示开头检测 /skill-name 和 /use skill-name。",
"slashSuggest": "建议相似技能",
"slashSuggestHint": "找不到请求的技能时显示接近匹配项。",
"slashPartial": "允许部分匹配",
"slashPartialHint": "允许像 /frontend 这样的唯一前缀激活匹配技能。",
"slashPrefix": "命令前缀",
"slashPrefixHint": "技能命令前使用的单个字符。"
},
"compaction": {
"title": "待处理消息压缩",