mirror of
https://github.com/langgenius/dify.git
synced 2026-09-28 14:23:33 +08:00
feat: skill support duplicate and export use the latest draft (#42424)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
parent
99ba5a57de
commit
bfd7ac015f
@@ -493,14 +493,14 @@ class WorkspaceSkillDuplicateApi(Resource):
|
||||
|
||||
@console_ns.route("/workspaces/current/skills/<string:skill_id>/export")
|
||||
class WorkspaceSkillExportApi(Resource):
|
||||
@console_ns.response(200, "Published Skill zip archive")
|
||||
@console_ns.response(200, "Draft Skill zip archive")
|
||||
@console_account_admission(
|
||||
rbac_checks=[RBACCheck(RBACPermission.SKILL_VIEW, Workspace())],
|
||||
)
|
||||
@with_session(write=False)
|
||||
def get(self, session: Session, request_context: RequestContext, skill_id: str):
|
||||
try:
|
||||
result = SkillManagementService(session=session).pull_published_archive(
|
||||
result = SkillManagementService(session=session).export_draft_archive(
|
||||
tenant_id=request_context.active_workspace_id, skill_id=skill_id
|
||||
)
|
||||
return send_file(
|
||||
|
||||
@@ -12127,7 +12127,7 @@ Import a Skill zip package from multipart form field `file`.
|
||||
|
||||
| Code | Description |
|
||||
| ---- | ----------- |
|
||||
| 200 | Published Skill zip archive |
|
||||
| 200 | Draft Skill zip archive |
|
||||
|
||||
### [PATCH] /workspaces/current/skills/{skill_id}/files
|
||||
#### Parameters
|
||||
|
||||
@@ -499,6 +499,13 @@ class SkillAssistActionResult:
|
||||
plan: SkillAssistActionPlan
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DraftSkillArchive:
|
||||
filename: str
|
||||
mime_type: str
|
||||
payload: bytes
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PublishedSkillArchive:
|
||||
filename: str
|
||||
@@ -2060,7 +2067,7 @@ class SkillManagementService:
|
||||
}
|
||||
|
||||
def duplicate_skill(self, *, tenant_id: str, user_id: str, skill_id: str) -> dict[str, Any]:
|
||||
"""Create a draft-only copy, preferring the latest published snapshot when present."""
|
||||
"""Create an unpublished copy of the current saved draft."""
|
||||
with self._session_scope() as session:
|
||||
source = self._require_skill(session, tenant_id=tenant_id, skill_id=skill_id)
|
||||
self._enforce_workspace_skill_limit(session, tenant_id=tenant_id)
|
||||
@@ -2085,47 +2092,10 @@ class SkillManagementService:
|
||||
skill_id=duplicate.id,
|
||||
tags=self._skill_tags_by_id(session, tenant_id=tenant_id, skill_ids=[source.id]).get(source.id, []),
|
||||
)
|
||||
latest_version_id = source.latest_published_version_id
|
||||
source_draft_files = list(
|
||||
session.scalars(select(SkillDraftFile).where(SkillDraftFile.skill_id == source.id))
|
||||
)
|
||||
copied_draft_files = [self._copy_draft_file(file, skill_id=duplicate_id) for file in source_draft_files]
|
||||
session.commit()
|
||||
|
||||
if latest_version_id is not None:
|
||||
archive = self._load_version_archive(tenant_id=tenant_id, version_id=latest_version_id)
|
||||
with self._session_scope() as session:
|
||||
duplicate = self._require_skill(session, tenant_id=tenant_id, skill_id=duplicate_id)
|
||||
duplicate_identity = (
|
||||
duplicate.name,
|
||||
duplicate.display_name,
|
||||
duplicate.description,
|
||||
duplicate.name_manually_edited,
|
||||
)
|
||||
files = self._draft_rows_from_archive_bytes(
|
||||
tenant_id=tenant_id,
|
||||
user_id=user_id,
|
||||
skill=duplicate,
|
||||
archive_bytes=archive,
|
||||
)
|
||||
# Parsing the published SKILL.md synchronizes metadata onto the
|
||||
# supplied ORM object. A duplicate must retain its new identity,
|
||||
# otherwise the reused request session autoflushes the source
|
||||
# name and violates the tenant/name unique constraint.
|
||||
(
|
||||
duplicate.name,
|
||||
duplicate.display_name,
|
||||
duplicate.description,
|
||||
duplicate.name_manually_edited,
|
||||
) = duplicate_identity
|
||||
else:
|
||||
files = copied_draft_files
|
||||
|
||||
with self._session_scope() as session:
|
||||
duplicate = self._require_skill(session, tenant_id=tenant_id, skill_id=duplicate_id)
|
||||
if latest_version_id is not None:
|
||||
for file in files:
|
||||
file.skill_id = duplicate.id
|
||||
files = [self._copy_draft_file(file, skill_id=duplicate_id) for file in source_draft_files]
|
||||
for file in files:
|
||||
if file.path == _SKILL_MD and file.content_text is not None:
|
||||
synced_content = self._sync_skill_md_text(duplicate, file.content_text)
|
||||
@@ -2251,6 +2221,20 @@ class SkillManagementService:
|
||||
# follow-up action so restoring history cannot unexpectedly activate it.
|
||||
return self.get_skill(tenant_id=tenant_id, skill_id=skill_id)
|
||||
|
||||
def export_draft_archive(self, *, tenant_id: str, skill_id: str) -> DraftSkillArchive:
|
||||
"""Export the current saved draft without requiring or creating a published version."""
|
||||
with self._session_scope() as session:
|
||||
skill = self._require_skill(session, tenant_id=tenant_id, skill_id=skill_id)
|
||||
files = list(session.scalars(select(SkillDraftFile).where(SkillDraftFile.skill_id == skill.id)))
|
||||
# Snapshot the loaded rows so archive storage reads happen outside this scope.
|
||||
draft_skill = Skill(
|
||||
tenant_id=tenant_id, name=skill.name, display_name=skill.display_name, description=skill.description
|
||||
)
|
||||
draft_files = [self._copy_draft_file(file, skill_id=skill.id) for file in files]
|
||||
filename = f"{skill.name}.zip"
|
||||
archive_bytes, _ = self._build_archive_from_draft(skill=draft_skill, files=draft_files)
|
||||
return DraftSkillArchive(filename=filename, mime_type="application/zip", payload=archive_bytes)
|
||||
|
||||
def pull_published_archive(self, *, tenant_id: str, skill_id: str) -> PublishedSkillArchive:
|
||||
with self._session_scope() as session:
|
||||
skill = self._require_skill(session, tenant_id=tenant_id, skill_id=skill_id)
|
||||
@@ -4808,6 +4792,7 @@ class SkillManagementService:
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DraftSkillArchive",
|
||||
"PublishedSkillArchive",
|
||||
"SkillAssistAttachmentPayload",
|
||||
"SkillAssistHistoryMessagePayload",
|
||||
|
||||
@@ -503,7 +503,7 @@ def test_export_skill_returns_archive_response(app: Flask, request_context: Requ
|
||||
api = WorkspaceSkillExportApi()
|
||||
method = unwrap(api.get)
|
||||
service = MagicMock()
|
||||
service.pull_published_archive.return_value = MagicMock(
|
||||
service.export_draft_archive.return_value = MagicMock(
|
||||
payload=b"zip-bytes",
|
||||
mime_type="application/zip",
|
||||
filename="finance-sop.zip",
|
||||
@@ -520,17 +520,17 @@ def test_export_skill_returns_archive_response(app: Flask, request_context: Requ
|
||||
assert response.headers["Content-Disposition"].startswith("attachment;")
|
||||
response.direct_passthrough = False
|
||||
assert response.get_data() == b"zip-bytes"
|
||||
service.pull_published_archive.assert_called_once_with(tenant_id="tenant-1", skill_id="skill-1")
|
||||
service.export_draft_archive.assert_called_once_with(tenant_id="tenant-1", skill_id="skill-1")
|
||||
|
||||
|
||||
def test_export_skill_maps_service_error(app: Flask, request_context: RequestContext) -> None:
|
||||
api = WorkspaceSkillExportApi()
|
||||
method = unwrap(api.get)
|
||||
service = MagicMock()
|
||||
service.pull_published_archive.side_effect = SkillManagementServiceError(
|
||||
"skill_not_published",
|
||||
"skill not published",
|
||||
status_code=409,
|
||||
service.export_draft_archive.side_effect = SkillManagementServiceError(
|
||||
"skill_not_found",
|
||||
"skill not found",
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
with (
|
||||
@@ -539,8 +539,8 @@ def test_export_skill_maps_service_error(app: Flask, request_context: RequestCon
|
||||
):
|
||||
payload, status = method(api, request_context, "skill-1")
|
||||
|
||||
assert status == 409
|
||||
assert payload == {"code": "skill_not_published", "message": "skill not published"}
|
||||
assert status == 404
|
||||
assert payload == {"code": "skill_not_found", "message": "skill not found"}
|
||||
|
||||
|
||||
def test_inner_api_pulls_published_skill_archive(app: Flask) -> None:
|
||||
|
||||
@@ -2493,7 +2493,7 @@ def test_apply_draft_file_operation_conflict_includes_current_file_version() ->
|
||||
assert exc_info.value.details["current_file_content"] == skill_md["content"]
|
||||
|
||||
|
||||
def test_duplicate_skill_copies_latest_published_content_without_history() -> None:
|
||||
def test_duplicate_skill_copies_current_draft_without_history() -> None:
|
||||
captured: dict[str, bytes] = {}
|
||||
|
||||
class CapturingToolFileManager(_FakeToolFileManager):
|
||||
@@ -2543,6 +2543,19 @@ def test_duplicate_skill_copies_latest_published_content_without_history() -> No
|
||||
)
|
||||
service.publish_skill(tenant_id=TENANT, user_id=USER, skill_id=created["id"], payload=SkillPublishPayload())
|
||||
|
||||
service.replace_draft_tree(
|
||||
tenant_id=TENANT,
|
||||
user_id=USER,
|
||||
skill_id=created["id"],
|
||||
payload=SkillDraftTreePayload(
|
||||
files=[
|
||||
{"path": "SKILL.md", "content": _skill_md(body="# Latest draft body")},
|
||||
{"path": "references", "kind": "directory"},
|
||||
{"path": "references/draft.md", "content": "Draft policy."},
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
with patch("services.skill_management_service.storage.load_once", return_value=captured["archive"]):
|
||||
duplicated = service.duplicate_skill(tenant_id=TENANT, user_id=USER, skill_id=created["id"])
|
||||
|
||||
@@ -2551,13 +2564,44 @@ def test_duplicate_skill_copies_latest_published_content_without_history() -> No
|
||||
assert duplicated["tags"] == ["Finance"]
|
||||
assert duplicated["latest_published_version_id"] is None
|
||||
assert "name: finance-sop-copy" in duplicated["files"][0]["content"]
|
||||
assert "# Published body" in duplicated["files"][0]["content"]
|
||||
assert "# Latest draft body" in duplicated["files"][0]["content"]
|
||||
references = next(file for file in duplicated["files"] if file["path"] == "references")
|
||||
assert references["kind"] == "directory"
|
||||
assert any(file["path"] == "references/policy.md" for file in duplicated["files"])
|
||||
assert any(file["path"] == "references/draft.md" for file in duplicated["files"])
|
||||
assert not any(file["path"] == "references/policy.md" for file in duplicated["files"])
|
||||
assert service.list_versions(tenant_id=TENANT, skill_id=duplicated["id"]) == {"data": []}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("published", [False, True])
|
||||
def test_export_skill_uses_current_draft(published: bool) -> None:
|
||||
service = SkillManagementService(tool_file_manager=_FakeToolFileManager())
|
||||
created = service.create_skill(tenant_id=TENANT, user_id=USER, payload=SkillCreatePayload(name="finance-sop"))
|
||||
if published:
|
||||
service.publish_skill(tenant_id=TENANT, user_id=USER, skill_id=created["id"], payload=SkillPublishPayload())
|
||||
service.replace_draft_tree(
|
||||
tenant_id=TENANT,
|
||||
user_id=USER,
|
||||
skill_id=created["id"],
|
||||
payload=SkillDraftTreePayload(
|
||||
files=[
|
||||
{"path": "SKILL.md", "content": _skill_md(body="# Latest draft body")},
|
||||
{"path": "references", "kind": "directory"},
|
||||
{"path": "references/draft.md", "content": "Draft policy."},
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
result = service.export_draft_archive(tenant_id=TENANT, skill_id=created["id"])
|
||||
|
||||
assert result.filename == "finance-sop.zip"
|
||||
assert result.mime_type == "application/zip"
|
||||
with zipfile.ZipFile(io.BytesIO(result.payload)) as archive:
|
||||
assert set(archive.namelist()) == {"SKILL.md", "references/", "references/draft.md"}
|
||||
assert "# Latest draft body" in archive.read("SKILL.md").decode()
|
||||
assert archive.read("references/draft.md") == b"Draft policy."
|
||||
assert len(service.list_versions(tenant_id=TENANT, skill_id=created["id"])["data"]) == int(published)
|
||||
|
||||
|
||||
def test_duplicate_skill_does_not_copy_agent_references() -> None:
|
||||
service = SkillManagementService(tool_file_manager=_FakeToolFileManager())
|
||||
created = service.create_skill(
|
||||
|
||||
@@ -5593,7 +5593,7 @@ export const zGetWorkspacesCurrentSkillsBySkillIdExportPath = z.object({
|
||||
})
|
||||
|
||||
/**
|
||||
* Published Skill zip archive
|
||||
* Draft Skill zip archive
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdExportResponse = z.record(z.string(), z.unknown())
|
||||
|
||||
|
||||
@@ -893,6 +893,14 @@ describe('SkillsPage', () => {
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByText('common.operation.export'))
|
||||
const dialog = await screen.findByRole('alertdialog', {
|
||||
name: 'skill.skillManagement.exportDialog.title',
|
||||
})
|
||||
expect(
|
||||
within(dialog).getByText('skill.skillManagement.exportDialog.description'),
|
||||
).toBeInTheDocument()
|
||||
expect(mocks.exportSkillArchiveBlob).not.toHaveBeenCalled()
|
||||
await user.click(within(dialog).getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.exportSkillArchiveBlob).toHaveBeenCalledWith('skill-1')
|
||||
@@ -903,7 +911,7 @@ describe('SkillsPage', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('does not show export for an unpublished skill', async () => {
|
||||
it('exports an unpublished skill from the card action menu', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.skills = [createSkill({ latest_published_version_id: null })]
|
||||
mocks.skillPages = [mocks.skills]
|
||||
@@ -915,7 +923,36 @@ describe('SkillsPage', () => {
|
||||
}),
|
||||
)
|
||||
|
||||
expect(screen.queryByText('common.operation.export')).not.toBeInTheDocument()
|
||||
await user.click(await screen.findByText('common.operation.export'))
|
||||
const dialog = await screen.findByRole('alertdialog', {
|
||||
name: 'skill.skillManagement.exportDialog.title',
|
||||
})
|
||||
expect(
|
||||
within(dialog).getByText('skill.skillManagement.exportDialog.description'),
|
||||
).toBeInTheDocument()
|
||||
expect(mocks.exportSkillArchiveBlob).not.toHaveBeenCalled()
|
||||
await user.click(within(dialog).getByRole('button', { name: 'common.operation.confirm' }))
|
||||
await waitFor(() => {
|
||||
expect(mocks.exportSkillArchiveBlob).toHaveBeenCalledWith('skill-1')
|
||||
})
|
||||
})
|
||||
|
||||
it('cancels export without downloading the skill', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderSkillsPage()
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'skill.skillManagement.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByText('common.operation.export'))
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
await user.click(within(dialog).getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument())
|
||||
expect(mocks.exportSkillArchiveBlob).not.toHaveBeenCalled()
|
||||
expect(mocks.downloadBlob).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('confirms deletion with the skill name and refreshes list data', async () => {
|
||||
|
||||
@@ -417,6 +417,7 @@ function SkillCard({
|
||||
const descriptionId = useId()
|
||||
const draftStatusId = useId()
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false)
|
||||
const [isExportOpen, setIsExportOpen] = useState(false)
|
||||
const duplicateMutation = useMutation(
|
||||
consoleQuery.workspaces.current.skills.bySkillId.duplicate.post.mutationOptions(),
|
||||
)
|
||||
@@ -424,6 +425,7 @@ function SkillCard({
|
||||
mutationFn: () => fetchSkillArchiveBlob(skill.id),
|
||||
onSuccess: (blob) => {
|
||||
downloadBlob({ data: blob, fileName: `${skill.name}.zip` })
|
||||
setIsExportOpen(false)
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(tCommon(($) => $['operation.downloadFailed']))
|
||||
@@ -526,60 +528,56 @@ function SkillCard({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(canEdit || canDelete || !!skill.latest_published_version_id) && (
|
||||
<div className="pointer-events-none absolute top-[-0.5px] right-[-0.5px] flex h-16 w-30 items-start justify-end bg-[linear-gradient(67deg,var(--color-components-card-bg-alt-transparent)_0%,var(--color-components-card-bg-alt)_75%)] p-2 opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 has-data-popup-open:opacity-100 [@media(hover:none)]:opacity-100">
|
||||
<div className="pointer-events-none flex items-center overflow-hidden rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg backdrop-blur-xs group-focus-within:pointer-events-auto group-hover:pointer-events-auto has-data-popup-open:pointer-events-auto [@media(hover:none)]:pointer-events-auto">
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<IconButton
|
||||
aria-label={t(($) => $['skillManagement.moreActions'], {
|
||||
name: skill.display_name,
|
||||
})}
|
||||
size="lg"
|
||||
className="data-popup-open:bg-state-base-hover"
|
||||
<div className="pointer-events-none absolute top-[-0.5px] right-[-0.5px] flex h-16 w-30 items-start justify-end bg-[linear-gradient(67deg,var(--color-components-card-bg-alt-transparent)_0%,var(--color-components-card-bg-alt)_75%)] p-2 opacity-0 group-focus-within:opacity-100 group-hover:opacity-100 has-data-popup-open:opacity-100 [@media(hover:none)]:opacity-100">
|
||||
<div className="pointer-events-none flex items-center overflow-hidden rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-lg backdrop-blur-xs group-focus-within:pointer-events-auto group-hover:pointer-events-auto has-data-popup-open:pointer-events-auto [@media(hover:none)]:pointer-events-auto">
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<IconButton
|
||||
aria-label={t(($) => $['skillManagement.moreActions'], {
|
||||
name: skill.display_name,
|
||||
})}
|
||||
size="lg"
|
||||
className="data-popup-open:bg-state-base-hover"
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4.5" />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} className="w-40">
|
||||
{canEdit && (
|
||||
<DropdownMenuItem className="gap-2" onClick={handleDuplicate}>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-file-copy-line size-4 shrink-0 text-text-tertiary"
|
||||
/>
|
||||
<span>{tCommon(($) => $['operation.duplicate'])}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem className="gap-2" onClick={() => setIsExportOpen(true)}>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-download-2-line size-4 shrink-0 text-text-tertiary"
|
||||
/>
|
||||
<span>{tCommon(($) => $['operation.export'])}</span>
|
||||
</DropdownMenuItem>
|
||||
{canDelete && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className="gap-2"
|
||||
onClick={() => setIsDeleteOpen(true)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4.5" />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} className="w-40">
|
||||
{canEdit && (
|
||||
<DropdownMenuItem className="gap-2" onClick={handleDuplicate}>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-file-copy-line size-4 shrink-0 text-text-tertiary"
|
||||
/>
|
||||
<span>{tCommon(($) => $['operation.duplicate'])}</span>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
|
||||
<span>{tCommon(($) => $['operation.delete'])}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{skill.latest_published_version_id && (
|
||||
<DropdownMenuItem className="gap-2" onClick={handleExport}>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-download-2-line size-4 shrink-0 text-text-tertiary"
|
||||
/>
|
||||
<span>{tCommon(($) => $['operation.export'])}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{canDelete && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className="gap-2"
|
||||
onClick={() => setIsDeleteOpen(true)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
|
||||
<span>{tCommon(($) => $['operation.delete'])}</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="pointer-events-none absolute top-26 right-3 left-3 flex h-6.5 min-w-0 items-start">
|
||||
<div className="pointer-events-auto w-full min-w-0">
|
||||
<SkillCardTags
|
||||
@@ -590,6 +588,35 @@ function SkillCard({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialog
|
||||
open={isExportOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!exportMutation.isPending) setIsExportOpen(open)
|
||||
}}
|
||||
>
|
||||
<AlertDialogContent>
|
||||
<div className="flex flex-col gap-2 px-6 pt-6 pb-4">
|
||||
<AlertDialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(($) => $['skillManagement.exportDialog.title'])}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="system-md-regular text-text-secondary">
|
||||
{t(($) => $['skillManagement.exportDialog.description'])}
|
||||
</AlertDialogDescription>
|
||||
</div>
|
||||
<AlertDialogActions>
|
||||
<AlertDialogCancelButton disabled={exportMutation.isPending}>
|
||||
{tCommon(($) => $['operation.cancel'])}
|
||||
</AlertDialogCancelButton>
|
||||
<AlertDialogConfirmButton
|
||||
tone="default"
|
||||
loading={exportMutation.isPending}
|
||||
onClick={handleExport}
|
||||
>
|
||||
{tCommon(($) => $['operation.confirm'])}
|
||||
</AlertDialogConfirmButton>
|
||||
</AlertDialogActions>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<DeleteSkillDialog skill={skill} open={isDeleteOpen} onOpenChange={setIsDeleteOpen} />
|
||||
</li>
|
||||
)
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "لا تحتوي هذه الحزمة على SKILL.md. أضف الملف وقم باستيراده مرة أخرى.",
|
||||
"skillManagement.errors.nameConflict": "توجد بالفعل مهارة اسمها \"{{name}}\". أعد تسميته إلى SKILL.md وقم باستيراده مرة أخرى.",
|
||||
"skillManagement.errors.workspaceLimit": "وصلت مساحة العمل الخاصة بك إلى الحد الأقصى وهو 500 مهارة. احذف مهارة لإضافة مهارة جديدة.",
|
||||
"skillManagement.exportDialog.description": "سيتم تصدير المسودة المحفوظة الحالية لمهارتك. إذا كانت لديك تغييرات غير محفوظة في المحرر، يُرجى حفظها قبل التصدير.",
|
||||
"skillManagement.exportDialog.title": "تصدير المسودة المحفوظة الحالية",
|
||||
"skillManagement.import": "استيراد",
|
||||
"skillManagement.importFailed": "فشل استيراد المهارة.",
|
||||
"skillManagement.importSuccess": "Skill مستورد.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Dieses Paket enthält kein SKILL.md. Fügen Sie die Datei hinzu und importieren Sie sie erneut.",
|
||||
"skillManagement.errors.nameConflict": "Ein Skill mit dem Namen „{{name}}“ ist bereits vorhanden. Benennen Sie es in SKILL.md um und importieren Sie es erneut.",
|
||||
"skillManagement.errors.workspaceLimit": "Ihr Arbeitsbereich hat die Grenze von 500 Fertigkeiten erreicht. Löschen Sie eine Fertigkeit, um eine neue hinzuzufügen.",
|
||||
"skillManagement.exportDialog.description": "Dadurch wird der aktuell gespeicherte Entwurf Ihres Skills exportiert. Speichern Sie nicht gespeicherte Änderungen im Editor vor dem Export.",
|
||||
"skillManagement.exportDialog.title": "Aktuell gespeicherten Entwurf exportieren",
|
||||
"skillManagement.import": "Importieren",
|
||||
"skillManagement.importFailed": "Der Skill konnte nicht importiert werden.",
|
||||
"skillManagement.importSuccess": "Skill importiert.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "This package has no SKILL.md. Add the file and import it again.",
|
||||
"skillManagement.errors.nameConflict": "A skill named \"{{name}}\" already exists. Rename it in SKILL.md and import again.",
|
||||
"skillManagement.errors.workspaceLimit": "Your workspace has reached the limit of 500 skills. Delete a skill to add a new one.",
|
||||
"skillManagement.exportDialog.description": "This will export the current saved draft of your skill. If you have unsaved changes in the editor, please save them before exporting.",
|
||||
"skillManagement.exportDialog.title": "Export Current Saved Draft",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Este paquete no tiene SKILL.md. Agregue el archivo e impórtelo nuevamente.",
|
||||
"skillManagement.errors.nameConflict": "Ya existe una habilidad denominada \"{{name}}\". Cambie el nombre a SKILL.md e impórtelo nuevamente.",
|
||||
"skillManagement.errors.workspaceLimit": "Tu espacio de trabajo ha alcanzado el límite de 500 habilidades. Elimina una habilidad para agregar una nueva.",
|
||||
"skillManagement.exportDialog.description": "Se exportará el borrador guardado actual de tu habilidad. Si tienes cambios sin guardar en el editor, guárdalos antes de exportar.",
|
||||
"skillManagement.exportDialog.title": "Exportar el borrador guardado actual",
|
||||
"skillManagement.import": "Importar",
|
||||
"skillManagement.importFailed": "No se pudo importar la habilidad.",
|
||||
"skillManagement.importSuccess": "Skill importado.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "این بسته فاقد SKILL.md است. فایل را اضافه کنید و دوباره آن را وارد کنید.",
|
||||
"skillManagement.errors.nameConflict": "مهارتی به نام \"{{name}}\" از قبل وجود دارد. نام آن را در SKILL.md تغییر دهید و دوباره وارد کنید.",
|
||||
"skillManagement.errors.workspaceLimit": "فضای کاری شما به حداکثر 500 مهارت رسیده است. برای افزودن مهارت جدید، مهارت را حذف کنید.",
|
||||
"skillManagement.exportDialog.description": "با این کار از پیشنویس ذخیرهشدهٔ فعلی مهارت شما خروجی گرفته میشود. اگر در ویرایشگر تغییرات ذخیرهنشدهای دارید، پیش از خروجی گرفتن آنها را ذخیره کنید.",
|
||||
"skillManagement.exportDialog.title": "خروجی گرفتن از پیشنویس ذخیرهشدهٔ فعلی",
|
||||
"skillManagement.import": "وارد کردن",
|
||||
"skillManagement.importFailed": "وارد کردن مهارت انجام نشد.",
|
||||
"skillManagement.importSuccess": "Skill وارد شده است.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Ce package ne contient pas de SKILL.md. Ajoutez le fichier et importez-le à nouveau.",
|
||||
"skillManagement.errors.nameConflict": "Une compétence nommée \"{{name}}\" existe déjà. Renommez-le en SKILL.md et importez-le à nouveau.",
|
||||
"skillManagement.errors.workspaceLimit": "Votre espace de travail a atteint la limite de 500 compétences. Supprimez une compétence pour en ajouter une nouvelle.",
|
||||
"skillManagement.exportDialog.description": "Le brouillon actuellement enregistré de votre compétence sera exporté. Si des modifications ne sont pas enregistrées dans l’éditeur, enregistrez-les avant l’exportation.",
|
||||
"skillManagement.exportDialog.title": "Exporter le brouillon actuellement enregistré",
|
||||
"skillManagement.import": "Importer",
|
||||
"skillManagement.importFailed": "Échec de l'importation de la compétence.",
|
||||
"skillManagement.importSuccess": "Skill importé.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "इस पैकेज में कोई SKILL.md नहीं है। फ़ाइल जोड़ें और इसे फिर से आयात करें।",
|
||||
"skillManagement.errors.nameConflict": "\"{{name}}\" नामक कौशल पहले से मौजूद है। इसे SKILL.md में नाम बदलें और फिर से आयात करें।",
|
||||
"skillManagement.errors.workspaceLimit": "आपका कार्यक्षेत्र 500 कौशल की सीमा तक पहुंच गया है। नया जोड़ने के लिए एक कौशल हटाएँ।",
|
||||
"skillManagement.exportDialog.description": "इससे आपके कौशल का वर्तमान सहेजा गया ड्राफ़्ट निर्यात होगा। यदि संपादक में कोई बदलाव सहेजे नहीं गए हैं, तो निर्यात करने से पहले उन्हें सहेज लें।",
|
||||
"skillManagement.exportDialog.title": "वर्तमान सहेजा गया ड्राफ़्ट निर्यात करें",
|
||||
"skillManagement.import": "आयात करें",
|
||||
"skillManagement.importFailed": "कौशल आयात करने में विफल.",
|
||||
"skillManagement.importSuccess": "Skill आयातित.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Paket ini tidak memiliki SKILL.md. Tambahkan file dan impor lagi.",
|
||||
"skillManagement.errors.nameConflict": "Skill bernama \"{{name}}\" sudah ada. Ubah namanya menjadi SKILL.md dan impor lagi.",
|
||||
"skillManagement.errors.workspaceLimit": "Ruang kerja Anda telah mencapai batas 500 keterampilan. Hapus keterampilan untuk menambahkan keterampilan baru.",
|
||||
"skillManagement.exportDialog.description": "Ini akan mengekspor draf keterampilan Anda yang tersimpan saat ini. Jika ada perubahan yang belum disimpan di editor, simpan terlebih dahulu sebelum mengekspor.",
|
||||
"skillManagement.exportDialog.title": "Ekspor Draf Tersimpan Saat Ini",
|
||||
"skillManagement.import": "Impor",
|
||||
"skillManagement.importFailed": "Gagal mengimpor keterampilan.",
|
||||
"skillManagement.importSuccess": "Skill diimpor.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Questo pacchetto non ha SKILL.md. Aggiungi il file e importalo di nuovo.",
|
||||
"skillManagement.errors.nameConflict": "Esiste già una competenza denominata \"{{name}}\". Rinominalo in SKILL.md e importalo di nuovo.",
|
||||
"skillManagement.errors.workspaceLimit": "La tua area di lavoro ha raggiunto il limite di 500 competenze. Elimina una competenza per aggiungerne una nuova.",
|
||||
"skillManagement.exportDialog.description": "Verrà esportata la bozza attualmente salvata della tua competenza. Se ci sono modifiche non salvate nell’editor, salvale prima di esportare.",
|
||||
"skillManagement.exportDialog.title": "Esporta la bozza attualmente salvata",
|
||||
"skillManagement.import": "Importa",
|
||||
"skillManagement.importFailed": "Impossibile importare la competenza.",
|
||||
"skillManagement.importSuccess": "Skill importato.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "このスキルパッケージには SKILL.md がありません。SKILL.md を追加してから、もう一度インポートしてください。",
|
||||
"skillManagement.errors.nameConflict": "「{{name}}」という名前のスキルはすでに存在します。SKILL.md で名前を変更してから、もう一度インポートしてください。",
|
||||
"skillManagement.errors.workspaceLimit": "ワークスペースに追加できるスキルは 500 個までです。スキルを 1 つ削除してから、新しいスキルを追加してください。",
|
||||
"skillManagement.exportDialog.description": "このスキルの保存済みの現在の下書きをエクスポートします。エディターに未保存の変更がある場合は、エクスポートする前に保存してください。",
|
||||
"skillManagement.exportDialog.title": "保存済みの現在の下書きをエクスポート",
|
||||
"skillManagement.import": "インポート",
|
||||
"skillManagement.importFailed": "スキルのインポートに失敗しました。",
|
||||
"skillManagement.importSuccess": "Skill がインポートされました。",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "이 패키지에는 SKILL.md가 없습니다. 파일을 추가하고 다시 가져오세요.",
|
||||
"skillManagement.errors.nameConflict": "\"{{name}}\"라는 스킬이 이미 존재합니다. SKILL.md에서 이름을 바꾸고 다시 가져옵니다.",
|
||||
"skillManagement.errors.workspaceLimit": "작업 영역이 스킬 한도인 500개에 도달했습니다. 새로운 스킬을 추가하려면 스킬을 삭제하세요.",
|
||||
"skillManagement.exportDialog.description": "이 스킬의 현재 저장된 초안을 내보냅니다. 편집기에 저장하지 않은 변경 사항이 있으면 내보내기 전에 저장해 주세요.",
|
||||
"skillManagement.exportDialog.title": "현재 저장된 초안 내보내기",
|
||||
"skillManagement.import": "가져오기",
|
||||
"skillManagement.importFailed": "스킬을 가져오지 못했습니다.",
|
||||
"skillManagement.importSuccess": "Skill 가져왔습니다.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "ຊຸດນີ້ບໍ່ມີ SKILL.md. ເພີ່ມໄຟລ໌ແລະນໍາເຂົ້າມັນອີກເທື່ອຫນຶ່ງ.",
|
||||
"skillManagement.errors.nameConflict": "ທັກສະທີ່ມີຊື່ວ່າ \"{{name}}\" ມີຢູ່ແລ້ວ. ປ່ຽນຊື່ມັນຢູ່ໃນ SKILL.md ແລະນໍາເຂົ້າອີກເທື່ອຫນຶ່ງ.",
|
||||
"skillManagement.errors.workspaceLimit": "ພື້ນທີ່ເຮັດວຽກຂອງທ່ານຮອດຂີດຈຳກັດຂອງ 500 ທັກສະແລ້ວ. ລຶບທັກສະເພື່ອເພີ່ມອັນໃໝ່.",
|
||||
"skillManagement.exportDialog.description": "ການດຳເນີນການນີ້ຈະສົ່ງອອກສະບັບຮ່າງຂອງທັກສະທີ່ບັນທຶກໄວ້ໃນປັດຈຸບັນ. ຖ້າມີການປ່ຽນແປງທີ່ຍັງບໍ່ໄດ້ບັນທຶກໃນຕົວແກ້ໄຂ, ກະລຸນາບັນທຶກກ່ອນສົ່ງອອກ.",
|
||||
"skillManagement.exportDialog.title": "ສົ່ງອອກສະບັບຮ່າງທີ່ບັນທຶກໄວ້ໃນປັດຈຸບັນ",
|
||||
"skillManagement.import": "ນຳເຂົ້າ",
|
||||
"skillManagement.importFailed": "ລົ້ມເຫລວໃນການນໍາເຂົ້າທັກສະ.",
|
||||
"skillManagement.importSuccess": "Skill ນໍາເຂົ້າ.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Dit pakket heeft geen SKILL.md. Voeg het bestand toe en importeer het opnieuw.",
|
||||
"skillManagement.errors.nameConflict": "Er bestaat al een vaardigheid met de naam \"{{name}}\". Hernoem het in SKILL.md en importeer opnieuw.",
|
||||
"skillManagement.errors.workspaceLimit": "Uw werkruimte heeft de limiet van 500 vaardigheden bereikt. Verwijder een vaardigheid om een nieuwe toe te voegen.",
|
||||
"skillManagement.exportDialog.description": "Hiermee exporteer je het huidige opgeslagen concept van je vaardigheid. Sla eventuele niet-opgeslagen wijzigingen in de editor op voordat je exporteert.",
|
||||
"skillManagement.exportDialog.title": "Huidige opgeslagen concept exporteren",
|
||||
"skillManagement.import": "Importeren",
|
||||
"skillManagement.importFailed": "Kan vaardigheid niet importeren.",
|
||||
"skillManagement.importSuccess": "Skill geïmporteerd.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Ten pakiet nie zawiera SKILL.md. Dodaj plik i zaimportuj go ponownie.",
|
||||
"skillManagement.errors.nameConflict": "Umiejętność o nazwie „{{name}}” już istnieje. Zmień jego nazwę na SKILL.md i zaimportuj ponownie.",
|
||||
"skillManagement.errors.workspaceLimit": "Twój obszar roboczy osiągnął limit 500 umiejętności. Usuń umiejętność, aby dodać nową.",
|
||||
"skillManagement.exportDialog.description": "Zostanie wyeksportowany aktualnie zapisany szkic umiejętności. Jeśli w edytorze są niezapisane zmiany, zapisz je przed eksportem.",
|
||||
"skillManagement.exportDialog.title": "Eksportuj aktualnie zapisany szkic",
|
||||
"skillManagement.import": "Importuj",
|
||||
"skillManagement.importFailed": "Nie udało się zaimportować umiejętności.",
|
||||
"skillManagement.importSuccess": "Skill zaimportowano.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Este pacote não possui SKILL.md. Adicione o arquivo e importe-o novamente.",
|
||||
"skillManagement.errors.nameConflict": "Uma habilidade chamada \"{{name}}\" já existe. Renomeie-o para SKILL.md e importe novamente.",
|
||||
"skillManagement.errors.workspaceLimit": "Seu espaço de trabalho atingiu o limite de 500 habilidades. Exclua uma habilidade para adicionar uma nova.",
|
||||
"skillManagement.exportDialog.description": "Isso exportará o rascunho salvo atual da sua habilidade. Se houver alterações não salvas no editor, salve-as antes de exportar.",
|
||||
"skillManagement.exportDialog.title": "Exportar o rascunho salvo atual",
|
||||
"skillManagement.import": "Importar",
|
||||
"skillManagement.importFailed": "Falha ao importar habilidade.",
|
||||
"skillManagement.importSuccess": "Skill importado.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Acest pachet nu are SKILL.md. Adăugați fișierul și importați-l din nou.",
|
||||
"skillManagement.errors.nameConflict": "O abilitate numită „{{name}}” există deja. Redenumiți-l în SKILL.md și importați din nou.",
|
||||
"skillManagement.errors.workspaceLimit": "Spațiul tău de lucru a atins limita de 500 de aptitudini. Ștergeți o abilitate pentru a adăuga una nouă.",
|
||||
"skillManagement.exportDialog.description": "Se va exporta schița salvată curentă a abilității. Dacă există modificări nesalvate în editor, salvează-le înainte de export.",
|
||||
"skillManagement.exportDialog.title": "Exportă schița salvată curentă",
|
||||
"skillManagement.import": "Importați",
|
||||
"skillManagement.importFailed": "Nu s-a putut importa abilitate.",
|
||||
"skillManagement.importSuccess": "Skill importat.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "В этом пакете нет SKILL.md. Добавьте файл и импортируйте его еще раз.",
|
||||
"skillManagement.errors.nameConflict": "Навык под названием «{{name}}» уже существует. Переименуйте его в SKILL.md и импортируйте снова.",
|
||||
"skillManagement.errors.workspaceLimit": "Ваше рабочее пространство достигло предела в 500 навыков. Удалите навык, чтобы добавить новый.",
|
||||
"skillManagement.exportDialog.description": "Будет экспортирован текущий сохранённый черновик навыка. Если в редакторе есть несохранённые изменения, сохраните их перед экспортом.",
|
||||
"skillManagement.exportDialog.title": "Экспорт текущего сохранённого черновика",
|
||||
"skillManagement.import": "Импортировать",
|
||||
"skillManagement.importFailed": "Не удалось импортировать навык.",
|
||||
"skillManagement.importSuccess": "Skill импортирован.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Ta paket nima SKILL.md. Dodajte datoteko in jo znova uvozite.",
|
||||
"skillManagement.errors.nameConflict": "Spretnost z imenom \"{{name}}\" že obstaja. Preimenujte ga v SKILL.md in znova uvozite.",
|
||||
"skillManagement.errors.workspaceLimit": "Vaš delovni prostor je dosegel omejitev 500 veščin. Izbrišite veščino, da dodate novo.",
|
||||
"skillManagement.exportDialog.description": "Izvožen bo trenutno shranjeni osnutek vaše veščine. Če imate v urejevalniku neshranjene spremembe, jih pred izvozom shranite.",
|
||||
"skillManagement.exportDialog.title": "Izvozi trenutno shranjeni osnutek",
|
||||
"skillManagement.import": "Uvozi",
|
||||
"skillManagement.importFailed": "Spretnosti ni bilo mogoče uvoziti.",
|
||||
"skillManagement.importSuccess": "Skill uvoženo.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "แพ็คเกจนี้ไม่มี SKILL.md เพิ่มไฟล์และนำเข้าอีกครั้ง",
|
||||
"skillManagement.errors.nameConflict": "มีทักษะชื่อ \"{{name}}\" อยู่แล้ว เปลี่ยนชื่อใน SKILL.md และนำเข้าอีกครั้ง",
|
||||
"skillManagement.errors.workspaceLimit": "พื้นที่ทำงานของคุณถึงขีดจำกัด 500 ทักษะแล้ว ลบทักษะเพื่อเพิ่มทักษะใหม่",
|
||||
"skillManagement.exportDialog.description": "การดำเนินการนี้จะส่งออกฉบับร่างของทักษะที่บันทึกไว้ปัจจุบัน หากมีการเปลี่ยนแปลงที่ยังไม่ได้บันทึกในตัวแก้ไข โปรดบันทึกก่อนส่งออก",
|
||||
"skillManagement.exportDialog.title": "ส่งออกฉบับร่างที่บันทึกไว้ปัจจุบัน",
|
||||
"skillManagement.import": "นำเข้า",
|
||||
"skillManagement.importFailed": "ไม่สามารถนำเข้าทักษะได้",
|
||||
"skillManagement.importSuccess": "Skill นำเข้า",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Bu pakette SKILL.md yok. Dosyayı ekleyin ve tekrar içe aktarın.",
|
||||
"skillManagement.errors.nameConflict": "\"{{name}}\" adlı bir beceri zaten mevcut. SKILL.md'de yeniden adlandırın ve tekrar içe aktarın.",
|
||||
"skillManagement.errors.workspaceLimit": "Çalışma alanınız 500 beceri sınırına ulaştı. Yeni bir beceri eklemek için bir beceriyi silin.",
|
||||
"skillManagement.exportDialog.description": "Becerinizin geçerli kayıtlı taslağı dışa aktarılacaktır. Düzenleyicide kaydedilmemiş değişiklikler varsa dışa aktarmadan önce kaydedin.",
|
||||
"skillManagement.exportDialog.title": "Geçerli Kayıtlı Taslağı Dışa Aktar",
|
||||
"skillManagement.import": "İçe Aktar",
|
||||
"skillManagement.importFailed": "Beceri içe aktarılamadı.",
|
||||
"skillManagement.importSuccess": "Skill içe aktarıldı.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Цей пакет не має SKILL.md. Додайте файл і імпортуйте його знову.",
|
||||
"skillManagement.errors.nameConflict": "Уміння під назвою \"{{name}}\" уже існує. Перейменуйте його в SKILL.md і імпортуйте знову.",
|
||||
"skillManagement.errors.workspaceLimit": "Ваша робоча область досягла ліміту в 500 навичок. Видаліть навик, щоб додати новий.",
|
||||
"skillManagement.exportDialog.description": "Буде експортовано поточну збережену чернетку навички. Якщо в редакторі є незбережені зміни, збережіть їх перед експортом.",
|
||||
"skillManagement.exportDialog.title": "Експорт поточної збереженої чернетки",
|
||||
"skillManagement.import": "Імпортувати",
|
||||
"skillManagement.importFailed": "Не вдалося імпортувати навички.",
|
||||
"skillManagement.importSuccess": "Skill імпортовано.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "Gói này không có SKILL.md. Thêm tệp và nhập lại.",
|
||||
"skillManagement.errors.nameConflict": "Một kỹ năng có tên \"{{name}}\" đã tồn tại. Đổi tên nó thành SKILL.md và nhập lại.",
|
||||
"skillManagement.errors.workspaceLimit": "Không gian làm việc của bạn đã đạt đến giới hạn 500 kỹ năng. Xóa một kỹ năng để thêm một kỹ năng mới.",
|
||||
"skillManagement.exportDialog.description": "Thao tác này sẽ xuất bản nháp hiện đã lưu của kỹ năng. Nếu có thay đổi chưa lưu trong trình chỉnh sửa, hãy lưu trước khi xuất.",
|
||||
"skillManagement.exportDialog.title": "Xuất bản nháp hiện đã lưu",
|
||||
"skillManagement.import": "Nhập",
|
||||
"skillManagement.importFailed": "Không thể nhập kỹ năng.",
|
||||
"skillManagement.importSuccess": "Skill đã nhập.",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "此 Skill 包中没有 SKILL.md。请添加该文件后重新导入。",
|
||||
"skillManagement.errors.nameConflict": "名为「{{name}}」的 Skill 已存在。请在 SKILL.md 中修改名称后重新导入。",
|
||||
"skillManagement.errors.workspaceLimit": "工作空间最多可添加 500 个 Skill。请先删除一个 Skill,再添加新 Skill。",
|
||||
"skillManagement.exportDialog.description": "此操作将导出此 Skill 当前已保存的草稿。如果编辑器中有未保存的更改,请先保存后再导出。",
|
||||
"skillManagement.exportDialog.title": "导出当前已保存的草稿",
|
||||
"skillManagement.import": "导入",
|
||||
"skillManagement.importFailed": "Skill 导入失败。",
|
||||
"skillManagement.importSuccess": "Skill 已导入。",
|
||||
|
||||
@@ -227,6 +227,8 @@
|
||||
"skillManagement.errors.missingSkillMd": "這個包沒有SKILL.md。新增檔案並再次匯入。",
|
||||
"skillManagement.errors.nameConflict": "名為「{{name}}」的技能已存在。將其重新命名為SKILL.md並再次匯入。",
|
||||
"skillManagement.errors.workspaceLimit": "您的工作區已達到 500 項技能的限制。刪除一項技能以新增技能。",
|
||||
"skillManagement.exportDialog.description": "此操作將匯出此 Skill 目前已儲存的草稿。如果編輯器中有尚未儲存的變更,請先儲存再匯出。",
|
||||
"skillManagement.exportDialog.title": "匯出目前已儲存的草稿",
|
||||
"skillManagement.import": "匯入",
|
||||
"skillManagement.importFailed": "Skill 匯入失敗。",
|
||||
"skillManagement.importSuccess": "Skill 已匯入。",
|
||||
|
||||
Reference in New Issue
Block a user