feat(skills): 安装成功后引导填写/AI生成中文名 + 生成超时提至60s
- 上传安装技能成功后,弹窗进入「设置中文名」步骤:用户可手动填写,或用所选大模型自动生成默认值,亦可跳过(回退英文名) - 技能中文名生成超时由 30s 提高到 60s(思考模式构建时已关闭) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3178b4c45e
commit
d658b05ee9
5 files changed
+153
-8
No files matched your search
@@ -71,9 +71,11 @@ import {
|
|||||||
useDeleteCustomSkill,
|
useDeleteCustomSkill,
|
||||||
useDuplicateSkill,
|
useDuplicateSkill,
|
||||||
useGenerateMissingSkillNameZh,
|
useGenerateMissingSkillNameZh,
|
||||||
|
useGenerateSkillNameZh,
|
||||||
useInstallSkillUpload,
|
useInstallSkillUpload,
|
||||||
useReconcileCustomSkills,
|
useReconcileCustomSkills,
|
||||||
useSetSkillFeatured,
|
useSetSkillFeatured,
|
||||||
|
useSetSkillNameZh,
|
||||||
useSetSkillPinned,
|
useSetSkillPinned,
|
||||||
useSetSkillPublished,
|
useSetSkillPublished,
|
||||||
useSkills,
|
useSkills,
|
||||||
@@ -1015,8 +1017,16 @@ function InstallSkillDialog({
|
|||||||
const [validationError, setValidationError] = useState<string | null>(null);
|
const [validationError, setValidationError] = useState<string | null>(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [targetCategory, setTargetCategory] = useState<"custom" | "public">("custom");
|
const [targetCategory, setTargetCategory] = useState<"custom" | "public">("custom");
|
||||||
|
// After a successful install the dialog switches to a "set Chinese name" step.
|
||||||
|
// `installedName` holds the just-installed skill name (null = still on the upload step).
|
||||||
|
const [installedName, setInstalledName] = useState<string | null>(null);
|
||||||
|
const [nameZhDraft, setNameZhDraft] = useState("");
|
||||||
|
// Optional model override for the "AI generate" button (null = configured default).
|
||||||
|
const [nameZhModel, setNameZhModel] = useState<string | null>(null);
|
||||||
const validateMutation = useValidateSkillUpload();
|
const validateMutation = useValidateSkillUpload();
|
||||||
const installMutation = useInstallSkillUpload();
|
const installMutation = useInstallSkillUpload();
|
||||||
|
const setNameZhMutation = useSetSkillNameZh();
|
||||||
|
const generateNameZhMutation = useGenerateSkillNameZh();
|
||||||
|
|
||||||
const reset = useCallback(() => {
|
const reset = useCallback(() => {
|
||||||
setFile(null);
|
setFile(null);
|
||||||
@@ -1024,14 +1034,30 @@ function InstallSkillDialog({
|
|||||||
setValidationError(null);
|
setValidationError(null);
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
setTargetCategory("custom");
|
setTargetCategory("custom");
|
||||||
|
setInstalledName(null);
|
||||||
|
setNameZhDraft("");
|
||||||
|
setNameZhModel(null);
|
||||||
if (inputRef.current) inputRef.current.value = "";
|
if (inputRef.current) inputRef.current.value = "";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
if (validateMutation.isPending || installMutation.isPending) return;
|
if (
|
||||||
|
validateMutation.isPending ||
|
||||||
|
installMutation.isPending ||
|
||||||
|
setNameZhMutation.isPending ||
|
||||||
|
generateNameZhMutation.isPending
|
||||||
|
)
|
||||||
|
return;
|
||||||
reset();
|
reset();
|
||||||
onClose();
|
onClose();
|
||||||
}, [installMutation.isPending, onClose, reset, validateMutation.isPending]);
|
}, [
|
||||||
|
generateNameZhMutation.isPending,
|
||||||
|
installMutation.isPending,
|
||||||
|
onClose,
|
||||||
|
reset,
|
||||||
|
setNameZhMutation.isPending,
|
||||||
|
validateMutation.isPending,
|
||||||
|
]);
|
||||||
|
|
||||||
const acceptFile = useCallback(
|
const acceptFile = useCallback(
|
||||||
async (selected: File) => {
|
async (selected: File) => {
|
||||||
@@ -1087,15 +1113,47 @@ function InstallSkillDialog({
|
|||||||
result.skill_name,
|
result.skill_name,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
reset();
|
// Stay open and advance to the optional "set Chinese name" step.
|
||||||
onClose();
|
setInstalledName(result.skill_name);
|
||||||
|
setNameZhDraft("");
|
||||||
|
setNameZhModel(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err instanceof Error ? err.message : String(err));
|
toast.error(err instanceof Error ? err.message : String(err));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dialog.installSuccess, dialog.installSuccessReplaced, file, installMutation, onClose, reset, targetCategory],
|
[dialog.installSuccess, dialog.installSuccessReplaced, file, installMutation, targetCategory],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleGenerateNameZh = useCallback(async () => {
|
||||||
|
if (!installedName) return;
|
||||||
|
try {
|
||||||
|
const result = await generateNameZhMutation.mutateAsync({
|
||||||
|
skillName: installedName,
|
||||||
|
modelName: nameZhModel,
|
||||||
|
});
|
||||||
|
setNameZhDraft(result.name_zh ?? "");
|
||||||
|
toast.success(dialog.nameZhGenerated);
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(err instanceof Error ? err.message : String(err));
|
||||||
|
}
|
||||||
|
}, [dialog.nameZhGenerated, generateNameZhMutation, installedName, nameZhModel]);
|
||||||
|
|
||||||
|
const handleSaveNameZh = useCallback(async () => {
|
||||||
|
if (!installedName) return;
|
||||||
|
const trimmed = nameZhDraft.trim();
|
||||||
|
try {
|
||||||
|
// Empty draft = keep whatever is stored (or none); just close.
|
||||||
|
if (trimmed) {
|
||||||
|
await setNameZhMutation.mutateAsync({ skillName: installedName, nameZh: trimmed });
|
||||||
|
toast.success(dialog.nameZhSaved);
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(err instanceof Error ? err.message : String(err));
|
||||||
|
}
|
||||||
|
}, [dialog.nameZhSaved, installedName, nameZhDraft, onClose, reset, setNameZhMutation]);
|
||||||
|
|
||||||
const isPublicConflict = validation?.conflict && validation.conflict_kind === "public";
|
const isPublicConflict = validation?.conflict && validation.conflict_kind === "public";
|
||||||
const isCustomConflict = validation?.conflict && validation.conflict_kind === "custom";
|
const isCustomConflict = validation?.conflict && validation.conflict_kind === "custom";
|
||||||
// Admin targeting public directory can overwrite an existing public skill.
|
// Admin targeting public directory can overwrite an existing public skill.
|
||||||
@@ -1107,8 +1165,70 @@ function InstallSkillDialog({
|
|||||||
<Dialog open={open} onOpenChange={(o) => !o && handleClose()}>
|
<Dialog open={open} onOpenChange={(o) => !o && handleClose()}>
|
||||||
<DialogContent className="max-w-xl">
|
<DialogContent className="max-w-xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>{dialog.title}</DialogTitle>
|
<DialogTitle>{installedName ? dialog.nameZhStepTitle : dialog.title}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
{installedName && (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
{dialog.nameZhStepHint.replace("{name}", desensitize(installedName))}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Input
|
||||||
|
value={nameZhDraft}
|
||||||
|
onChange={(e) => setNameZhDraft(e.currentTarget.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") void handleSaveNameZh();
|
||||||
|
}}
|
||||||
|
placeholder={dialog.nameZhPlaceholder}
|
||||||
|
className="h-9 flex-1"
|
||||||
|
autoFocus
|
||||||
|
disabled={setNameZhMutation.isPending || generateNameZhMutation.isPending}
|
||||||
|
/>
|
||||||
|
<ModelNameSelect
|
||||||
|
value={nameZhModel}
|
||||||
|
onChange={setNameZhModel}
|
||||||
|
disabled={setNameZhMutation.isPending || generateNameZhMutation.isPending}
|
||||||
|
triggerClassName="h-9 w-[9rem] shrink-0"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="h-9 shrink-0 gap-1 border-violet-300 bg-violet-50 text-violet-700 hover:bg-violet-100 hover:text-violet-800 dark:border-violet-500/40 dark:bg-violet-500/15 dark:text-violet-300 dark:hover:bg-violet-500/25"
|
||||||
|
onClick={() => void handleGenerateNameZh()}
|
||||||
|
disabled={setNameZhMutation.isPending || generateNameZhMutation.isPending}
|
||||||
|
title={dialog.nameZhGenerate}
|
||||||
|
>
|
||||||
|
{generateNameZhMutation.isPending ? (
|
||||||
|
<Loader2 className="size-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<SparklesIcon className="size-4" />
|
||||||
|
)}
|
||||||
|
{dialog.nameZhGenerate}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleClose}
|
||||||
|
disabled={setNameZhMutation.isPending || generateNameZhMutation.isPending}
|
||||||
|
>
|
||||||
|
{dialog.nameZhSkip}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => void handleSaveNameZh()}
|
||||||
|
disabled={setNameZhMutation.isPending || generateNameZhMutation.isPending}
|
||||||
|
>
|
||||||
|
{setNameZhMutation.isPending && <Loader2 className="size-4 animate-spin" />}
|
||||||
|
{t.common.save}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!installedName && (
|
||||||
|
<>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div
|
<div
|
||||||
onDragOver={(e) => {
|
onDragOver={(e) => {
|
||||||
@@ -1271,6 +1391,8 @@ function InstallSkillDialog({
|
|||||||
{installMutation.isPending ? dialog.installing : dialog.install}
|
{installMutation.isPending ? dialog.installing : dialog.install}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -598,6 +598,14 @@ export const enUS: Translations = {
|
|||||||
targetCategoryLabel: "Install to",
|
targetCategoryLabel: "Install to",
|
||||||
categoryCustom: "Custom skill",
|
categoryCustom: "Custom skill",
|
||||||
categoryPublic: "Built-in (admin)",
|
categoryPublic: "Built-in (admin)",
|
||||||
|
nameZhStepTitle: "Set Chinese name",
|
||||||
|
nameZhStepHint:
|
||||||
|
"Skill '{name}' is installed. You can give it a Chinese display name, or let the model generate a default one (leave blank to use the English name).",
|
||||||
|
nameZhPlaceholder: "Enter a Chinese name (optional)",
|
||||||
|
nameZhGenerate: "AI generate",
|
||||||
|
nameZhSkip: "Skip",
|
||||||
|
nameZhGenerated: "Chinese name generated",
|
||||||
|
nameZhSaved: "Chinese name saved",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
skillEvolution: {
|
skillEvolution: {
|
||||||
|
|||||||
@@ -523,6 +523,13 @@ export interface Translations {
|
|||||||
targetCategoryLabel: string;
|
targetCategoryLabel: string;
|
||||||
categoryCustom: string;
|
categoryCustom: string;
|
||||||
categoryPublic: string;
|
categoryPublic: string;
|
||||||
|
nameZhStepTitle: string;
|
||||||
|
nameZhStepHint: string;
|
||||||
|
nameZhPlaceholder: string;
|
||||||
|
nameZhGenerate: string;
|
||||||
|
nameZhSkip: string;
|
||||||
|
nameZhGenerated: string;
|
||||||
|
nameZhSaved: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
skillEvolution: {
|
skillEvolution: {
|
||||||
|
|||||||
@@ -571,6 +571,14 @@ export const zhCN: Translations = {
|
|||||||
targetCategoryLabel: `安装到`,
|
targetCategoryLabel: `安装到`,
|
||||||
categoryCustom: `自定义技能`,
|
categoryCustom: `自定义技能`,
|
||||||
categoryPublic: `内置技能(管理员)`,
|
categoryPublic: `内置技能(管理员)`,
|
||||||
|
nameZhStepTitle: "设置中文名",
|
||||||
|
nameZhStepHint:
|
||||||
|
"技能 “{name}” 已安装。你可以为它填写一个中文名,或让大模型自动生成一个默认中文名(留空则使用英文名)。",
|
||||||
|
nameZhPlaceholder: "请输入中文名(可留空)",
|
||||||
|
nameZhGenerate: "AI 生成",
|
||||||
|
nameZhSkip: "跳过",
|
||||||
|
nameZhGenerated: "已生成中文名称",
|
||||||
|
nameZhSaved: "已保存中文名",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
skillEvolution: {
|
skillEvolution: {
|
||||||
|
|||||||
@@ -2444,11 +2444,11 @@ async def _generate_skill_name_zh(skill: Skill, app_config: AppConfig, model: An
|
|||||||
],
|
],
|
||||||
config={"run_name": "skill_name_zh"},
|
config={"run_name": "skill_name_zh"},
|
||||||
),
|
),
|
||||||
timeout=30,
|
timeout=60,
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError as e:
|
except asyncio.TimeoutError as e:
|
||||||
logger.warning("Skill name_zh LLM call timed out for %s", skill.name)
|
logger.warning("Skill name_zh LLM call timed out for %s", skill.name)
|
||||||
raise SkillNameZhModelError("大模型响应超时(30 秒)") from e
|
raise SkillNameZhModelError("大模型响应超时(60 秒)") from e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Skill name_zh LLM call failed for %s", skill.name, exc_info=True)
|
logger.warning("Skill name_zh LLM call failed for %s", skill.name, exc_info=True)
|
||||||
raise SkillNameZhModelError(str(e) or "大模型调用失败") from e
|
raise SkillNameZhModelError(str(e) or "大模型调用失败") from e
|
||||||
|
|||||||
Reference in new issue
Block a user