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,
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user