feat(skills): 安装成功后引导填写/AI生成中文名 + 生成超时提至60s

- 上传安装技能成功后,弹窗进入「设置中文名」步骤:用户可手动填写,或用所选大模型自动生成默认值,亦可跳过(回退英文名)
- 技能中文名生成超时由 30s 提高到 60s(思考模式构建时已关闭)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-10 17:14:42 +08:00
1 parent 3178b4c45e
commit d658b05ee9
5 files changed
+153 -8

No files matched your search

@@ -71,9 +71,11 @@ import {
useDeleteCustomSkill,
useDuplicateSkill,
useGenerateMissingSkillNameZh,
useGenerateSkillNameZh,
useInstallSkillUpload,
useReconcileCustomSkills,
useSetSkillFeatured,
useSetSkillNameZh,
useSetSkillPinned,
useSetSkillPublished,
useSkills,
@@ -1015,8 +1017,16 @@ function InstallSkillDialog({
const [validationError, setValidationError] = useState<string | null>(null);
const [isDragging, setIsDragging] = useState(false);
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 installMutation = useInstallSkillUpload();
const setNameZhMutation = useSetSkillNameZh();
const generateNameZhMutation = useGenerateSkillNameZh();
const reset = useCallback(() => {
setFile(null);
@@ -1024,14 +1034,30 @@ function InstallSkillDialog({
setValidationError(null);
setIsDragging(false);
setTargetCategory("custom");
setInstalledName(null);
setNameZhDraft("");
setNameZhModel(null);
if (inputRef.current) inputRef.current.value = "";
}, []);
const handleClose = useCallback(() => {
if (validateMutation.isPending || installMutation.isPending) return;
if (
validateMutation.isPending ||
installMutation.isPending ||
setNameZhMutation.isPending ||
generateNameZhMutation.isPending
)
return;
reset();
onClose();
}, [installMutation.isPending, onClose, reset, validateMutation.isPending]);
}, [
generateNameZhMutation.isPending,
installMutation.isPending,
onClose,
reset,
setNameZhMutation.isPending,
validateMutation.isPending,
]);
const acceptFile = useCallback(
async (selected: File) => {
@@ -1087,15 +1113,47 @@ function InstallSkillDialog({
result.skill_name,
),
);
reset();
onClose();
// Stay open and advance to the optional "set Chinese name" step.
setInstalledName(result.skill_name);
setNameZhDraft("");
setNameZhModel(null);
} catch (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 isCustomConflict = validation?.conflict && validation.conflict_kind === "custom";
// Admin targeting public directory can overwrite an existing public skill.
@@ -1107,8 +1165,70 @@ function InstallSkillDialog({
<Dialog open={open} onOpenChange={(o) => !o && handleClose()}>
<DialogContent className="max-w-xl">
<DialogHeader>
<DialogTitle>{dialog.title}</DialogTitle>
<DialogTitle>{installedName ? dialog.nameZhStepTitle : dialog.title}</DialogTitle>
</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
onDragOver={(e) => {
@@ -1271,6 +1391,8 @@ function InstallSkillDialog({
{installMutation.isPending ? dialog.installing : dialog.install}
</Button>
</DialogFooter>
</>
)}
</DialogContent>
</Dialog>
);
@@ -598,6 +598,14 @@ export const enUS: Translations = {
targetCategoryLabel: "Install to",
categoryCustom: "Custom skill",
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: {
@@ -523,6 +523,13 @@ export interface Translations {
targetCategoryLabel: string;
categoryCustom: string;
categoryPublic: string;
nameZhStepTitle: string;
nameZhStepHint: string;
nameZhPlaceholder: string;
nameZhGenerate: string;
nameZhSkip: string;
nameZhGenerated: string;
nameZhSaved: string;
};
};
skillEvolution: {
@@ -571,6 +571,14 @@ export const zhCN: Translations = {
targetCategoryLabel: `安装到`,
categoryCustom: `自定义技能`,
categoryPublic: `内置技能(管理员)`,
nameZhStepTitle: "设置中文名",
nameZhStepHint:
"技能 “{name}” 已安装。你可以为它填写一个中文名,或让大模型自动生成一个默认中文名(留空则使用英文名)。",
nameZhPlaceholder: "请输入中文名(可留空)",
nameZhGenerate: "AI 生成",
nameZhSkip: "跳过",
nameZhGenerated: "已生成中文名称",
nameZhSaved: "已保存中文名",
},
},
skillEvolution: {
@@ -2444,11 +2444,11 @@ async def _generate_skill_name_zh(skill: Skill, app_config: AppConfig, model: An
],
config={"run_name": "skill_name_zh"},
),
timeout=30,
timeout=60,
)
except asyncio.TimeoutError as e:
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:
logger.warning("Skill name_zh LLM call failed for %s", skill.name, exc_info=True)
raise SkillNameZhModelError(str(e) or "大模型调用失败") from e