fix(frontend): show empty skill export requirements (#5659)

This commit is contained in:
dong 2026-09-22 11:44:43 +08:00 committed by GitHub
parent ef3c1c2aee
commit b200e5b91b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 67 additions and 8 deletions

View File

@ -217,8 +217,11 @@ export default function SkillExportDialog({
{text.exportTools} {text.exportTools}
</dt> </dt>
<dd className="break-words"> <dd className="break-words">
{manifest.requirements.allowed_tools?.join(", ") ?? {manifest.requirements.allowed_tools === null
text.exportUndeclared} ? text.exportUndeclared
: manifest.requirements.allowed_tools.length === 0
? text.exportNone
: manifest.requirements.allowed_tools.join(", ")}
</dd> </dd>
</div> </div>
<div> <div>
@ -226,12 +229,16 @@ export default function SkillExportDialog({
{text.exportSecrets} {text.exportSecrets}
</dt> </dt>
<dd className="break-words"> <dd className="break-words">
{manifest.requirements.required_secrets {manifest.requirements.required_secrets === null
?.map( ? text.exportUndeclared
(secret) => : manifest.requirements.required_secrets.length === 0
`${secret.name} (${secret.optional ? text.exportOptional : text.exportRequired})`, ? text.exportNone
) : manifest.requirements.required_secrets
.join(", ") ?? text.exportUndeclared} .map(
(secret) =>
`${secret.name} (${secret.optional ? text.exportOptional : text.exportRequired})`,
)
.join(", ")}
</dd> </dd>
</div> </div>
</dl> </dl>

View File

@ -1792,6 +1792,7 @@ export const enUS: Translations = {
exportOptional: "optional", exportOptional: "optional",
exportRequired: "required", exportRequired: "required",
exportUndeclared: "Not declared", exportUndeclared: "Not declared",
exportNone: "None",
exportScope: exportScope:
"Includes all files inside this skill. Account settings, conversations and history outside the skill folder are excluded. Configure tools and credentials again on the destination.", "Includes all files inside this skill. Account settings, conversations and history outside the skill folder are excluded. Configure tools and credentials again on the destination.",
exportWarnings: "Check package contents", exportWarnings: "Check package contents",

View File

@ -1434,6 +1434,7 @@ export interface Translations {
exportOptional: string; exportOptional: string;
exportRequired: string; exportRequired: string;
exportUndeclared: string; exportUndeclared: string;
exportNone: string;
exportScope: string; exportScope: string;
exportWarnings: string; exportWarnings: string;
exportWarningDescription: string; exportWarningDescription: string;

View File

@ -1683,6 +1683,7 @@ export const zhCN: Translations = {
exportOptional: "可选", exportOptional: "可选",
exportRequired: "必需", exportRequired: "必需",
exportUndeclared: "未声明", exportUndeclared: "未声明",
exportNone: "无",
exportScope: exportScope:
"包含此技能目录内的全部文件。账号配置、对话和目录外的历史不会导出;目标环境需重新配置工具与凭据。", "包含此技能目录内的全部文件。账号配置、对话和目录外的历史不会导出;目标环境需重新配置工具与凭据。",
exportWarnings: "请检查包内文件", exportWarnings: "请检查包内文件",

View File

@ -66,6 +66,55 @@ beforeEach(() => {
afterEach(cleanup); afterEach(cleanup);
describe("export dialog lifecycle", () => { describe("export dialog lifecycle", () => {
it.each([
{
name: "undeclared",
allowedTools: null,
requiredSecrets: null,
expectedTools: "Not declared",
expectedSecrets: "Not declared",
},
{
name: "explicitly empty",
allowedTools: [],
requiredSecrets: [],
expectedTools: "None",
expectedSecrets: "None",
},
{
name: "declared values",
allowedTools: ["read_file"],
requiredSecrets: [{ name: "API_KEY", optional: false }],
expectedTools: "read_file",
expectedSecrets: "API_KEY (required)",
},
])(
"renders $name requirements",
async ({
allowedTools,
requiredSecrets,
expectedTools,
expectedSecrets,
}) => {
mocks.load.mockResolvedValue({
...manifest,
requirements: {
...manifest.requirements,
allowed_tools: allowedTools,
required_secrets: requiredSecrets,
},
});
render(<SkillExportDialog name="demo" onClose={rs.fn()} />);
await screen.findByText("Declared requirements");
expect(
screen.getByText("Allowed tools").nextElementSibling?.textContent,
).toBe(expectedTools);
expect(
screen.getByText("Credential names").nextElementSibling?.textContent,
).toBe(expectedSecrets);
},
);
it("pages the file list and distinguishes undeclared dependencies", async () => { it("pages the file list and distinguishes undeclared dependencies", async () => {
render(<SkillExportDialog name="demo" onClose={rs.fn()} />); render(<SkillExportDialog name="demo" onClose={rs.fn()} />);
await screen.findByText("DEMO_KEY (optional)"); await screen.findByText("DEMO_KEY (optional)");