Compare commits

...
19 Commits
Author SHA1 Message Date
fatelei 77e41a37ee feat: agent can op file and fix version check 2026-07-28 18:36:06 +08:00
zxhlyh 79229b7ede fix(web): preserve IME composition in skill builder 2026-07-28 17:46:12 +08:00
zxhlyh 36ae648a63 fix(web): protect skill bindings in version view 2026-07-28 16:21:43 +08:00
zxhlyh c409d531da fix(web): paginate workspace skills list 2026-07-28 15:49:44 +08:00
autofix-ci[bot]andGitHub 511ca69e4e [autofix.ci] apply automated fixes 2026-07-28 07:17:11 +00:00
fatelei 4c7e60d7e7 fix: fix db version 2026-07-28 15:12:58 +08:00
fatelei a9e163403d fix: fix model not change 2026-07-28 14:52:16 +08:00
zxhlyhandfatelei bc136b89f9 fix(web): prevent duplicate skill autosaves 2026-07-28 14:52:16 +08:00
fatelei b1bd4f9a8b fix: fix model not update 2026-07-28 14:52:16 +08:00
fatelei 06d3927f05 fix: fix failed test 2026-07-28 14:52:16 +08:00
fatelei 5c3516cae8 fix: fix skill not in agent 2026-07-28 14:52:16 +08:00
fatelei 1c18d8ddbd fix: fix frontend issue and let draft check relax 2026-07-28 14:52:16 +08:00
fatelei 253dfe9351 fix: fix ci failed 2026-07-28 14:52:15 +08:00
fatelei 2d3999e984 fix: fix ci failed 2026-07-28 14:52:15 +08:00
fatelei a431cc726d fix: fix scroll and live mode can not enter 2026-07-28 14:52:15 +08:00
fatelei 08493d2429 fix: fix some bugs 2026-07-28 14:52:15 +08:00
fatelei 8dd0969006 chore: adjust skill entrance 2026-07-28 14:52:15 +08:00
fatelei 2d80d3c35c fix: fix delete skill ref 2026-07-28 14:52:14 +08:00
fatelei 4da764904c feat: support agent skill 2026-07-28 14:46:34 +08:00
111 changed files with 23814 additions and 898 deletions
+2
View File
@@ -144,6 +144,7 @@ from .workspace import (
models,
plugin,
rbac,
skills,
snippets,
tool_providers,
trigger_providers,
@@ -225,6 +226,7 @@ __all__ = [
"saved_message",
"setup",
"site",
"skills",
"snippet_workflow",
"snippet_workflow_draft_variable",
"snippets",
+1 -1
View File
@@ -59,7 +59,7 @@ class TagBindingRemovePayload(BaseModel):
class TagListQueryParam(BaseModel):
type: Literal["knowledge", "app", "snippet", ""] = Field("", description="Tag type filter")
type: TagType | Literal[""] = Field("", description="Tag type filter")
keyword: str | None = Field(None, description="Search keyword")
+834
View File
@@ -0,0 +1,834 @@
"""Console API for workspace-level Skill Management."""
from __future__ import annotations
import io
from flask import request, send_file
from flask_restx import Resource
from pydantic import BaseModel, ConfigDict, Field, ValidationError
from controllers.common.fields import BinaryFileResponse
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
from controllers.console import console_ns
from controllers.console.wraps import (
account_initialization_required,
edit_permission_required,
setup_required,
with_current_tenant_id,
with_current_user,
)
from fields.base import ResponseModel
from libs import helper
from libs.helper import dump_response
from libs.login import login_required
from models.account import Account
from services.skill_management_service import (
SkillAssistMessagePayload,
SkillCreatePayload,
SkillDraftFileOperationPayload,
SkillDraftTreePayload,
SkillImportPayload,
SkillManagementService,
SkillManagementServiceError,
SkillMetadataPayload,
SkillPublishPayload,
SkillRestorePayload,
SkillVersionUpdatePayload,
)
_FILE_UPLOAD_PARAMS = {
"file": {
"description": "Skill draft file payload",
"in": "formData",
"type": "file",
"required": True,
},
}
class WorkspaceSkillsQuery(BaseModel):
keyword: str | None = Field(default=None, description="Search keyword matching skill name or description.")
page: int = Field(default=1, ge=1, le=99999, description="Page number.")
limit: int = Field(default=20, ge=1, le=100, description="Number of items per page.")
tag: list[str] = Field(
default_factory=list,
description="Skill tag filters. Repeat the parameter for multiple tags.",
)
class SkillDeletePayload(BaseModel):
model_config = ConfigDict(extra="forbid")
confirmation_name: str | None = Field(
default=None,
description="Required when deleting a referenced Skill. Must match the Skill name.",
)
class AgentSkillBindingsPayload(BaseModel):
model_config = ConfigDict(extra="forbid")
skill_ids: list[str] = Field(default_factory=list, description="Ordered Skill IDs bound to the Agent.")
class SkillFileQuery(BaseModel):
path: str = Field(description="Skill file path relative to the Skill root.")
version_id: str | None = Field(default=None, description="Optional published version ID. Omit for current draft.")
class SkillResponse(ResponseModel):
id: str
name: str
display_name: str
icon: str
description: str
tags: list[str] = Field(default_factory=list)
name_manually_edited: bool = False
visibility: str
latest_published_version_id: str | None = None
reference_count: int = 0
created_by: str | None = None
created_by_name: str | None = None
updated_by: str | None = None
updated_by_name: str | None = None
created_at: int
updated_at: int
class SkillFileResponse(ResponseModel):
id: str | None = None
path: str
kind: str
storage: str | None = None
mime_type: str | None = None
content: str | None = None
tool_file_id: str | None = None
size: int | None = None
hash: str | None = None
class SkillFilePreviewResponse(ResponseModel):
path: str
mime_type: str
content: str
size: int
hash: str
class SkillFileUploadResponse(ResponseModel):
id: str
name: str
mime_type: str
size: int
hash: str
class SkillDetailResponse(SkillResponse):
files: list[SkillFileResponse] = Field(default_factory=list)
class SkillListResponse(ResponseModel):
data: list[SkillResponse] = Field(default_factory=list)
has_more: bool = False
limit: int = 20
page: int = 1
total: int = 0
class SkillTagResponse(ResponseModel):
tag: str
count: int
class SkillTagListResponse(ResponseModel):
data: list[SkillTagResponse] = Field(default_factory=list)
class SkillVersionResponse(ResponseModel):
id: str
skill_id: str
version_number: int
version_name: str
publish_note: str
hash_code: str
archive_size: int
published_by: str | None = None
published_by_name: str | None = None
is_latest: bool = False
created_at: int
class SkillVersionListResponse(ResponseModel):
data: list[SkillVersionResponse] = Field(default_factory=list)
class SkillVersionDetailResponse(SkillVersionResponse):
files: list[SkillFileResponse] = Field(default_factory=list)
class SkillVersionDeleteResponse(ResponseModel):
id: str
deleted: bool
latest_published_version_id: str | None = None
class SkillReferenceResponse(ResponseModel):
type: str
agent_id: str
agent_icon: str | None = None
agent_icon_background: str | None = None
agent_icon_type: str | None = None
app_id: str | None = None
name: str
display_name: str
workflow_id: str | None = None
workflow_name: str | None = None
workflow_icon: str | None = None
workflow_icon_background: str | None = None
workflow_icon_type: str | None = None
workflow_version: str | None = None
node_id: str | None = None
node_name: str | None = None
class SkillReferenceListResponse(ResponseModel):
data: list[SkillReferenceResponse] = Field(default_factory=list)
class SkillDeleteResponse(ResponseModel):
id: str
deleted: bool
class AgentSkillBindingItemResponse(ResponseModel):
id: str
priority: int
name: str
display_name: str
icon: str
description: str
tags: list[str] = Field(default_factory=list)
status: str
file_count: int
latest_published_version_id: str | None = None
latest_published_at: int | None = None
updated_at: int
class AgentSkillBindingsResponse(ResponseModel):
agent_id: str
skill_ids: list[str] = Field(default_factory=list)
data: list[AgentSkillBindingItemResponse] = Field(default_factory=list)
register_schema_models(
console_ns,
WorkspaceSkillsQuery,
SkillCreatePayload,
SkillAssistMessagePayload,
SkillMetadataPayload,
SkillDraftFileOperationPayload,
SkillDraftTreePayload,
SkillPublishPayload,
SkillRestorePayload,
SkillVersionUpdatePayload,
SkillDeletePayload,
SkillFileQuery,
AgentSkillBindingsPayload,
)
register_response_schema_models(
console_ns,
SkillResponse,
SkillFileResponse,
SkillFilePreviewResponse,
SkillFileUploadResponse,
SkillDetailResponse,
SkillListResponse,
SkillTagResponse,
SkillTagListResponse,
SkillVersionResponse,
SkillVersionListResponse,
SkillVersionDetailResponse,
SkillVersionDeleteResponse,
SkillReferenceResponse,
SkillReferenceListResponse,
SkillDeleteResponse,
AgentSkillBindingItemResponse,
AgentSkillBindingsResponse,
BinaryFileResponse,
)
def _error_response(exc: SkillManagementServiceError) -> tuple[dict[str, object], int]:
body: dict[str, object] = {"code": exc.code, "message": exc.message}
if exc.details:
body["details"] = exc.details
return body, exc.status_code
@console_ns.route("/workspaces/current/skills")
class WorkspaceSkillsApi(Resource):
@console_ns.doc(params=query_params_from_model(WorkspaceSkillsQuery))
@console_ns.response(200, "Workspace skills", console_ns.models[SkillListResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str):
query_input: dict[str, object] = {
"keyword": request.args.get("keyword"),
"tag": request.args.getlist("tag"),
}
if "limit" in request.args:
query_input["limit"] = request.args.get("limit")
if "page" in request.args:
query_input["page"] = request.args.get("page")
query = WorkspaceSkillsQuery.model_validate(query_input)
result = SkillManagementService().list_skills(
tenant_id=current_tenant_id,
keyword=query.keyword,
page=query.page,
limit=query.limit,
tags=[tag for tag in query.tag if tag],
)
return dump_response(SkillListResponse, result)
@console_ns.expect(console_ns.models[SkillCreatePayload.__name__])
@console_ns.response(201, "Skill created", console_ns.models[SkillDetailResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def post(self, current_tenant_id: str, current_user: Account):
try:
payload = SkillCreatePayload.model_validate(console_ns.payload or {})
result = SkillManagementService().create_skill(
tenant_id=current_tenant_id,
user_id=current_user.id,
payload=payload,
)
return dump_response(SkillDetailResponse, result), 201
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except ValueError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/files/upload")
class WorkspaceSkillFileUploadApi(Resource):
@console_ns.doc(consumes=["multipart/form-data"], params=_FILE_UPLOAD_PARAMS)
@console_ns.response(201, "Skill draft file uploaded", console_ns.models[SkillFileUploadResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def post(self, current_tenant_id: str, current_user: Account):
if "file" not in request.files:
return {"code": "no_file_uploaded", "message": "no file uploaded"}, 400
file = request.files["file"]
if not file.filename:
return {"code": "filename_missing", "message": "filename is required"}, 400
result = SkillManagementService().upload_file(
tenant_id=current_tenant_id,
user_id=current_user.id,
filename=file.filename,
content=file.stream.read(),
mime_type=file.mimetype,
)
return dump_response(SkillFileUploadResponse, result), 201
@console_ns.route("/workspaces/current/skills/tags")
class WorkspaceSkillTagsApi(Resource):
@console_ns.response(200, "Workspace Skill tags", console_ns.models[SkillTagListResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str):
result = SkillManagementService().list_tags(tenant_id=current_tenant_id)
return dump_response(SkillTagListResponse, result)
@console_ns.route("/workspaces/current/skills/import")
class WorkspaceSkillImportApi(Resource):
@console_ns.doc(description="Import a Skill zip package from multipart form field `file`.")
@console_ns.response(201, "Skill imported", console_ns.models[SkillDetailResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def post(self, current_tenant_id: str, current_user: Account):
upload = request.files.get("file")
if upload is None:
return {"code": "invalid_request", "message": "file is required"}, 400
try:
payload = SkillImportPayload(content=upload.read(), filename=upload.filename or "skill.zip")
result = SkillManagementService().import_skill(
tenant_id=current_tenant_id,
user_id=current_user.id,
payload=payload,
)
return dump_response(SkillDetailResponse, result), 201
except (ValidationError, ValueError) as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>")
class WorkspaceSkillApi(Resource):
@console_ns.response(200, "Skill detail", console_ns.models[SkillDetailResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, skill_id: str):
try:
result = SkillManagementService().get_skill(tenant_id=current_tenant_id, skill_id=skill_id)
return dump_response(SkillDetailResponse, result)
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.expect(console_ns.models[SkillMetadataPayload.__name__])
@console_ns.response(200, "Skill updated", console_ns.models[SkillResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def patch(self, current_tenant_id: str, current_user: Account, skill_id: str):
try:
payload = SkillMetadataPayload.model_validate(console_ns.payload or {})
result = SkillManagementService().update_metadata(
tenant_id=current_tenant_id,
user_id=current_user.id,
skill_id=skill_id,
payload=payload,
)
return dump_response(SkillResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except ValueError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.expect(console_ns.models[SkillDeletePayload.__name__])
@console_ns.response(200, "Skill deleted", console_ns.models[SkillDeleteResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_tenant_id
def delete(self, current_tenant_id: str, skill_id: str):
try:
payload = SkillDeletePayload.model_validate(console_ns.payload or {})
result = SkillManagementService().delete_skill(
tenant_id=current_tenant_id,
skill_id=skill_id,
confirmation_name=payload.confirmation_name,
)
return dump_response(SkillDeleteResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/duplicate")
class WorkspaceSkillDuplicateApi(Resource):
@console_ns.response(201, "Skill duplicated", console_ns.models[SkillDetailResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def post(self, current_tenant_id: str, current_user: Account, skill_id: str):
try:
result = SkillManagementService().duplicate_skill(
tenant_id=current_tenant_id,
user_id=current_user.id,
skill_id=skill_id,
)
return dump_response(SkillDetailResponse, result), 201
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/export")
class WorkspaceSkillExportApi(Resource):
@console_ns.response(200, "Published Skill zip archive")
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, skill_id: str):
try:
result = SkillManagementService().pull_published_archive(tenant_id=current_tenant_id, skill_id=skill_id)
return send_file(
io.BytesIO(result.payload),
mimetype=result.mime_type,
as_attachment=True,
download_name=result.filename,
)
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/assist/messages")
class WorkspaceSkillAssistMessageApi(Resource):
"""Stream read-only Skill Authoring suggestions from the default workspace model."""
@console_ns.expect(console_ns.models[SkillAssistMessagePayload.__name__])
@console_ns.response(200, "Skill Authoring assistant event stream")
@setup_required
@login_required
@account_initialization_required
@with_current_user
@with_current_tenant_id
def post(self, current_tenant_id: str, current_user: Account, skill_id: str):
try:
payload = SkillAssistMessagePayload.model_validate(console_ns.payload or {})
response = SkillManagementService().create_assistant_action_stream(
tenant_id=current_tenant_id,
skill_id=skill_id,
user_id=current_user.id,
message=payload.message,
attachments=payload.attachments,
model_payload=payload.model,
target_path=payload.target_path,
)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
return helper.compact_generate_response(response)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/files")
class WorkspaceSkillFilesApi(Resource):
@console_ns.expect(console_ns.models[SkillDraftFileOperationPayload.__name__])
@console_ns.response(200, "Draft file operation applied", console_ns.models[SkillDetailResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def patch(self, current_tenant_id: str, current_user: Account, skill_id: str):
try:
payload = SkillDraftFileOperationPayload.model_validate(console_ns.payload or {})
result = SkillManagementService().apply_draft_file_operation(
tenant_id=current_tenant_id,
user_id=current_user.id,
skill_id=skill_id,
payload=payload,
)
return dump_response(SkillDetailResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except ValueError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.expect(console_ns.models[SkillDraftTreePayload.__name__])
@console_ns.response(200, "Draft files replaced", console_ns.models[SkillDetailResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def put(self, current_tenant_id: str, current_user: Account, skill_id: str):
try:
payload = SkillDraftTreePayload.model_validate(console_ns.payload or {})
result = SkillManagementService().replace_draft_tree(
tenant_id=current_tenant_id,
user_id=current_user.id,
skill_id=skill_id,
payload=payload,
)
return dump_response(SkillDetailResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except ValueError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/files/preview")
class WorkspaceSkillFilePreviewApi(Resource):
@console_ns.doc(params=query_params_from_model(SkillFileQuery))
@console_ns.response(200, "Skill file text preview", console_ns.models[SkillFilePreviewResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, skill_id: str):
try:
query = SkillFileQuery.model_validate(
{
"path": request.args.get("path"),
"version_id": request.args.get("version_id"),
}
)
result = SkillManagementService().preview_file(
tenant_id=current_tenant_id,
skill_id=skill_id,
path=query.path,
version_id=query.version_id,
)
return dump_response(SkillFilePreviewResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except ValueError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/files/content")
class WorkspaceSkillFileContentApi(Resource):
@console_ns.doc(params={**query_params_from_model(SkillFileQuery), "download": "Return as an attachment when 1."})
@console_ns.response(200, "Skill file content", console_ns.models[BinaryFileResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, skill_id: str):
try:
query = SkillFileQuery.model_validate(
{
"path": request.args.get("path"),
"version_id": request.args.get("version_id"),
}
)
result = SkillManagementService().pull_file(
tenant_id=current_tenant_id,
skill_id=skill_id,
path=query.path,
version_id=query.version_id,
)
return send_file(
io.BytesIO(result.payload),
mimetype=result.mime_type,
as_attachment=request.args.get("download") == "1",
download_name=result.filename,
)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except ValueError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/publish")
class WorkspaceSkillPublishApi(Resource):
@console_ns.expect(console_ns.models[SkillPublishPayload.__name__])
@console_ns.response(200, "Skill published", console_ns.models[SkillVersionResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def post(self, current_tenant_id: str, current_user: Account, skill_id: str):
try:
payload = SkillPublishPayload.model_validate(console_ns.payload or {})
result = SkillManagementService().publish_skill(
tenant_id=current_tenant_id,
user_id=current_user.id,
skill_id=skill_id,
payload=payload,
)
return dump_response(SkillVersionResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/restore")
class WorkspaceSkillRestoreApi(Resource):
@console_ns.expect(console_ns.models[SkillRestorePayload.__name__])
@console_ns.response(200, "Skill version restored", console_ns.models[SkillVersionResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def post(self, current_tenant_id: str, current_user: Account, skill_id: str):
try:
payload = SkillRestorePayload.model_validate(console_ns.payload or {})
result = SkillManagementService().restore_version(
tenant_id=current_tenant_id,
user_id=current_user.id,
skill_id=skill_id,
payload=payload,
)
return dump_response(SkillVersionResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/references")
class WorkspaceSkillReferencesApi(Resource):
@console_ns.response(200, "Skill references", console_ns.models[SkillReferenceListResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, skill_id: str):
try:
result = SkillManagementService().list_skill_references(tenant_id=current_tenant_id, skill_id=skill_id)
return dump_response(SkillReferenceListResponse, result)
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/versions")
class WorkspaceSkillVersionsApi(Resource):
@console_ns.response(200, "Skill versions", console_ns.models[SkillVersionListResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, skill_id: str):
try:
result = SkillManagementService().list_versions(tenant_id=current_tenant_id, skill_id=skill_id)
return dump_response(SkillVersionListResponse, result)
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/skills/<string:skill_id>/versions/<string:version_id>")
class WorkspaceSkillVersionApi(Resource):
@console_ns.response(200, "Skill version detail", console_ns.models[SkillVersionDetailResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, skill_id: str, version_id: str):
try:
result = SkillManagementService().get_version(
tenant_id=current_tenant_id,
skill_id=skill_id,
version_id=version_id,
)
return dump_response(SkillVersionDetailResponse, result)
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.expect(console_ns.models[SkillVersionUpdatePayload.__name__])
@console_ns.response(200, "Skill version updated", console_ns.models[SkillVersionResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_tenant_id
def patch(self, current_tenant_id: str, skill_id: str, version_id: str):
try:
payload = SkillVersionUpdatePayload.model_validate(console_ns.payload or {})
result = SkillManagementService().update_version(
tenant_id=current_tenant_id,
skill_id=skill_id,
version_id=version_id,
payload=payload,
)
return dump_response(SkillVersionResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.response(200, "Skill version deleted", console_ns.models[SkillVersionDeleteResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def delete(self, current_tenant_id: str, current_user: Account, skill_id: str, version_id: str):
try:
result = SkillManagementService().delete_version(
tenant_id=current_tenant_id,
user_id=current_user.id,
skill_id=skill_id,
version_id=version_id,
)
return dump_response(SkillVersionDeleteResponse, result)
except SkillManagementServiceError as exc:
return _error_response(exc)
@console_ns.route("/workspaces/current/agents/<string:agent_id>/skills")
class WorkspaceAgentSkillBindingsApi(Resource):
@console_ns.response(200, "Agent Skill bindings", console_ns.models[AgentSkillBindingsResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, current_tenant_id: str, agent_id: str):
result = SkillManagementService().list_agent_bindings(tenant_id=current_tenant_id, agent_id=agent_id)
return dump_response(AgentSkillBindingsResponse, result)
@console_ns.expect(console_ns.models[AgentSkillBindingsPayload.__name__])
@console_ns.response(200, "Agent Skill bindings replaced", console_ns.models[AgentSkillBindingsResponse.__name__])
@setup_required
@login_required
@account_initialization_required
@edit_permission_required
@with_current_user
@with_current_tenant_id
def put(self, current_tenant_id: str, current_user: Account, agent_id: str):
try:
payload = AgentSkillBindingsPayload.model_validate(console_ns.payload or {})
result = SkillManagementService().replace_agent_bindings(
tenant_id=current_tenant_id,
user_id=current_user.id,
agent_id=agent_id,
skill_ids=payload.skill_ids,
)
return dump_response(AgentSkillBindingsResponse, result)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
__all__ = [
"WorkspaceAgentSkillBindingsApi",
"WorkspaceSkillApi",
"WorkspaceSkillDuplicateApi",
"WorkspaceSkillExportApi",
"WorkspaceSkillFilesApi",
"WorkspaceSkillImportApi",
"WorkspaceSkillPublishApi",
"WorkspaceSkillReferencesApi",
"WorkspaceSkillRestoreApi",
"WorkspaceSkillTagsApi",
"WorkspaceSkillVersionApi",
"WorkspaceSkillVersionsApi",
"WorkspaceSkillsApi",
]
+2
View File
@@ -23,6 +23,7 @@ from .knowledge import retrieval as _knowledge_retrieval
from .plugin import agent_config as _agent_config
from .plugin import agent_drive as _agent_drive
from .plugin import plugin as _plugin
from .plugin import skills as _skills
from .workspace import workspace as _workspace
api.add_namespace(inner_api_ns)
@@ -36,6 +37,7 @@ __all__ = [
"_mail",
"_plugin",
"_runtime_credentials",
"_skills",
"_workspace",
"api",
"bp",
@@ -0,0 +1,54 @@
"""Inner API for published workspace Skills.
These endpoints are called by trusted runtime services. They expose only
published Skill artifacts, never draft files or editable metadata.
"""
from __future__ import annotations
import io
from flask import request, send_file
from flask_restx import Resource
from pydantic import BaseModel, ValidationError
from controllers.console.wraps import setup_required
from controllers.inner_api import inner_api_ns
from controllers.inner_api.wraps import plugin_inner_api_only
from services.skill_management_service import SkillManagementService, SkillManagementServiceError
class _SkillTargetQuery(BaseModel):
tenant_id: str
def _target_query_from_request() -> _SkillTargetQuery:
return _SkillTargetQuery.model_validate({"tenant_id": request.args.get("tenant_id")})
def _error_response(exc: SkillManagementServiceError) -> tuple[dict[str, str], int]:
return {"code": exc.code, "message": exc.message}, exc.status_code
@inner_api_ns.route("/skills/<string:skill_id>/pull")
class PublishedSkillPullApi(Resource):
@setup_required
@plugin_inner_api_only
@inner_api_ns.doc("published_skill_pull")
def get(self, skill_id: str):
try:
query = _target_query_from_request()
result = SkillManagementService().pull_published_archive(tenant_id=query.tenant_id, skill_id=skill_id)
return send_file(
io.BytesIO(result.payload),
mimetype=result.mime_type,
as_attachment=True,
download_name=result.filename,
)
except ValidationError as exc:
return {"code": "invalid_request", "message": str(exc)}, 400
except SkillManagementServiceError as exc:
return _error_response(exc)
__all__ = ["PublishedSkillPullApi"]
@@ -43,6 +43,7 @@ from core.workflow.nodes.agent_v2.runtime_request_builder import (
build_config_layer_config,
build_knowledge_layer_config,
build_shell_layer_config,
load_runtime_agent_skill_configs,
)
from models.agent_config_entities import AgentSoulConfig, AgentSoulToolsConfig
from models.provider_ids import ModelProviderID
@@ -125,14 +126,22 @@ class AgentAppRuntimeRequestBuilder:
"cli_tool_count": len(agent_soul.tools.cli_tools),
}
runtime_config_skills = load_runtime_agent_skill_configs(
tenant_id=context.dify_context.tenant_id,
agent_id=context.agent_id,
)
config_layer_config, config_warnings = build_config_layer_config(
agent_soul,
agent_id=context.agent_id,
config_version_id=context.agent_config_snapshot_id,
config_version_kind=context.agent_config_version_kind,
runtime_config_skills=runtime_config_skills,
)
append_runtime_warnings(metadata, config_warnings)
soul_prompt_resolver = build_config_aware_soul_mention_resolver(agent_soul)
soul_prompt_resolver = build_config_aware_soul_mention_resolver(
agent_soul,
runtime_config_skills=runtime_config_skills,
)
knowledge_config = build_knowledge_layer_config(agent_soul)
request = self._request_builder.build_for_agent_app(
@@ -38,6 +38,7 @@ from dify_agent.layers.shell import (
)
from dify_agent.protocol import CreateRunRequest, DeferredToolResultsPayload
from pydantic import BaseModel, ValidationError
from sqlalchemy.exc import OperationalError
from clients.agent_backend import (
AgentBackendModelConfig,
@@ -206,14 +207,22 @@ class WorkflowAgentRuntimeRequestBuilder:
"cli_tool_count": len(agent_soul.tools.cli_tools),
}
runtime_config_skills = load_runtime_agent_skill_configs(
tenant_id=context.dify_context.tenant_id,
agent_id=context.agent.id,
)
config_layer_config, config_warnings = build_config_layer_config(
agent_soul,
agent_id=context.agent.id,
config_version_id=context.snapshot.id,
config_version_kind="snapshot",
runtime_config_skills=runtime_config_skills,
)
append_runtime_warnings(metadata, config_warnings)
soul_prompt_resolver = build_config_aware_soul_mention_resolver(agent_soul)
soul_prompt_resolver = build_config_aware_soul_mention_resolver(
agent_soul,
runtime_config_skills=runtime_config_skills,
)
soul_prompt = expand_prompt_mentions(agent_soul.prompt.system_prompt, soul_prompt_resolver).strip()
knowledge_config = build_knowledge_layer_config(agent_soul)
@@ -883,11 +892,16 @@ def append_runtime_warnings(metadata: dict[str, Any], warnings: list[dict[str, s
existing.extend(warnings)
def build_config_aware_soul_mention_resolver(agent_soul: AgentSoulConfig):
def build_config_aware_soul_mention_resolver(
agent_soul: AgentSoulConfig,
*,
runtime_config_skills: Sequence[DifyConfigSkillConfig] = (),
):
"""Resolve config skill/file mentions and delegate the rest to Agent Soul."""
base_resolver = build_soul_mention_resolver(agent_soul)
skill_names = {item.name for item in agent_soul.config_skills if not item.is_missing}
skill_names.update(item.name for item in runtime_config_skills)
file_names = {item.name for item in agent_soul.config_files if not item.is_missing}
def _resolve(mention: object) -> str | None:
@@ -905,12 +919,34 @@ def build_config_aware_soul_mention_resolver(agent_soul: AgentSoulConfig):
return _resolve
def load_runtime_agent_skill_configs(*, tenant_id: str, agent_id: str) -> list[DifyConfigSkillConfig]:
"""Return workspace-bound Skills as prompt-safe runtime config skills."""
from services.skill_management_service import SkillManagementService
try:
runtime_skills = SkillManagementService().list_runtime_agent_skills(tenant_id=tenant_id, agent_id=agent_id)
except OperationalError as exc:
if "no such table: agent_skill_bindings" not in str(exc.orig):
raise
runtime_skills = []
return [
DifyConfigSkillConfig(
name=str(item["name"]),
description=str(item.get("description") or ""),
size=cast(int | None, item.get("size")),
mime_type=cast(str | None, item.get("mime_type")),
)
for item in runtime_skills
]
def build_config_layer_config(
agent_soul: AgentSoulConfig,
*,
agent_id: str | None = None,
config_version_id: str | None = None,
config_version_kind: Literal["snapshot", "draft", "build_draft"] = "snapshot",
runtime_config_skills: Sequence[DifyConfigSkillConfig] = (),
) -> tuple[DifyConfigLayerConfig, list[dict[str, str]]]:
"""Build the always-present Agent config layer from Agent Soul state.
@@ -927,8 +963,23 @@ def build_config_layer_config(
)
)
available_skills = [skill for skill in agent_soul.config_skills if not skill.is_missing]
skill_configs = [
DifyConfigSkillConfig(
name=skill.name,
description=skill.description,
size=skill.size,
mime_type=skill.mime_type,
)
for skill in available_skills
]
seen_skill_names = {skill.name for skill in skill_configs}
for skill in runtime_config_skills:
if skill.name in seen_skill_names:
continue
seen_skill_names.add(skill.name)
skill_configs.append(skill)
available_files = [file_ref for file_ref in agent_soul.config_files if not file_ref.is_missing]
skill_names = {skill.name for skill in available_skills}
skill_names = {skill.name for skill in skill_configs}
file_names = {file_ref.name for file_ref in available_files}
warnings: list[dict[str, str]] = [
{
@@ -965,15 +1016,7 @@ def build_config_layer_config(
kind=config_version_kind,
writable=config_version_kind == "build_draft",
),
skills=[
DifyConfigSkillConfig(
name=skill.name,
description=skill.description,
size=skill.size,
mime_type=skill.mime_type,
)
for skill in available_skills
],
skills=skill_configs,
files=[
DifyConfigFileConfig(
name=file_ref.name,
@@ -0,0 +1,114 @@
"""add workspace skill management
Revision ID: a4f8d2c9e1b0
Revises: 6f5a9c2d8e1b
Create Date: 2026-07-09 12:00:00.000000
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import mysql
from models.types import StringUUID
# revision identifiers, used by Alembic.
revision = "a4f8d2c9e1b0"
down_revision = "6f5a9c2d8e1b"
branch_labels = None
depends_on = None
def _uuid_column(name: str, *, nullable: bool = False) -> sa.Column:
return sa.Column(name, StringUUID(), nullable=nullable)
def _long_text() -> sa.types.TypeEngine:
return sa.Text().with_variant(mysql.LONGTEXT(), "mysql")
def upgrade() -> None:
op.create_table(
"skills",
_uuid_column("id"),
_uuid_column("tenant_id"),
sa.Column("name", sa.String(length=64), nullable=False),
sa.Column("display_name", sa.String(length=128), nullable=False),
sa.Column("icon", sa.String(length=16), nullable=False, server_default="📄"),
sa.Column("description", sa.String(length=1024), nullable=False, server_default=""),
sa.Column("name_manually_edited", sa.Boolean(), nullable=False, server_default=sa.false()),
sa.Column("visibility", sa.String(length=32), nullable=False, server_default="workspace"),
_uuid_column("latest_published_version_id", nullable=True),
_uuid_column("created_by", nullable=True),
_uuid_column("updated_by", nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
sa.PrimaryKeyConstraint("id", name="skill_pkey"),
sa.UniqueConstraint("tenant_id", "name", name="skill_tenant_name_unique"),
)
op.create_index("skills_tenant_updated_at_idx", "skills", ["tenant_id", "updated_at"])
op.create_table(
"skill_draft_files",
_uuid_column("id"),
_uuid_column("skill_id"),
sa.Column("path", sa.String(length=512), nullable=False),
sa.Column("kind", sa.String(length=32), nullable=False),
sa.Column("storage", sa.String(length=32), nullable=True),
sa.Column("mime_type", sa.String(length=255), nullable=True),
sa.Column("content_text", _long_text(), nullable=True),
_uuid_column("tool_file_id", nullable=True),
sa.Column("size", sa.BigInteger(), nullable=True),
sa.Column("hash", sa.String(length=255), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
sa.PrimaryKeyConstraint("id", name="skill_draft_file_pkey"),
sa.UniqueConstraint("skill_id", "path", name="skill_draft_file_skill_path_unique"),
)
op.create_index("skill_draft_files_skill_path_idx", "skill_draft_files", ["skill_id", "path"])
op.create_table(
"skill_versions",
_uuid_column("id"),
_uuid_column("skill_id"),
sa.Column("version_number", sa.Integer(), nullable=False),
sa.Column("version_name", sa.String(length=128), nullable=False, server_default=""),
sa.Column("publish_note", sa.String(length=1024), nullable=False, server_default=""),
sa.Column("manifest", _long_text(), nullable=False),
_uuid_column("archive_tool_file_id"),
sa.Column("hash_code", sa.String(length=255), nullable=False),
sa.Column("archive_size", sa.BigInteger(), nullable=False),
_uuid_column("published_by", nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
sa.PrimaryKeyConstraint("id", name="skill_version_pkey"),
sa.UniqueConstraint("skill_id", "version_number", name="skill_version_skill_number_unique"),
)
op.create_index("skill_versions_skill_created_at_idx", "skill_versions", ["skill_id", "created_at"])
op.create_table(
"agent_skill_bindings",
_uuid_column("id"),
_uuid_column("tenant_id"),
_uuid_column("agent_id"),
_uuid_column("skill_id"),
sa.Column("priority", sa.Integer(), nullable=False),
_uuid_column("created_by", nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
sa.Column("updated_at", sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()),
sa.PrimaryKeyConstraint("id", name="agent_skill_binding_pkey"),
sa.UniqueConstraint("tenant_id", "agent_id", "skill_id", name="agent_skill_binding_unique"),
sa.UniqueConstraint("tenant_id", "agent_id", "priority", name="agent_skill_binding_priority_unique"),
)
op.create_index("agent_skill_bindings_skill_idx", "agent_skill_bindings", ["tenant_id", "skill_id"])
def downgrade() -> None:
op.drop_index("agent_skill_bindings_skill_idx", table_name="agent_skill_bindings")
op.drop_table("agent_skill_bindings")
op.drop_index("skill_versions_skill_created_at_idx", table_name="skill_versions")
op.drop_table("skill_versions")
op.drop_index("skill_draft_files_skill_path_idx", table_name="skill_draft_files")
op.drop_table("skill_draft_files")
op.drop_index("skills_tenant_updated_at_idx", table_name="skills")
op.drop_table("skills")
+7
View File
@@ -113,6 +113,7 @@ from .provider import (
TenantDefaultModel,
TenantPreferredModelProvider,
)
from .skill import AgentSkillBinding, Skill, SkillDraftFile, SkillFileKind, SkillFileStorage, SkillVersion
from .snippet import CustomizedSnippet, SnippetType
from .source import DataSourceApiKeyAuthBinding, DataSourceOauthBinding
from .task import CeleryTask, CeleryTaskSet
@@ -173,6 +174,7 @@ __all__ = [
"AgentRuntimeSessionOwnerType",
"AgentRuntimeSessionStatus",
"AgentScope",
"AgentSkillBinding",
"AgentSource",
"AgentStatus",
"ApiRequest",
@@ -246,6 +248,11 @@ __all__ = [
"RecommendedApp",
"SavedMessage",
"Site",
"Skill",
"SkillDraftFile",
"SkillFileKind",
"SkillFileStorage",
"SkillVersion",
"SnippetType",
"Tag",
"TagBinding",
+1
View File
@@ -249,6 +249,7 @@ class TagType(StrEnum):
KNOWLEDGE = "knowledge"
APP = "app"
SNIPPET = "snippet"
SKILL = "skill"
class DatasetMetadataType(StrEnum):
+1 -1
View File
@@ -2667,7 +2667,7 @@ class Tag(TypeBase):
sa.Index("tag_name_idx", "name"),
)
TAG_TYPE_LIST = ["knowledge", "app", "snippet"]
TAG_TYPE_LIST = ["knowledge", "app", "snippet", "skill"]
id: Mapped[str] = mapped_column(
StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False
+164
View File
@@ -0,0 +1,164 @@
"""Workspace-level Skill Management models.
These tables are the source of truth for reusable workspace Skills. Agent Soul
``config_skills`` and Agent Drive skill rows remain per-agent runtime/config
assets; they may consume a published Skill snapshot but do not own the Skill's
draft, metadata, version history, or Agent binding priority.
"""
from enum import StrEnum
import sqlalchemy as sa
from pydantic import BaseModel, ConfigDict
from sqlalchemy import Index, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from models.base import Base, DefaultFieldsMixin
from models.types import EnumText, JSONModelColumn, LongText, StringUUID
class SkillFileKind(StrEnum):
"""Draft file entry kind."""
FILE = "file"
DIRECTORY = "directory"
class SkillFileStorage(StrEnum):
"""How a draft file's content is stored."""
TEXT = "text"
TOOL_FILE = "tool_file"
class SkillVersionManifestFile(BaseModel):
"""One file entry captured in a published Skill snapshot manifest."""
path: str
mime_type: str | None = None
size: int
hash: str
model_config = ConfigDict(extra="forbid")
class SkillVersionManifest(BaseModel):
"""Published Skill snapshot file index."""
files: list[SkillVersionManifestFile]
model_config = ConfigDict(extra="forbid")
class Skill(DefaultFieldsMixin, Base):
"""Workspace-level reusable Skill metadata and draft status."""
__tablename__ = "skills"
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="skill_pkey"),
UniqueConstraint("tenant_id", "name", name="skill_tenant_name_unique"),
Index("skills_tenant_updated_at_idx", "tenant_id", "updated_at"),
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
name: Mapped[str] = mapped_column(sa.String(64), nullable=False)
display_name: Mapped[str] = mapped_column(sa.String(128), nullable=False)
icon: Mapped[str] = mapped_column(sa.String(16), nullable=False, default="📄", server_default="📄")
description: Mapped[str] = mapped_column(sa.String(1024), nullable=False, default="", server_default="")
name_manually_edited: Mapped[bool] = mapped_column(
sa.Boolean,
nullable=False,
default=False,
server_default=sa.false(),
)
visibility: Mapped[str] = mapped_column(
sa.String(32),
nullable=False,
default="workspace",
server_default="workspace",
)
latest_published_version_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
created_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
updated_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
class SkillDraftFile(DefaultFieldsMixin, Base):
"""One draft file or directory in a workspace Skill."""
__tablename__ = "skill_draft_files"
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="skill_draft_file_pkey"),
UniqueConstraint("skill_id", "path", name="skill_draft_file_skill_path_unique"),
Index("skill_draft_files_skill_path_idx", "skill_id", "path"),
)
skill_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
path: Mapped[str] = mapped_column(sa.String(512), nullable=False)
kind: Mapped[SkillFileKind] = mapped_column(EnumText(SkillFileKind, length=32), nullable=False)
storage: Mapped[SkillFileStorage | None] = mapped_column(EnumText(SkillFileStorage, length=32), nullable=True)
mime_type: Mapped[str | None] = mapped_column(sa.String(255), nullable=True)
content_text: Mapped[str | None] = mapped_column(LongText, nullable=True)
tool_file_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
size: Mapped[int | None] = mapped_column(sa.BigInteger, nullable=True)
hash: Mapped[str | None] = mapped_column(sa.String(255), nullable=True)
class SkillVersion(DefaultFieldsMixin, Base):
"""Immutable published Skill snapshot.
``hash_code`` uniquely identifies a published version for downstream
execution audit. It includes Skill identity, version number, and archive
content digest instead of being only the archive content hash.
"""
__tablename__ = "skill_versions"
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="skill_version_pkey"),
UniqueConstraint("skill_id", "version_number", name="skill_version_skill_number_unique"),
Index("skill_versions_skill_created_at_idx", "skill_id", "created_at"),
)
skill_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
version_number: Mapped[int] = mapped_column(sa.Integer, nullable=False)
version_name: Mapped[str] = mapped_column(sa.String(128), nullable=False, default="", server_default="")
publish_note: Mapped[str] = mapped_column(sa.String(1024), nullable=False, default="", server_default="")
manifest: Mapped[SkillVersionManifest] = mapped_column(JSONModelColumn(SkillVersionManifest), nullable=False)
archive_tool_file_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
hash_code: Mapped[str] = mapped_column(sa.String(255), nullable=False)
archive_size: Mapped[int] = mapped_column(sa.BigInteger, nullable=False)
published_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
class AgentSkillBinding(DefaultFieldsMixin, Base):
"""Direct Agent-to-workspace-Skill binding.
``priority`` is retained as an internal ordering column for the current
schema constraints. Runtime Skill selection is Agent-driven and must not
treat it as a matching priority.
"""
__tablename__ = "agent_skill_bindings"
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="agent_skill_binding_pkey"),
UniqueConstraint("tenant_id", "agent_id", "skill_id", name="agent_skill_binding_unique"),
UniqueConstraint("tenant_id", "agent_id", "priority", name="agent_skill_binding_priority_unique"),
Index("agent_skill_bindings_skill_idx", "tenant_id", "skill_id"),
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
agent_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
skill_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
priority: Mapped[int] = mapped_column(sa.Integer, nullable=False)
created_by: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
__all__ = [
"AgentSkillBinding",
"Skill",
"SkillDraftFile",
"SkillFileKind",
"SkillFileStorage",
"SkillVersion",
"SkillVersionManifest",
"SkillVersionManifestFile",
]
+728 -2
View File
@@ -9551,7 +9551,7 @@ Remove one or more tag bindings from a target.
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| keyword | query | Search keyword | No | string |
| type | query | Tag type filter | No | string, <br>**Available values:** "", "app", "knowledge", "snippet" |
| type | query | Tag type filter | No | string |
#### Responses
@@ -10087,6 +10087,38 @@ Get list of available agent providers
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [AgentProviderListResponse](#agentproviderlistresponse)<br> |
### [GET] /workspaces/current/agents/{agent_id}/skills
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| agent_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Agent Skill bindings | **application/json**: [AgentSkillBindingsResponse](#agentskillbindingsresponse)<br> |
### [PUT] /workspaces/current/agents/{agent_id}/skills
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| agent_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [AgentSkillBindingsPayload](#agentskillbindingspayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Agent Skill bindings replaced | **application/json**: [AgentSkillBindingsResponse](#agentskillbindingsresponse)<br> |
### [GET] /workspaces/current/customized-snippets
**List customized snippets with pagination and search**
@@ -11988,6 +12020,341 @@ Returns permission flags that control workspace features like member invitations
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [WorkspaceAccessMatrix](#workspaceaccessmatrix)<br> |
### [GET] /workspaces/current/skills
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| keyword | query | Search keyword matching skill name or description. | No | string |
| limit | query | Number of items per page. | No | integer, <br>**Default:** 20 |
| page | query | Page number. | No | integer, <br>**Default:** 1 |
| tag | query | Skill tag filters. Repeat the parameter for multiple tags. | No | [ string ] |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Workspace skills | **application/json**: [SkillListResponse](#skilllistresponse)<br> |
### [POST] /workspaces/current/skills
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillCreatePayload](#skillcreatepayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 201 | Skill created | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
### [POST] /workspaces/current/skills/files/upload
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **multipart/form-data**: { **"file"**: binary }<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 201 | Skill draft file uploaded | **application/json**: [SkillFileUploadResponse](#skillfileuploadresponse)<br> |
### [POST] /workspaces/current/skills/import
Import a Skill zip package from multipart form field `file`.
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 201 | Skill imported | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
### [GET] /workspaces/current/skills/tags
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Workspace Skill tags | **application/json**: [SkillTagListResponse](#skilltaglistresponse)<br> |
### [DELETE] /workspaces/current/skills/{skill_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillDeletePayload](#skilldeletepayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill deleted | **application/json**: [SkillDeleteResponse](#skilldeleteresponse)<br> |
### [GET] /workspaces/current/skills/{skill_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill detail | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
### [PATCH] /workspaces/current/skills/{skill_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillMetadataPayload](#skillmetadatapayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill updated | **application/json**: [SkillResponse](#skillresponse)<br> |
### [POST] /workspaces/current/skills/{skill_id}/assist/messages
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillAssistMessagePayload](#skillassistmessagepayload)<br> |
#### Responses
| Code | Description |
| ---- | ----------- |
| 200 | Skill Authoring assistant event stream |
### [POST] /workspaces/current/skills/{skill_id}/duplicate
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 201 | Skill duplicated | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
### [GET] /workspaces/current/skills/{skill_id}/export
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Responses
| Code | Description |
| ---- | ----------- |
| 200 | Published Skill zip archive |
### [PATCH] /workspaces/current/skills/{skill_id}/files
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillDraftFileOperationPayload](#skilldraftfileoperationpayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Draft file operation applied | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
### [PUT] /workspaces/current/skills/{skill_id}/files
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillDraftTreePayload](#skilldrafttreepayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Draft files replaced | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
### [GET] /workspaces/current/skills/{skill_id}/files/content
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| download | query | Return as an attachment when 1. | No | string |
| path | query | Skill file path relative to the Skill root. | Yes | string |
| version_id | query | Optional published version ID. Omit for current draft. | No | string |
| skill_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill file content | **application/json**: [BinaryFileResponse](#binaryfileresponse)<br> |
### [GET] /workspaces/current/skills/{skill_id}/files/preview
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| path | query | Skill file path relative to the Skill root. | Yes | string |
| version_id | query | Optional published version ID. Omit for current draft. | No | string |
| skill_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill file text preview | **application/json**: [SkillFilePreviewResponse](#skillfilepreviewresponse)<br> |
### [POST] /workspaces/current/skills/{skill_id}/publish
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillPublishPayload](#skillpublishpayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill published | **application/json**: [SkillVersionResponse](#skillversionresponse)<br> |
### [GET] /workspaces/current/skills/{skill_id}/references
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill references | **application/json**: [SkillReferenceListResponse](#skillreferencelistresponse)<br> |
### [POST] /workspaces/current/skills/{skill_id}/restore
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillRestorePayload](#skillrestorepayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill version restored | **application/json**: [SkillVersionResponse](#skillversionresponse)<br> |
### [GET] /workspaces/current/skills/{skill_id}/versions
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill versions | **application/json**: [SkillVersionListResponse](#skillversionlistresponse)<br> |
### [DELETE] /workspaces/current/skills/{skill_id}/versions/{version_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
| version_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill version deleted | **application/json**: [SkillVersionDeleteResponse](#skillversiondeleteresponse)<br> |
### [GET] /workspaces/current/skills/{skill_id}/versions/{version_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
| version_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill version detail | **application/json**: [SkillVersionDetailResponse](#skillversiondetailresponse)<br> |
### [PATCH] /workspaces/current/skills/{skill_id}/versions/{version_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| skill_id | path | | Yes | string |
| version_id | path | | Yes | string |
#### Request Body
| Required | Schema |
| -------- | ------ |
| Yes | **application/json**: [SkillVersionUpdatePayload](#skillversionupdatepayload)<br> |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Skill version updated | **application/json**: [SkillVersionResponse](#skillversionresponse)<br> |
### [GET] /workspaces/current/tool-labels
#### Responses
@@ -14709,6 +15076,37 @@ Visibility and lifecycle scope of an Agent record.
| ---- | ---- | ----------- | -------- |
| result | string | | Yes |
#### AgentSkillBindingItemResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| description | string | | Yes |
| display_name | string | | Yes |
| file_count | integer | | Yes |
| icon | string | | Yes |
| id | string | | Yes |
| latest_published_at | integer | | No |
| latest_published_version_id | string | | No |
| name | string | | Yes |
| priority | integer | | Yes |
| status | string | | Yes |
| tags | [ string ] | | No |
| updated_at | integer | | Yes |
#### AgentSkillBindingsPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| skill_ids | [ string ] | Ordered Skill IDs bound to the Agent. | No |
#### AgentSkillBindingsResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| agent_id | string | | Yes |
| data | [ [AgentSkillBindingItemResponse](#agentskillbindingitemresponse) ] | | No |
| skill_ids | [ string ] | | No |
#### AgentSkillRefConfig
| Name | Type | Description | Required |
@@ -21665,6 +22063,186 @@ Simple provider entity response.
| title | string | | Yes |
| use_icon_as_answer_icon | boolean | | Yes |
#### SkillAssistAttachmentPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| mime_type | string | | No |
| name | string | | Yes |
| size | integer | | No |
| tool_file_id | string | | Yes |
#### SkillAssistMessagePayload
One user message and optional uploaded context for the read-only Skill Authoring assistant.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| attachments | [ [SkillAssistAttachmentPayload](#skillassistattachmentpayload) ] | | No |
| message | string | | Yes |
| model | [SkillAssistModelPayload](#skillassistmodelpayload) | | No |
#### SkillAssistModelPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| model | string | | Yes |
| model_settings | object | | No |
| plugin_id | string | | No |
| provider | string | | Yes |
#### SkillCreatePayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| description | string | | No |
| display_name | string | | No |
| icon | string, <br>**Default:** 📄 | | No |
| name | string | | No |
| tags | [ string ] | | No |
#### SkillDeletePayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| confirmation_name | string | Required when deleting a referenced Skill. Must match the Skill name. | No |
#### SkillDeleteResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| deleted | boolean | | Yes |
| id | string | | Yes |
#### SkillDetailResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| created_at | integer | | Yes |
| created_by | string | | No |
| created_by_name | string | | No |
| description | string | | Yes |
| display_name | string | | Yes |
| files | [ [SkillFileResponse](#skillfileresponse) ] | | No |
| icon | string | | Yes |
| id | string | | Yes |
| latest_published_version_id | string | | No |
| name | string | | Yes |
| name_manually_edited | boolean | | No |
| reference_count | integer | | No |
| tags | [ string ] | | No |
| updated_at | integer | | Yes |
| updated_by | string | | No |
| updated_by_name | string | | No |
| visibility | string | | Yes |
#### SkillDraftFileOperation
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| SkillDraftFileOperation | string | | |
#### SkillDraftFileOperationPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| content | string | | No |
| expected_updated_at | integer | | No |
| hash | string | | No |
| mime_type | string | | No |
| operation | [SkillDraftFileOperation](#skilldraftfileoperation) | | Yes |
| path | string | | Yes |
| size | integer | | No |
| target_path | string | | No |
| tool_file_id | string | | No |
#### SkillDraftTreeItemPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| content | string | | No |
| hash | string | | No |
| kind | [SkillFileKind](#skillfilekind) | | No |
| mime_type | string | | No |
| path | string | | Yes |
| size | integer | | No |
| storage | [SkillFileStorage](#skillfilestorage) | | No |
| tool_file_id | string | | No |
#### SkillDraftTreePayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| expected_updated_at | integer | | No |
| files | [ [SkillDraftTreeItemPayload](#skilldrafttreeitempayload) ] | | No |
#### SkillFileKind
Draft file entry kind.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| SkillFileKind | string | Draft file entry kind. | |
#### SkillFilePreviewResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| content | string | | Yes |
| hash | string | | Yes |
| mime_type | string | | Yes |
| path | string | | Yes |
| size | integer | | Yes |
#### SkillFileQuery
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| path | string | Skill file path relative to the Skill root. | Yes |
| version_id | string | Optional published version ID. Omit for current draft. | No |
#### SkillFileResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| content | string | | No |
| hash | string | | No |
| id | string | | No |
| kind | string | | Yes |
| mime_type | string | | No |
| path | string | | Yes |
| size | integer | | No |
| storage | string | | No |
| tool_file_id | string | | No |
#### SkillFileStorage
How a draft file's content is stored.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| SkillFileStorage | string | How a draft file's content is stored. | |
#### SkillFileUploadResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| hash | string | | Yes |
| id | string | | Yes |
| mime_type | string | | Yes |
| name | string | | Yes |
| size | integer | | Yes |
#### SkillListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| data | [ [SkillResponse](#skillresponse) ] | | No |
| has_more | boolean | | No |
| limit | integer, <br>**Default:** 20 | | No |
| page | integer, <br>**Default:** 1 | | No |
| total | integer | | No |
#### SkillManifest
Validated metadata extracted from a Skill package.
@@ -21678,6 +22256,91 @@ Validated metadata extracted from a Skill package.
| name | string | | Yes |
| size | integer | | Yes |
#### SkillMetadataPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| display_name | string | | No |
| expected_updated_at | integer | | No |
| icon | string | | No |
| tags | [ string ] | | No |
#### SkillPublishPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| publish_note | string | | No |
| version_name | string | | No |
#### SkillReferenceListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| data | [ [SkillReferenceResponse](#skillreferenceresponse) ] | | No |
#### SkillReferenceResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| agent_icon | string | | No |
| agent_icon_background | string | | No |
| agent_icon_type | string | | No |
| agent_id | string | | Yes |
| app_id | string | | No |
| display_name | string | | Yes |
| name | string | | Yes |
| node_id | string | | No |
| node_name | string | | No |
| type | string | | Yes |
| workflow_icon | string | | No |
| workflow_icon_background | string | | No |
| workflow_icon_type | string | | No |
| workflow_id | string | | No |
| workflow_name | string | | No |
| workflow_version | string | | No |
#### SkillResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| created_at | integer | | Yes |
| created_by | string | | No |
| created_by_name | string | | No |
| description | string | | Yes |
| display_name | string | | Yes |
| icon | string | | Yes |
| id | string | | Yes |
| latest_published_version_id | string | | No |
| name | string | | Yes |
| name_manually_edited | boolean | | No |
| reference_count | integer | | No |
| tags | [ string ] | | No |
| updated_at | integer | | Yes |
| updated_by | string | | No |
| updated_by_name | string | | No |
| visibility | string | | Yes |
#### SkillRestorePayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| publish_note | string | | No |
| version_id | string | | Yes |
| version_name | string | | No |
#### SkillTagListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| data | [ [SkillTagResponse](#skilltagresponse) ] | | No |
#### SkillTagResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| count | integer | | Yes |
| tag | string | | Yes |
#### SkillToolInferenceResult
| Name | Type | Description | Required |
@@ -21686,6 +22349,60 @@ Validated metadata extracted from a Skill package.
| inferable | boolean | | Yes |
| reason | string | | No |
#### SkillVersionDeleteResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| deleted | boolean | | Yes |
| id | string | | Yes |
| latest_published_version_id | string | | No |
#### SkillVersionDetailResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| archive_size | integer | | Yes |
| created_at | integer | | Yes |
| files | [ [SkillFileResponse](#skillfileresponse) ] | | No |
| hash_code | string | | Yes |
| id | string | | Yes |
| is_latest | boolean | | No |
| publish_note | string | | Yes |
| published_by | string | | No |
| published_by_name | string | | No |
| skill_id | string | | Yes |
| version_name | string | | Yes |
| version_number | integer | | Yes |
#### SkillVersionListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| data | [ [SkillVersionResponse](#skillversionresponse) ] | | No |
#### SkillVersionResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| archive_size | integer | | Yes |
| created_at | integer | | Yes |
| hash_code | string | | Yes |
| id | string | | Yes |
| is_latest | boolean | | No |
| publish_note | string | | Yes |
| published_by | string | | No |
| published_by_name | string | | No |
| skill_id | string | | Yes |
| version_name | string | | Yes |
| version_number | integer | | Yes |
#### SkillVersionUpdatePayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| publish_note | string | | No |
| version_name | string | | No |
#### SnippetDependencyCheckResponse
| Name | Type | Description | Required |
@@ -22153,7 +22870,7 @@ Non-sensitive bootstrap snapshot exposed before Console or Web authentication.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| keyword | string | Search keyword | No |
| type | string, <br>**Available values:** "", "app", "knowledge", "snippet" | Tag type filter<br>*Enum:* `""`, `"app"`, `"knowledge"`, `"snippet"` | No |
| type | [TagType](#tagtype)<br>string | Tag type filter | No |
#### TagListResponse
@@ -24339,6 +25056,15 @@ Workflow tool configuration
| ---- | ---- | ----------- | -------- |
| permission_keys | [ string ] | | No |
#### WorkspaceSkillsQuery
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| keyword | string | Search keyword matching skill name or description. | No |
| limit | integer, <br>**Default:** 20 | Number of items per page. | No |
| page | integer, <br>**Default:** 1 | Page number. | No |
| tag | [ string ] | Skill tag filters. Repeat the parameter for multiple tags. | No |
#### WorkspaceTenantResultResponse
| Name | Type | Description | Required |
+46 -20
View File
@@ -19,12 +19,11 @@ from __future__ import annotations
import hashlib
import io
import posixpath
import re
import zipfile
import zlib
import yaml
from pydantic import BaseModel
from pydantic import BaseModel, Field, ValidationError, field_validator
# Bounds — generous but finite so a hostile upload can't exhaust memory/disk.
_MAX_ARCHIVE_BYTES = 50 * 1024 * 1024
@@ -33,7 +32,8 @@ _MAX_SKILL_MD_BYTES = 1 * 1024 * 1024
_MAX_ENTRIES = 5000
_ALLOWED_EXTENSIONS = (".zip", ".skill")
_SKILL_MD_NAME = "SKILL.md"
_HEADING_RE = re.compile(r"^\s*#\s+(.+?)\s*$", re.MULTILINE)
_SKILL_NAME_PATTERN = r"^[a-z0-9]+(?:-[a-z0-9]+)*$"
_MAX_SKILL_DESCRIPTION_LENGTH = 1024
class SkillPackageError(Exception):
@@ -53,13 +53,18 @@ class SkillPackageError(Exception):
class SkillManifest(BaseModel):
"""Validated metadata extracted from a Skill package."""
name: str
description: str
name: str = Field(min_length=1, max_length=64, pattern=_SKILL_NAME_PATTERN)
description: str = Field(min_length=1, max_length=_MAX_SKILL_DESCRIPTION_LENGTH)
entry_path: str # path of SKILL.md inside the archive
files: list[str] # all (safe) file paths inside the archive
size: int # total uncompressed bytes
hash: str # sha256 of the archive bytes
@field_validator("name", "description", mode="before")
@classmethod
def _strip_required_string(cls, value: object) -> object:
return value.strip() if isinstance(value, str) else value
class NormalizedSkillPackage(BaseModel):
"""Canonical skill package bytes and metadata ready to store in agent drive."""
@@ -108,14 +113,17 @@ class SkillPackageService:
normalized_size = sum(max(info.file_size, 0) for info in normalized_members.values())
name, description = self._parse_skill_md(skill_md)
manifest = SkillManifest(
name=name,
description=description,
entry_path=_SKILL_MD_NAME,
files=sorted(normalized_members),
size=normalized_size,
hash=hashlib.sha256(normalized_archive_bytes).hexdigest(),
)
try:
manifest = SkillManifest(
name=name,
description=description,
entry_path=_SKILL_MD_NAME,
files=sorted(normalized_members),
size=normalized_size,
hash=hashlib.sha256(normalized_archive_bytes).hexdigest(),
)
except ValidationError as exc:
raise self._manifest_validation_error(exc) from exc
return NormalizedSkillPackage(
manifest=manifest,
archive_bytes=normalized_archive_bytes,
@@ -123,6 +131,31 @@ class SkillPackageService:
strip_prefix=strip_prefix,
)
@staticmethod
def _manifest_validation_error(exc: ValidationError) -> SkillPackageError:
first_error = exc.errors()[0]
loc = first_error["loc"]
field = loc[0] if loc else "manifest"
error_type = first_error["type"]
if field == "name":
code = "missing_skill_name" if error_type == "string_too_short" else "invalid_skill_name"
message = (
"SKILL.md frontmatter name is required"
if code == "missing_skill_name"
else "SKILL.md frontmatter name must be lowercase letters, numbers, and hyphens only, "
"must not start or end with a hyphen, and must be at most 64 characters"
)
return SkillPackageError(code, message, status_code=400)
if field == "description":
code = "missing_skill_description" if error_type == "string_too_short" else "invalid_skill_description"
message = (
"SKILL.md frontmatter description is required"
if code == "missing_skill_description"
else f"SKILL.md frontmatter description must be at most {_MAX_SKILL_DESCRIPTION_LENGTH} characters"
)
return SkillPackageError(code, message, status_code=400)
return SkillPackageError("invalid_skill_manifest", "SKILL.md frontmatter is invalid", status_code=400)
def _open_archive(self, *, content: bytes, filename: str) -> zipfile.ZipFile:
self._check_extension(filename)
if not content:
@@ -280,13 +313,6 @@ class SkillPackageService:
frontmatter = cls._parse_frontmatter(content)
name = str(frontmatter.get("name") or "").strip()
description = str(frontmatter.get("description") or "").strip()
if not name:
heading = _HEADING_RE.search(content)
name = heading.group(1).strip() if heading else ""
if not name:
raise SkillPackageError(
"missing_skill_name", "SKILL.md must declare a name (frontmatter or top heading)", status_code=400
)
return name, description
@staticmethod
+81 -12
View File
@@ -46,6 +46,7 @@ from models.tools import ToolFile
from services.agent.config_skill_normalize_service import ConfigSkillNormalizeService
from services.agent.skill_package_service import SkillPackageError
from services.agent_drive_service import DriveFileRef
from services.skill_management_service import SkillManagementService, SkillManagementServiceError
class AgentConfigVersionKind(StrEnum):
@@ -98,6 +99,7 @@ class ConfigPushPayload(BaseModel):
@dataclass(slots=True)
class AgentConfigTarget:
tenant_id: str
agent_id: str
version_id: str
kind: AgentConfigVersionKind
@@ -146,6 +148,7 @@ class AgentConfigService:
user_id=user_id,
)
return AgentConfigTarget(
tenant_id=tenant_id,
agent_id=target.agent_id,
version_id=target.version_id,
kind=target.kind,
@@ -191,7 +194,7 @@ class AgentConfigService:
return {
"agent_id": target.agent_id,
"config_version": self._config_version_payload(target),
"items": [self._serialize_skill_item(skill) for skill in target.agent_soul.config_skills],
"items": self._skill_items_for_target(target),
}
def list_files(
@@ -233,10 +236,27 @@ class AgentConfigService:
config_version_kind=config_version_kind,
user_id=user_id,
)
skill = self._require_skill(target.agent_soul, name=name)
file_id = self._available_skill_file_id(skill)
payload, mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id)
return ConfigDownload(filename=f"{skill.name}.zip", mime_type=mime_type or "application/zip", payload=payload)
try:
skill = self._require_skill(target.agent_soul, name=name)
file_id = self._available_skill_file_id(skill)
payload, mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id)
return ConfigDownload(
filename=f"{skill.name}.zip",
mime_type=mime_type or "application/zip",
payload=payload,
)
except AgentConfigServiceError as exc:
if exc.code != "config_skill_not_found":
raise
try:
result = SkillManagementService().pull_runtime_agent_skill(
tenant_id=tenant_id,
agent_id=agent_id,
name=name,
)
return ConfigDownload(filename=result.filename, mime_type=result.mime_type, payload=result.payload)
except SkillManagementServiceError as exc:
raise AgentConfigServiceError("config_skill_not_found", "config skill not found", status_code=404) from exc
def download_skill_url(
self,
@@ -279,9 +299,45 @@ class AgentConfigService:
config_version_kind=config_version_kind,
user_id=user_id,
)
skill = self._require_skill(target.agent_soul, name=name)
file_id = self._available_skill_file_id(skill)
archive_bytes, _mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id)
try:
skill = self._require_skill(target.agent_soul, name=name)
file_id = self._available_skill_file_id(skill)
archive_bytes, _mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id)
skill_item = self._serialize_skill_item(skill)
except AgentConfigServiceError as exc:
if exc.code != "config_skill_not_found":
raise
try:
workspace_archive = SkillManagementService().pull_runtime_agent_skill(
tenant_id=tenant_id,
agent_id=agent_id,
name=name,
)
except SkillManagementServiceError as skill_exc:
raise AgentConfigServiceError(
"config_skill_not_found",
"config skill not found",
status_code=404,
) from skill_exc
archive_bytes = workspace_archive.payload
skill_item = next(
(
item
for item in SkillManagementService().list_runtime_agent_skills(
tenant_id=tenant_id,
agent_id=agent_id,
)
if item["name"] == name
),
{
"id": name,
"name": name,
"description": "",
"size": None,
"hash": None,
"mime_type": "application/zip",
},
)
try:
archive_items, skill_md = self._inspect_skill_archive(archive_bytes)
except (OSError, ValueError, zipfile.BadZipFile) as exc:
@@ -291,7 +347,7 @@ class AgentConfigService:
status_code=500,
) from exc
return {
**self._serialize_skill_item(skill),
**skill_item,
"source": "config_skill_zip",
"files": archive_items,
"skill_md": skill_md,
@@ -839,6 +895,7 @@ class AgentConfigService:
status_code=404,
)
return AgentConfigTarget(
tenant_id=tenant_id,
agent_id=agent_id,
version_id=version.id,
kind=config_version_kind,
@@ -1133,9 +1190,7 @@ class AgentConfigService:
return {
"agent_id": target.agent_id,
"config_version": AgentConfigService._config_version_payload(target),
"skills": {
"items": [AgentConfigService._serialize_skill_item(skill) for skill in target.agent_soul.config_skills]
},
"skills": {"items": AgentConfigService._skill_items_for_target(target)},
"files": {
"items": [
AgentConfigService._serialize_file_item(file_ref) for file_ref in target.agent_soul.config_files
@@ -1145,6 +1200,20 @@ class AgentConfigService:
"note": target.agent_soul.config_note,
}
@staticmethod
def _skill_items_for_target(target: AgentConfigTarget) -> list[dict[str, object]]:
items = [AgentConfigService._serialize_skill_item(skill) for skill in target.agent_soul.config_skills]
seen_names = {str(item["name"]) for item in items}
for item in SkillManagementService().list_runtime_agent_skills(
tenant_id=target.tenant_id,
agent_id=target.agent_id,
):
if item["name"] in seen_names:
continue
seen_names.add(str(item["name"]))
items.append(item)
return items
@staticmethod
def _config_version_payload(target: AgentConfigTarget) -> dict[str, object]:
return {
File diff suppressed because it is too large Load Diff
+7
View File
@@ -12,6 +12,7 @@ from werkzeug.exceptions import NotFound
from models.dataset import Dataset
from models.enums import TagType
from models.model import App, Tag, TagBinding
from models.skill import Skill
from models.snippet import CustomizedSnippet
type _TagTypeLike = TagType | str
@@ -282,5 +283,11 @@ class TagService:
)
if not snippet:
raise NotFound("Snippet not found")
elif type == "skill":
skill = session.scalar(
select(Skill).where(Skill.tenant_id == current_user.current_tenant_id, Skill.id == target_id).limit(1)
)
if not skill:
raise NotFound("Skill not found")
else:
raise NotFound("Invalid binding type")
@@ -0,0 +1,394 @@
from __future__ import annotations
from inspect import unwrap
from unittest.mock import MagicMock, PropertyMock, patch
import pytest
from flask import Flask
from controllers.console import console_ns
from controllers.console.workspace.skills import (
WorkspaceAgentSkillBindingsApi,
WorkspaceSkillAssistMessageApi,
WorkspaceSkillFilesApi,
WorkspaceSkillsApi,
WorkspaceSkillTagsApi,
WorkspaceSkillVersionApi,
)
from models.account import Account
from services.skill_management_service import SkillAssistAttachmentPayload, SkillManagementServiceError
@pytest.fixture
def app() -> Flask:
flask_app = Flask("test_workspace_skills")
flask_app.config["TESTING"] = True
return flask_app
@pytest.fixture
def current_user() -> Account:
user = Account(name="Test User", email="test@example.com")
user.id = "user-1"
return user
def _skill_detail() -> dict:
return {
"id": "skill-1",
"name": "finance-sop",
"display_name": "Finance SOP",
"icon": "📄",
"description": "",
"tags": [],
"name_manually_edited": False,
"visibility": "workspace",
"latest_published_version_id": None,
"reference_count": 0,
"created_by": "user-1",
"created_by_name": "Test User",
"updated_by": "user-1",
"updated_by_name": "Test User",
"created_at": 1,
"updated_at": 1,
"files": [
{
"id": "file-1",
"path": "SKILL.md",
"kind": "file",
"storage": "text",
"mime_type": "text/markdown",
"content": "---\nname: finance-sop\n---\n# Body",
"tool_file_id": None,
"size": 32,
"hash": "hash",
}
],
}
def test_create_skill_validates_payload_and_returns_detail(app: Flask, current_user: Account) -> None:
api = WorkspaceSkillsApi()
method = unwrap(api.post)
service = MagicMock()
service.create_skill.return_value = _skill_detail()
with (
app.test_request_context("/", method="POST"),
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value={}),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload, status = method(api, "tenant-1", current_user)
assert status == 201
assert payload["id"] == "skill-1"
assert payload["files"][0]["path"] == "SKILL.md"
service.create_skill.assert_called_once()
assert service.create_skill.call_args.kwargs["tenant_id"] == "tenant-1"
assert service.create_skill.call_args.kwargs["user_id"] == "user-1"
def test_list_skills_uses_default_pagination_when_query_omits_page_and_limit(app: Flask) -> None:
api = WorkspaceSkillsApi()
method = unwrap(api.get)
service = MagicMock()
service.list_skills.return_value = {
"data": [],
"has_more": False,
"limit": 20,
"page": 1,
"total": 0,
}
with (
app.test_request_context("/?keyword=finance&tag=ops&tag=", method="GET"),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload = method(api, "tenant-1")
assert payload == {
"data": [],
"has_more": False,
"limit": 20,
"page": 1,
"total": 0,
}
service.list_skills.assert_called_once_with(
tenant_id="tenant-1",
keyword="finance",
page=1,
limit=20,
tags=["ops"],
)
def test_get_agent_skill_bindings_returns_card_data(app: Flask) -> None:
api = WorkspaceAgentSkillBindingsApi()
method = unwrap(api.get)
service = MagicMock()
service.list_agent_bindings.return_value = {
"agent_id": "agent-1",
"skill_ids": ["skill-1"],
"data": [
{
"id": "skill-1",
"priority": 0,
"name": "finance-sop",
"display_name": "Finance SOP",
"icon": "📄",
"description": "Handle finance.",
"tags": ["Finance"],
"status": "published",
"file_count": 2,
"latest_published_version_id": "version-1",
"latest_published_at": 123,
"updated_at": 124,
}
],
}
with (
app.test_request_context("/", method="GET"),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload = method(api, "tenant-1", "agent-1")
assert payload["skill_ids"] == ["skill-1"]
assert payload["data"][0]["display_name"] == "Finance SOP"
assert payload["data"][0]["file_count"] == 2
service.list_agent_bindings.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
def test_patch_skill_file_operation_validates_payload_and_returns_detail(app: Flask, current_user: Account) -> None:
api = WorkspaceSkillFilesApi()
method = unwrap(api.patch)
service = MagicMock()
service.apply_draft_file_operation.return_value = _skill_detail()
request_payload = {
"operation": "upsert_text",
"path": "references/policy.md",
"content": "Policy",
}
with (
app.test_request_context("/", method="PATCH"),
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=request_payload),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload = method(api, "tenant-1", current_user, "skill-1")
assert payload["id"] == "skill-1"
service.apply_draft_file_operation.assert_called_once()
call = service.apply_draft_file_operation.call_args.kwargs
assert call["tenant_id"] == "tenant-1"
assert call["user_id"] == "user-1"
assert call["skill_id"] == "skill-1"
assert call["payload"].operation == "upsert_text"
def test_patch_skill_file_operation_returns_error_details(app: Flask, current_user: Account) -> None:
api = WorkspaceSkillFilesApi()
method = unwrap(api.patch)
service = MagicMock()
service.apply_draft_file_operation.side_effect = SkillManagementServiceError(
"missing_skill_name",
"SKILL.md frontmatter name is required",
details={"path": "SKILL.md", "field": "name", "line": 2},
)
with (
app.test_request_context("/", method="PATCH"),
patch.object(
type(console_ns),
"payload",
new_callable=PropertyMock,
return_value={"operation": "delete", "path": "SKILL.md"},
),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload, status = method(api, "tenant-1", current_user, "skill-1")
assert status == 400
assert payload == {
"code": "missing_skill_name",
"message": "SKILL.md frontmatter name is required",
"details": {"path": "SKILL.md", "field": "name", "line": 2},
}
def test_list_skill_tags_returns_filter_options(app: Flask) -> None:
api = WorkspaceSkillTagsApi()
method = unwrap(api.get)
service = MagicMock()
service.list_tags.return_value = {"data": [{"tag": "finance", "count": 2}]}
with (
app.test_request_context("/", method="GET"),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload = method(api, "tenant-1")
assert payload == {"data": [{"tag": "finance", "count": 2}]}
service.list_tags.assert_called_once_with(tenant_id="tenant-1")
def test_get_skill_version_returns_version_detail(app: Flask) -> None:
api = WorkspaceSkillVersionApi()
method = unwrap(api.get)
service = MagicMock()
service.get_version.return_value = {
"id": "version-1",
"skill_id": "skill-1",
"version_number": 1,
"version_name": "Initial finance policy",
"publish_note": "Initial finance policy",
"hash_code": "hash-code",
"archive_size": 123,
"published_by": "user-1",
"published_by_name": "Li Wei",
"is_latest": True,
"created_at": 1,
"files": [
{
"id": None,
"path": "SKILL.md",
"kind": "file",
"storage": "text",
"mime_type": "text/markdown",
"content": "# Version",
"tool_file_id": None,
"size": 9,
"hash": "file-hash",
}
],
}
with (
app.test_request_context("/", method="GET"),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload = method(api, "tenant-1", "skill-1", "version-1")
assert payload["files"][0]["content"] == "# Version"
service.get_version.assert_called_once_with(
tenant_id="tenant-1",
skill_id="skill-1",
version_id="version-1",
)
def test_patch_skill_version_renames_version(app: Flask) -> None:
api = WorkspaceSkillVersionApi()
method = unwrap(api.patch)
service = MagicMock()
service.update_version.return_value = {
"id": "version-1",
"skill_id": "skill-1",
"version_number": 1,
"version_name": "Approval threshold",
"publish_note": "",
"hash_code": "hash-code",
"archive_size": 123,
"published_by": "user-1",
"published_by_name": "Li Wei",
"is_latest": True,
"created_at": 1,
}
with (
app.test_request_context("/", method="PATCH"),
patch.object(
type(console_ns),
"payload",
new_callable=PropertyMock,
return_value={"version_name": "Approval threshold"},
),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload = method(api, "tenant-1", "skill-1", "version-1")
assert payload["version_name"] == "Approval threshold"
service.update_version.assert_called_once()
assert service.update_version.call_args.kwargs["payload"].version_name == "Approval threshold"
def test_delete_skill_version_returns_new_latest(app: Flask, current_user: Account) -> None:
api = WorkspaceSkillVersionApi()
method = unwrap(api.delete)
service = MagicMock()
service.delete_version.return_value = {
"id": "version-2",
"deleted": True,
"latest_published_version_id": "version-1",
}
with (
app.test_request_context("/", method="DELETE"),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
):
payload = method(api, "tenant-1", current_user, "skill-1", "version-2")
assert payload == {"id": "version-2", "deleted": True, "latest_published_version_id": "version-1"}
service.delete_version.assert_called_once_with(
tenant_id="tenant-1",
user_id="user-1",
skill_id="skill-1",
version_id="version-2",
)
def test_skill_assistant_runs_agent_app_stream(app: Flask, current_user: Account) -> None:
api = WorkspaceSkillAssistMessageApi()
method = unwrap(api.post)
service = MagicMock()
assistant_app = MagicMock()
assistant_app.id = "assistant-app-1"
service.get_or_create_assistant_app.return_value = (assistant_app, "<skill_draft>draft</skill_draft>")
app_model = MagicMock()
app_response = MagicMock()
compact_response = MagicMock()
with (
app.test_request_context("/", method="POST"),
patch.object(
type(console_ns),
"payload",
new_callable=PropertyMock,
return_value={
"attachments": [
{
"tool_file_id": "tool-file-1",
"name": "requirements.md",
"mime_type": "text/markdown",
"size": 128,
}
],
"message": "Create an approval checklist.",
},
),
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
patch(
"controllers.console.workspace.skills.db.session",
return_value=MagicMock(get=MagicMock(return_value=app_model)),
),
patch("controllers.console.workspace.skills.AppGenerateService.generate", return_value=app_response),
patch("controllers.console.workspace.skills.helper.compact_generate_response", return_value=compact_response),
):
response = method(api, "tenant-1", current_user, "skill-1")
assert response is compact_response
service.get_or_create_assistant_app.assert_called_once_with(
tenant_id="tenant-1",
skill_id="skill-1",
user_id="user-1",
attachments=[
SkillAssistAttachmentPayload(
tool_file_id="tool-file-1",
name="requirements.md",
mime_type="text/markdown",
size=128,
)
],
message="Create an approval checklist.",
model_payload=None,
)
@@ -7,6 +7,7 @@ from types import SimpleNamespace
from typing import Any
import pytest
from dify_agent.layers.config import DifyConfigSkillConfig
from dify_agent.layers.dify_core_tools import DifyCoreToolConfig, DifyCoreToolsLayerConfig
from dify_agent.layers.dify_plugin import DifyPluginToolConfig, DifyPluginToolsLayerConfig
from dify_agent.layers.execution_context import DifyExecutionContextLayerConfig
@@ -29,6 +30,14 @@ from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom
from models.agent_config_entities import AgentSoulConfig
@pytest.fixture(autouse=True)
def _no_runtime_agent_skills(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(
"core.app.apps.agent_app.runtime_request_builder.load_runtime_agent_skill_configs",
lambda *, tenant_id, agent_id: [],
)
def _exec_ctx() -> DifyExecutionContextLayerConfig:
return DifyExecutionContextLayerConfig(
tenant_id="tenant-1",
@@ -514,6 +523,33 @@ class TestAgentAppConfigLayer:
"mentioned_file_names": [],
}
def test_config_layer_includes_bound_workspace_skills(self, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(
"core.app.apps.agent_app.runtime_request_builder.load_runtime_agent_skill_configs",
lambda *, tenant_id, agent_id: [
DifyConfigSkillConfig(
name="workspace-skill",
description="Bound workspace skill.",
size=123,
mime_type="application/zip",
)
],
)
soul = _soul_with_model()
soul.prompt.system_prompt = "Use [§skill:workspace-skill:Workspace Skill§]."
builder = AgentAppRuntimeRequestBuilder(
credentials_provider=_FakeCredentialsProvider(),
dify_tools_builder=_NoToolsBuilder(), # type: ignore[arg-type]
)
result = builder.build(_ctx(soul))
config = next(layer for layer in result.request.composition.layers if layer.name == DIFY_CONFIG_LAYER_ID)
assert [skill.name for skill in config.config.skills] == ["workspace-skill"]
assert config.config.mentioned_skill_names == ["workspace-skill"]
prompt_layer = next(layer for layer in result.request.composition.layers if layer.name == "agent_soul_prompt")
assert prompt_layer.config.prefix == "Use workspace-skill."
@pytest.mark.parametrize(
("system_prompt", "expected_prefix"),
[
@@ -5,6 +5,7 @@ from typing import cast
import pytest
from agenton.compositor import CompositorSessionSnapshot
from dify_agent.layers.config import DifyConfigSkillConfig
from dify_agent.layers.dify_core_tools import DifyCoreToolConfig, DifyCoreToolsLayerConfig
from dify_agent.layers.dify_plugin import DifyPluginToolConfig, DifyPluginToolsLayerConfig
from dify_agent.protocol import DIFY_AGENT_HISTORY_LAYER_ID, DIFY_AGENT_MODEL_LAYER_ID
@@ -40,6 +41,14 @@ from models.agent_config_entities import (
)
@pytest.fixture(autouse=True)
def _no_runtime_agent_skills(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(
"core.workflow.nodes.agent_v2.runtime_request_builder.load_runtime_agent_skill_configs",
lambda *, tenant_id, agent_id: [],
)
class FakeCredentialsProvider:
def fetch(self, provider_name: str, model_name: str) -> dict[str, object]:
assert provider_name == "openai"
@@ -1420,6 +1429,30 @@ def test_build_config_layer_config_includes_soul_context_and_mentions():
assert warnings == []
def test_build_config_layer_config_includes_runtime_agent_skills():
from core.workflow.nodes.agent_v2.runtime_request_builder import build_config_layer_config
soul = AgentSoulConfig(
prompt={"system_prompt": "Use [§skill:workspace-skill:Workspace Skill§]."},
model=AgentSoulModelConfig(plugin_id="langgenius/openai", model_provider="openai", model="gpt-test"),
)
config, warnings = build_config_layer_config(
soul,
runtime_config_skills=[
DifyConfigSkillConfig(
name="workspace-skill",
description="Bound workspace skill.",
size=123,
mime_type="application/zip",
)
],
)
assert [skill.name for skill in config.skills] == ["workspace-skill"]
assert config.mentioned_skill_names == ["workspace-skill"]
assert warnings == []
def test_build_config_layer_config_returns_empty_config_for_empty_agent_soul():
from core.workflow.nodes.agent_v2.runtime_request_builder import build_config_layer_config
@@ -1505,6 +1538,33 @@ def test_workflow_run_request_contains_config_layer():
assert any(spec.name == DIFY_CONFIG_LAYER_ID and spec.type == "dify.config" for spec in specs)
def test_workflow_run_request_includes_bound_workspace_skills(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(
"core.workflow.nodes.agent_v2.runtime_request_builder.load_runtime_agent_skill_configs",
lambda *, tenant_id, agent_id: [
DifyConfigSkillConfig(
name="workspace-skill",
description="Bound workspace skill.",
size=123,
mime_type="application/zip",
)
],
)
context = _context()
context.snapshot.config_snapshot = AgentSoulConfig(
prompt={"system_prompt": "Use [§skill:workspace-skill:Workspace Skill§]."},
model=AgentSoulModelConfig(plugin_id="langgenius/openai", model_provider="openai", model="gpt-test"),
)
result = WorkflowAgentRuntimeRequestBuilder(credentials_provider=FakeCredentialsProvider()).build(context)
config = next(layer for layer in result.request.composition.layers if layer.name == DIFY_CONFIG_LAYER_ID)
assert [skill.name for skill in config.config.skills] == ["workspace-skill"]
assert config.config.mentioned_skill_names == ["workspace-skill"]
soul_prompt = next(layer for layer in result.request.composition.layers if layer.name == "agent_soul_prompt")
assert soul_prompt.config.prefix == "Use workspace-skill."
def test_workflow_runtime_expands_config_mentions_in_agent_soul_prompt():
context = _context()
context.snapshot.config_snapshot = _soul_with_config_assets()
@@ -13,7 +13,7 @@ from services.agent import skill_package_service as skill_package_service_module
from services.agent.skill_package_service import NormalizedSkillPackage, SkillPackageError, SkillPackageService
_SKILL_MD = """---
name: PDF Toolkit
name: pdf-toolkit
description: Tools for working with PDF files.
---
@@ -43,7 +43,7 @@ def _archive_members(content: bytes) -> list[str]:
def test_valid_skill_normalizes_manifest():
manifest = _normalize({"SKILL.md": _SKILL_MD.encode(), "scripts/run.py": b"print('hi')\n"}).manifest
assert manifest.name == "PDF Toolkit"
assert manifest.name == "pdf-toolkit"
assert manifest.description == "Tools for working with PDF files."
assert manifest.entry_path == "SKILL.md"
assert set(manifest.files) == {"SKILL.md", "scripts/run.py"}
@@ -51,10 +51,10 @@ def test_valid_skill_normalizes_manifest():
assert len(manifest.hash) == 64
def test_name_falls_back_to_heading_without_frontmatter():
manifest = _normalize({"SKILL.md": b"# Heading Name\n\nbody"}).manifest
assert manifest.name == "Heading Name"
assert manifest.description == ""
def test_name_and_description_are_required_in_frontmatter():
with pytest.raises(SkillPackageError) as exc_info:
_normalize({"SKILL.md": b"# heading-name\n\nbody"})
assert exc_info.value.code == "missing_skill_name"
def test_shallowest_skill_md_preferred_during_normalization():
@@ -155,7 +155,18 @@ def test_validate_and_normalize_strips_deeper_selected_skill_root():
({"README.md": b"x"}, "skill.zip", "missing_skill_md"),
({"SKILL.md": _SKILL_MD.encode()}, "skill.tar", "unsupported_extension"),
({"SKILL.md": b""}, "skill.zip", "empty_skill_md"),
({"SKILL.md": b"no name here"}, "skill.zip", "missing_skill_name"),
({"SKILL.md": b"---\ndescription: valid\n---\n# no name here"}, "skill.zip", "missing_skill_name"),
({"SKILL.md": b"---\nname: pdf-toolkit\n---\n# no description"}, "skill.zip", "missing_skill_description"),
(
{"SKILL.md": b"---\nname: PDF Toolkit\ndescription: valid\n---\n# invalid name"},
"skill.zip",
"invalid_skill_name",
),
(
{"SKILL.md": f"---\nname: pdf-toolkit\ndescription: {'x' * 1025}\n---\n# long".encode()},
"skill.zip",
"invalid_skill_description",
),
({"SKILL.md": b"\xff\xfenot utf8"}, "skill.zip", "skill_md_not_utf8"),
],
)
@@ -224,10 +235,10 @@ def test_bad_frontmatter_yaml_rejected():
assert exc_info.value.code == "invalid_frontmatter"
def test_unterminated_frontmatter_falls_back_to_heading():
# leading '---' with no closing fence -> no frontmatter, use the heading
manifest = _normalize({"SKILL.md": b"---\n# Heading Wins\nbody"}).manifest
assert manifest.name == "Heading Wins"
def test_unterminated_frontmatter_rejected():
with pytest.raises(SkillPackageError) as exc_info:
_normalize({"SKILL.md": b"---\n# heading-wins\nbody"})
assert exc_info.value.code == "missing_skill_name"
def test_validate_and_normalize_rejects_files_outside_selected_skill_root():
@@ -20,7 +20,7 @@ _AGENT_ID = "22222222-2222-2222-2222-222222222222"
_USER_ID = "33333333-3333-3333-3333-333333333333"
_SKILL_MD = b"""---
name: PDF Toolkit
name: pdf-toolkit
description: Work with PDFs.
---
@@ -121,7 +121,7 @@ def test_standardize_creates_drive_owned_toolfiles_and_commits_archive_manifest(
assert skill_row.is_skill is True
assert skill_row.skill_metadata is not None
skill_metadata = DriveSkillMetadata.model_validate_json(skill_row.skill_metadata)
assert skill_metadata.name == "PDF Toolkit"
assert skill_metadata.name == "pdf-toolkit"
assert skill_metadata.manifest_files == ["SKILL.md", "scripts/run.py"]
assert archive_row.file_kind == AgentDriveFileKind.TOOL_FILE
assert archive_row.file_id == archive_tool_file.id
@@ -132,7 +132,7 @@ def test_standardize_creates_drive_owned_toolfiles_and_commits_archive_manifest(
# The returned upload response carries only the drive-derived fields the UI needs.
skill = result["skill"]
assert skill["path"] == "pdf-toolkit"
assert skill["name"] == "PDF Toolkit"
assert skill["name"] == "pdf-toolkit"
assert skill["archive_key"] == "pdf-toolkit/.DIFY-SKILL-FULL.zip"
assert skill["skill_md_key"] == "pdf-toolkit/SKILL.md"
assert result["manifest"]["entry_path"] == "SKILL.md"
@@ -63,6 +63,7 @@ def _target(
) -> AgentConfigTarget:
agent_soul = soul or _soul()
return AgentConfigTarget(
tenant_id=TENANT,
agent_id=AGENT,
version_id=version_id,
kind=kind,
@@ -508,7 +509,9 @@ def test_manifest_uses_items_shape_without_download_urls() -> None:
),
)
manifest = AgentConfigService._manifest_for_target(target)
with patch(f"{MODULE}.SkillManagementService") as skill_management_service:
skill_management_service.return_value.list_runtime_agent_skills.return_value = []
manifest = AgentConfigService._manifest_for_target(target)
assert manifest == {
"agent_id": AGENT,
@@ -557,7 +560,9 @@ def test_manifest_preserves_missing_config_assets_and_pull_rejects_them() -> Non
target = _target(kind=AgentConfigVersionKind.DRAFT, writable=False, soul=soul)
service = AgentConfigService()
manifest = service._manifest_for_target(target)
with patch(f"{MODULE}.SkillManagementService") as skill_management_service:
skill_management_service.return_value.list_runtime_agent_skills.return_value = []
manifest = service._manifest_for_target(target)
assert manifest["skills"]["items"][0]["is_missing"] is True # type: ignore[index]
assert manifest["files"]["items"][0]["is_missing"] is True # type: ignore[index]
@@ -606,6 +611,42 @@ def test_config_asset_refs_require_file_id_unless_marked_missing() -> None:
)
def test_manifest_appends_published_workspace_skills() -> None:
target = _target(
kind=AgentConfigVersionKind.DRAFT,
writable=False,
soul=_soul(
config_skills=[AgentConfigSkillRefConfig(name="alpha", description="Alpha skill", file_id="tool-file-1")]
),
)
with patch(f"{MODULE}.SkillManagementService") as skill_management_service:
skill_management_service.return_value.list_runtime_agent_skills.return_value = [
{
"id": "workspace-skill-id",
"name": "beta",
"file_id": "tool-file-2",
"description": "Beta workspace skill",
"size": 123,
"hash": "sha256:beta",
"mime_type": "application/zip",
},
{
"id": "duplicate",
"name": "alpha",
"file_id": "tool-file-ignored",
"description": "Duplicate workspace skill",
"size": 456,
"hash": "sha256:ignored",
"mime_type": "application/zip",
},
]
manifest = AgentConfigService._manifest_for_target(target)
assert [item["name"] for item in manifest["skills"]["items"]] == ["alpha", "beta"]
assert manifest["skills"]["items"][1]["file_id"] == "tool-file-2"
def test_preview_skill_file_returns_text_preview() -> None:
service = AgentConfigService()
target = _target(
File diff suppressed because it is too large Load Diff
@@ -997,11 +997,15 @@ export const zSandboxListResponse = z.object({
* Validated metadata extracted from a Skill package.
*/
export const zSkillManifest = z.object({
description: z.string(),
description: z.string().min(1).max(1024),
entry_path: z.string(),
files: z.array(z.string()),
hash: z.string(),
name: z.string(),
name: z
.string()
.min(1)
.max(64)
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
size: z.int(),
})
@@ -1373,11 +1373,15 @@ export const zAgentLogMetaResponse = z.object({
* Validated metadata extracted from a Skill package.
*/
export const zSkillManifest = z.object({
description: z.string(),
description: z.string().min(1).max(1024),
entry_path: z.string(),
files: z.array(z.string()),
hash: z.string(),
name: z.string(),
name: z
.string()
.min(1)
.max(64)
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
size: z.int(),
})
@@ -20,7 +20,7 @@ export type TagBindingRemovePayload = {
type: TagType
}
export type TagType = 'app' | 'knowledge' | 'snippet'
export type TagType = 'app' | 'knowledge' | 'skill' | 'snippet'
export type PostTagBindingsData = {
body: TagBindingPayload
@@ -14,7 +14,7 @@ export const zSimpleResultResponse = z.object({
*
* Tag type
*/
export const zTagType = z.enum(['app', 'knowledge', 'snippet'])
export const zTagType = z.enum(['app', 'knowledge', 'skill', 'snippet'])
/**
* TagBindingPayload
@@ -22,14 +22,14 @@ export type TagUpdateRequestPayload = {
name: string
}
export type TagType = 'app' | 'knowledge' | 'snippet'
export type TagType = 'app' | 'knowledge' | 'skill' | 'snippet'
export type GetTagsData = {
body?: never
path?: never
query?: {
keyword?: string
type?: '' | 'app' | 'knowledge' | 'snippet'
type?: string
}
url: '/tags'
}
@@ -29,7 +29,7 @@ export const zTagUpdateRequestPayload = z.object({
*
* Tag type
*/
export const zTagType = z.enum(['app', 'knowledge', 'snippet'])
export const zTagType = z.enum(['app', 'knowledge', 'skill', 'snippet'])
/**
* TagBasePayload
@@ -41,7 +41,7 @@ export const zTagBasePayload = z.object({
export const zGetTagsQuery = z.object({
keyword: z.string().optional(),
type: z.enum(['', 'app', 'knowledge', 'snippet']).optional().default(''),
type: z.string().optional().default(''),
})
/**
File diff suppressed because it is too large Load Diff
@@ -32,6 +32,16 @@ export type AgentProviderListResponse = Array<{
[key: string]: unknown
}>
export type AgentSkillBindingsResponse = {
agent_id: string
data?: Array<AgentSkillBindingItemResponse>
skill_ids?: Array<string>
}
export type AgentSkillBindingsPayload = {
skill_ids?: Array<string>
}
export type SnippetPaginationResponse = {
data: Array<SnippetListItemResponse>
has_more: boolean
@@ -635,6 +645,179 @@ export type WorkspaceAccessMatrix = {
pagination?: Pagination | null
}
export type SkillListResponse = {
data?: Array<SkillResponse>
has_more?: boolean
limit?: number
page?: number
total?: number
}
export type SkillCreatePayload = {
description?: string
display_name?: string | null
icon?: string
name?: string | null
tags?: Array<string>
}
export type SkillDetailResponse = {
created_at: number
created_by?: string | null
created_by_name?: string | null
description: string
display_name: string
files?: Array<SkillFileResponse>
icon: string
id: string
latest_published_version_id?: string | null
name: string
name_manually_edited?: boolean
reference_count?: number
tags?: Array<string>
updated_at: number
updated_by?: string | null
updated_by_name?: string | null
visibility: string
}
export type SkillFileUploadResponse = {
hash: string
id: string
mime_type: string
name: string
size: number
}
export type SkillTagListResponse = {
data?: Array<SkillTagResponse>
}
export type SkillDeletePayload = {
confirmation_name?: string | null
}
export type SkillDeleteResponse = {
deleted: boolean
id: string
}
export type SkillMetadataPayload = {
display_name?: string | null
expected_updated_at?: number | null
icon?: string | null
tags?: Array<string> | null
}
export type SkillResponse = {
created_at: number
created_by?: string | null
created_by_name?: string | null
description: string
display_name: string
icon: string
id: string
latest_published_version_id?: string | null
name: string
name_manually_edited?: boolean
reference_count?: number
tags?: Array<string>
updated_at: number
updated_by?: string | null
updated_by_name?: string | null
visibility: string
}
export type SkillAssistMessagePayload = {
attachments?: Array<SkillAssistAttachmentPayload>
message: string
model?: SkillAssistModelPayload | null
}
export type SkillDraftFileOperationPayload = {
content?: string | null
expected_updated_at?: number | null
hash?: string | null
mime_type?: string | null
operation: SkillDraftFileOperation
path: string
size?: number | null
target_path?: string | null
tool_file_id?: string | null
}
export type SkillDraftTreePayload = {
expected_updated_at?: number | null
files?: Array<SkillDraftTreeItemPayload>
}
export type SkillFilePreviewResponse = {
content: string
hash: string
mime_type: string
path: string
size: number
}
export type SkillPublishPayload = {
publish_note?: string
version_name?: string | null
}
export type SkillVersionResponse = {
archive_size: number
created_at: number
hash_code: string
id: string
is_latest?: boolean
publish_note: string
published_by?: string | null
published_by_name?: string | null
skill_id: string
version_name: string
version_number: number
}
export type SkillReferenceListResponse = {
data?: Array<SkillReferenceResponse>
}
export type SkillRestorePayload = {
publish_note?: string
version_id: string
version_name?: string | null
}
export type SkillVersionListResponse = {
data?: Array<SkillVersionResponse>
}
export type SkillVersionDeleteResponse = {
deleted: boolean
id: string
latest_published_version_id?: string | null
}
export type SkillVersionDetailResponse = {
archive_size: number
created_at: number
files?: Array<SkillFileResponse>
hash_code: string
id: string
is_latest?: boolean
publish_note: string
published_by?: string | null
published_by_name?: string | null
skill_id: string
version_name: string
version_number: number
}
export type SkillVersionUpdatePayload = {
publish_note?: string
version_name?: string | null
}
export type ToolLabelListResponse = Array<ToolLabel>
export type ApiToolProviderAddPayload = {
@@ -1042,6 +1225,21 @@ export type WorkspaceCustomConfigResponse = {
replace_webapp_logo?: string | null
}
export type AgentSkillBindingItemResponse = {
description: string
display_name: string
file_count: number
icon: string
id: string
latest_published_at?: number | null
latest_published_version_id?: string | null
name: string
priority: number
status: string
tags?: Array<string>
updated_at: number
}
export type SnippetListItemResponse = {
author_name: string | null
created_at: number
@@ -1502,6 +1700,76 @@ export type AccessPolicyRole = {
role_tag?: string
}
export type SkillFileResponse = {
content?: string | null
hash?: string | null
id?: string | null
kind: string
mime_type?: string | null
path: string
size?: number | null
storage?: string | null
tool_file_id?: string | null
}
export type SkillTagResponse = {
count: number
tag: string
}
export type SkillAssistAttachmentPayload = {
mime_type?: string | null
name: string
size?: number | null
tool_file_id: string
}
export type SkillAssistModelPayload = {
model: string
model_settings?: {
[key: string]: unknown
} | null
plugin_id?: string | null
provider: string
}
export type SkillDraftFileOperation =
| 'delete'
| 'mkdir'
| 'rename'
| 'upsert_text'
| 'upsert_tool_file'
export type SkillDraftTreeItemPayload = {
content?: string | null
hash?: string | null
kind?: SkillFileKind
mime_type?: string | null
path: string
size?: number | null
storage?: SkillFileStorage | null
tool_file_id?: string | null
}
export type SkillReferenceResponse = {
agent_icon?: string | null
agent_icon_background?: string | null
agent_icon_type?: string | null
agent_id: string
app_id?: string | null
display_name: string
name: string
node_id?: string | null
node_name?: string | null
type: string
workflow_icon?: string | null
workflow_icon_background?: string | null
workflow_icon_type?: string | null
workflow_id?: string | null
workflow_name?: string | null
workflow_version?: string | null
}
export type ToolLabel = {
icon: string
label: I18nObject
@@ -1978,6 +2246,10 @@ export type PermissionCatalogItem = {
name: string
}
export type SkillFileKind = 'directory' | 'file'
export type SkillFileStorage = 'text' | 'tool_file'
export type ToolParameter = {
auto_generate?: PluginParameterAutoGenerate | null
default?:
@@ -2473,6 +2745,38 @@ export type GetWorkspacesCurrentAgentProvidersResponses = {
export type GetWorkspacesCurrentAgentProvidersResponse =
GetWorkspacesCurrentAgentProvidersResponses[keyof GetWorkspacesCurrentAgentProvidersResponses]
export type GetWorkspacesCurrentAgentsByAgentIdSkillsData = {
body?: never
path: {
agent_id: string
}
query?: never
url: '/workspaces/current/agents/{agent_id}/skills'
}
export type GetWorkspacesCurrentAgentsByAgentIdSkillsResponses = {
200: AgentSkillBindingsResponse
}
export type GetWorkspacesCurrentAgentsByAgentIdSkillsResponse =
GetWorkspacesCurrentAgentsByAgentIdSkillsResponses[keyof GetWorkspacesCurrentAgentsByAgentIdSkillsResponses]
export type PutWorkspacesCurrentAgentsByAgentIdSkillsData = {
body: AgentSkillBindingsPayload
path: {
agent_id: string
}
query?: never
url: '/workspaces/current/agents/{agent_id}/skills'
}
export type PutWorkspacesCurrentAgentsByAgentIdSkillsResponses = {
200: AgentSkillBindingsResponse
}
export type PutWorkspacesCurrentAgentsByAgentIdSkillsResponse =
PutWorkspacesCurrentAgentsByAgentIdSkillsResponses[keyof PutWorkspacesCurrentAgentsByAgentIdSkillsResponses]
export type GetWorkspacesCurrentCustomizedSnippetsData = {
body?: never
path?: never
@@ -4625,6 +4929,369 @@ export type GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses = {
export type GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponse =
GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses[keyof GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses]
export type GetWorkspacesCurrentSkillsData = {
body?: never
path?: never
query?: {
keyword?: string
limit?: number
page?: number
tag?: Array<string>
}
url: '/workspaces/current/skills'
}
export type GetWorkspacesCurrentSkillsResponses = {
200: SkillListResponse
}
export type GetWorkspacesCurrentSkillsResponse =
GetWorkspacesCurrentSkillsResponses[keyof GetWorkspacesCurrentSkillsResponses]
export type PostWorkspacesCurrentSkillsData = {
body: SkillCreatePayload
path?: never
query?: never
url: '/workspaces/current/skills'
}
export type PostWorkspacesCurrentSkillsResponses = {
201: SkillDetailResponse
}
export type PostWorkspacesCurrentSkillsResponse =
PostWorkspacesCurrentSkillsResponses[keyof PostWorkspacesCurrentSkillsResponses]
export type PostWorkspacesCurrentSkillsFilesUploadData = {
body: {
file: Blob | File
}
path?: never
query?: never
url: '/workspaces/current/skills/files/upload'
}
export type PostWorkspacesCurrentSkillsFilesUploadResponses = {
201: SkillFileUploadResponse
}
export type PostWorkspacesCurrentSkillsFilesUploadResponse =
PostWorkspacesCurrentSkillsFilesUploadResponses[keyof PostWorkspacesCurrentSkillsFilesUploadResponses]
export type PostWorkspacesCurrentSkillsImportData = {
body?: never
path?: never
query?: never
url: '/workspaces/current/skills/import'
}
export type PostWorkspacesCurrentSkillsImportResponses = {
201: SkillDetailResponse
}
export type PostWorkspacesCurrentSkillsImportResponse =
PostWorkspacesCurrentSkillsImportResponses[keyof PostWorkspacesCurrentSkillsImportResponses]
export type GetWorkspacesCurrentSkillsTagsData = {
body?: never
path?: never
query?: never
url: '/workspaces/current/skills/tags'
}
export type GetWorkspacesCurrentSkillsTagsResponses = {
200: SkillTagListResponse
}
export type GetWorkspacesCurrentSkillsTagsResponse =
GetWorkspacesCurrentSkillsTagsResponses[keyof GetWorkspacesCurrentSkillsTagsResponses]
export type DeleteWorkspacesCurrentSkillsBySkillIdData = {
body: SkillDeletePayload
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}'
}
export type DeleteWorkspacesCurrentSkillsBySkillIdResponses = {
200: SkillDeleteResponse
}
export type DeleteWorkspacesCurrentSkillsBySkillIdResponse =
DeleteWorkspacesCurrentSkillsBySkillIdResponses[keyof DeleteWorkspacesCurrentSkillsBySkillIdResponses]
export type GetWorkspacesCurrentSkillsBySkillIdData = {
body?: never
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}'
}
export type GetWorkspacesCurrentSkillsBySkillIdResponses = {
200: SkillDetailResponse
}
export type GetWorkspacesCurrentSkillsBySkillIdResponse =
GetWorkspacesCurrentSkillsBySkillIdResponses[keyof GetWorkspacesCurrentSkillsBySkillIdResponses]
export type PatchWorkspacesCurrentSkillsBySkillIdData = {
body: SkillMetadataPayload
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}'
}
export type PatchWorkspacesCurrentSkillsBySkillIdResponses = {
200: SkillResponse
}
export type PatchWorkspacesCurrentSkillsBySkillIdResponse =
PatchWorkspacesCurrentSkillsBySkillIdResponses[keyof PatchWorkspacesCurrentSkillsBySkillIdResponses]
export type PostWorkspacesCurrentSkillsBySkillIdAssistMessagesData = {
body: SkillAssistMessagePayload
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/assist/messages'
}
export type PostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponses = {
200: {
[key: string]: unknown
}
}
export type PostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponse =
PostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponses[keyof PostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponses]
export type PostWorkspacesCurrentSkillsBySkillIdDuplicateData = {
body?: never
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/duplicate'
}
export type PostWorkspacesCurrentSkillsBySkillIdDuplicateResponses = {
201: SkillDetailResponse
}
export type PostWorkspacesCurrentSkillsBySkillIdDuplicateResponse =
PostWorkspacesCurrentSkillsBySkillIdDuplicateResponses[keyof PostWorkspacesCurrentSkillsBySkillIdDuplicateResponses]
export type GetWorkspacesCurrentSkillsBySkillIdExportData = {
body?: never
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/export'
}
export type GetWorkspacesCurrentSkillsBySkillIdExportResponses = {
200: {
[key: string]: unknown
}
}
export type GetWorkspacesCurrentSkillsBySkillIdExportResponse =
GetWorkspacesCurrentSkillsBySkillIdExportResponses[keyof GetWorkspacesCurrentSkillsBySkillIdExportResponses]
export type PatchWorkspacesCurrentSkillsBySkillIdFilesData = {
body: SkillDraftFileOperationPayload
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/files'
}
export type PatchWorkspacesCurrentSkillsBySkillIdFilesResponses = {
200: SkillDetailResponse
}
export type PatchWorkspacesCurrentSkillsBySkillIdFilesResponse =
PatchWorkspacesCurrentSkillsBySkillIdFilesResponses[keyof PatchWorkspacesCurrentSkillsBySkillIdFilesResponses]
export type PutWorkspacesCurrentSkillsBySkillIdFilesData = {
body: SkillDraftTreePayload
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/files'
}
export type PutWorkspacesCurrentSkillsBySkillIdFilesResponses = {
200: SkillDetailResponse
}
export type PutWorkspacesCurrentSkillsBySkillIdFilesResponse =
PutWorkspacesCurrentSkillsBySkillIdFilesResponses[keyof PutWorkspacesCurrentSkillsBySkillIdFilesResponses]
export type GetWorkspacesCurrentSkillsBySkillIdFilesContentData = {
body?: never
path: {
skill_id: string
}
query: {
download?: string
path: string
version_id?: string
}
url: '/workspaces/current/skills/{skill_id}/files/content'
}
export type GetWorkspacesCurrentSkillsBySkillIdFilesContentResponses = {
200: BinaryFileResponse
}
export type GetWorkspacesCurrentSkillsBySkillIdFilesContentResponse =
GetWorkspacesCurrentSkillsBySkillIdFilesContentResponses[keyof GetWorkspacesCurrentSkillsBySkillIdFilesContentResponses]
export type GetWorkspacesCurrentSkillsBySkillIdFilesPreviewData = {
body?: never
path: {
skill_id: string
}
query: {
path: string
version_id?: string
}
url: '/workspaces/current/skills/{skill_id}/files/preview'
}
export type GetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponses = {
200: SkillFilePreviewResponse
}
export type GetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponse =
GetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponses[keyof GetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponses]
export type PostWorkspacesCurrentSkillsBySkillIdPublishData = {
body: SkillPublishPayload
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/publish'
}
export type PostWorkspacesCurrentSkillsBySkillIdPublishResponses = {
200: SkillVersionResponse
}
export type PostWorkspacesCurrentSkillsBySkillIdPublishResponse =
PostWorkspacesCurrentSkillsBySkillIdPublishResponses[keyof PostWorkspacesCurrentSkillsBySkillIdPublishResponses]
export type GetWorkspacesCurrentSkillsBySkillIdReferencesData = {
body?: never
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/references'
}
export type GetWorkspacesCurrentSkillsBySkillIdReferencesResponses = {
200: SkillReferenceListResponse
}
export type GetWorkspacesCurrentSkillsBySkillIdReferencesResponse =
GetWorkspacesCurrentSkillsBySkillIdReferencesResponses[keyof GetWorkspacesCurrentSkillsBySkillIdReferencesResponses]
export type PostWorkspacesCurrentSkillsBySkillIdRestoreData = {
body: SkillRestorePayload
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/restore'
}
export type PostWorkspacesCurrentSkillsBySkillIdRestoreResponses = {
200: SkillVersionResponse
}
export type PostWorkspacesCurrentSkillsBySkillIdRestoreResponse =
PostWorkspacesCurrentSkillsBySkillIdRestoreResponses[keyof PostWorkspacesCurrentSkillsBySkillIdRestoreResponses]
export type GetWorkspacesCurrentSkillsBySkillIdVersionsData = {
body?: never
path: {
skill_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/versions'
}
export type GetWorkspacesCurrentSkillsBySkillIdVersionsResponses = {
200: SkillVersionListResponse
}
export type GetWorkspacesCurrentSkillsBySkillIdVersionsResponse =
GetWorkspacesCurrentSkillsBySkillIdVersionsResponses[keyof GetWorkspacesCurrentSkillsBySkillIdVersionsResponses]
export type DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdData = {
body?: never
path: {
skill_id: string
version_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/versions/{version_id}'
}
export type DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses = {
200: SkillVersionDeleteResponse
}
export type DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses[keyof DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses]
export type GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdData = {
body?: never
path: {
skill_id: string
version_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/versions/{version_id}'
}
export type GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses = {
200: SkillVersionDetailResponse
}
export type GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses[keyof GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses]
export type PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdData = {
body: SkillVersionUpdatePayload
path: {
skill_id: string
version_id: string
}
query?: never
url: '/workspaces/current/skills/{skill_id}/versions/{version_id}'
}
export type PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses = {
200: SkillVersionResponse
}
export type PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses[keyof PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses]
export type GetWorkspacesCurrentToolLabelsData = {
body?: never
path?: never
@@ -12,6 +12,13 @@ export const zAgentProviderResponse = z.record(z.string(), z.unknown())
*/
export const zAgentProviderListResponse = z.array(z.record(z.string(), z.unknown()))
/**
* AgentSkillBindingsPayload
*/
export const zAgentSkillBindingsPayload = z.object({
skill_ids: z.array(z.string()).optional(),
})
/**
* SnippetImportPayload
*
@@ -436,6 +443,155 @@ export const zReplaceBindingsRequest = z.object({
role_ids: z.array(z.string()).optional(),
})
/**
* SkillCreatePayload
*/
export const zSkillCreatePayload = z.object({
description: z.string().optional().default(''),
display_name: z.string().nullish(),
icon: z.string().optional().default('📄'),
name: z.string().nullish(),
tags: z.array(z.string()).optional(),
})
/**
* SkillFileUploadResponse
*/
export const zSkillFileUploadResponse = z.object({
hash: z.string(),
id: z.string(),
mime_type: z.string(),
name: z.string(),
size: z.int(),
})
/**
* SkillDeletePayload
*/
export const zSkillDeletePayload = z.object({
confirmation_name: z.string().nullish(),
})
/**
* SkillDeleteResponse
*/
export const zSkillDeleteResponse = z.object({
deleted: z.boolean(),
id: z.string(),
})
/**
* SkillMetadataPayload
*/
export const zSkillMetadataPayload = z.object({
display_name: z.string().nullish(),
expected_updated_at: z.int().nullish(),
icon: z.string().nullish(),
tags: z.array(z.string()).nullish(),
})
/**
* SkillResponse
*/
export const zSkillResponse = z.object({
created_at: z.int(),
created_by: z.string().nullish(),
created_by_name: z.string().nullish(),
description: z.string(),
display_name: z.string(),
icon: z.string(),
id: z.string(),
latest_published_version_id: z.string().nullish(),
name: z.string(),
name_manually_edited: z.boolean().optional().default(false),
reference_count: z.int().optional().default(0),
tags: z.array(z.string()).optional(),
updated_at: z.int(),
updated_by: z.string().nullish(),
updated_by_name: z.string().nullish(),
visibility: z.string(),
})
/**
* SkillListResponse
*/
export const zSkillListResponse = z.object({
data: z.array(zSkillResponse).optional(),
has_more: z.boolean().optional().default(false),
limit: z.int().optional().default(20),
page: z.int().optional().default(1),
total: z.int().optional().default(0),
})
/**
* SkillFilePreviewResponse
*/
export const zSkillFilePreviewResponse = z.object({
content: z.string(),
hash: z.string(),
mime_type: z.string(),
path: z.string(),
size: z.int(),
})
/**
* SkillPublishPayload
*/
export const zSkillPublishPayload = z.object({
publish_note: z.string().max(1024).optional().default(''),
version_name: z.string().max(128).nullish(),
})
/**
* SkillVersionResponse
*/
export const zSkillVersionResponse = z.object({
archive_size: z.int(),
created_at: z.int(),
hash_code: z.string(),
id: z.string(),
is_latest: z.boolean().optional().default(false),
publish_note: z.string(),
published_by: z.string().nullish(),
published_by_name: z.string().nullish(),
skill_id: z.string(),
version_name: z.string(),
version_number: z.int(),
})
/**
* SkillRestorePayload
*/
export const zSkillRestorePayload = z.object({
publish_note: z.string().max(1024).optional().default(''),
version_id: z.string(),
version_name: z.string().max(128).nullish(),
})
/**
* SkillVersionListResponse
*/
export const zSkillVersionListResponse = z.object({
data: z.array(zSkillVersionResponse).optional(),
})
/**
* SkillVersionDeleteResponse
*/
export const zSkillVersionDeleteResponse = z.object({
deleted: z.boolean(),
id: z.string(),
latest_published_version_id: z.string().nullish(),
})
/**
* SkillVersionUpdatePayload
*/
export const zSkillVersionUpdatePayload = z.object({
publish_note: z.string().max(1024).optional().default(''),
version_name: z.string().max(128).nullish(),
})
/**
* ApiToolProviderDeletePayload
*/
@@ -656,6 +812,33 @@ export const zSwitchWorkspaceResponse = z.object({
result: z.string(),
})
/**
* AgentSkillBindingItemResponse
*/
export const zAgentSkillBindingItemResponse = z.object({
description: z.string(),
display_name: z.string(),
file_count: z.int(),
icon: z.string(),
id: z.string(),
latest_published_at: z.int().nullish(),
latest_published_version_id: z.string().nullish(),
name: z.string(),
priority: z.int(),
status: z.string(),
tags: z.array(z.string()).optional(),
updated_at: z.int(),
})
/**
* AgentSkillBindingsResponse
*/
export const zAgentSkillBindingsResponse = z.object({
agent_id: z.string(),
data: z.array(zAgentSkillBindingItemResponse).optional(),
skill_ids: z.array(z.string()).optional(),
})
/**
* IconInfo
*
@@ -1244,6 +1427,163 @@ export const zWorkspaceAccessMatrix = z.object({
pagination: zPagination.nullish(),
})
/**
* SkillFileResponse
*/
export const zSkillFileResponse = z.object({
content: z.string().nullish(),
hash: z.string().nullish(),
id: z.string().nullish(),
kind: z.string(),
mime_type: z.string().nullish(),
path: z.string(),
size: z.int().nullish(),
storage: z.string().nullish(),
tool_file_id: z.string().nullish(),
})
/**
* SkillDetailResponse
*/
export const zSkillDetailResponse = z.object({
created_at: z.int(),
created_by: z.string().nullish(),
created_by_name: z.string().nullish(),
description: z.string(),
display_name: z.string(),
files: z.array(zSkillFileResponse).optional(),
icon: z.string(),
id: z.string(),
latest_published_version_id: z.string().nullish(),
name: z.string(),
name_manually_edited: z.boolean().optional().default(false),
reference_count: z.int().optional().default(0),
tags: z.array(z.string()).optional(),
updated_at: z.int(),
updated_by: z.string().nullish(),
updated_by_name: z.string().nullish(),
visibility: z.string(),
})
/**
* SkillVersionDetailResponse
*/
export const zSkillVersionDetailResponse = z.object({
archive_size: z.int(),
created_at: z.int(),
files: z.array(zSkillFileResponse).optional(),
hash_code: z.string(),
id: z.string(),
is_latest: z.boolean().optional().default(false),
publish_note: z.string(),
published_by: z.string().nullish(),
published_by_name: z.string().nullish(),
skill_id: z.string(),
version_name: z.string(),
version_number: z.int(),
})
/**
* SkillTagResponse
*/
export const zSkillTagResponse = z.object({
count: z.int(),
tag: z.string(),
})
/**
* SkillTagListResponse
*/
export const zSkillTagListResponse = z.object({
data: z.array(zSkillTagResponse).optional(),
})
/**
* SkillAssistAttachmentPayload
*/
export const zSkillAssistAttachmentPayload = z.object({
mime_type: z.string().min(1).max(255).nullish(),
name: z.string().min(1).max(255),
size: z.int().gte(0).nullish(),
tool_file_id: z.string().min(1),
})
/**
* SkillAssistModelPayload
*/
export const zSkillAssistModelPayload = z.object({
model: z.string().min(1).max(255),
model_settings: z.record(z.string(), z.unknown()).nullish(),
plugin_id: z.string().min(1).max(255).nullish(),
provider: z.string().min(1).max(255),
})
/**
* SkillAssistMessagePayload
*
* One user message and optional uploaded context for the read-only Skill Authoring assistant.
*/
export const zSkillAssistMessagePayload = z.object({
attachments: z.array(zSkillAssistAttachmentPayload).max(10).optional(),
message: z.string().min(1).max(8000),
model: zSkillAssistModelPayload.nullish(),
})
/**
* SkillDraftFileOperation
*/
export const zSkillDraftFileOperation = z.enum([
'delete',
'mkdir',
'rename',
'upsert_text',
'upsert_tool_file',
])
/**
* SkillDraftFileOperationPayload
*/
export const zSkillDraftFileOperationPayload = z.object({
content: z.string().nullish(),
expected_updated_at: z.int().nullish(),
hash: z.string().nullish(),
mime_type: z.string().nullish(),
operation: zSkillDraftFileOperation,
path: z.string(),
size: z.int().gte(0).nullish(),
target_path: z.string().nullish(),
tool_file_id: z.string().nullish(),
})
/**
* SkillReferenceResponse
*/
export const zSkillReferenceResponse = z.object({
agent_icon: z.string().nullish(),
agent_icon_background: z.string().nullish(),
agent_icon_type: z.string().nullish(),
agent_id: z.string(),
app_id: z.string().nullish(),
display_name: z.string(),
name: z.string(),
node_id: z.string().nullish(),
node_name: z.string().nullish(),
type: z.string(),
workflow_icon: z.string().nullish(),
workflow_icon_background: z.string().nullish(),
workflow_icon_type: z.string().nullish(),
workflow_id: z.string().nullish(),
workflow_name: z.string().nullish(),
workflow_version: z.string().nullish(),
})
/**
* SkillReferenceListResponse
*/
export const zSkillReferenceListResponse = z.object({
data: z.array(zSkillReferenceResponse).optional(),
})
/**
* ToolEmojiIcon
*/
@@ -2096,6 +2436,42 @@ export const zPermissionCatalogResponse = z.object({
groups: z.array(zPermissionCatalogGroup).optional(),
})
/**
* SkillFileKind
*
* Draft file entry kind.
*/
export const zSkillFileKind = z.enum(['directory', 'file'])
/**
* SkillFileStorage
*
* How a draft file's content is stored.
*/
export const zSkillFileStorage = z.enum(['text', 'tool_file'])
/**
* SkillDraftTreeItemPayload
*/
export const zSkillDraftTreeItemPayload = z.object({
content: z.string().nullish(),
hash: z.string().nullish(),
kind: zSkillFileKind.optional().default('file'),
mime_type: z.string().nullish(),
path: z.string(),
size: z.int().gte(0).nullish(),
storage: zSkillFileStorage.nullish(),
tool_file_id: z.string().nullish(),
})
/**
* SkillDraftTreePayload
*/
export const zSkillDraftTreePayload = z.object({
expected_updated_at: z.int().nullish(),
files: z.array(zSkillDraftTreeItemPayload).optional(),
})
/**
* Option
*/
@@ -3421,6 +3797,26 @@ export const zGetWorkspacesCurrentAgentProviderByProviderNameResponse = zAgentPr
*/
export const zGetWorkspacesCurrentAgentProvidersResponse = zAgentProviderListResponse
export const zGetWorkspacesCurrentAgentsByAgentIdSkillsPath = z.object({
agent_id: z.string(),
})
/**
* Agent Skill bindings
*/
export const zGetWorkspacesCurrentAgentsByAgentIdSkillsResponse = zAgentSkillBindingsResponse
export const zPutWorkspacesCurrentAgentsByAgentIdSkillsBody = zAgentSkillBindingsPayload
export const zPutWorkspacesCurrentAgentsByAgentIdSkillsPath = z.object({
agent_id: z.string(),
})
/**
* Agent Skill bindings replaced
*/
export const zPutWorkspacesCurrentAgentsByAgentIdSkillsResponse = zAgentSkillBindingsResponse
export const zGetWorkspacesCurrentCustomizedSnippetsQuery = z.object({
creators: z.array(z.string()).optional(),
is_published: z.boolean().optional(),
@@ -4694,6 +5090,234 @@ export const zGetWorkspacesCurrentRbacWorkspaceDatasetsAccessPoliciesByPolicyIdR
*/
export const zGetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponse = zWorkspaceAccessMatrix
export const zGetWorkspacesCurrentSkillsQuery = z.object({
keyword: z.string().optional(),
limit: z.int().gte(1).lte(100).optional().default(20),
page: z.int().gte(1).lte(99999).optional().default(1),
tag: z.array(z.string()).optional(),
})
/**
* Workspace skills
*/
export const zGetWorkspacesCurrentSkillsResponse = zSkillListResponse
export const zPostWorkspacesCurrentSkillsBody = zSkillCreatePayload
/**
* Skill created
*/
export const zPostWorkspacesCurrentSkillsResponse = zSkillDetailResponse
export const zPostWorkspacesCurrentSkillsFilesUploadBody = z.object({
file: z.custom<Blob | File>((value) => value instanceof Blob || value instanceof File),
})
/**
* Skill draft file uploaded
*/
export const zPostWorkspacesCurrentSkillsFilesUploadResponse = zSkillFileUploadResponse
/**
* Skill imported
*/
export const zPostWorkspacesCurrentSkillsImportResponse = zSkillDetailResponse
/**
* Workspace Skill tags
*/
export const zGetWorkspacesCurrentSkillsTagsResponse = zSkillTagListResponse
export const zDeleteWorkspacesCurrentSkillsBySkillIdBody = zSkillDeletePayload
export const zDeleteWorkspacesCurrentSkillsBySkillIdPath = z.object({
skill_id: z.string(),
})
/**
* Skill deleted
*/
export const zDeleteWorkspacesCurrentSkillsBySkillIdResponse = zSkillDeleteResponse
export const zGetWorkspacesCurrentSkillsBySkillIdPath = z.object({
skill_id: z.string(),
})
/**
* Skill detail
*/
export const zGetWorkspacesCurrentSkillsBySkillIdResponse = zSkillDetailResponse
export const zPatchWorkspacesCurrentSkillsBySkillIdBody = zSkillMetadataPayload
export const zPatchWorkspacesCurrentSkillsBySkillIdPath = z.object({
skill_id: z.string(),
})
/**
* Skill updated
*/
export const zPatchWorkspacesCurrentSkillsBySkillIdResponse = zSkillResponse
export const zPostWorkspacesCurrentSkillsBySkillIdAssistMessagesBody = zSkillAssistMessagePayload
export const zPostWorkspacesCurrentSkillsBySkillIdAssistMessagesPath = z.object({
skill_id: z.string(),
})
/**
* Skill Authoring assistant event stream
*/
export const zPostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponse = z.record(
z.string(),
z.unknown(),
)
export const zPostWorkspacesCurrentSkillsBySkillIdDuplicatePath = z.object({
skill_id: z.string(),
})
/**
* Skill duplicated
*/
export const zPostWorkspacesCurrentSkillsBySkillIdDuplicateResponse = zSkillDetailResponse
export const zGetWorkspacesCurrentSkillsBySkillIdExportPath = z.object({
skill_id: z.string(),
})
/**
* Published Skill zip archive
*/
export const zGetWorkspacesCurrentSkillsBySkillIdExportResponse = z.record(z.string(), z.unknown())
export const zPatchWorkspacesCurrentSkillsBySkillIdFilesBody = zSkillDraftFileOperationPayload
export const zPatchWorkspacesCurrentSkillsBySkillIdFilesPath = z.object({
skill_id: z.string(),
})
/**
* Draft file operation applied
*/
export const zPatchWorkspacesCurrentSkillsBySkillIdFilesResponse = zSkillDetailResponse
export const zPutWorkspacesCurrentSkillsBySkillIdFilesBody = zSkillDraftTreePayload
export const zPutWorkspacesCurrentSkillsBySkillIdFilesPath = z.object({
skill_id: z.string(),
})
/**
* Draft files replaced
*/
export const zPutWorkspacesCurrentSkillsBySkillIdFilesResponse = zSkillDetailResponse
export const zGetWorkspacesCurrentSkillsBySkillIdFilesContentPath = z.object({
skill_id: z.string(),
})
export const zGetWorkspacesCurrentSkillsBySkillIdFilesContentQuery = z.object({
download: z.string().optional(),
path: z.string(),
version_id: z.string().optional(),
})
/**
* Skill file content
*/
export const zGetWorkspacesCurrentSkillsBySkillIdFilesContentResponse = zBinaryFileResponse
export const zGetWorkspacesCurrentSkillsBySkillIdFilesPreviewPath = z.object({
skill_id: z.string(),
})
export const zGetWorkspacesCurrentSkillsBySkillIdFilesPreviewQuery = z.object({
path: z.string(),
version_id: z.string().optional(),
})
/**
* Skill file text preview
*/
export const zGetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponse = zSkillFilePreviewResponse
export const zPostWorkspacesCurrentSkillsBySkillIdPublishBody = zSkillPublishPayload
export const zPostWorkspacesCurrentSkillsBySkillIdPublishPath = z.object({
skill_id: z.string(),
})
/**
* Skill published
*/
export const zPostWorkspacesCurrentSkillsBySkillIdPublishResponse = zSkillVersionResponse
export const zGetWorkspacesCurrentSkillsBySkillIdReferencesPath = z.object({
skill_id: z.string(),
})
/**
* Skill references
*/
export const zGetWorkspacesCurrentSkillsBySkillIdReferencesResponse = zSkillReferenceListResponse
export const zPostWorkspacesCurrentSkillsBySkillIdRestoreBody = zSkillRestorePayload
export const zPostWorkspacesCurrentSkillsBySkillIdRestorePath = z.object({
skill_id: z.string(),
})
/**
* Skill version restored
*/
export const zPostWorkspacesCurrentSkillsBySkillIdRestoreResponse = zSkillVersionResponse
export const zGetWorkspacesCurrentSkillsBySkillIdVersionsPath = z.object({
skill_id: z.string(),
})
/**
* Skill versions
*/
export const zGetWorkspacesCurrentSkillsBySkillIdVersionsResponse = zSkillVersionListResponse
export const zDeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdPath = z.object({
skill_id: z.string(),
version_id: z.string(),
})
/**
* Skill version deleted
*/
export const zDeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
zSkillVersionDeleteResponse
export const zGetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdPath = z.object({
skill_id: z.string(),
version_id: z.string(),
})
/**
* Skill version detail
*/
export const zGetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
zSkillVersionDetailResponse
export const zPatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdBody =
zSkillVersionUpdatePayload
export const zPatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdPath = z.object({
skill_id: z.string(),
version_id: z.string(),
})
/**
* Skill version updated
*/
export const zPatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
zSkillVersionResponse
/**
* Tool labels retrieved successfully
*/
@@ -18,6 +18,7 @@ The current Flask-RESTX generator still emits these response entries under `appl
| service | GET | `/files/{file_id}/preview` | Original file MIME type, optionally attachment | `BinaryFileResponse` |
| console | GET | `/workspaces/current/plugin/icon` | Plugin asset MIME type | `BinaryFileResponse` |
| console | GET | `/workspaces/current/plugin/asset` | `application/octet-stream` | `BinaryFileResponse` |
| console | GET | `/workspaces/current/skills/{skill_id}/files/content` | Skill file MIME type, optionally attachment | `BinaryFileResponse` |
| console | GET | `/workspaces/current/tool-provider/builtin/{provider}/icon` | Tool icon MIME type | `BinaryFileResponse` |
| console | GET | `/workspaces/current/trigger-provider/{provider}/icon` | Trigger icon response | `BinaryFileResponse` |
| console | GET | `/workspaces/{tenant_id}/model-providers/{provider}/{icon_type}/{lang}` | Model provider icon MIME type | `BinaryFileResponse` |
@@ -0,0 +1,5 @@
import SkillDetailPage from '@/features/skills/detail-page'
export default function Page() {
return <SkillDetailPage />
}
+5
View File
@@ -0,0 +1,5 @@
import SkillsPage from '@/features/skills/page'
export default function Page() {
return <SkillsPage />
}
@@ -91,9 +91,12 @@ describe('AgentRosterResponseContent', () => {
await user.click(processToggle)
expect(processToggle).toHaveAttribute('aria-expanded', 'true')
await waitFor(() => {
expect(screen.getByText('history answer')).toBeInTheDocument()
})
await waitFor(
() => {
expect(screen.getByText('history answer')).toBeInTheDocument()
},
{ timeout: 5000 },
)
expect(screen.queryByText('internal thought should not render')).not.toBeInTheDocument()
})
@@ -122,9 +125,12 @@ describe('AgentRosterResponseContent', () => {
render(<AgentRosterResponseContent item={item} />)
await user.click(screen.getByRole('button', { name: 'Thinking' }))
await waitFor(() => {
expect(screen.getByText('const answer = 42').tagName).toBe('CODE')
})
await waitFor(
() => {
expect(screen.getByText('const answer = 42').tagName).toBe('CODE')
},
{ timeout: 5000 },
)
})
it('should keep one collapsible thinking timeline while response parts interleave', async () => {
@@ -1,416 +1,73 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import type { ReactNode } from 'react'
import { fireEvent, render, screen } from '@testing-library/react'
import ModelParameterModal from '../index'
let parameterRules: Array<Record<string, unknown>> | undefined = [
{
name: 'temperature',
label: { en_US: 'Temperature' },
type: 'float',
default: 0.7,
min: 0,
max: 1,
help: { en_US: 'Control randomness' },
},
]
let isRulesLoading = false
let isRulesPending = false
let currentProvider: Record<string, unknown> | undefined = {
provider: 'openai',
label: { en_US: 'OpenAI' },
}
let currentModel: Record<string, unknown> | undefined = {
model: 'gpt-3.5-turbo',
status: 'active',
model_properties: { mode: 'chat' },
}
let activeTextGenerationModelList: Array<Record<string, unknown>> = [
{
provider: 'openai',
models: [
{
model: 'gpt-3.5-turbo',
model_properties: { mode: 'chat' },
features: ['vision'],
},
{
model: 'gpt-4.1',
model_properties: { mode: 'chat' },
features: ['vision', 'tool-call'],
},
],
},
]
const mocks = vi.hoisted(() => ({
openIntegrationsSetting: vi.fn(),
}))
vi.mock('@/context/provider-context', () => ({
useProviderContext: () => ({
isAPIKeySet: true,
}),
vi.mock('@/app/components/header/account-setting/use-integrations-setting', () => ({
useIntegrationsSetting: () => mocks.openIntegrationsSetting,
}))
vi.mock('@/service/use-common', () => ({
useModelParameterRules: () => ({
data: {
data: parameterRules,
data: [],
},
isLoading: isRulesLoading,
isPending: isRulesPending,
isLoading: false,
}),
}))
vi.mock('../../hooks', () => ({
useTextGenerationCurrentProviderAndModelAndModelList: () => ({
currentProvider,
currentModel,
activeTextGenerationModelList,
activeTextGenerationModelList: [],
currentModel: undefined,
currentProvider: undefined,
}),
}))
vi.mock('../parameter-item', () => ({
default: ({
parameterRule,
onChange,
onSwitch,
nodesOutputVars,
availableNodes,
}: {
parameterRule: { name: string; label: { en_US: string } }
onChange: (v: number) => void
onSwitch: (checked: boolean, val: unknown) => void
nodesOutputVars?: unknown[]
availableNodes?: unknown[]
}) => (
<div
data-testid={`param-${parameterRule.name}`}
data-has-nodes-output-vars={!!nodesOutputVars}
data-has-available-nodes={!!availableNodes}
>
{parameterRule.label.en_US}
<button onClick={() => onChange(0.9)}>Change</button>
<button onClick={() => onSwitch(false, undefined)}>Remove</button>
<button onClick={() => onSwitch(true, 'assigned')}>Add</button>
</div>
vi.mock('../../model-selector', () => ({
default: ({ onConfigureEmptyState }: { onConfigureEmptyState?: () => void }) => (
<button type="button" onClick={onConfigureEmptyState}>
configure-empty-model
</button>
),
}))
vi.mock('../../model-selector', () => ({
default: ({
onHide,
onSelect,
}: {
onHide: () => void
onSelect: (value: { provider: string; model: string }) => void
}) => (
<div data-testid="model-selector">
<button onClick={() => onSelect({ provider: 'openai', model: 'gpt-4.1' })}>
Select GPT-4.1
</button>
<button onClick={onHide}>hide</button>
</div>
),
vi.mock('@/app/components/base/loading', () => ({
default: () => <div>loading</div>,
}))
vi.mock('../parameter-item', () => ({
default: () => <div>parameter-item</div>,
}))
vi.mock('../presets-parameter', () => ({
default: ({
onSelect,
supportedParameterNames,
}: {
onSelect: (id: number) => void
supportedParameterNames?: string[]
}) => {
if (supportedParameterNames && !supportedParameterNames.includes('temperature')) return null
return <button onClick={() => onSelect(1)}>Preset 1</button>
},
default: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
}))
vi.mock('../presets-parameter-utils', () => ({
getSupportedPresetConfig: (_toneId: number, supportedParameterNames?: string[]) => {
if (supportedParameterNames && !supportedParameterNames.includes('temperature')) return {}
return { temperature: 0.8 }
},
}))
vi.mock('../trigger', () => ({
default: () => <button type="button">Open Settings</button>,
}))
vi.mock('@/config', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/config')>()
return {
...actual,
PROVIDER_WITH_PRESET_TONE: ['openai'],
}
})
describe('ModelParameterModal', () => {
const openSettings = () =>
fireEvent.click(screen.getByRole('button', { name: /modelProvider\.modelSettings/i }))
const defaultProps = {
isAdvancedMode: false,
modelId: 'gpt-3.5-turbo',
provider: 'openai',
setModel: vi.fn(),
completionParams: { temperature: 0.7 },
onCompletionParamsChange: vi.fn(),
hideDebugWithMultipleModel: false,
debugWithMultipleModel: false,
onDebugWithMultipleModelChange: vi.fn(),
readonly: false,
}
beforeEach(() => {
vi.clearAllMocks()
isRulesLoading = false
isRulesPending = false
parameterRules = [
{
name: 'temperature',
label: { en_US: 'Temperature' },
type: 'float',
default: 0.7,
min: 0,
max: 1,
help: { en_US: 'Control randomness' },
},
]
currentProvider = { provider: 'openai', label: { en_US: 'OpenAI' } }
currentModel = {
model: 'gpt-3.5-turbo',
status: 'active',
model_properties: { mode: 'chat' },
}
activeTextGenerationModelList = [
{
provider: 'openai',
models: [
{
model: 'gpt-3.5-turbo',
model_properties: { mode: 'chat' },
features: ['vision'],
},
{
model: 'gpt-4.1',
model_properties: { mode: 'chat' },
features: ['vision', 'tool-call'],
},
],
},
]
})
it('should render trigger and open modal content when trigger is clicked', () => {
render(<ModelParameterModal {...defaultProps} />)
openSettings()
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
expect(screen.getByTestId('param-temperature')).toBeInTheDocument()
})
it('should keep model selection and model settings as separate actions', () => {
render(<ModelParameterModal {...defaultProps} />)
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
fireEvent.click(screen.getByText('Select GPT-4.1'))
expect(defaultProps.setModel).toHaveBeenCalledWith({
modelId: 'gpt-4.1',
provider: 'openai',
mode: 'chat',
features: ['vision', 'tool-call'],
})
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: /modelProvider\.modelSettings/i }))
expect(screen.getByTestId('param-temperature')).toBeInTheDocument()
})
it('should disable model settings when no model is selected', () => {
render(<ModelParameterModal {...defaultProps} provider="" modelId="" />)
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
expect(screen.getByRole('button', { name: /modelProvider\.modelSettings/i })).toBeDisabled()
})
it('should call onCompletionParamsChange when parameter changes and switch actions happen', () => {
render(<ModelParameterModal {...defaultProps} />)
openSettings()
fireEvent.click(screen.getByText('Change'))
expect(defaultProps.onCompletionParamsChange).toHaveBeenCalledWith({
...defaultProps.completionParams,
temperature: 0.9,
})
fireEvent.click(screen.getByText('Remove'))
expect(defaultProps.onCompletionParamsChange).toHaveBeenCalledWith({})
fireEvent.click(screen.getByText('Add'))
expect(defaultProps.onCompletionParamsChange).toHaveBeenCalledWith({
...defaultProps.completionParams,
temperature: 'assigned',
})
})
it('should call onCompletionParamsChange when preset is selected', () => {
render(<ModelParameterModal {...defaultProps} />)
openSettings()
fireEvent.click(screen.getByText('Preset 1'))
expect(defaultProps.onCompletionParamsChange).toHaveBeenCalledWith({
...defaultProps.completionParams,
temperature: 0.8,
})
})
it('should not render preset control when visible parameters do not support preset keys', () => {
parameterRules = [
{
name: 'max_tokens',
label: { en_US: 'Max Tokens' },
type: 'int',
default: 256,
min: 1,
max: 4096,
},
]
render(<ModelParameterModal {...defaultProps} />)
openSettings()
expect(screen.queryByText('Preset 1')).not.toBeInTheDocument()
})
it('should call setModel when model selector picks another model', () => {
render(<ModelParameterModal {...defaultProps} />)
openSettings()
fireEvent.click(screen.getByText('Select GPT-4.1'))
expect(defaultProps.setModel).toHaveBeenCalledWith({
modelId: 'gpt-4.1',
provider: 'openai',
mode: 'chat',
features: ['vision', 'tool-call'],
})
})
it('should toggle debug mode when debug footer is clicked', () => {
render(<ModelParameterModal {...defaultProps} />)
openSettings()
fireEvent.click(screen.getByText(/debugAsMultipleModel/i))
expect(defaultProps.onDebugWithMultipleModelChange).toHaveBeenCalled()
})
it('should render loading state when parameter rules are loading', () => {
isRulesLoading = true
isRulesPending = true
render(<ModelParameterModal {...defaultProps} />)
openSettings()
expect(screen.getByRole('status')).toBeInTheDocument()
})
it('should not render parameter loading when model is not configured and parameter rules query is pending but disabled', () => {
isRulesPending = true
parameterRules = []
render(<ModelParameterModal {...defaultProps} provider="" modelId="" />)
openSettings()
expect(screen.queryByRole('status')).not.toBeInTheDocument()
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
})
it('should not open content when readonly is true', () => {
render(<ModelParameterModal {...defaultProps} readonly />)
expect(screen.getByRole('button', { name: /modelProvider\.modelSettings/i })).toBeDisabled()
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
})
it('should render no parameter items when rules are undefined', () => {
parameterRules = undefined
render(<ModelParameterModal {...defaultProps} />)
openSettings()
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
})
it('should pass nodesOutputVars and availableNodes to ParameterItem', () => {
const mockNodesOutputVars = [{ nodeId: 'n1', title: 'Node', vars: [] }]
const mockAvailableNodes = [{ id: 'n1', data: { title: 'Node', type: 'llm' } }]
it('opens provider settings from the model selector empty state', () => {
render(
<ModelParameterModal
{...defaultProps}
isInWorkflow
nodesOutputVars={mockNodesOutputVars as never}
availableNodes={mockAvailableNodes as never}
/>,
)
openSettings()
const paramEl = screen.getByTestId('param-temperature')
expect(paramEl).toHaveAttribute('data-has-nodes-output-vars', 'true')
expect(paramEl).toHaveAttribute('data-has-available-nodes', 'true')
})
it('should support custom triggers, workflow mode, and missing default model values', async () => {
render(
<ModelParameterModal
{...defaultProps}
provider=""
isAdvancedMode
modelId=""
isInWorkflow
renderTrigger={({ open }) => <span>{open ? 'Custom Open' : 'Custom Closed'}</span>}
provider=""
completionParams={{}}
setModel={vi.fn()}
onCompletionParamsChange={vi.fn()}
/>,
)
fireEvent.click(screen.getByText('Custom Closed'))
fireEvent.click(screen.getByText('configure-empty-model'))
expect(screen.getByText('Custom Open')).toBeInTheDocument()
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
fireEvent.click(screen.getByText('hide'))
await waitFor(() => {
expect(screen.queryByTestId('model-selector')).not.toBeInTheDocument()
expect(mocks.openIntegrationsSetting).toHaveBeenCalledWith({
payload: 'provider',
})
})
it('should append the stop parameter in advanced mode and show the single-model debug label', () => {
render(<ModelParameterModal {...defaultProps} isAdvancedMode debugWithMultipleModel />)
openSettings()
expect(screen.getByTestId('param-stop')).toBeInTheDocument()
expect(screen.getByText(/debugAsSingleModel/i)).toBeInTheDocument()
})
it('should render the empty loading fallback when rules resolve to an empty list', () => {
parameterRules = []
isRulesLoading = true
isRulesPending = true
render(<ModelParameterModal {...defaultProps} />)
openSettings()
expect(screen.getByRole('status')).toBeInTheDocument()
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
})
it('should support custom trigger placement outside workflow mode', () => {
render(
<ModelParameterModal
{...defaultProps}
renderTrigger={({ open }) => <span>{open ? 'Popup Open' : 'Popup Closed'}</span>}
/>,
)
fireEvent.click(screen.getByText('Popup Closed'))
expect(screen.getByText('Popup Open')).toBeInTheDocument()
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
})
})
@@ -1,5 +1,5 @@
import type { FC, ReactNode } from 'react'
import type { DefaultModel, FormValue, ModelParameterRule } from '../declarations'
import type { DefaultModel, FormValue, Model, ModelParameterRule } from '../declarations'
import type { ParameterValue } from './parameter-item'
import type { TriggerProps } from './types'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
@@ -7,8 +7,9 @@ import { cn } from '@langgenius/dify-ui/cn'
import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
import Loading from '@/app/components/base/loading'
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
import { useIntegrationsSetting } from '@/app/components/header/account-setting/use-integrations-setting'
import { PROVIDER_WITH_PRESET_TONE, STOP_PARAMETER_RULE } from '@/config'
import { useModelParameterRules } from '@/service/use-common'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '../hooks'
@@ -34,8 +35,10 @@ export type ModelParameterModalProps = {
debugWithMultipleModel?: boolean
onDebugWithMultipleModelChange?: () => void
renderTrigger?: (v: TriggerProps) => ReactNode
triggerContainerClassName?: string
readonly?: boolean
isInWorkflow?: boolean
modelList?: Model[]
scope?: string
nodesOutputVars?: NodeOutPutVar[]
availableNodes?: Node[]
@@ -53,17 +56,25 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
debugWithMultipleModel,
onDebugWithMultipleModelChange,
renderTrigger,
triggerContainerClassName,
readonly,
isInWorkflow,
modelList,
nodesOutputVars,
availableNodes,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const openIntegrationsSetting = useIntegrationsSetting()
const { data: parameterRulesData, isLoading } = useModelParameterRules(provider, modelId)
const isRulesLoading = !!provider && !!modelId && isLoading
const { currentProvider, currentModel, activeTextGenerationModelList } =
useTextGenerationCurrentProviderAndModelAndModelList({ provider, model: modelId })
const availableTextGenerationModelList = modelList ?? activeTextGenerationModelList
const selectedProvider =
modelList?.find((modelItem) => modelItem.provider === provider) ?? currentProvider
const selectedModel =
selectedProvider?.models?.find((modelItem) => modelItem.model === modelId) ?? currentModel
const parameterRules: ModelParameterRule[] = useMemo(() => {
return parameterRulesData?.data || []
@@ -71,6 +82,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
const supportedPresetParameterNames = useMemo(() => {
return parameterRules.map((parameterRule) => parameterRule.name)
}, [parameterRules])
const hasSelectedModel = !!provider && !!modelId
const handleParamChange = (key: string, value: ParameterValue) => {
onCompletionParamsChange({
@@ -80,10 +92,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
}
const handleChangeModel = ({ provider, model }: DefaultModel) => {
const targetProvider = activeTextGenerationModelList.find(
const targetProvider = availableTextGenerationModelList.find(
(modelItem) => modelItem.provider === provider,
)
const targetModelItem = targetProvider?.models.find((modelItem) => modelItem.model === model)
const targetModelItem = targetProvider?.models?.find((modelItem) => modelItem.model === model)
setModel({
modelId: model,
provider,
@@ -91,6 +103,15 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
features: targetModelItem?.features || [],
})
}
const handleOpenModelSettings = () => {
if (readonly || !hasSelectedModel) return
setOpen(true)
}
const handleConfigureEmptyState = () => {
if (readonly) return
openIntegrationsSetting({ payload: ACCOUNT_SETTING_TAB.PROVIDER })
}
const handleSwitch = (key: string, value: boolean, assignValue: ParameterValue) => {
if (!value) {
@@ -114,8 +135,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
})
}
const hasSelectedModel = !!provider && !!modelId
return (
<Popover
open={open}
@@ -133,8 +152,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
>
{renderTrigger({
open,
currentProvider,
currentModel,
currentProvider: selectedProvider,
currentModel: selectedModel,
providerName: provider,
modelId,
})}
@@ -142,11 +161,16 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
}
/>
) : (
<div className="flex h-8 min-w-[296px] items-center gap-px overflow-hidden rounded-lg">
<div
className={cn(
'flex h-8 min-w-[296px] items-center gap-px overflow-hidden rounded-lg',
triggerContainerClassName,
)}
>
<div className="min-w-0 flex-1">
<ModelSelector
defaultModel={provider || modelId ? { provider, model: modelId } : undefined}
modelList={activeTextGenerationModelList}
modelList={availableTextGenerationModelList}
readonly={readonly}
triggerClassName={cn(
'h-8! w-full rounded-r-none!',
@@ -154,6 +178,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
'border border-workflow-block-parma-bg bg-workflow-block-parma-bg hover:bg-workflow-block-parma-bg',
)}
onSelect={handleChangeModel}
onConfigureEmptyState={handleConfigureEmptyState}
onOpenProviderSettings={handleOpenModelSettings}
/>
</div>
<PopoverTrigger
@@ -187,8 +213,9 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
<div className="px-4 pt-2 pb-4">
<ModelSelector
defaultModel={hasSelectedModel ? { provider, model: modelId } : undefined}
modelList={activeTextGenerationModelList}
modelList={availableTextGenerationModelList}
onSelect={handleChangeModel}
onOpenProviderSettings={handleOpenModelSettings}
onHide={() => setOpen(false)}
/>
</div>
@@ -249,7 +276,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
{debugWithMultipleModel
? t(($) => $.debugAsSingleModel, { ns: 'appDebug' })
: t(($) => $.debugAsMultipleModel, { ns: 'appDebug' })}
<ArrowNarrowLeft className="size-3 rotate-180" />
<span
aria-hidden
className="i-custom-vender-line-arrows-arrow-narrow-left size-3 rotate-180"
/>
</div>
)}
</PopoverContent>
@@ -33,6 +33,7 @@ type ModelSelectorProps = {
hideProviderSettingsFooter?: boolean
onConfigureEmptyState?: () => void
onOpenMarketplace?: () => void
onOpenProviderSettings?: () => void
providerSettingsSource?: 'agent'
showModelMeta?: boolean
modelPredicate?: ModelSelectorModelPredicate
@@ -52,6 +53,7 @@ function ModelSelector({
hideProviderSettingsFooter,
onConfigureEmptyState,
onOpenMarketplace,
onOpenProviderSettings,
providerSettingsSource,
showModelMeta,
modelPredicate,
@@ -180,6 +182,7 @@ function ModelSelector({
modelSuggestionPredicate={modelSuggestionPredicate}
onConfigureEmptyState={onConfigureEmptyState ? handleConfigureEmptyState : undefined}
onOpenMarketplace={onOpenMarketplace}
onOpenProviderSettings={onOpenProviderSettings}
onInputValueChange={setInputValue}
onHide={handleHide}
/>
@@ -9,7 +9,6 @@ import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { useCredentialPermissions } from '@/hooks/use-credential-permissions'
@@ -57,7 +56,8 @@ function PopupItem({
const updateModelProviders = useUpdateModelProviders()
const currentProvider = modelProviders.find((provider) => provider.provider === model.provider)
const { canUseCredential, canCreateCredential, canManageCredential } = useCredentialPermissions()
const canOpenCredentialDropdown = canUseCredential || canCreateCredential || canManageCredential
const canOpenCredentialDropdown =
!!currentProvider && (canUseCredential || canCreateCredential || canManageCredential)
const handleOpenModelModal = () => {
if (!canCreateCredential) return
@@ -77,7 +77,8 @@ function PopupItem({
})
}
const state = useCredentialPanelState(currentProvider)
// oxlint-disable-next-line eslint-react/use-state -- This domain hook returns credential panel state, not a React useState tuple.
const credentialPanelState = useCredentialPanelState(currentProvider)
const { isChangingPriority, handleChangePriority } = useChangeProviderPriority(currentProvider)
const groupItems = useMemo(
() =>
@@ -90,10 +91,11 @@ function PopupItem({
[model.models, model.provider],
)
const isUsingCredits = state.priority === 'credits'
const hasCredits = !state.isCreditsExhausted
const isApiKeyActive = state.variant === 'api-active' || state.variant === 'api-fallback'
const { credentialName } = state
const isUsingCredits = credentialPanelState.priority === 'credits'
const hasCredits = !credentialPanelState.isCreditsExhausted
const isApiKeyActive =
credentialPanelState.variant === 'api-active' || credentialPanelState.variant === 'api-fallback'
const { credentialName } = credentialPanelState
const handleCloseDropdown = useCallback(() => {
setDropdownOpen(false)
@@ -129,7 +131,10 @@ function PopupItem({
{isUsingCredits ? (
hasCredits ? (
<>
<CreditsCoin className="size-3" />
<span
aria-hidden
className="i-custom-vender-line-financeandecommerce-credits-coin size-3"
/>
<span className="ml-1 truncate">
{t(($) => $['modelProvider.selector.aiCredits'], { ns: 'common' })}
</span>
@@ -161,15 +166,17 @@ function PopupItem({
</button>
}
/>
<PopoverContent placement="bottom-end">
<DropdownContent
provider={currentProvider}
state={state}
isChangingPriority={isChangingPriority}
onChangePriority={handleChangePriority}
onClose={handleCloseDropdown}
/>
</PopoverContent>
{currentProvider && (
<PopoverContent placement="bottom-end">
<DropdownContent
provider={currentProvider}
state={credentialPanelState}
isChangingPriority={isChangingPriority}
onChangePriority={handleChangePriority}
onClose={handleCloseDropdown}
/>
</PopoverContent>
)}
</Popover>
</div>
{!collapsed &&
@@ -215,7 +222,7 @@ function PopupItem({
</ModelName>
</div>
{defaultModel?.model === modelItem.model &&
defaultModel.provider === currentProvider.provider && (
defaultModel.provider === model.provider && (
<ComboboxItemIndicator className="shrink-0 text-text-accent">
<span
className="i-custom-vender-line-general-check size-4"
@@ -69,6 +69,7 @@ export type PopupProps = {
onConfigureEmptyState?: () => void
onInputValueChange: (value: string) => void
onOpenMarketplace?: () => void
onOpenProviderSettings?: () => void
onHide: () => void
}
function Popup({
@@ -83,6 +84,7 @@ function Popup({
onConfigureEmptyState,
onInputValueChange,
onOpenMarketplace,
onOpenProviderSettings,
onHide,
}: PopupProps) {
const { t } = useTranslation()
@@ -255,11 +257,16 @@ function Popup({
const handleOpenSettings = useCallback(() => {
onHide()
if (onOpenProviderSettings) {
onOpenProviderSettings()
return
}
openIntegrationsSetting({
payload: ACCOUNT_SETTING_TAB.PROVIDER,
source: providerSettingsSource,
})
}, [onHide, openIntegrationsSetting, providerSettingsSource])
}, [onHide, onOpenProviderSettings, openIntegrationsSetting, providerSettingsSource])
const handleClosePreviewCard = useCallback(() => {
previewCardHandle.close()
}, [previewCardHandle])
@@ -564,6 +564,10 @@ describe('MainNav', () => {
expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps')
expect(screen.getByRole('link', { name: /Agents/ })).toHaveAttribute('href', '/agents')
expect(screen.getByRole('link', { name: /Agents common.menus.status/ })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /common.mainNav.skills/ })).toHaveAttribute(
'href',
'/skills',
)
expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveAttribute(
'href',
'/datasets',
@@ -584,6 +588,10 @@ describe('MainNav', () => {
renderMainNav()
expect(screen.queryByRole('link', { name: /Agents/ })).not.toBeInTheDocument()
expect(screen.getByRole('link', { name: /common.mainNav.skills/ })).toHaveAttribute(
'href',
'/skills',
)
})
it('hides the roster entry when the user lacks agent.manage', () => {
@@ -743,6 +751,7 @@ describe('MainNav', () => {
expect(screen.getByRole('link', { name: /common.mainNav.home/ })).toHaveAttribute('href', '/')
expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps')
expect(screen.queryByRole('link', { name: /Agents/ })).not.toBeInTheDocument()
expect(screen.queryByRole('link', { name: /common.mainNav.skills/ })).not.toBeInTheDocument()
expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveAttribute(
'href',
'/datasets',
+11
View File
@@ -32,6 +32,8 @@ export type DetailSidebarVisibilityOptions = Pick<
const VISIBLE_TO_ALL: MainNavRouteVisibility = () => true
const CAN_MANAGE_AGENTS: MainNavRouteVisibility = (options) => options.canManageAgents
const CAN_USE_APP_DEPLOY: MainNavRouteVisibility = (options) => options.canUseAppDeploy
const NOT_DATASET_OPERATOR: MainNavRouteVisibility = (options) =>
!options.isCurrentWorkspaceDatasetOperator
function isPathUnderRoute(pathname: string, route: string) {
return pathname === route || pathname.startsWith(`${route}/`)
@@ -69,6 +71,15 @@ export const MAIN_NAV_ROUTES = [
visibility: CAN_MANAGE_AGENTS,
feature: 'agentV2',
},
{
key: 'skills',
href: '/skills',
labelKey: 'mainNav.skills',
active: (path: string) => isPathUnderRoute(path, '/skills'),
icon: 'i-ri-box-3-line',
activeIcon: 'i-ri-box-3-fill',
visibility: NOT_DATASET_OPERATOR,
},
{
key: 'datasets',
href: '/datasets',
@@ -119,8 +119,10 @@ describe('ConversationVariableModal', () => {
})
expect(screen.getAllByText('session_state')).toHaveLength(2)
expect(screen.getByText((content) => content.includes('formatted-100'))).toBeInTheDocument()
expect(screen.getByTestId('conversation-code-editor')).toHaveTextContent('{"latest":1}')
expect(
await screen.findByText((content) => content.includes('formatted-100')),
).toBeInTheDocument()
expect(await screen.findByTestId('conversation-code-editor')).toHaveTextContent('{"latest":1}')
await user.click(screen.getByText('summary'))
expect(screen.getByText('latest text')).toBeInTheDocument()
+4
View File
@@ -16,6 +16,10 @@ vi.mock('@/next/navigation', () => ({
useRouter: () => ({ push: mockPush, replace: mockReplace }),
}))
vi.mock('@/hooks/use-document-title', () => ({
default: vi.fn(),
}))
vi.mock('@/service/common', () => ({
fetchSetupStatus: vi.fn(),
fetchInitValidateStatus: vi.fn(),
+22
View File
@@ -27,6 +27,23 @@ export const viewport: Viewport = {
viewportFit: 'cover',
}
const resizeObserverErrorFilterScript = `
(() => {
const ignoredMessages = new Set([
'ResizeObserver loop completed with undelivered notifications.',
'ResizeObserver loop limit exceeded',
]);
const ignore = (event) => {
const message = event?.message || event?.reason?.message;
if (!ignoredMessages.has(message)) return;
event.preventDefault();
event.stopImmediatePropagation();
};
window.addEventListener('error', ignore, true);
window.addEventListener('unhandledrejection', ignore, true);
})();
`
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const datasetMap = getDatasetMap()
const queryClient = getQueryClientServer()
@@ -43,6 +60,11 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<html lang={locale ?? 'en'} className="h-full" suppressHydrationWarning>
<head>
<link rel="manifest" href="/manifest.json" />
<script
nonce={nonce}
// oxlint-disable-next-line eslint-react/dom-no-dangerously-set-innerhtml -- Static early listener must run before the dev error overlay registers.
dangerouslySetInnerHTML={{ __html: resizeObserverErrorFilterScript }}
/>
<meta name="theme-color" content="#1C64F2" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
@@ -8,15 +8,15 @@ import type {
} from './add-actions-context'
import { useCallback, useMemo, useState } from 'react'
import { AgentOrchestrateAddActionsContext } from './add-actions-context'
import { useAgentOrchestrateReadOnly } from './read-only-context'
import { useAgentOrchestrateViewingVersion } from './read-only-context'
export function AgentOrchestrateAddActionsProvider({ children }: { children: ReactNode }) {
const readOnly = useAgentOrchestrateReadOnly()
const isViewingVersion = useAgentOrchestrateViewingVersion()
const [actions, setActions] = useState<AgentOrchestrateAddActions>({})
const registerAction = useCallback(
(key: AgentOrchestrateAddActionKey, action: AgentOrchestrateAddAction) => {
if (readOnly) return () => undefined
if (isViewingVersion) return () => undefined
setActions((currentActions) => {
if (currentActions[key] === action) return currentActions
@@ -37,15 +37,15 @@ export function AgentOrchestrateAddActionsProvider({ children }: { children: Rea
})
}
},
[readOnly],
[isViewingVersion],
)
const value = useMemo(
() => ({
actions: readOnly ? {} : actions,
actions: isViewingVersion ? {} : actions,
registerAction,
}),
[actions, readOnly, registerAction],
[actions, isViewingVersion, registerAction],
)
return (
@@ -4,7 +4,7 @@ import type { ButtonProps } from '@langgenius/dify-ui/button'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { useTranslation } from 'react-i18next'
import { useAgentOrchestrateReadOnly } from '../read-only-context'
import { useAgentOrchestrateViewingVersion } from '../read-only-context'
type ConfigureSectionAddButtonProps = Omit<
ButtonProps,
@@ -19,9 +19,9 @@ export function ConfigureSectionAddButton({
...props
}: ConfigureSectionAddButtonProps) {
const { t } = useTranslation('common')
const readOnly = useAgentOrchestrateReadOnly()
const isViewingVersion = useAgentOrchestrateViewingVersion()
if (readOnly) return null
if (isViewingVersion) return null
return (
<Button
@@ -13,7 +13,10 @@ import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store
import { QueryClientTestProvider } from '@/test/console/query-provider'
import { createSystemFeaturesFixture } from '@/test/console/system-features'
import { AgentConfigApiContextProvider } from '../../config-context'
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context'
import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from '../../read-only-context'
import { AgentFiles } from '../index'
type ConfigFileQueryOptionsInput = {
@@ -171,10 +174,12 @@ function renderAgentFiles({
initialDraft = createInitialDraft(),
apiContext = { agentId: 'agent-1', draftType: 'draft' } satisfies AgentConfigApiContext,
readOnly = false,
viewingVersion = false,
}: {
initialDraft?: AgentSoulConfigFormState
apiContext?: AgentConfigApiContext
readOnly?: boolean
viewingVersion?: boolean
} = {}) {
const queryClient = new QueryClient({
defaultOptions: {
@@ -191,10 +196,12 @@ function renderAgentFiles({
<QueryClientTestProvider queryClient={queryClient}>
<AgentConfigApiContextProvider value={apiContext}>
<AgentComposerProvider initialDraft={initialDraft}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<AgentFiles />
<ConfigSnapshotProbe />
</AgentOrchestrateReadOnlyContext>
<AgentOrchestrateViewingVersionContext value={viewingVersion}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<AgentFiles />
<ConfigSnapshotProbe />
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
</AgentComposerProvider>
</AgentConfigApiContextProvider>
</QueryClientTestProvider>,
@@ -642,8 +649,8 @@ describe('AgentFiles', () => {
expect(snapshot.config_note).toBe('')
})
it('should keep flat config files visible without drive-prefix filtering and disable add in read-only mode', () => {
renderAgentFiles({ readOnly: true })
it('should keep flat config files visible without drive-prefix filtering and disable add when viewing a version', () => {
renderAgentFiles({ readOnly: true, viewingVersion: true })
expect(screen.getByText('diagram.png')).toBeInTheDocument()
expect(screen.getByText('brief.md')).toBeInTheDocument()
@@ -651,4 +658,12 @@ describe('AgentFiles', () => {
screen.queryByRole('button', { name: /agentV2\.agentDetail\.configure\.files\.add/i }),
).not.toBeInTheDocument()
})
it('should keep add action available for build drafts', () => {
renderAgentFiles({ readOnly: true })
expect(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.files\.add/i }),
).toBeInTheDocument()
})
})
@@ -23,7 +23,10 @@ import { AgentKnowledgeRetrieval } from './knowledge'
import { AgentModelField } from './model-config/field'
import { AgentPromptEditor } from './prompt-editor'
import { AgentConfigurePublishBar } from './publish-bar'
import { AgentOrchestrateReadOnlyContext } from './read-only-context'
import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from './read-only-context'
import { AgentSkills } from './skills'
import { AgentTools } from './tools'
@@ -132,41 +135,43 @@ export function AgentOrchestratePanel({
/>
)}
<AgentOrchestrateReadOnlyContext value={readOnly}>
<div aria-readonly={readOnly} className="flex min-h-0 flex-1 flex-col">
<ScrollArea
className="min-h-0 flex-1 overflow-hidden"
label={showHeader ? undefined : orchestrateLabel}
slotClassNames={{
viewport: 'overscroll-contain',
content: cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20'),
scrollbar: hasBottomAction ? 'z-20' : undefined,
}}
>
<AgentConfigApiContextProvider value={configApiContext}>
<AgentOrchestrateAddActionsProvider>
<AgentBuildDraftChangedKeysProvider
changedKeys={
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
}
>
<AgentModelField
currentModel={currentModel}
textGenerationModelList={textGenerationModelList}
onSelect={onSelectModel}
/>
<AgentPromptEditor />
<AgentSkills />
<AgentFiles />
<AgentTools />
<AgentKnowledgeRetrieval />
<AgentAdvancedSettings />
</AgentBuildDraftChangedKeysProvider>
</AgentOrchestrateAddActionsProvider>
</AgentConfigApiContextProvider>
</ScrollArea>
</div>
</AgentOrchestrateReadOnlyContext>
<AgentOrchestrateViewingVersionContext value={!!selectedVersionSnapshot}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<div aria-readonly={readOnly} className="flex min-h-0 flex-1 flex-col">
<ScrollArea
className="min-h-0 flex-1 overflow-hidden"
label={showHeader ? undefined : orchestrateLabel}
slotClassNames={{
viewport: 'overscroll-contain',
content: cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20'),
scrollbar: hasBottomAction ? 'z-20' : undefined,
}}
>
<AgentConfigApiContextProvider value={configApiContext}>
<AgentOrchestrateAddActionsProvider>
<AgentBuildDraftChangedKeysProvider
changedKeys={
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
}
>
<AgentModelField
currentModel={currentModel}
textGenerationModelList={textGenerationModelList}
onSelect={onSelectModel}
/>
<AgentPromptEditor />
<AgentSkills />
<AgentFiles />
<AgentTools />
<AgentKnowledgeRetrieval />
<AgentAdvancedSettings />
</AgentBuildDraftChangedKeysProvider>
</AgentOrchestrateAddActionsProvider>
</AgentConfigApiContextProvider>
</ScrollArea>
</div>
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
{orchestrateBottomAction ? (
<AgentOrchestrateBottomActions shrinkOnOpen={!bottomAction}>
@@ -10,7 +10,10 @@ import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provid
import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
import { RerankingModeEnum } from '@/models/datasets'
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context'
import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from '../../read-only-context'
import { AgentKnowledgeRetrieval } from '../index'
vi.mock('@/context/workspace-state', async () => {
@@ -107,17 +110,21 @@ function ConfigSnapshotPreview() {
function renderKnowledgeRetrieval({
initialDraft = agentKnowledgeDraft,
readOnly = false,
viewingVersion = false,
showConfigSnapshot = false,
}: {
initialDraft?: AgentSoulConfigFormState
readOnly?: boolean
viewingVersion?: boolean
showConfigSnapshot?: boolean
} = {}) {
return render(
<AgentComposerProvider initialDraft={initialDraft}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<AgentKnowledgeRetrieval />
</AgentOrchestrateReadOnlyContext>
<AgentOrchestrateViewingVersionContext value={viewingVersion}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<AgentKnowledgeRetrieval />
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
{showConfigSnapshot && <ConfigSnapshotPreview />}
</AgentComposerProvider>,
)
@@ -154,8 +161,8 @@ describe('AgentKnowledgeRetrieval', () => {
).not.toBeInTheDocument()
})
it('should hide add, edit, and remove actions when readonly', () => {
renderKnowledgeRetrieval({ readOnly: true })
it('should hide add, edit, and remove actions when viewing a version', () => {
renderKnowledgeRetrieval({ readOnly: true, viewingVersion: true })
expect(
screen.getByText('agentV2.agentDetail.configure.knowledgeRetrieval.retrievalOne'),
@@ -176,6 +183,16 @@ describe('AgentKnowledgeRetrieval', () => {
}),
).not.toBeInTheDocument()
})
it('should keep add action available for build drafts', () => {
renderKnowledgeRetrieval({ readOnly: true })
expect(
screen.getByRole('button', {
name: 'agentV2.agentDetail.configure.knowledgeRetrieval.add',
}),
).toBeInTheDocument()
})
})
describe('User Interactions', () => {
@@ -1,7 +1,12 @@
import { createContext, use } from 'react'
export const AgentOrchestrateReadOnlyContext = createContext(false)
export const AgentOrchestrateViewingVersionContext = createContext(false)
export function useAgentOrchestrateReadOnly() {
return use(AgentOrchestrateReadOnlyContext)
}
export function useAgentOrchestrateViewingVersion() {
return use(AgentOrchestrateViewingVersionContext)
}
@@ -1,3 +1,4 @@
import type { SkillResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import type { AgentConfigApiContext } from '../../config-context'
import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
import { toast } from '@langgenius/dify-ui/toast'
@@ -11,7 +12,10 @@ import { defaultAgentSoulConfigFormState } from '@/features/agent-v2/agent-compo
import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider'
import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
import { AgentConfigApiContextProvider } from '../../config-context'
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context'
import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from '../../read-only-context'
import { AgentSkills } from '../index'
type ConfigSkillInspectQueryOptionsInput = {
@@ -39,10 +43,16 @@ type ConfigSkillDownloadQueryOptionsInput = {
}
const mocks = vi.hoisted(() => ({
agentSkillBindingsKey: vi.fn((_options: unknown): unknown[] => ['workspace-agent-skills']),
agentSkillBindingsQueryOptions: vi.fn((_options: unknown) => ({})),
deleteSkillMutationFn: vi.fn(async (_input: unknown) => ({
removed_names: ['Tender Analyzer'],
result: 'success',
})),
replaceAgentSkillBindingsMutationFn: vi.fn(async (input: { body: { skill_ids?: string[] } }) => ({
agent_id: 'agent-1',
skill_ids: input.body.skill_ids ?? [],
})),
uploadSkillMutationFn: vi.fn(async (_input: unknown) => ({
config_version: { id: 'draft-1', kind: 'draft', writable: true },
skill: {
@@ -59,6 +69,8 @@ const mocks = vi.hoisted(() => ({
inspectQueryOptions: vi.fn((_options: ConfigSkillInspectQueryOptionsInput) => ({})),
previewQueryOptions: vi.fn((_options: ConfigSkillFileQueryOptionsInput) => ({})),
downloadQueryOptions: vi.fn((_options: ConfigSkillFileQueryOptionsInput) => ({})),
workspaceSkillsQueryOptions: vi.fn((_options: unknown) => ({})),
workspaceSkillsInfiniteOptions: vi.fn((_options: unknown) => ({})),
downloadBlob: vi.fn(),
downloadUrl: vi.fn(),
fetch: vi.fn(),
@@ -165,9 +177,43 @@ vi.mock('@/service/client', () => ({
},
},
},
workspaces: {
current: {
agents: {
byAgentId: {
skills: {
get: {
key: mocks.agentSkillBindingsKey,
queryOptions: mocks.agentSkillBindingsQueryOptions,
},
put: {
mutationOptions: () => ({ mutationFn: mocks.replaceAgentSkillBindingsMutationFn }),
},
},
},
},
skills: {
get: {
queryOptions: mocks.workspaceSkillsQueryOptions,
infiniteOptions: mocks.workspaceSkillsInfiniteOptions,
},
},
},
},
},
}))
async function openUploadSkillDialog(user: ReturnType<typeof userEvent.setup>) {
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
await user.click(
screen.getByRole('button', {
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.upload\.label/i,
}),
)
}
function ConfigSnapshotProbe() {
const draft = useAtomValue(agentComposerDraftAtom)
const configSnapshot = formStateToAgentSoulConfig({ formState: draft })
@@ -175,6 +221,23 @@ function ConfigSnapshotProbe() {
return <pre aria-label="config snapshot">{JSON.stringify(configSnapshot)}</pre>
}
function createWorkspaceSkill(overrides: Partial<SkillResponse> = {}): SkillResponse {
return {
id: 'workspace-skill-1',
name: 'refund-approval',
display_name: 'Refund approval',
description: 'Handle refund requests.',
icon: '💳',
latest_published_version_id: 'version-1',
reference_count: 0,
tags: [],
visibility: 'workspace',
created_at: 1,
updated_at: 1,
...overrides,
}
}
function renderAgentSkills({
initialDraft = {
...defaultAgentSoulConfigFormState,
@@ -189,10 +252,12 @@ function renderAgentSkills({
},
apiContext = { agentId: 'agent-1', draftType: 'draft' } satisfies AgentConfigApiContext,
readOnly = false,
viewingVersion = false,
}: {
initialDraft?: AgentSoulConfigFormState
apiContext?: AgentConfigApiContext
readOnly?: boolean
viewingVersion?: boolean
} = {}) {
const queryClient = new QueryClient({
defaultOptions: {
@@ -205,10 +270,12 @@ function renderAgentSkills({
<QueryClientProvider client={queryClient}>
<AgentConfigApiContextProvider value={apiContext}>
<AgentComposerProvider initialDraft={initialDraft}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<AgentSkills />
<ConfigSnapshotProbe />
</AgentOrchestrateReadOnlyContext>
<AgentOrchestrateViewingVersionContext value={viewingVersion}>
<AgentOrchestrateReadOnlyContext value={readOnly}>
<AgentSkills />
<ConfigSnapshotProbe />
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
</AgentComposerProvider>
</AgentConfigApiContextProvider>
</QueryClientProvider>,
@@ -225,6 +292,22 @@ describe('AgentSkills', () => {
headers: { 'Content-Type': 'application/octet-stream' },
}),
)
mocks.agentSkillBindingsKey.mockImplementation((options) => {
const { input } = options as { input: { params: { agent_id: string } } }
return ['workspace-agent-skills', input]
})
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
const { input } = options as { input: { params: { agent_id: string } } }
return {
queryKey: ['workspace-agent-skills', input],
queryFn: async () => ({
agent_id: input.params.agent_id,
skill_ids: [],
data: [],
}),
}
})
mocks.inspectQueryOptions.mockImplementation(({ input }) => ({
queryKey: ['inspect-skill', input],
queryFn: async () => ({
@@ -293,6 +376,38 @@ describe('AgentSkills', () => {
url: `https://example.com/${input.params.name}.skill`,
}),
}))
mocks.workspaceSkillsQueryOptions.mockImplementation((options) => {
const { input } = options as { input: { query?: { keyword?: string } } }
return {
queryKey: ['workspace-skills', input],
queryFn: async () => ({
data: [],
}),
}
})
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
const { input, getNextPageParam, initialPageParam } = options as {
input: (pageParam: number) => {
query?: { keyword?: string; limit?: number; page?: number }
}
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
initialPageParam: number
}
return {
queryKey: ['workspace-skills', input(initialPageParam)],
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
data: [],
has_more: false,
limit: input(pageParam).query?.limit ?? 20,
page: pageParam,
total: 0,
}),
getNextPageParam,
initialPageParam,
}
})
})
afterEach(() => {
@@ -372,9 +487,7 @@ describe('AgentSkills', () => {
const user = userEvent.setup()
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
await openUploadSkillDialog(user)
const input = await waitFor(() => {
const element = document.querySelector('input[type="file"]')
@@ -418,13 +531,375 @@ describe('AgentSkills', () => {
expect(toast.success).toHaveBeenCalled()
})
it('should hide skill package guidance before an upload fails', async () => {
it('should bind workspace skills without adding them to inline config skills', async () => {
const user = userEvent.setup()
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
const { input, getNextPageParam, initialPageParam } = options as {
input: (pageParam: number) => {
query?: { keyword?: string; limit?: number; page?: number }
}
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
initialPageParam: number
}
return {
queryKey: ['workspace-skills', input(initialPageParam)],
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
data: [
{
id: 'workspace-skill-1',
name: 'refund-approval',
display_name: 'Refund approval',
description: 'Handle refund requests.',
icon: '💳',
latest_published_version_id: 'version-1',
reference_count: 0,
tags: [],
visibility: 'workspace',
created_at: 1,
updated_at: 1,
},
],
has_more: false,
limit: 20,
page: pageParam,
total: 1,
}),
getNextPageParam,
initialPageParam,
}
})
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
await user.click(
screen.getByRole('button', {
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
}),
)
await user.click(await screen.findByRole('button', { name: /Refund approval/ }))
await waitFor(() => {
expect(mocks.replaceAgentSkillBindingsMutationFn.mock.calls[0]?.[0]).toEqual({
params: {
agent_id: 'agent-1',
},
body: {
skill_ids: ['workspace-skill-1'],
},
})
})
const snapshot = JSON.parse(screen.getByLabelText('config snapshot').textContent ?? '{}')
expect(snapshot.config_skills).toEqual([])
})
it('should allow workflow agent nodes to bind workspace skills', async () => {
const user = userEvent.setup()
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
const { input, getNextPageParam, initialPageParam } = options as {
input: (pageParam: number) => {
query?: { keyword?: string; limit?: number; page?: number }
}
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
initialPageParam: number
}
return {
queryKey: ['workspace-skills', input(initialPageParam)],
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
data: [
{
id: 'workspace-skill-1',
name: 'refund-approval',
display_name: 'Refund approval',
description: 'Handle refund requests.',
icon: '💳',
latest_published_version_id: 'version-1',
reference_count: 0,
tags: [],
visibility: 'workspace',
created_at: 1,
updated_at: 1,
},
],
has_more: false,
limit: 20,
page: pageParam,
total: 1,
}),
getNextPageParam,
initialPageParam,
}
})
renderAgentSkills({
initialDraft: defaultAgentSoulConfigFormState,
apiContext: {
agentId: 'workflow-agent-1',
draftType: 'draft',
workflow: {
appId: 'workflow-app-1',
nodeId: 'agent-node-1',
},
},
})
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
const workspaceMenuItem = screen.getByRole('button', {
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
})
expect(workspaceMenuItem).not.toBeDisabled()
await user.click(workspaceMenuItem)
await user.click(await screen.findByRole('button', { name: /Refund approval/ }))
await waitFor(() => {
expect(mocks.replaceAgentSkillBindingsMutationFn.mock.calls[0]?.[0]).toEqual({
params: {
agent_id: 'workflow-agent-1',
},
body: {
skill_ids: ['workspace-skill-1'],
},
})
})
})
it('should mark already bound workspace skills as added and prevent duplicate binding', async () => {
const user = userEvent.setup()
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
const { input } = options as { input: { params: { agent_id: string } } }
return {
queryKey: ['workspace-agent-skills', input],
queryFn: async () => ({
agent_id: input.params.agent_id,
skill_ids: ['workspace-skill-1'],
data: [
{
...createWorkspaceSkill(),
priority: 0,
status: 'published',
file_count: 1,
latest_published_at: 1,
},
],
}),
}
})
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
const { input, getNextPageParam, initialPageParam } = options as {
input: (pageParam: number) => {
query?: { keyword?: string; limit?: number; page?: number }
}
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
initialPageParam: number
}
return {
queryKey: ['workspace-skills', input(initialPageParam)],
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
data: [
createWorkspaceSkill(),
createWorkspaceSkill({
id: 'draft-skill',
name: 'draft-skill',
display_name: 'Draft skill',
latest_published_version_id: null,
}),
],
has_more: false,
limit: 20,
page: pageParam,
total: 2,
}),
getNextPageParam,
initialPageParam,
}
})
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
await user.click(
screen.getByRole('button', {
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
}),
)
expect(
await screen.findByText('agentV2.agentDetail.configure.skills.workspaceSelector.added'),
).toBeInTheDocument()
expect(
screen.getByText('agentV2.agentDetail.configure.skills.workspaceSelector.draft'),
).toBeInTheDocument()
const addedSkillButton = screen
.getByText('agentV2.agentDetail.configure.skills.workspaceSelector.added')
.closest('button')
const draftSkillButton = screen
.getByText('agentV2.agentDetail.configure.skills.workspaceSelector.draft')
.closest('button')
expect(addedSkillButton).toBeDisabled()
expect(draftSkillButton).toBeDisabled()
expect(mocks.replaceAgentSkillBindingsMutationFn).not.toHaveBeenCalled()
})
it('should fetch the next workspace skill page when scrolling the selector', async () => {
const user = userEvent.setup()
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
const { input, getNextPageParam, initialPageParam } = options as {
input: (pageParam: number) => {
query?: { keyword?: string; limit?: number; page?: number }
}
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
initialPageParam: number
}
return {
queryKey: ['workspace-skills', input(initialPageParam)],
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
data:
pageParam === 1
? [createWorkspaceSkill()]
: [
createWorkspaceSkill({
id: 'workspace-skill-2',
name: 'sales-follow-up',
display_name: 'Sales follow-up',
}),
],
has_more: pageParam === 1,
limit: 20,
page: pageParam,
total: 2,
}),
getNextPageParam,
initialPageParam,
}
})
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
await user.click(
screen.getByRole('button', {
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
}),
)
await waitFor(() => {
expect(screen.getAllByText('Refund approval').length).toBeGreaterThan(1)
})
const scrollContainer = document.querySelector('.overflow-y-auto')
expect(scrollContainer).not.toBeNull()
Object.defineProperties(scrollContainer!, {
clientHeight: { configurable: true, value: 100 },
scrollHeight: { configurable: true, value: 160 },
scrollTop: { configurable: true, value: 80 },
})
fireEvent.scroll(scrollContainer!)
expect(await screen.findByText('Sales follow-up')).toBeInTheDocument()
})
it('should remove workspace skill bindings from the configured agent', async () => {
const user = userEvent.setup()
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
const { input } = options as { input: { params: { agent_id: string } } }
return {
queryKey: ['workspace-agent-skills', input],
queryFn: async () => ({
agent_id: input.params.agent_id,
skill_ids: ['workspace-skill-1'],
data: [
{
...createWorkspaceSkill(),
priority: 0,
status: 'published',
file_count: 1,
latest_published_at: 1,
},
],
}),
}
})
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click(
await screen.findByRole('button', {
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Refund approval"}',
}),
)
await user.click(await screen.findByText('agentV2.agentDetail.configure.skills.removeAction'))
await waitFor(() => {
expect(mocks.replaceAgentSkillBindingsMutationFn.mock.calls[0]?.[0]).toEqual({
params: {
agent_id: 'agent-1',
},
body: {
skill_ids: [],
},
})
})
expect(toast.success).toHaveBeenCalledWith(
'agentV2.agentDetail.configure.skills.workspaceSelector.removeSuccess',
)
})
it('should open workspace skill details in a new tab from the row menu', async () => {
const user = userEvent.setup()
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
const { input } = options as { input: { params: { agent_id: string } } }
return {
queryKey: ['workspace-agent-skills', input],
queryFn: async () => ({
agent_id: input.params.agent_id,
skill_ids: ['workspace-skill-1'],
data: [
{
...createWorkspaceSkill(),
priority: 0,
status: 'published',
file_count: 1,
latest_published_at: 1,
},
],
}),
}
})
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click(
await screen.findByRole('button', {
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Refund approval"}',
}),
)
await user.click(await screen.findByText('agentV2.agentDetail.configure.skills.openInLibrary'))
expect(openSpy).toHaveBeenCalledWith(
'/skills/workspace-skill-1',
'_blank',
'noopener,noreferrer',
)
})
it('should hide skill package guidance before an upload fails', async () => {
const user = userEvent.setup()
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await openUploadSkillDialog(user)
expect(
screen.queryByText('agentV2.agentDetail.configure.skills.upload.warning.specification'),
@@ -438,9 +913,7 @@ describe('AgentSkills', () => {
.mockImplementationOnce(() => new Promise<never>(() => undefined))
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
await openUploadSkillDialog(user)
const input = await waitFor(() => {
const element = document.querySelector('input[type="file"]')
expect(element).not.toBeNull()
@@ -477,9 +950,7 @@ describe('AgentSkills', () => {
mocks.uploadSkillMutationFn.mockRejectedValueOnce(new Error('Backend upload error'))
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
await openUploadSkillDialog(user)
const input = await waitFor(() => {
const element = document.querySelector('input[type="file"]')
@@ -515,9 +986,7 @@ describe('AgentSkills', () => {
},
})
await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
await openUploadSkillDialog(user)
const input = await waitFor(() => {
const element = document.querySelector('input[type="file"]')
expect(element).not.toBeNull()
@@ -869,12 +1338,87 @@ describe('AgentSkills', () => {
)
})
it('should disable add and remove actions when the section is read only', () => {
const { container } = renderAgentSkills({ readOnly: true })
it('should disable add and remove actions when viewing a version', async () => {
const user = userEvent.setup()
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
const { input } = options as { input: { params: { agent_id: string } } }
return {
queryKey: ['workspace-agent-skills', input],
queryFn: async () => ({
agent_id: input.params.agent_id,
skill_ids: ['workspace-skill-1'],
data: [
{
...createWorkspaceSkill(),
priority: 0,
status: 'published',
file_count: 1,
latest_published_at: 1,
},
],
}),
}
})
const { container } = renderAgentSkills({
apiContext: {
agentId: 'agent-1',
draftType: 'draft',
versionId: 'version-1',
},
readOnly: true,
viewingVersion: true,
})
expect(
screen.queryByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
).not.toBeInTheDocument()
expect(container.querySelector('[data-agent-skill-remove-button]')).toBeNull()
await user.click(
await screen.findByRole('button', {
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Refund approval"}',
}),
)
expect(
screen.getByText('agentV2.agentDetail.configure.skills.openInLibrary'),
).toBeInTheDocument()
expect(
screen.queryByText('agentV2.agentDetail.configure.skills.removeAction'),
).not.toBeInTheDocument()
expect(mocks.replaceAgentSkillBindingsMutationFn).not.toHaveBeenCalled()
})
it('should keep the add menu available for build draft skills', async () => {
const user = userEvent.setup()
renderAgentSkills({
apiContext: {
agentId: 'agent-1',
draftType: 'debug_build',
},
initialDraft: {
...defaultAgentSoulConfigFormState,
skills: [],
},
readOnly: true,
})
await user.click(
await screen.findByRole('button', {
name: /agentV2\.agentDetail\.configure\.skills\.add/i,
}),
)
expect(
await screen.findByRole('button', {
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
}),
).toBeInTheDocument()
expect(
screen.getByRole('button', {
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.upload\.label/i,
}),
).toBeInTheDocument()
})
})
@@ -1,30 +1,391 @@
'use client'
import type {
AgentSkillBindingItemResponse,
SkillResponse,
} from '@dify/contracts/api/console/workspaces/types.gen'
import type { UIEvent } from 'react'
import type { AgentOrchestrateAddActionOptions } from '../add-actions-context'
import type { AgentSkill } from '@/features/agent-v2/agent-composer/form-state'
import { useMutation } from '@tanstack/react-query'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { toast } from '@langgenius/dify-ui/toast'
import {
keepPreviousData,
useInfiniteQuery,
useMutation,
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import { useDebounce } from 'ahooks'
import { useAtomValue, useSetAtom } from 'jotai'
import { useCallback, useRef, useState } from 'react'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SearchInput } from '@/app/components/base/search-input'
import { SkeletonRectangle } from '@/app/components/base/skeleton'
import {
agentComposerSkillsAtom,
removeAgentSkillAtom,
upsertAgentSkillAtom,
} from '@/features/agent-v2/agent-composer/store-modules/skills'
import Link from '@/next/link'
import { consoleQuery } from '@/service/client'
import { useRegisterAgentOrchestrateAddAction } from '../add-actions-context'
import { ConfigureSectionAddButton } from '../common/add-button'
import { ConfigureSectionEmpty } from '../common/empty'
import { ConfigureSection } from '../common/section'
import { AgentConfigureTipContent } from '../common/tip-content'
import { useAgentConfigApiContext } from '../config-context'
import {
useAgentOrchestrateReadOnly,
useAgentOrchestrateViewingVersion,
} from '../read-only-context'
import { AgentSkillItem } from './item'
import { AgentSkillUploadDialog } from './upload-dialog'
const WORKSPACE_SKILLS_PAGE_SIZE = 20
function AgentSkillAddMenuItem({
badge,
description,
disabled,
iconClassName,
label,
onClick,
}: {
badge?: string
description: string
disabled?: boolean
iconClassName: string
label: string
onClick: () => void
}) {
return (
<button
type="button"
disabled={disabled}
onClick={onClick}
className="flex w-full min-w-0 items-start gap-3 rounded-lg px-2 py-2 text-left outline-hidden hover:not-disabled:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:opacity-50"
>
<span
aria-hidden
className={cn('mt-0.5 size-4 shrink-0 text-text-tertiary', iconClassName)}
/>
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="flex min-w-0 items-center gap-2">
<span className="truncate system-sm-medium text-text-secondary">{label}</span>
{badge && (
<span className="shrink-0 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1.5 py-0.5 system-2xs-medium-uppercase text-text-tertiary">
{badge}
</span>
)}
</span>
<span className="line-clamp-2 system-xs-regular text-text-tertiary">{description}</span>
</span>
</button>
)
}
function WorkspaceSkillIcon({ icon }: { icon?: string }) {
return (
<span className="flex size-5 shrink-0 items-center justify-center rounded-md border-[0.5px] border-divider-subtle bg-background-default-dodge">
{icon ? (
<span className="text-[12px] leading-none">{icon}</span>
) : (
<span aria-hidden className="i-ri-box-3-line size-3.5 text-text-tertiary" />
)}
</span>
)
}
function WorkspaceSkillRow({
disabled,
isAdded,
isPending,
onSelect,
onPreview,
selected,
skill,
}: {
disabled: boolean
isAdded: boolean
isPending: boolean
onSelect: (skill: SkillResponse) => void
onPreview: (skill: SkillResponse) => void
selected: boolean
skill: SkillResponse
}) {
const { t } = useTranslation('agentV2')
return (
<button
type="button"
disabled={disabled || isAdded || isPending}
onClick={() => onSelect(skill)}
onFocus={() => onPreview(skill)}
onMouseEnter={() => onPreview(skill)}
className={cn(
'flex h-12 w-full min-w-0 items-center gap-2 rounded-lg px-2 text-left outline-hidden hover:not-disabled:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-default disabled:opacity-60',
selected && 'bg-state-base-hover',
)}
>
<WorkspaceSkillIcon icon={skill.icon} />
<span className="flex w-0 min-w-0 flex-1 flex-col gap-0.5">
<span className="truncate system-sm-medium text-text-secondary">{skill.display_name}</span>
<span className="truncate system-xs-regular text-text-tertiary">{skill.name}</span>
</span>
{isAdded && (
<span className="shrink-0 system-xs-medium text-text-tertiary">
{t(($) => $['agentDetail.configure.skills.workspaceSelector.added'])}
</span>
)}
{!isAdded && disabled && (
<span className="shrink-0 system-xs-medium text-text-tertiary">
{t(($) => $['agentDetail.configure.skills.workspaceSelector.draft'])}
</span>
)}
</button>
)
}
function WorkspaceSkillPreview({ skill }: { skill?: SkillResponse }) {
const { t } = useTranslation('agentV2')
if (!skill) {
return (
<div className="flex h-full items-center justify-center px-6 text-center system-xs-regular text-text-tertiary">
{t(($) => $['agentDetail.configure.skills.workspaceSelector.empty'])}
</div>
)
}
return (
<div className="flex h-full flex-col gap-4 p-4">
<div className="flex min-w-0 items-start gap-3">
<WorkspaceSkillIcon icon={skill.icon} />
<div className="min-w-0 flex-1">
<div className="truncate system-md-semibold text-text-primary">{skill.display_name}</div>
<div className="mt-0.5 truncate system-xs-regular text-text-tertiary">{skill.name}</div>
</div>
</div>
{!!skill.tags?.length && (
<div className="flex flex-wrap gap-1">
{skill.tags.slice(0, 5).map((tag) => (
<span
key={tag}
className="rounded-[5px] border border-divider-subtle bg-components-badge-bg-dimm px-1.5 py-0.5 system-2xs-medium-uppercase text-text-tertiary"
>
{tag}
</span>
))}
</div>
)}
<p className="line-clamp-6 system-sm-regular text-text-secondary">{skill.description}</p>
{(skill.updated_by_name || skill.created_by_name) && (
<div className="mt-auto system-xs-regular text-text-tertiary">
{skill.updated_by_name || skill.created_by_name}
</div>
)}
</div>
)
}
function WorkspaceSkillSelector({
boundSkillIds,
isBindingPending,
onSelect,
}: {
boundSkillIds: string[]
isBindingPending: boolean
onSelect: (skill: SkillResponse) => void
}) {
const { t } = useTranslation('agentV2')
const [keyword, setKeyword] = useState('')
const [previewSkillId, setPreviewSkillId] = useState<string | undefined>(undefined)
const debouncedKeyword = useDebounce(keyword.trim(), { wait: 300 })
const skillsQuery = useInfiniteQuery({
...consoleQuery.workspaces.current.skills.get.infiniteOptions({
input: (pageParam) => ({
query: {
limit: WORKSPACE_SKILLS_PAGE_SIZE,
page: Number(pageParam),
...(debouncedKeyword ? { keyword: debouncedKeyword } : {}),
},
}),
getNextPageParam: (lastPage) => (lastPage.has_more ? (lastPage.page ?? 1) + 1 : undefined),
initialPageParam: 1,
placeholderData: keepPreviousData,
}),
})
const boundSkillIdSet = useMemo(() => new Set(boundSkillIds), [boundSkillIds])
const skills = skillsQuery.data?.pages.flatMap((page) => page.data ?? []) ?? []
const previewSkill = skills.find((skill) => skill.id === previewSkillId) ?? skills[0]
const hasNextPage = skillsQuery.hasNextPage ?? false
const isFetchingNextPage = skillsQuery.isFetchingNextPage
const fetchNextPage = skillsQuery.fetchNextPage
const handleListScroll = useCallback(
(event: UIEvent<HTMLDivElement>) => {
const target = event.currentTarget
const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight
if (scrollBottom < 80 && hasNextPage && !isFetchingNextPage) void fetchNextPage()
},
[fetchNextPage, hasNextPage, isFetchingNextPage],
)
return (
<div className="flex h-[520px] w-[560px] overflow-hidden rounded-xl border border-divider-regular bg-components-panel-bg shadow-lg">
<div className="flex min-w-0 flex-1 flex-col border-r border-divider-subtle">
<div className="border-b border-divider-subtle p-3">
<div className="relative">
<SearchInput
value={keyword}
onValueChange={setKeyword}
placeholder={t(($) => $['agentDetail.configure.skills.workspaceSelector.search'])}
/>
<span
aria-hidden
className="pointer-events-none absolute top-1/2 right-8 i-ri-price-tag-3-line size-4 -translate-y-1/2 text-text-tertiary"
/>
</div>
</div>
<div className="min-h-0 flex-1 overflow-y-auto p-1.5" onScroll={handleListScroll}>
{skillsQuery.isPending && (
<div className="space-y-2 p-1">
<SkeletonRectangle className="h-10 rounded-lg" />
<SkeletonRectangle className="h-10 rounded-lg" />
<SkeletonRectangle className="h-10 rounded-lg" />
</div>
)}
{!skillsQuery.isPending && skills.length === 0 && (
<div className="flex h-full items-center justify-center px-4 text-center system-xs-regular text-text-tertiary">
{t(($) => $['agentDetail.configure.skills.workspaceSelector.empty'])}
</div>
)}
{!skillsQuery.isPending &&
skills.map((skill) => (
<WorkspaceSkillRow
key={skill.id}
disabled={!skill.latest_published_version_id}
isAdded={boundSkillIdSet.has(skill.id)}
isPending={isBindingPending}
selected={previewSkill?.id === skill.id}
skill={skill}
onPreview={(skill) => setPreviewSkillId(skill.id)}
onSelect={onSelect}
/>
))}
{skillsQuery.isFetchingNextPage && (
<div className="space-y-2 p-1">
<SkeletonRectangle className="h-10 rounded-lg" />
<SkeletonRectangle className="h-10 rounded-lg" />
</div>
)}
</div>
<Link
href="/skills"
className="flex h-10 items-center justify-between border-t border-divider-subtle px-3 system-sm-medium text-text-secondary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
>
<span>{t(($) => $['agentDetail.configure.skills.workspaceSelector.manage'])}</span>
<span aria-hidden className="i-ri-arrow-right-up-line size-4 text-text-tertiary" />
</Link>
</div>
<div className="w-[240px] shrink-0 bg-background-default">
<WorkspaceSkillPreview skill={previewSkill} />
</div>
</div>
)
}
function WorkspaceAgentSkillItem({
canRemove,
skill,
onRemove,
}: {
canRemove: boolean
skill: AgentSkillBindingItemResponse
onRemove: (skillId: string) => void
}) {
const { t } = useTranslation('agentV2')
const readOnly = useAgentOrchestrateReadOnly()
const displayName = skill.display_name || skill.name
const handleOpenInLibrary = useCallback(() => {
window.open(`/skills/${skill.id}`, '_blank', 'noopener,noreferrer')
}, [skill.id])
return (
<div className="group relative h-8 overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs shadow-shadow-shadow-3 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm">
<Link
href={`/skills/${skill.id}`}
target="_blank"
rel="noreferrer"
className="flex h-full w-full min-w-0 cursor-pointer items-center gap-1 rounded-lg py-1 pr-8 pl-2 text-left outline-hidden select-none focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
>
<WorkspaceSkillIcon icon={skill.icon} />
<span className="flex w-0 min-w-0 flex-1 items-center gap-1">
<span className="min-w-0 truncate system-sm-medium text-text-secondary">
{displayName}
</span>
<span
aria-hidden
className="i-ri-arrow-right-up-line size-3.5 shrink-0 text-text-quaternary opacity-0 group-focus-within:opacity-100 group-hover:opacity-100"
/>
</span>
<span
className={cn(
'shrink-0 system-xs-regular text-text-tertiary',
!readOnly && 'group-focus-within:opacity-0 group-hover:opacity-0',
)}
>
{skill.name}
</span>
</Link>
<DropdownMenu modal={false}>
<DropdownMenuTrigger
aria-label={t(($) => $['agentDetail.configure.skills.moreActions'], {
name: displayName,
})}
className="absolute top-1/2 right-1 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary"
onClick={(event) => event.stopPropagation()}
>
<span aria-hidden className="i-ri-more-fill size-4" />
</DropdownMenuTrigger>
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-48">
<DropdownMenuItem className="gap-2" onClick={handleOpenInLibrary}>
<span aria-hidden className="i-ri-arrow-right-up-line size-4 shrink-0" />
<span>{t(($) => $['agentDetail.configure.skills.openInLibrary'])}</span>
</DropdownMenuItem>
{canRemove && (
<DropdownMenuItem
variant="destructive"
className="gap-2"
onClick={() => onRemove(skill.id)}
>
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
<span>{t(($) => $['agentDetail.configure.skills.removeAction'])}</span>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}
export function AgentSkills() {
const { t } = useTranslation('agentV2')
const { t: tCommon } = useTranslation('common')
const skillsTip = t(($) => $['agentDetail.configure.skills.tip'])
const skillsListId = 'agent-configure-skills-list'
const queryClient = useQueryClient()
const isViewingVersion = useAgentOrchestrateViewingVersion()
const [addMenuOpen, setAddMenuOpen] = useState(false)
const [addMenuView, setAddMenuView] = useState<'menu' | 'workspace-selector'>('menu')
const [isUploadOpen, setIsUploadOpen] = useState(false)
const promptAddCallbackRef = useRef<AgentOrchestrateAddActionOptions['onAdded']>(undefined)
const apiContext = useAgentConfigApiContext()
@@ -37,6 +398,68 @@ export function AgentSkills() {
const { mutate: deleteAppSkill } = useMutation(
consoleQuery.apps.byAppId.agent.config.skills.byName.delete.mutationOptions(),
)
const agentSkillBindingsQueryOptions =
consoleQuery.workspaces.current.agents.byAgentId.skills.get.queryOptions({
input: {
params: {
agent_id: apiContext.agentId,
},
},
})
const agentSkillBindingsQuery = useQuery({
...agentSkillBindingsQueryOptions,
})
const { isPending: isReplacingAgentSkillBindings, mutate: replaceAgentSkillBindings } =
useMutation(consoleQuery.workspaces.current.agents.byAgentId.skills.put.mutationOptions())
const workspaceSkills = agentSkillBindingsQuery.data?.data ?? []
const boundSkillIds =
agentSkillBindingsQuery.data?.skill_ids ?? workspaceSkills.map((skill) => skill.id)
const hasSkills = skills.length > 0 || workspaceSkills.length > 0
const invalidateAgentSkillBindings = useCallback(() => {
void queryClient.invalidateQueries({
queryKey: consoleQuery.workspaces.current.agents.byAgentId.skills.get.key({
type: 'query',
input: {
params: {
agent_id: apiContext.agentId,
},
},
}),
})
}, [apiContext.agentId, queryClient])
const replaceWorkspaceSkillBindings = useCallback(
(skillIds: string[], onSuccess?: () => void) => {
if (isViewingVersion) return
replaceAgentSkillBindings(
{
params: {
agent_id: apiContext.agentId,
},
body: {
skill_ids: skillIds,
},
},
{
onError: () => {
toast.error(t(($) => $['agentDetail.configure.skills.workspaceSelector.saveFailed']))
},
onSuccess: () => {
invalidateAgentSkillBindings()
onSuccess?.()
},
},
)
},
[
apiContext.agentId,
invalidateAgentSkillBindings,
isViewingVersion,
replaceAgentSkillBindings,
t,
],
)
const handleOpenUpload = useCallback((options?: AgentOrchestrateAddActionOptions) => {
promptAddCallbackRef.current = options?.onAdded
@@ -44,6 +467,20 @@ export function AgentSkills() {
}, [])
useRegisterAgentOrchestrateAddAction('skills', handleOpenUpload)
const handleAddMenuOpenChange = useCallback((open: boolean) => {
setAddMenuOpen(open)
if (!open) setAddMenuView('menu')
}, [])
const handleOpenWorkspaceSelector = useCallback(() => {
setAddMenuView('workspace-selector')
}, [])
const handleOpenUploadFromMenu = useCallback(() => {
setAddMenuOpen(false)
handleOpenUpload()
}, [handleOpenUpload])
const handleUploaded = useCallback(
(skill: AgentSkill) => {
upsertAgentSkill(skill)
@@ -53,11 +490,36 @@ export function AgentSkills() {
[upsertAgentSkill],
)
const handleSelectWorkspaceSkill = useCallback(
(skill: SkillResponse) => {
if (!skill.latest_published_version_id || boundSkillIds.includes(skill.id)) return
replaceWorkspaceSkillBindings([...boundSkillIds, skill.id], () => {
toast.success(t(($) => $['agentDetail.configure.skills.workspaceSelector.addSuccess']))
setAddMenuOpen(false)
setAddMenuView('menu')
})
},
[boundSkillIds, replaceWorkspaceSkillBindings, t],
)
const handleUploadOpenChange = useCallback((open: boolean) => {
if (!open) promptAddCallbackRef.current = undefined
setIsUploadOpen(open)
}, [])
const handleRemoveWorkspaceSkill = useCallback(
(skillId: string) => {
replaceWorkspaceSkillBindings(
boundSkillIds.filter((item) => item !== skillId),
() => {
toast.success(t(($) => $['agentDetail.configure.skills.workspaceSelector.removeSuccess']))
},
)
},
[boundSkillIds, replaceWorkspaceSkillBindings, t],
)
const handleRemoveSkill = useCallback(
(skillId: string) => {
const skill = skills.find((item) => item.id === skillId)
@@ -113,26 +575,91 @@ export function AgentSkills() {
rootClassName="border-b border-divider-subtle pt-4"
panelContentClassName="flex flex-col gap-1 pb-4"
actions={
<ConfigureSectionAddButton
ariaLabel={t(($) => $['agentDetail.configure.skills.add'])}
onClick={() => handleOpenUpload()}
/>
!isViewingVersion && (
<Popover open={addMenuOpen} onOpenChange={handleAddMenuOpenChange}>
<PopoverTrigger
render={
<Button
aria-label={t(($) => $['agentDetail.configure.skills.add'])}
variant="ghost"
size="small"
className="shrink-0 gap-1 px-2"
>
<span aria-hidden className="i-ri-add-line size-3.5" />
<span>{tCommon(($) => $['operation.add'])}</span>
</Button>
}
/>
<PopoverContent
placement="bottom-end"
sideOffset={4}
popupClassName={
addMenuView === 'menu'
? 'w-[320px] bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[5px]'
: 'w-[560px] overflow-hidden border-none bg-transparent p-0 shadow-none'
}
>
{addMenuView === 'menu' ? (
<>
<AgentSkillAddMenuItem
iconClassName="i-custom-public-agent-building-blocks"
label={t(($) => $['agentDetail.configure.skills.addMenu.workspace.label'])}
description={t(
($) => $['agentDetail.configure.skills.addMenu.workspace.description'],
)}
onClick={handleOpenWorkspaceSelector}
/>
<AgentSkillAddMenuItem
badge={t(($) => $['agentDetail.configure.skills.addMenu.upload.badge'])}
iconClassName="i-ri-upload-cloud-2-line"
label={t(($) => $['agentDetail.configure.skills.addMenu.upload.label'])}
description={t(
($) => $['agentDetail.configure.skills.addMenu.upload.description'],
)}
onClick={handleOpenUploadFromMenu}
/>
</>
) : (
<WorkspaceSkillSelector
boundSkillIds={boundSkillIds}
isBindingPending={isReplacingAgentSkillBindings}
onSelect={handleSelectWorkspaceSkill}
/>
)}
</PopoverContent>
</Popover>
)
}
>
{skills.length === 0 ? (
{!hasSkills ? (
<ConfigureSectionEmpty
title={t(($) => $['agentDetail.configure.skills.empty.title'])}
description={t(($) => $['agentDetail.configure.skills.empty.description'])}
/>
) : (
skills.map((skill) => (
<AgentSkillItem
key={skill.id}
apiContext={apiContext}
skill={skill}
onRemove={handleRemoveSkill}
/>
))
<>
{workspaceSkills.length > 0 && (
<div className="px-1 pt-1 pb-0.5 system-xs-medium-uppercase text-text-tertiary">
{t(($) => $['agentDetail.configure.skills.fromSkillLibrary'])}
</div>
)}
{workspaceSkills.map((skill) => (
<WorkspaceAgentSkillItem
key={skill.id}
canRemove={!isViewingVersion}
skill={skill}
onRemove={handleRemoveWorkspaceSkill}
/>
))}
{skills.map((skill) => (
<AgentSkillItem
key={skill.id}
apiContext={apiContext}
skill={skill}
onRemove={handleRemoveSkill}
/>
))}
</>
)}
</ConfigureSection>
<AgentSkillUploadDialog
@@ -15,7 +15,10 @@ import {
agentComposerSavedDraftAtom,
isAgentComposerDirtyAtom,
} from '@/features/agent-v2/agent-composer/store'
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context'
import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from '../../read-only-context'
import { AgentTools } from '../index'
const toolProviderState = vi.hoisted(() => ({
@@ -340,7 +343,13 @@ function renderAgentToolsWithStore(initialDraft: AgentSoulConfigFormState = agen
}
}
function renderReadonlyAgentTools(initialDraft: AgentSoulConfigFormState = agentToolsDraft) {
function renderReadonlyAgentTools({
initialDraft = agentToolsDraft,
viewingVersion = false,
}: {
initialDraft?: AgentSoulConfigFormState
viewingVersion?: boolean
} = {}) {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
@@ -352,9 +361,11 @@ function renderReadonlyAgentTools(initialDraft: AgentSoulConfigFormState = agent
return render(
<QueryClientProvider client={queryClient}>
<AgentComposerProvider initialDraft={initialDraft}>
<AgentOrchestrateReadOnlyContext value>
<AgentTools />
</AgentOrchestrateReadOnlyContext>
<AgentOrchestrateViewingVersionContext value={viewingVersion}>
<AgentOrchestrateReadOnlyContext value>
<AgentTools />
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
</AgentComposerProvider>
</QueryClientProvider>,
)
@@ -441,9 +452,9 @@ describe('AgentTools', () => {
).toBeInTheDocument()
})
it('should hide add, edit, and remove actions when readonly', async () => {
it('should hide add, edit, and remove actions when viewing a version', async () => {
const user = userEvent.setup()
renderReadonlyAgentTools()
renderReadonlyAgentTools({ viewingVersion: true })
expect(
screen.queryByRole('button', {
@@ -484,6 +495,16 @@ describe('AgentTools', () => {
).not.toBeInTheDocument()
})
it('should keep add action available for build drafts', () => {
renderReadonlyAgentTools()
expect(
screen.getByRole('button', {
name: 'agentV2.agentDetail.configure.tools.add',
}),
).toBeInTheDocument()
})
it('should hide CLI tool rows while CLI tools are disabled', () => {
renderAgentTools()
@@ -36,7 +36,7 @@ import { ConfigureSectionAddButton } from '../common/add-button'
import { ConfigureSectionEmpty } from '../common/empty'
import { ConfigureSection } from '../common/section'
import { AgentConfigureTipContent } from '../common/tip-content'
import { useAgentOrchestrateReadOnly } from '../read-only-context'
import { useAgentOrchestrateViewingVersion } from '../read-only-context'
import { CliToolDialog } from './cli-tool/dialog'
import { AgentCliToolItem } from './cli-tool/item'
import {
@@ -400,7 +400,7 @@ function AddToolMenu({
export function AgentTools() {
const { t } = useTranslation('agentV2')
const readOnly = useAgentOrchestrateReadOnly()
const isViewingVersion = useAgentOrchestrateViewingVersion()
const setProviderToolCredential = useSetAtom(setProviderToolCredentialAtom)
const providerById = useAgentToolProviderMap()
const tools = useAtomValue(agentComposerToolsAtom)
@@ -533,7 +533,7 @@ export function AgentTools() {
rootClassName="border-b border-divider-subtle pt-4"
panelContentClassName="flex flex-col gap-1 pb-4"
actions={
!readOnly ? (
!isViewingVersion ? (
<AddToolMenu
onAddCliTool={openCliToolDialog}
onAddTools={addTools}
@@ -2,6 +2,7 @@
import type { AgentAppPartial, AgentIconType } from '@dify/contracts/api/console/agent/types.gen'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenu,
DropdownMenuContent,
@@ -224,7 +225,12 @@ function AgentRosterItem({ agent }: { agent: AgentAppPartial }) {
</div>
</div>
</div>
<div className="pointer-events-none absolute top-2 right-2 z-20 flex items-center overflow-hidden rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 opacity-0 shadow-lg backdrop-blur-xs transition-opacity group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 has-data-popup-open:pointer-events-auto has-data-popup-open:opacity-100">
<div
className={cn(
'pointer-events-none absolute right-2 z-20 flex items-center overflow-hidden rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 opacity-0 shadow-lg backdrop-blur-xs transition-opacity group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 has-data-popup-open:pointer-events-auto has-data-popup-open:opacity-100',
isDraft ? 'top-7' : 'top-2',
)}
>
<DropdownMenu modal={false}>
<DropdownMenuTrigger
aria-label={t(($) => $['roster.moreActions'], { name: agent.name })}
+15
View File
@@ -0,0 +1,15 @@
# Skills
Workspace Skill management UI. This module owns the Skills list, filters, and list-level actions.
## Internal Modules
None.
## External Modules
- app/components/base/search-input
- app/components/base/skeleton
- app/components/base/tooltip
- hooks/use-document-title
- hooks/use-timestamp
File diff suppressed because it is too large Load Diff
+487
View File
@@ -0,0 +1,487 @@
import type {
SkillResponse,
SkillTagResponse,
} from '@dify/contracts/api/console/workspaces/types.gen'
import type { ReactNode } from 'react'
import { toast } from '@langgenius/dify-ui/toast'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import SkillsPage from '../page'
type SkillsInfiniteOptions = {
getNextPageParam: (lastPage: { has_more: boolean; page: number }) => number | undefined
initialPageParam: number
input: (pageParam: unknown) => {
query: Record<string, unknown>
}
}
const mocks = vi.hoisted(() => ({
createSkillMutationFn: vi.fn(),
deleteSkillMutationFn: vi.fn(),
downloadBlob: vi.fn(),
duplicateSkillMutationFn: vi.fn(),
exportSkillArchiveBlob: vi.fn(),
importSkillMutationFn: vi.fn(),
push: vi.fn(),
queryState: {
keyword: '',
tag: [] as string[],
},
skills: [] as SkillResponse[],
skillPages: [] as SkillResponse[][],
skillsKey: vi.fn((_options: unknown): unknown[] => ['skills']),
skillsQueryOptions: vi.fn((_options: SkillsInfiniteOptions) => ({})),
tags: [] as SkillTagResponse[],
tagsKey: vi.fn((_options: unknown): unknown[] => ['skill-tags']),
tagsQueryOptions: vi.fn((_options: unknown) => ({})),
}))
vi.mock('@langgenius/dify-ui/toast', () => ({
toast: {
error: vi.fn(),
success: vi.fn(),
},
}))
vi.mock('ahooks', () => ({
useDebounce: (value: unknown) => value,
}))
vi.mock('nuqs', async () => {
const React = await import('react')
const listeners = new Map<'keyword' | 'tag', Set<() => void>>()
const createParser = () => ({
withDefault: () => ({
withOptions: () => ({}),
}),
})
return {
debounce: () => undefined,
parseAsArrayOf: () => ({
withDefault: () => ({}),
}),
parseAsString: createParser(),
useQueryState: (name: 'keyword' | 'tag') => {
const [value, setValue] = React.useState(mocks.queryState[name])
React.useEffect(() => {
const nameListeners = listeners.get(name) ?? new Set<() => void>()
listeners.set(name, nameListeners)
const listener = () => setValue(mocks.queryState[name])
nameListeners.add(listener)
return () => {
nameListeners.delete(listener)
}
}, [name])
const setQueryValue = (nextValue: string | string[]) => {
mocks.queryState[name] = nextValue as never
setValue(nextValue as never)
listeners.get(name)?.forEach((listener) => listener())
return Promise.resolve(new URLSearchParams())
}
return [value, setQueryValue] as const
},
}
})
vi.mock('@/hooks/use-document-title', () => ({
default: vi.fn(),
}))
vi.mock('@/hooks/use-timestamp', () => ({
default: () => ({
formatTime: () => '2026-07-22 10:00',
}),
}))
vi.mock('@/next/link', () => ({
default: ({ children, href, ...props }: { children: ReactNode; href: string }) => (
<a href={href} {...props}>
{children}
</a>
),
}))
vi.mock('@/next/navigation', () => ({
useRouter: () => ({
push: mocks.push,
}),
}))
vi.mock('@/utils/download', () => ({
downloadBlob: mocks.downloadBlob,
}))
vi.mock('../client', () => ({
fetchSkillArchiveBlob: mocks.exportSkillArchiveBlob,
uploadSkillFile: vi.fn(),
}))
vi.mock('@/service/client', () => ({
consoleQuery: {
workspaces: {
current: {
skills: {
get: {
key: mocks.skillsKey,
infiniteOptions: mocks.skillsQueryOptions,
},
post: {
mutationOptions: () => ({ mutationFn: mocks.createSkillMutationFn }),
},
import: {
post: {
mutationOptions: () => ({ mutationFn: mocks.importSkillMutationFn }),
},
},
tags: {
get: {
key: mocks.tagsKey,
queryOptions: mocks.tagsQueryOptions,
},
},
bySkillId: {
delete: {
mutationOptions: () => ({ mutationFn: mocks.deleteSkillMutationFn }),
},
duplicate: {
post: {
mutationOptions: () => ({ mutationFn: mocks.duplicateSkillMutationFn }),
},
},
},
},
},
},
},
}))
function createSkill(overrides: Partial<SkillResponse> = {}): SkillResponse {
return {
id: 'skill-1',
name: 'refund-approval',
display_name: 'Refund approval',
icon: '💳',
description: 'Handle refund requests.',
tags: ['support'],
visibility: 'workspace',
latest_published_version_id: 'version-1',
reference_count: 2,
created_at: 1784631405,
updated_at: 1784638487,
...overrides,
}
}
function renderSkillsPage() {
const queryClient = new QueryClient({
defaultOptions: {
mutations: { retry: false },
queries: { retry: false },
},
})
return render(
<QueryClientProvider client={queryClient}>
<SkillsPage />
</QueryClientProvider>,
)
}
describe('SkillsPage', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.queryState.keyword = ''
mocks.queryState.tag = []
mocks.skills = [createSkill()]
mocks.skillPages = [mocks.skills]
mocks.tags = [
{ count: 2, tag: 'support' },
{ count: 1, tag: 'sales' },
]
mocks.skillsKey.mockImplementation((options) => ['skills', options])
mocks.tagsKey.mockImplementation((options) => ['skill-tags', options])
mocks.skillsQueryOptions.mockImplementation((options) => ({
queryKey: ['skills', options],
queryFn: async ({ pageParam }: { pageParam: unknown }) => {
const page = Number(pageParam)
return {
data: mocks.skillPages[page - 1] ?? [],
has_more: page < mocks.skillPages.length,
page,
total: mocks.skillPages.flat().length,
}
},
getNextPageParam: options.getNextPageParam,
initialPageParam: options.initialPageParam,
}))
mocks.tagsQueryOptions.mockImplementation((options) => ({
queryKey: ['skill-tags', options],
queryFn: async () => ({
data: mocks.tags,
}),
}))
mocks.createSkillMutationFn.mockResolvedValue(createSkill({ id: 'created-skill' }))
mocks.importSkillMutationFn.mockResolvedValue(createSkill({ id: 'imported-skill' }))
mocks.duplicateSkillMutationFn.mockResolvedValue(createSkill({ id: 'duplicated-skill' }))
mocks.exportSkillArchiveBlob.mockResolvedValue(new Blob(['skill archive']))
mocks.deleteSkillMutationFn.mockResolvedValue({
deleted: true,
id: 'skill-1',
})
})
it('renders skills with tags, reference count, and detail links', async () => {
renderSkillsPage()
const skillLink = await screen.findByRole('link', { name: /Refund approval/ })
expect(skillLink).toHaveAttribute('href', '/skills/skill-1')
expect(screen.getByText('refund-approval')).toBeInTheDocument()
expect(screen.getByText('Handle refund requests.')).toBeInTheDocument()
expect(screen.getByText('support')).toBeInTheDocument()
expect(
screen.getByText('agentV2.skillManagement.referenceCount:{"count":2}'),
).toBeInTheDocument()
})
it('passes keyword and selected tags to the list query', async () => {
const user = userEvent.setup()
renderSkillsPage()
await user.type(
await screen.findByRole('searchbox', {
name: 'agentV2.skillManagement.searchLabel',
}),
'refund',
)
await waitFor(() => {
const queryOptions = mocks.skillsQueryOptions.mock.lastCall?.[0]
expect(queryOptions?.input(1)).toEqual({
query: {
keyword: 'refund',
limit: 20,
page: 1,
},
})
})
await user.click(screen.getByRole('button', { name: 'agentV2.skillManagement.tags' }))
await waitFor(() => {
expect(screen.getAllByText('support').length).toBeGreaterThan(1)
})
await user.click(screen.getAllByText('support').at(-1)!)
await waitFor(() => {
const queryOptions = mocks.skillsQueryOptions.mock.lastCall?.[0]
expect(queryOptions?.input(1)).toEqual({
query: {
keyword: 'refund',
limit: 20,
page: 1,
tag: ['support'],
},
})
})
})
it('loads the next skill page when the list scrolls near the bottom', async () => {
const firstPageSkills = Array.from({ length: 20 }, (_, index) =>
createSkill({
id: `skill-${index + 1}`,
name: `skill-${index + 1}`,
display_name: `Skill ${index + 1}`,
}),
)
const nextPageSkill = createSkill({
id: 'skill-21',
name: 'skill-21',
display_name: 'Skill 21',
})
mocks.skills = firstPageSkills
mocks.skillPages = [firstPageSkills, [nextPageSkill]]
renderSkillsPage()
const skillList = await screen.findByRole('region', {
name: 'agentV2.skillManagement.listLabel',
})
await screen.findByRole('heading', { name: 'Skill 1' })
expect(within(skillList).getAllByRole('article')).toHaveLength(20)
const scrollViewport = skillList.parentElement?.parentElement
expect(scrollViewport).not.toBeNull()
Object.defineProperties(scrollViewport!, {
clientHeight: { configurable: true, value: 600 },
scrollHeight: { configurable: true, value: 1200 },
scrollTop: { configurable: true, value: 560 },
})
fireEvent.scroll(scrollViewport!)
expect(await screen.findByRole('heading', { name: 'Skill 21' })).toBeInTheDocument()
expect(within(skillList).getAllByRole('article')).toHaveLength(21)
expect(mocks.skillsQueryOptions.mock.lastCall?.[0].input(2)).toEqual({
query: {
limit: 20,
page: 2,
},
})
})
it('creates a placeholder skill and navigates to its detail page', async () => {
const user = userEvent.setup()
renderSkillsPage()
await user.click(await screen.findByRole('button', { name: 'agentV2.skillManagement.create' }))
await waitFor(() => {
expect(mocks.createSkillMutationFn).toHaveBeenCalledWith(
{
body: {},
},
expect.anything(),
)
})
expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.createSuccess')
expect(mocks.push).toHaveBeenCalledWith('/skills/created-skill')
})
it('imports a package file and navigates to the imported skill', async () => {
const user = userEvent.setup()
const { container } = renderSkillsPage()
const fileInput = container.querySelector<HTMLInputElement>('input[type="file"]')
expect(fileInput).not.toBeNull()
const file = new File(['skill'], 'refund.skill', { type: 'application/zip' })
await user.upload(fileInput!, file)
await waitFor(() => {
expect(mocks.importSkillMutationFn).toHaveBeenCalledWith(
{
body: {
file,
},
},
expect.anything(),
)
})
expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.importSuccess')
expect(mocks.push).toHaveBeenCalledWith('/skills/imported-skill')
})
it('duplicates a skill from the card action menu', async () => {
const user = userEvent.setup()
renderSkillsPage()
await user.click(
await screen.findByRole('button', {
name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}',
}),
)
await user.click(await screen.findByText('common.operation.duplicate'))
await waitFor(() => {
expect(mocks.duplicateSkillMutationFn).toHaveBeenCalledWith(
{
params: {
skill_id: 'skill-1',
},
},
expect.anything(),
)
})
expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.duplicateSuccess')
})
it('exports a published skill from the card action menu', async () => {
const user = userEvent.setup()
renderSkillsPage()
await user.click(
await screen.findByRole('button', {
name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}',
}),
)
await user.click(await screen.findByText('common.operation.export'))
await waitFor(() => {
expect(mocks.exportSkillArchiveBlob).toHaveBeenCalledWith('skill-1')
})
expect(mocks.downloadBlob).toHaveBeenCalledWith({
data: expect.any(Blob),
fileName: 'refund-approval.zip',
})
})
it('does not show export for an unpublished skill', async () => {
const user = userEvent.setup()
mocks.skills = [createSkill({ latest_published_version_id: null })]
renderSkillsPage()
await user.click(
await screen.findByRole('button', {
name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}',
}),
)
expect(screen.queryByText('common.operation.export')).not.toBeInTheDocument()
})
it('confirms deletion with the skill name and refreshes list data', async () => {
const user = userEvent.setup()
renderSkillsPage()
await user.click(
await screen.findByRole('button', {
name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}',
}),
)
await user.click(await screen.findByText('common.operation.delete'))
const dialog = await screen.findByRole('alertdialog')
expect(
within(dialog).getByText(
'agentV2.skillManagement.deleteDialog.referencedDescription:{"count":2}',
),
).toBeInTheDocument()
await user.click(within(dialog).getByRole('button', { name: 'common.operation.delete' }))
await waitFor(() => {
expect(mocks.deleteSkillMutationFn).toHaveBeenCalledWith(
{
body: {
confirmation_name: 'refund-approval',
},
params: {
skill_id: 'skill-1',
},
},
expect.anything(),
)
})
expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.deleteSuccess')
})
it('shows the empty-search state without create or import actions', async () => {
mocks.queryState.keyword = 'missing'
mocks.skills = []
mocks.skillPages = [[]]
renderSkillsPage()
expect(await screen.findByText('agentV2.skillManagement.emptySearch')).toBeInTheDocument()
expect(
screen.queryByText('agentV2.skillManagement.emptyAction.createTitle'),
).not.toBeInTheDocument()
expect(
screen.queryByText('agentV2.skillManagement.emptyAction.importTitle'),
).not.toBeInTheDocument()
})
})
+195
View File
@@ -0,0 +1,195 @@
import type {
SkillAssistAttachmentPayload,
SkillFileUploadResponse,
} from '@dify/contracts/api/console/workspaces/types.gen'
import type {
DefaultModel,
FormValue,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
// oxlint-disable-next-line no-restricted-imports
import type { IOnCompleted, IOnData, IOnError } from '@/service/base'
// oxlint-disable-next-line no-restricted-imports
import { get, post, ssePost, upload } from '@/service/base'
function parseSkillUploadErrorMessage(message: string) {
const trimmedMessage = message.trim()
if (!trimmedMessage.startsWith('{')) return trimmedMessage
try {
const parsed: unknown = JSON.parse(trimmedMessage)
if (parsed && typeof parsed === 'object') {
const parsedMessage = (parsed as Record<string, unknown>).message
if (typeof parsedMessage === 'string' && parsedMessage.trim()) return parsedMessage.trim()
}
} catch {
return trimmedMessage
}
return trimmedMessage
}
function readSkillUploadErrorMessage(
error: unknown,
visited = new Set<unknown>(),
): string | undefined {
if (!error || visited.has(error)) return undefined
if (typeof error === 'string') return parseSkillUploadErrorMessage(error)
if (typeof error !== 'object') return undefined
visited.add(error)
const record = error as Record<string, unknown>
for (const key of ['data', 'body', 'error', 'cause', 'response']) {
const nestedMessage = readSkillUploadErrorMessage(record[key], visited)
if (nestedMessage) return nestedMessage
}
const message = record.message
if (typeof message === 'string' && message.trim()) return parseSkillUploadErrorMessage(message)
return undefined
}
async function getSkillUploadResponseErrorMessage(response: Response) {
try {
const data: unknown = await response.clone().json()
return readSkillUploadErrorMessage(data)
} catch {
try {
const text = await response.clone().text()
if (text.trim()) return parseSkillUploadErrorMessage(text)
} catch {}
}
}
export async function uploadSkillFile(
file: File,
options?: {
onProgress?: (progress: number) => void
},
) {
const body = new FormData()
body.append('file', file)
try {
if (options?.onProgress) {
const onProgress = (event: ProgressEvent) => {
if (!event.lengthComputable) return
options.onProgress?.(Math.floor((event.loaded / event.total) * 100))
}
const response = await upload(
{
xhr: new XMLHttpRequest(),
data: body,
onprogress: onProgress,
},
false,
'/workspaces/current/skills/files/upload',
)
return response as SkillFileUploadResponse
}
return await post<SkillFileUploadResponse>(
'/workspaces/current/skills/files/upload',
{ body },
{
bodyStringify: false,
deleteContentType: true,
silent: true,
},
)
} catch (error) {
const message =
error instanceof Response
? await getSkillUploadResponseErrorMessage(error)
: readSkillUploadErrorMessage(error)
if (message) {
const normalizedError = new Error(message)
normalizedError.cause = error
throw normalizedError
}
throw error
}
}
export async function fetchSkillFileBlob({
download = false,
path,
skillId,
versionId,
}: {
download?: boolean
path: string
skillId: string
versionId: string | null
}) {
const params = new URLSearchParams({ path })
if (versionId) params.set('version_id', versionId)
if (download) params.set('download', '1')
const response = await get<Response>(
`/workspaces/current/skills/${encodeURIComponent(skillId)}/files/content?${params.toString()}`,
{},
{ needAllResponseContent: true },
)
return response.blob()
}
export async function fetchSkillArchiveBlob(skillId: string) {
const response = await get<Response>(
`/workspaces/current/skills/${encodeURIComponent(skillId)}/export`,
{},
{ needAllResponseContent: true },
)
return response.blob()
}
export function sendSkillAssistMessage({
attachments,
getAbortController,
message,
model,
onCompleted,
onData,
onError,
onUnhandledEvent,
skillId,
targetPath,
}: {
attachments?: SkillAssistAttachmentPayload[]
getAbortController?: (abortController: AbortController) => void
message: string
model?: DefaultModel & {
model_settings?: FormValue
}
onCompleted?: IOnCompleted
onData?: IOnData
onError?: IOnError
onUnhandledEvent?: (event: Record<string, unknown>) => void
skillId: string
targetPath?: string
}) {
return ssePost(
`/workspaces/current/skills/${encodeURIComponent(skillId)}/assist/messages`,
{
body: {
attachments,
message,
model,
target_path: targetPath,
},
},
{
getAbortController,
onCompleted,
onData,
onError,
onUnhandledEvent,
},
)
}
File diff suppressed because it is too large Load Diff
+759
View File
@@ -0,0 +1,759 @@
'use client'
import type { SkillResponse } from '@dify/contracts/api/console/workspaces/types.gen'
import type { UIEvent } from 'react'
import {
AlertDialog,
AlertDialogActions,
AlertDialogCancelButton,
AlertDialogConfirmButton,
AlertDialogContent,
AlertDialogDescription,
AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import {
ScrollAreaContent,
ScrollAreaRoot,
ScrollAreaScrollbar,
ScrollAreaThumb,
ScrollAreaViewport,
} from '@langgenius/dify-ui/scroll-area'
import { toast } from '@langgenius/dify-ui/toast'
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useDebounce } from 'ahooks'
import { useQueryState } from 'nuqs'
import { useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SearchInput } from '@/app/components/base/search-input'
import { SkeletonRectangle } from '@/app/components/base/skeleton'
import useDocumentTitle from '@/hooks/use-document-title'
import useTimestamp from '@/hooks/use-timestamp'
import Link from '@/next/link'
import { useRouter } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import { downloadBlob } from '@/utils/download'
import { fetchSkillArchiveBlob } from './client'
import { skillKeywordQueryParser, skillQueryParamNames, skillTagQueryParser } from './query-params'
const placeholderCardIds = Array.from(
{ length: 16 },
(_, index) => `skill-placeholder-card-${index}`,
)
const skeletonRows = ['primary', 'secondary', 'tertiary'] as const
const SKILLS_PAGE_SIZE = 20
function skillsListQueryKey() {
return consoleQuery.workspaces.current.skills.get.key({ type: 'query' })
}
function SkillIcon({ icon }: { icon?: string }) {
return (
<div className="flex size-10 shrink-0 items-center justify-center rounded-[10px] border-[0.5px] border-divider-regular bg-background-default-dodge">
{icon ? (
<span className="system-lg-medium text-text-secondary">{icon}</span>
) : (
<span aria-hidden className="i-ri-box-3-line size-5 text-text-tertiary" />
)}
</div>
)
}
function SkillTagBadge({ tag }: { tag: string }) {
return (
<span className="flex min-w-4 shrink-0 items-center justify-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1.5 py-0.5 system-2xs-medium-uppercase text-text-tertiary">
<span className="max-w-28 truncate">{tag}</span>
</span>
)
}
function SkillCardSkeleton() {
return (
<>
{skeletonRows.map((row) => (
<div
key={row}
className="relative h-42 rounded-xl border-[0.5px] border-components-card-border bg-components-card-bg shadow-xs shadow-shadow-shadow-3"
>
<div className="flex items-center gap-3 px-4 pt-4 pb-2">
<SkeletonRectangle className="my-0 size-10 shrink-0 rounded-[10px] opacity-20" />
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
<SkeletonRectangle className="my-0 h-3 w-36 max-w-full rounded-md opacity-20" />
<SkeletonRectangle className="my-0 h-2 w-24 max-w-full rounded-md opacity-12" />
</div>
</div>
<div className="px-4 py-1">
<SkeletonRectangle className="my-0 h-2 w-full rounded-md opacity-12" />
<SkeletonRectangle className="my-0 mt-2 h-2 w-3/4 rounded-md opacity-10" />
</div>
<div className="flex gap-1 px-4 pt-2">
<SkeletonRectangle className="my-0 h-5 w-14 rounded-md opacity-12" />
<SkeletonRectangle className="my-0 h-5 w-20 rounded-md opacity-10" />
</div>
</div>
))}
</>
)
}
function SkillPlaceholderState({
creating,
importing,
isEmptySearch,
onCreate,
onImport,
title,
}: {
creating?: boolean
importing?: boolean
isEmptySearch?: boolean
onCreate?: () => void
onImport?: () => void
title: string
}) {
const { t } = useTranslation('agentV2')
return (
<section
aria-labelledby="skill-placeholder-title"
className="relative col-span-full min-h-[calc(100vh-142px)] overflow-hidden"
>
<div className="pointer-events-none absolute inset-0 grid grid-cols-[repeat(auto-fill,minmax(296px,1fr))] grid-rows-4 gap-3">
{placeholderCardIds.map((id) => (
<div key={id} className="rounded-xl bg-background-default-lighter opacity-75" />
))}
</div>
<div className="pointer-events-none absolute inset-0 bg-linear-to-b from-background-body/0 to-background-body" />
<div className="absolute inset-0 flex items-center justify-center overflow-hidden p-2">
<div className="flex w-[420px] max-w-full flex-col items-center gap-3">
<div className="flex size-14 items-center justify-center rounded-[10px]">
<div className="flex size-full min-w-px items-center justify-center overflow-hidden rounded-xl border border-dashed border-divider-regular bg-components-card-bg p-1 backdrop-blur-md">
<span aria-hidden className="i-ri-box-3-line size-6 text-text-tertiary" />
</div>
</div>
<h2
id="skill-placeholder-title"
className="system-sm-regular whitespace-nowrap text-text-tertiary"
>
{title}
</h2>
{!isEmptySearch && (
<div className="mt-2 flex w-full flex-col gap-2">
<button
type="button"
disabled={creating || importing}
className="flex h-11 w-full cursor-pointer items-center gap-3 rounded-xl bg-components-card-bg px-4 text-left shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:opacity-50"
onClick={onCreate}
>
<span
aria-hidden
className={cn(
'size-4 shrink-0 text-text-tertiary',
creating ? 'i-ri-loader-4-line animate-spin' : 'i-ri-sparkling-2-line',
)}
/>
<span className="min-w-0 flex-1">
<span className="block truncate system-sm-medium text-text-secondary">
{t(($) => $['skillManagement.emptyAction.createTitle'])}
</span>
<span className="block truncate system-xs-regular text-text-tertiary">
{t(($) => $['skillManagement.emptyAction.createDescription'])}
</span>
</span>
</button>
<button
type="button"
disabled={creating || importing}
className="flex h-11 w-full cursor-pointer items-center gap-3 rounded-xl bg-components-card-bg px-4 text-left shadow-xs outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:opacity-50"
onClick={onImport}
>
<span
aria-hidden
className={cn(
'size-4 shrink-0 text-text-tertiary',
importing ? 'i-ri-loader-4-line animate-spin' : 'i-ri-upload-line',
)}
/>
<span className="min-w-0 flex-1">
<span className="block truncate system-sm-medium text-text-secondary">
{t(($) => $['skillManagement.emptyAction.importTitle'])}
</span>
<span className="block truncate system-xs-regular text-text-tertiary">
{t(($) => $['skillManagement.emptyAction.importDescription'])}
</span>
</span>
</button>
</div>
)}
</div>
</div>
</section>
)
}
function DeleteSkillDialog({
open,
skill,
onOpenChange,
}: {
open: boolean
skill: SkillResponse
onOpenChange: (open: boolean) => void
}) {
const { t } = useTranslation('agentV2')
const { t: tCommon } = useTranslation('common')
const queryClient = useQueryClient()
const deleteMutation = useMutation(
consoleQuery.workspaces.current.skills.bySkillId.delete.mutationOptions(),
)
const referenceCount = skill.reference_count ?? 0
const description =
referenceCount > 0
? t(($) => $['skillManagement.deleteDialog.referencedDescription'], {
count: referenceCount,
})
: t(($) => $['skillManagement.deleteDialog.description'])
const handleDelete = () => {
if (deleteMutation.isPending) return
deleteMutation.mutate(
{
params: {
skill_id: skill.id,
},
body: {
confirmation_name: skill.name,
},
},
{
onSuccess: () => {
toast.success(t(($) => $['skillManagement.deleteSuccess']))
void queryClient.invalidateQueries({ queryKey: skillsListQueryKey() })
void queryClient.invalidateQueries({
queryKey: consoleQuery.workspaces.current.skills.tags.get.key({ type: 'query' }),
})
onOpenChange(false)
},
onError: () => {
toast.error(t(($) => $['skillManagement.deleteFailed']))
},
},
)
}
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent className="p-6">
<AlertDialogTitle className="truncate title-2xl-semi-bold text-text-primary">
{t(($) => $['skillManagement.deleteDialog.title'], { name: skill.display_name })}
</AlertDialogTitle>
<AlertDialogDescription className="mt-2 system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
{description}
</AlertDialogDescription>
<AlertDialogActions className="p-0 pt-6">
<AlertDialogCancelButton disabled={deleteMutation.isPending}>
{tCommon(($) => $['operation.cancel'])}
</AlertDialogCancelButton>
<AlertDialogConfirmButton
tone="destructive"
loading={deleteMutation.isPending}
onClick={handleDelete}
>
{tCommon(($) => $['operation.delete'])}
</AlertDialogConfirmButton>
</AlertDialogActions>
</AlertDialogContent>
</AlertDialog>
)
}
function SkillCard({ skill }: { skill: SkillResponse }) {
const { t } = useTranslation('agentV2')
const { t: tCommon } = useTranslation('common')
const { formatTime } = useTimestamp()
const queryClient = useQueryClient()
const [isDeleteOpen, setIsDeleteOpen] = useState(false)
const duplicateMutation = useMutation(
consoleQuery.workspaces.current.skills.bySkillId.duplicate.post.mutationOptions(),
)
const exportMutation = useMutation({
mutationFn: () => fetchSkillArchiveBlob(skill.id),
onSuccess: (blob) => {
downloadBlob({ data: blob, fileName: `${skill.name}.zip` })
},
onError: () => {
toast.error(tCommon(($) => $['operation.downloadFailed']))
},
})
const tags = skill.tags ?? []
const isDraft = !skill.latest_published_version_id
const updatedAt = formatTime(
skill.updated_at,
t(($) => $['skillManagement.dateTimeFormat']),
)
const handleDuplicate = () => {
if (duplicateMutation.isPending) return
duplicateMutation.mutate(
{
params: {
skill_id: skill.id,
},
},
{
onSuccess: () => {
toast.success(t(($) => $['skillManagement.duplicateSuccess']))
void queryClient.invalidateQueries({ queryKey: skillsListQueryKey() })
void queryClient.invalidateQueries({
queryKey: consoleQuery.workspaces.current.skills.tags.get.key({ type: 'query' }),
})
},
onError: () => {
toast.error(t(($) => $['skillManagement.duplicateFailed']))
},
},
)
}
const handleExport = () => {
if (exportMutation.isPending) return
exportMutation.mutate()
}
return (
<article className="group relative col-span-1 h-42 min-w-0 overflow-hidden rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-xs shadow-shadow-shadow-3 transition-shadow duration-200 ease-in-out hover:shadow-lg">
<div className="flex h-full min-w-0 flex-col">
<Link
href={`/skills/${skill.id}`}
className="block min-w-0 shrink-0 cursor-pointer outline-hidden"
>
<div className="flex items-center gap-3 px-4 pt-4 pb-2">
<SkillIcon icon={skill.icon} />
<div className="flex min-w-0 flex-1 flex-col gap-0.5 py-px">
<h2 className="truncate system-md-semibold text-text-secondary">
{skill.display_name}
</h2>
<p className="truncate system-xs-regular text-text-tertiary">{skill.name}</p>
</div>
</div>
<div className="px-4 py-1 system-xs-regular text-text-tertiary">
<div className="line-clamp-2 min-h-8">{skill.description}</div>
</div>
</Link>
<div className="relative h-6 shrink-0 px-3">
{tags.length > 0 && (
<div className="flex min-w-0 gap-1 overflow-hidden p-1">
{tags.slice(0, 4).map((tag) => (
<SkillTagBadge key={tag} tag={tag} />
))}
</div>
)}
<div className="pointer-events-none absolute top-0 right-0 bottom-0 w-14 bg-linear-to-r from-components-card-bg-transparent to-components-card-bg" />
</div>
<div className="flex min-w-0 shrink-0 items-center px-4 pt-2 pb-3 system-xs-regular text-text-tertiary">
<div className="flex min-w-0 flex-1 items-center gap-1">
<span className="shrink-0">
{t(($) => $['skillManagement.referenceCount'], {
count: skill.reference_count ?? 0,
})}
</span>
<span aria-hidden className="shrink-0 text-text-quaternary">
·
</span>
<span className="min-w-0 truncate">
{isDraft
? t(($) => $['skillManagement.editedAt'], { time: updatedAt })
: t(($) => $['skillManagement.publishedAt'], { time: updatedAt })}
</span>
</div>
</div>
</div>
{isDraft && (
<div className="absolute top-[-0.5px] right-0 flex h-5 items-start overflow-hidden">
<div className="h-5 w-3 bg-background-section-burn [clip-path:polygon(0_0,100%_0,100%_100%)]" />
<div className="flex h-5 items-center bg-background-section-burn pr-2 pl-0.5 system-2xs-medium-uppercase text-text-tertiary">
{t(($) => $['skillManagement.draft'])}
</div>
</div>
)}
<div
className={cn(
'pointer-events-none absolute right-2 z-20 flex items-center overflow-hidden rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 opacity-0 shadow-lg backdrop-blur-xs transition-opacity group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 has-data-popup-open:pointer-events-auto has-data-popup-open:opacity-100',
isDraft ? 'top-7' : 'top-2',
)}
>
<DropdownMenu modal={false}>
<DropdownMenuTrigger
aria-label={t(($) => $['skillManagement.moreActions'], { name: skill.display_name })}
className="flex size-8 cursor-pointer items-center justify-center rounded-lg p-1.5 hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:bg-state-base-hover"
onClick={(event) => event.stopPropagation()}
>
<span className="sr-only">
{t(($) => $['skillManagement.moreActions'], { name: skill.display_name })}
</span>
<span aria-hidden className="i-ri-more-fill size-4.5 text-text-tertiary" />
</DropdownMenuTrigger>
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-40">
<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>
{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>
)}
<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>
<DeleteSkillDialog skill={skill} open={isDeleteOpen} onOpenChange={setIsDeleteOpen} />
</article>
)
}
function SkillTagFilter({ tags }: { tags: string[] }) {
const { t } = useTranslation('agentV2')
const [selectedTags, setSelectedTags] = useQueryState(
skillQueryParamNames.tag,
skillTagQueryParser,
)
const selectedTagSet = new Set(selectedTags)
const toggleTag = (tag: string) => {
const nextTags = selectedTagSet.has(tag)
? selectedTags.filter((item) => item !== tag)
: [...selectedTags, tag]
void setSelectedTags(nextTags)
}
return (
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
'flex h-8 shrink-0 cursor-pointer items-center gap-1 rounded-lg bg-components-input-bg-normal px-2 py-1 system-sm-regular text-text-tertiary hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden',
selectedTags.length > 0 && 'text-text-secondary',
)}
>
<span>{t(($) => $['skillManagement.tags'])}</span>
{selectedTags.length > 0 && (
<span className="flex min-w-4 shrink-0 items-center justify-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 py-0.5 system-2xs-medium-uppercase text-text-tertiary tabular-nums">
{selectedTags.length}
</span>
)}
<span aria-hidden className="i-ri-arrow-down-s-line size-4 text-text-tertiary" />
</DropdownMenuTrigger>
<DropdownMenuContent placement="bottom-start" sideOffset={4} popupClassName="w-52">
{tags.length === 0 ? (
<DropdownMenuItem disabled>{t(($) => $['skillManagement.noTags'])}</DropdownMenuItem>
) : (
tags.map((tag) => (
<DropdownMenuItem key={tag} className="gap-2" onClick={() => toggleTag(tag)}>
<span
aria-hidden
className={cn(
'i-ri-check-line size-4 shrink-0',
selectedTagSet.has(tag) ? 'text-text-accent' : 'text-transparent',
)}
/>
<span className="min-w-0 flex-1 truncate">{tag}</span>
</DropdownMenuItem>
))
)}
{selectedTags.length > 0 && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2" onClick={() => setSelectedTags([])}>
<span aria-hidden className="i-ri-close-line size-4 shrink-0 text-text-tertiary" />
<span>{t(($) => $['skillManagement.clearTags'])}</span>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
)
}
function SkillsToolbar({
creating,
importing,
onCreate,
onImport,
tags,
}: {
creating: boolean
importing: boolean
onCreate: () => void
onImport: () => void
tags: string[]
}) {
const { t } = useTranslation('agentV2')
const [keyword, setKeyword] = useQueryState(skillQueryParamNames.keyword, skillKeywordQueryParser)
const isMutating = creating || importing
return (
<div className="flex min-w-0 items-center gap-2">
<SkillTagFilter tags={tags} />
<SearchInput
aria-label={t(($) => $['skillManagement.searchLabel'])}
className="h-8 w-50 min-w-0 shrink"
placeholder={t(($) => $['skillManagement.searchPlaceholder'])}
value={keyword}
onValueChange={(value) => {
void setKeyword(value)
}}
/>
<div className="ml-auto flex shrink-0 items-center gap-2">
<Button
className="h-8 gap-1 px-3"
disabled={isMutating}
loading={importing}
onClick={onImport}
>
<span aria-hidden className="i-ri-upload-line size-4" />
<span className="px-0.5 system-sm-medium">{t(($) => $['skillManagement.import'])}</span>
</Button>
<Button
variant="primary"
className="h-8 gap-0.5 px-3"
disabled={isMutating}
loading={creating}
onClick={onCreate}
>
<span aria-hidden className="i-ri-add-line size-4" />
<span className="px-0.5 system-sm-medium">{t(($) => $['skillManagement.create'])}</span>
</Button>
</div>
</div>
)
}
function SkillGrid({
creating,
importing,
isEmptySearch,
isError,
isFetching,
isFetchingNextPage,
isPending,
onCreate,
onImport,
skills,
}: {
creating: boolean
importing: boolean
isEmptySearch: boolean
isError: boolean
isFetching: boolean
isFetchingNextPage: boolean
isPending: boolean
onCreate: () => void
onImport: () => void
skills: SkillResponse[]
}) {
const { t } = useTranslation('agentV2')
return (
<section
aria-label={t(($) => $['skillManagement.listLabel'])}
className="grid grid-cols-[repeat(auto-fill,minmax(296px,1fr))] gap-2.5"
aria-busy={isFetching || undefined}
>
{isPending && <SkillCardSkeleton />}
{!isPending && isError && (
<SkillPlaceholderState title={t(($) => $['skillManagement.loadingError'])} />
)}
{!isPending && !isError && skills.length === 0 && (
<SkillPlaceholderState
creating={creating}
importing={importing}
isEmptySearch={isEmptySearch}
onCreate={onCreate}
onImport={onImport}
title={
isEmptySearch
? t(($) => $['skillManagement.emptySearch'])
: t(($) => $['skillManagement.empty'])
}
/>
)}
{!isPending && !isError && skills.map((skill) => <SkillCard key={skill.id} skill={skill} />)}
{!isPending && !isError && isFetchingNextPage && <SkillCardSkeleton />}
</section>
)
}
export default function SkillsPage() {
const { t } = useTranslation('agentV2')
const router = useRouter()
const queryClient = useQueryClient()
const importInputRef = useRef<HTMLInputElement>(null)
const [keyword] = useQueryState(skillQueryParamNames.keyword, skillKeywordQueryParser)
const [selectedTags] = useQueryState(skillQueryParamNames.tag, skillTagQueryParser)
const debouncedKeyword = useDebounce(keyword.trim(), { wait: 300 })
const createMutation = useMutation(consoleQuery.workspaces.current.skills.post.mutationOptions())
const importMutation = useMutation(
consoleQuery.workspaces.current.skills.import.post.mutationOptions(),
)
const skillsQuery = useInfiniteQuery({
...consoleQuery.workspaces.current.skills.get.infiniteOptions({
input: (pageParam) => ({
query: {
limit: SKILLS_PAGE_SIZE,
page: Number(pageParam),
...(debouncedKeyword ? { keyword: debouncedKeyword } : {}),
...(selectedTags.length > 0 ? { tag: selectedTags } : {}),
},
}),
getNextPageParam: (lastPage) => (lastPage.has_more ? (lastPage.page ?? 1) + 1 : undefined),
initialPageParam: 1,
}),
})
const tagsQuery = useQuery(consoleQuery.workspaces.current.skills.tags.get.queryOptions())
const skills = skillsQuery.data?.pages.flatMap((page) => page.data ?? []) ?? []
const tags = (tagsQuery.data?.data ?? []).map((tag) => tag.tag)
useDocumentTitle(t(($) => $['skillManagement.title']))
const invalidateSkills = () => {
void queryClient.invalidateQueries({ queryKey: skillsListQueryKey() })
void queryClient.invalidateQueries({
queryKey: consoleQuery.workspaces.current.skills.tags.get.key({ type: 'query' }),
})
}
const handleCreate = () => {
if (createMutation.isPending) return
createMutation.mutate(
{
body: {},
},
{
onSuccess: (skill) => {
toast.success(t(($) => $['skillManagement.createSuccess']))
invalidateSkills()
router.push(`/skills/${skill.id}`)
},
onError: () => {
toast.error(t(($) => $['skillManagement.createFailed']))
},
},
)
}
const handleFileChange = (file: File | undefined) => {
if (!file || importMutation.isPending) return
importMutation.mutate(
{
body: {
file,
},
},
{
onSuccess: (skill) => {
toast.success(t(($) => $['skillManagement.importSuccess']))
invalidateSkills()
router.push(`/skills/${skill.id}`)
},
onError: () => {
toast.error(t(($) => $['skillManagement.importFailed']))
},
onSettled: () => {
if (importInputRef.current) importInputRef.current.value = ''
},
},
)
}
const handleListScroll = (event: UIEvent<HTMLDivElement>) => {
const target = event.currentTarget
const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight
if (scrollBottom < 80 && skillsQuery.hasNextPage && !skillsQuery.isFetchingNextPage)
void skillsQuery.fetchNextPage()
}
return (
<div className="flex h-0 min-w-0 grow flex-col overflow-hidden bg-background-body">
<div className="shrink-0 bg-background-body px-8 pt-4 pb-2">
<div className="flex h-6 min-w-0 items-center justify-between gap-4">
<h1 className="min-w-0 flex-1 truncate text-[18px]/[21.6px] font-semibold text-text-primary">
{t(($) => $['skillManagement.title'])}
</h1>
</div>
<div className="mt-3.5">
<input
ref={importInputRef}
type="file"
accept=".zip,.skill,application/zip"
className="hidden"
onChange={(event) => handleFileChange(event.currentTarget.files?.[0])}
/>
<SkillsToolbar
creating={createMutation.isPending}
importing={importMutation.isPending}
onCreate={handleCreate}
onImport={() => importInputRef.current?.click()}
tags={tags}
/>
</div>
</div>
<div className="min-h-0 flex-1">
<ScrollAreaRoot className="relative h-full min-h-0 min-w-0 overflow-hidden">
<ScrollAreaViewport
tabIndex={-1}
className="overscroll-contain"
onScroll={handleListScroll}
>
<ScrollAreaContent className="min-h-full px-8 pt-2 pb-8">
<SkillGrid
creating={createMutation.isPending}
importing={importMutation.isPending}
skills={skills}
isEmptySearch={!!debouncedKeyword || selectedTags.length > 0}
isError={skillsQuery.isError}
isFetching={skillsQuery.isFetching}
isFetchingNextPage={skillsQuery.isFetchingNextPage}
isPending={skillsQuery.isPending}
onCreate={handleCreate}
onImport={() => importInputRef.current?.click()}
/>
</ScrollAreaContent>
</ScrollAreaViewport>
<ScrollAreaScrollbar>
<ScrollAreaThumb />
</ScrollAreaScrollbar>
</ScrollAreaRoot>
</div>
</div>
)
}
+12
View File
@@ -0,0 +1,12 @@
import { debounce, parseAsArrayOf, parseAsString } from 'nuqs'
export const skillQueryParamNames = {
keyword: 'keyword',
tag: 'tag',
} as const
export const skillKeywordQueryParser = parseAsString.withDefault('').withOptions({
limitUrlUpdates: debounce(300),
})
export const skillTagQueryParser = parseAsArrayOf(parseAsString, ';').withDefault([])
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "يشغل Preview الوكيل المكتمل كما سيراه المستخدمون، مع ردود واضحة وميزات الدردشة.",
"agentDetail.configure.rightPanel.previewTipTitle": "عاين وكيلك",
"agentDetail.configure.skills.add": "إضافة مهارة",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "محتوى تفاصيل المهارة",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ملفات",
"agentDetail.configure.skills.detail.files": "الملفات",
"agentDetail.configure.skills.empty.description": "تمنح المهارات الوكيل خبرة قابلة لإعادة الاستخدام يمكنه استدعاؤها أثناء العمل",
"agentDetail.configure.skills.empty.title": "لا توجد مهارات بعد",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "مهارة",
"agentDetail.configure.skills.label": "المهارات",
"agentDetail.configure.skills.missing": "المهارة غير موجودة",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "إزالة {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "اجمع التعليمات والملفات والبرامج النصية لمهمة متكررة في Skill. أشر إليها باستخدام / في Prompt. <docLink>معرفة المزيد</docLink>\n\nفي وضع Build، يمكن للوكيل إعدادها لك.",
"agentDetail.configure.skills.tip": "اجمع التعليمات والملفات والبرامج النصية لمهمة متكررة في Skill. أشر إليها باستخدام / في Prompt. معرفة المزيد\n\nفي وضع Build، يمكن للوكيل إعدادها لك.",
"agentDetail.configure.skills.toggle": "تبديل المهارات",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "خيارات الفرز",
"roster.sort.recentlyCreated": "الأحدث إنشاءً",
"roster.updateSuccess": "تم تحديث الوكيل.",
"roster.usageStatus.draft": "مسودة"
"roster.usageStatus.draft": "مسودة",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "طي الشريط الجانبي",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "توسيع الشريط الجانبي",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "لا توجد ملفات مطابقة.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "البحث عن الملفات",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -201,6 +201,7 @@
"mainNav.home": "الرئيسية",
"mainNav.integrations": "التكاملات",
"mainNav.marketplace": "سوق الإضافات",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "لم يتم العثور على تطبيقات ويب",
"mainNav.webApps.openApp": "فتح تطبيق الويب {{name}}",
"mainNav.webApps.searchPlaceholder": "البحث في تطبيقات الويب",
@@ -462,6 +463,7 @@
"operation.downloading": "جارٍ التنزيل...",
"operation.duplicate": "تكرار",
"operation.edit": "تعديل",
"operation.export": "تصدير",
"operation.exporting": "جارٍ التصدير",
"operation.fill": "ملء تلقائي",
"operation.format": "تنسيق",
@@ -503,6 +505,7 @@
"operation.sure": "أنا متأكد",
"operation.toggleFullscreen": "تبديل ملء الشاشة",
"operation.toggleMute": "تبديل كتم الصوت",
"operation.upload": "Upload",
"operation.view": "عرض",
"operation.viewDetails": "عرض التفاصيل",
"operation.viewMore": "عرض المزيد",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview führt den fertigen Agenten so aus, wie deine Benutzer ihn sehen, mit klaren Antworten und Chat-Funktionen.",
"agentDetail.configure.rightPanel.previewTipTitle": "Agenten vorschauen",
"agentDetail.configure.skills.add": "Skill hinzufügen",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Skill-Detailinhalt",
"agentDetail.configure.skills.detail.fileCount": "{{count}} DATEIEN",
"agentDetail.configure.skills.detail.files": "Dateien",
"agentDetail.configure.skills.empty.description": "Skills geben dem Agenten wiederverwendbare Fachkenntnisse, die er bei der Arbeit nutzen kann",
"agentDetail.configure.skills.empty.title": "Noch keine Skills",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Skill",
"agentDetail.configure.skills.label": "Skills",
"agentDetail.configure.skills.missing": "Skill nicht gefunden",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "{{name}} entfernen",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Bündeln Sie Anweisungen, Dateien und Skripte für eine wiederkehrende Aufgabe in einer Skill. Verweisen Sie im Prompt mit / darauf. <docLink>Mehr erfahren</docLink>\n\nIm Build-Modus kann der Agent diese Einrichtung für Sie übernehmen.",
"agentDetail.configure.skills.tip": "Bündeln Sie Anweisungen, Dateien und Skripte für eine wiederkehrende Aufgabe in einer Skill. Verweisen Sie im Prompt mit / darauf. Mehr erfahren\n\nIm Build-Modus kann der Agent diese Einrichtung für Sie übernehmen.",
"agentDetail.configure.skills.toggle": "Skills umschalten",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Sortieroptionen",
"roster.sort.recentlyCreated": "Zuletzt erstellt",
"roster.updateSuccess": "Agent aktualisiert.",
"roster.usageStatus.draft": "Entwurf"
"roster.usageStatus.draft": "Entwurf",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Seitenleiste einklappen",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Seitenleiste ausklappen",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Keine passenden Dateien.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Dateien suchen",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Startseite",
"mainNav.integrations": "Integrationen",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Keine Web-Apps gefunden",
"mainNav.webApps.openApp": "Web-App {{name}} öffnen",
"mainNav.webApps.searchPlaceholder": "Web-Apps suchen",
@@ -463,6 +464,7 @@
"operation.downloading": "Wird heruntergeladen...",
"operation.duplicate": "Duplikat",
"operation.edit": "Bearbeiten",
"operation.export": "Exportieren",
"operation.exporting": "Exportiere",
"operation.fill": "Automatisch ausfüllen",
"operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "Ich bin sicher",
"operation.toggleFullscreen": "Vollbild umschalten",
"operation.toggleMute": "Stummschaltung umschalten",
"operation.upload": "Upload",
"operation.view": "Ansehen",
"operation.viewDetails": "Details anzeigen",
"operation.viewMore": "MEHR SEHEN",
+196 -1
View File
@@ -203,15 +203,24 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview runs the finished agent the way your users will see it, with clean replies and chat features.",
"agentDetail.configure.rightPanel.previewTipTitle": "Preview your agent",
"agentDetail.configure.skills.add": "Add skill",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.description": "A .zip containing SKILL.md. It's embedded in this app and won't update with the library.",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.description": "Reuses a shared skill and follows its published updates.",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Skill detail content",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILES",
"agentDetail.configure.skills.detail.files": "Files",
"agentDetail.configure.skills.empty.description": "Skills give the agent reusable expertise it can call while working",
"agentDetail.configure.skills.empty.title": "No skills yet",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Skill",
"agentDetail.configure.skills.label": "Skills",
"agentDetail.configure.skills.missing": "Skill not found",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Remove {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Package the instructions, files, and scripts for a recurring task into a skill. Reference with / in the Prompt. <docLink>Learn more</docLink>\n\nIn Build mode, the agent can set these up for you.",
"agentDetail.configure.skills.tip": "Package the instructions, files, and scripts for a recurring task into a skill. Reference with / in the Prompt. Learn more\n\nIn Build mode, the agent can set these up for you.",
"agentDetail.configure.skills.toggle": "Toggle skills",
@@ -226,6 +235,15 @@
"agentDetail.configure.skills.upload.title": "Upload skill",
"agentDetail.configure.skills.upload.warning.files": "If you only need to use Markdown files, upload them to Files and reference them in your prompt.",
"agentDetail.configure.skills.upload.warning.specification": "Uploaded skills must follow the <specificationLink>Agent Skills specification</specificationLink>.",
"agentDetail.configure.skills.workspaceItemType": "Workspace",
"agentDetail.configure.skills.workspaceSelector.addSuccess": "Workspace skill added.",
"agentDetail.configure.skills.workspaceSelector.added": "Added",
"agentDetail.configure.skills.workspaceSelector.draft": "Draft",
"agentDetail.configure.skills.workspaceSelector.empty": "No workspace skills found.",
"agentDetail.configure.skills.workspaceSelector.manage": "Manage in Skills",
"agentDetail.configure.skills.workspaceSelector.removeSuccess": "Workspace skill removed.",
"agentDetail.configure.skills.workspaceSelector.saveFailed": "Failed to update workspace skills.",
"agentDetail.configure.skills.workspaceSelector.search": "Search skills...",
"agentDetail.configure.switchToPreviewConfirm.title": "Switch to Preview mode?",
"agentDetail.configure.title": "Configure",
"agentDetail.configure.tools.add": "Add tool",
@@ -421,5 +439,182 @@
"roster.sort.optionsLabel": "Sort options",
"roster.sort.recentlyCreated": "Recently created",
"roster.updateSuccess": "Agent updated.",
"roster.usageStatus.draft": "Draft"
"roster.usageStatus.draft": "Draft",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.referencedDescription": "This Skill is referenced by {{count}} Agent, Workflow, or Chatflow item. Deleting it will remove the Skill from the workspace and clear those bindings.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.builder.applyFailed": "Failed to apply draft updates.",
"skillManagement.detail.builder.attach": "Attach file",
"skillManagement.detail.builder.attachFailed": "Failed to attach file.",
"skillManagement.detail.builder.attachUnsupported": "Only text and document files can be attached.",
"skillManagement.detail.builder.attachmentOnlyMessage": "Use the attached file to help improve this Skill.",
"skillManagement.detail.builder.close": "Close Skill Builder",
"skillManagement.detail.builder.compatibleModelsOnly": "Only compatible models are shown",
"skillManagement.detail.builder.editIntro": "I've reviewed this Skill. What would you like to adjust?",
"skillManagement.detail.builder.exampleIssueTriage": "Customer issue triage",
"skillManagement.detail.builder.exampleOnboarding": "New hire onboarding guide",
"skillManagement.detail.builder.exampleSalesFollowUp": "Sales lead follow-up strategy",
"skillManagement.detail.builder.followUpDisplayName": "Use Refund approval as the display name",
"skillManagement.detail.builder.followUpNameIcon": "Apply the suggested name and icon",
"skillManagement.detail.builder.fromMarketplace": "From Marketplace",
"skillManagement.detail.builder.model": "GPT-4o",
"skillManagement.detail.builder.modelCredits.all": "All credits",
"skillManagement.detail.builder.modelCredits.configure": "Configure required",
"skillManagement.detail.builder.modelCredits.exhausted": "Credits exhausted",
"skillManagement.detail.builder.modelProviderSettings": "Model Provider Settings",
"skillManagement.detail.builder.modelSearch": "Search models...",
"skillManagement.detail.builder.modifyPlaceholder": "Ask AI to modify this skill...",
"skillManagement.detail.builder.open": "Open Skill Builder",
"skillManagement.detail.builder.placeholder": "Describe the scenario...",
"skillManagement.detail.builder.promptDescription": "Describe it and a draft appears in the editor, with steps and files included.",
"skillManagement.detail.builder.promptTitle": "What should this Skill handle?",
"skillManagement.detail.builder.removeAttachment": "Remove {{name}}",
"skillManagement.detail.builder.restart": "Restart builder",
"skillManagement.detail.builder.send": "Send message",
"skillManagement.detail.builder.sendFailed": "Skill Builder failed to respond.",
"skillManagement.detail.builder.title": "Skill Builder",
"skillManagement.detail.builder.tryExample": "Try an example",
"skillManagement.detail.builder.voice": "Voice input",
"skillManagement.detail.builder.voiceUnavailable": "Voice input is not available yet.",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.closeVersions": "Close versions",
"skillManagement.detail.collapseSidebar": "Collapse sidebar",
"skillManagement.detail.copyFile": "Copy",
"skillManagement.detail.copyFileSuccess": "File copied to clipboard.",
"skillManagement.detail.copyVersionId": "Copy ID",
"skillManagement.detail.copyVersionIdSuccess": "ID copied to clipboard.",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.cutFile": "Cut",
"skillManagement.detail.cutFileSuccess": "File cut to clipboard.",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.editVersionInfo": "Edit version info",
"skillManagement.detail.exitVersions": "Exit versions",
"skillManagement.detail.expandSidebar": "Expand sidebar",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.nameThisVersion": "Name this version",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "No matching files.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.pasteFile": "Paste",
"skillManagement.detail.pasteFileSuccess": "File pasted.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishReferencesDescription": "Publishing will make this draft active for {{count}} references using this Skill.",
"skillManagement.detail.publishReferencesTitle": "Publish skill",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.publishUpdate": "Publish update",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveConflict": "This Skill changed in another tab. Refreshed the latest draft and will retry saving your changes.",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Search files",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesFailedStatus": "{{count}} file uploads failed.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.uploadFilesProgress": "Uploading {{completed}}/{{total}}",
"skillManagement.detail.uploadFilesResult": "{{uploaded}} uploaded · {{failed}} failed",
"skillManagement.detail.uploadFilesStatus": "Upload Status",
"skillManagement.detail.uploadStatusDismiss": "Dismiss upload status",
"skillManagement.detail.versionHistory": "Open version history",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versionPublishNote": "Release notes",
"skillManagement.detail.versionPublishNotePlaceholder": "Describe this change",
"skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}",
"skillManagement.detail.versionTitle": "Title",
"skillManagement.detail.versions": "Versions",
"skillManagement.detail.viewOnly": "View only",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptyAction.createDescription": "Describe a scenario in plain words and get a draft skill to review.",
"skillManagement.emptyAction.createTitle": "Create with Skill Builder",
"skillManagement.emptyAction.importDescription": "Bring a .zip with SKILL.md, in the agentskills.io format.",
"skillManagement.emptyAction.importTitle": "Import a skill package",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Home",
"mainNav.integrations": "Integrations",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "No web apps found",
"mainNav.webApps.openApp": "Open {{name}} web app",
"mainNav.webApps.searchPlaceholder": "Search web apps",
@@ -463,6 +464,7 @@
"operation.downloading": "Downloading",
"operation.duplicate": "Duplicate",
"operation.edit": "Edit",
"operation.export": "Export",
"operation.exporting": "Exporting",
"operation.fill": "Autofill",
"operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "I'm sure",
"operation.toggleFullscreen": "Toggle fullscreen",
"operation.toggleMute": "Toggle mute",
"operation.upload": "Upload",
"operation.view": "View",
"operation.viewDetails": "View Details",
"operation.viewMore": "VIEW MORE",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview ejecuta el agente terminado como lo verán tus usuarios, con respuestas claras y funciones de chat.",
"agentDetail.configure.rightPanel.previewTipTitle": "Previsualiza tu agente",
"agentDetail.configure.skills.add": "Agregar habilidad",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Contenido de los detalles de la habilidad",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ARCHIVOS",
"agentDetail.configure.skills.detail.files": "Archivos",
"agentDetail.configure.skills.empty.description": "Las habilidades le dan al agente experiencia reutilizable que puede invocar mientras trabaja",
"agentDetail.configure.skills.empty.title": "Aún no hay habilidades",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Habilidad",
"agentDetail.configure.skills.label": "Habilidades",
"agentDetail.configure.skills.missing": "Habilidad no encontrada",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Eliminar {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Empaqueta las instrucciones, archivos y scripts de una tarea recurrente en una skill. Haz referencia con / en el Prompt. <docLink>Más información</docLink>\n\nEn modo Build, el agente puede configurarlas por ti.",
"agentDetail.configure.skills.tip": "Empaqueta las instrucciones, archivos y scripts de una tarea recurrente en una skill. Haz referencia con / en el Prompt. Más información\n\nEn modo Build, el agente puede configurarlas por ti.",
"agentDetail.configure.skills.toggle": "Alternar habilidades",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opciones de ordenación",
"roster.sort.recentlyCreated": "Creados recientemente",
"roster.updateSuccess": "Agente actualizado.",
"roster.usageStatus.draft": "Borrador"
"roster.usageStatus.draft": "Borrador",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Contraer barra lateral",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Expandir barra lateral",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "No hay archivos coincidentes.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Buscar archivos",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Inicio",
"mainNav.integrations": "Integraciones",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "No se encontraron aplicaciones web",
"mainNav.webApps.openApp": "Abrir la aplicación web {{name}}",
"mainNav.webApps.searchPlaceholder": "Buscar aplicaciones web",
@@ -463,6 +464,7 @@
"operation.downloading": "Descargando...",
"operation.duplicate": "Duplicar",
"operation.edit": "Editar",
"operation.export": "Exportar",
"operation.exporting": "Exportando",
"operation.fill": "Autocompletar",
"operation.format": "Formato",
@@ -504,6 +506,7 @@
"operation.sure": "Estoy seguro",
"operation.toggleFullscreen": "Alternar pantalla completa",
"operation.toggleMute": "Alternar silencio",
"operation.upload": "Upload",
"operation.view": "Vista",
"operation.viewDetails": "Ver detalles",
"operation.viewMore": "VER MÁS",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview عامل تکمیل‌شده را همان‌طور که کاربران می‌بینند اجرا می‌کند، با پاسخ‌های تمیز و قابلیت‌های گفتگو.",
"agentDetail.configure.rightPanel.previewTipTitle": "پیش‌نمایش عامل",
"agentDetail.configure.skills.add": "افزودن مهارت",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "محتوای جزئیات مهارت",
"agentDetail.configure.skills.detail.fileCount": "{{count}} فایل",
"agentDetail.configure.skills.detail.files": "فایل‌ها",
"agentDetail.configure.skills.empty.description": "مهارت‌ها به عامل تخصص قابل استفاده مجدد می‌دهند که هنگام کار می‌تواند فراخوانی کند",
"agentDetail.configure.skills.empty.title": "هنوز مهارتی وجود ندارد",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "مهارت",
"agentDetail.configure.skills.label": "مهارت‌ها",
"agentDetail.configure.skills.missing": "مهارت یافت نشد",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "حذف {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "دستورالعمل‌ها، فایل‌ها و اسکریپت‌های یک کار تکرارشونده را در یک Skill بسته‌بندی کنید. در Prompt با / به آن ارجاع دهید. <docLink>بیشتر بدانید</docLink>\n\nدر حالت Build، عامل می‌تواند این موارد را برای شما تنظیم کند.",
"agentDetail.configure.skills.tip": "دستورالعمل‌ها، فایل‌ها و اسکریپت‌های یک کار تکرارشونده را در یک Skill بسته‌بندی کنید. در Prompt با / به آن ارجاع دهید. بیشتر بدانید\n\nدر حالت Build، عامل می‌تواند این موارد را برای شما تنظیم کند.",
"agentDetail.configure.skills.toggle": "تغییر مهارت‌ها",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "گزینه‌های مرتب‌سازی",
"roster.sort.recentlyCreated": "تازه‌ترین ایجاد",
"roster.updateSuccess": "عامل به‌روزرسانی شد.",
"roster.usageStatus.draft": "پیش‌نویس"
"roster.usageStatus.draft": "پیش‌نویس",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "جمع کردن نوار کناری",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "باز کردن نوار کناری",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "فایل منطبقی نیست.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "جستجوی فایل‌ها",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "خانه",
"mainNav.integrations": "یکپارچه‌سازی‌ها",
"mainNav.marketplace": "بازارچه",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "هیچ برنامه وبی یافت نشد",
"mainNav.webApps.openApp": "باز کردن برنامه وب {{name}}",
"mainNav.webApps.searchPlaceholder": "جستجوی برنامه‌های وب",
@@ -463,6 +464,7 @@
"operation.downloading": "در حال دانلود...",
"operation.duplicate": "تکرار",
"operation.edit": "ویرایش",
"operation.export": "خروجی گرفتن",
"operation.exporting": "در حال خروجی گرفتن",
"operation.fill": "پر کردن خودکار",
"operation.format": "قالب",
@@ -504,6 +506,7 @@
"operation.sure": "مطمئن هستم",
"operation.toggleFullscreen": "تغییر حالت تمام‌صفحه",
"operation.toggleMute": "تغییر حالت بی‌صدا",
"operation.upload": "Upload",
"operation.view": "مشاهده",
"operation.viewDetails": "دیدن جزئیات",
"operation.viewMore": "بیشتر ببینید",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview exécute lagent final comme vos utilisateurs le verront, avec des réponses claires et les fonctions de chat.",
"agentDetail.configure.rightPanel.previewTipTitle": "Prévisualiser votre agent",
"agentDetail.configure.skills.add": "Ajouter une compétence",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Contenu des détails de la compétence",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FICHIERS",
"agentDetail.configure.skills.detail.files": "Fichiers",
"agentDetail.configure.skills.empty.description": "Les compétences offrent à lagent une expertise réutilisable quil peut invoquer pendant son travail",
"agentDetail.configure.skills.empty.title": "Pas encore de compétences",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Compétence",
"agentDetail.configure.skills.label": "Compétences",
"agentDetail.configure.skills.missing": "Compétence introuvable",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Supprimer {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Regroupez les instructions, fichiers et scripts d'une tâche récurrente dans une skill. Référencez-la avec / dans le Prompt. <docLink>En savoir plus</docLink>\n\nEn mode Build, l'agent peut les configurer pour vous.",
"agentDetail.configure.skills.tip": "Regroupez les instructions, fichiers et scripts d'une tâche récurrente dans une skill. Référencez-la avec / dans le Prompt. En savoir plus\n\nEn mode Build, l'agent peut les configurer pour vous.",
"agentDetail.configure.skills.toggle": "Basculer les compétences",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Options de tri",
"roster.sort.recentlyCreated": "Récemment créés",
"roster.updateSuccess": "Agent mis à jour.",
"roster.usageStatus.draft": "Brouillon"
"roster.usageStatus.draft": "Brouillon",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Réduire la barre latérale",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Développer la barre latérale",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Aucun fichier correspondant.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Rechercher des fichiers",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Accueil",
"mainNav.integrations": "Intégrations",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Aucune application web trouvée",
"mainNav.webApps.openApp": "Ouvrir lapplication web {{name}}",
"mainNav.webApps.searchPlaceholder": "Rechercher des applications web",
@@ -463,6 +464,7 @@
"operation.downloading": "Téléchargement...",
"operation.duplicate": "Dupliquer",
"operation.edit": "Modifier",
"operation.export": "Exporter",
"operation.exporting": "Exportation",
"operation.fill": "Remplissage auto",
"operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "Je suis sûr",
"operation.toggleFullscreen": "Basculer en plein écran",
"operation.toggleMute": "Activer/désactiver le son",
"operation.upload": "Upload",
"operation.view": "Vue",
"operation.viewDetails": "Voir les détails",
"operation.viewMore": "VOIR PLUS",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview तैयार agent को वैसे चलाता है जैसे आपके users उसे देखेंगे, साफ replies और chat features के साथ।",
"agentDetail.configure.rightPanel.previewTipTitle": "अपने agent का preview करें",
"agentDetail.configure.skills.add": "कौशल जोड़ें",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "कौशल विवरण सामग्री",
"agentDetail.configure.skills.detail.fileCount": "{{count}} फ़ाइलें",
"agentDetail.configure.skills.detail.files": "फ़ाइलें",
"agentDetail.configure.skills.empty.description": "कौशल एजेंट को पुनः उपयोग योग्य विशेषज्ञता देते हैं जिसे वह काम करते समय कॉल कर सकता है",
"agentDetail.configure.skills.empty.title": "अभी तक कोई कौशल नहीं",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "कौशल",
"agentDetail.configure.skills.label": "कौशल",
"agentDetail.configure.skills.missing": "कौशल नहीं मिला",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "{{name}} हटाएँ",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "किसी दोहराए जाने वाले कार्य के निर्देशों, फ़ाइलों और स्क्रिप्ट को एक Skill में पैकेज करें। Prompt में / से संदर्भ दें। <docLink>और जानें</docLink>\n\nBuild mode में, एजेंट इन्हें आपके लिए सेट कर सकता है।",
"agentDetail.configure.skills.tip": "किसी दोहराए जाने वाले कार्य के निर्देशों, फ़ाइलों और स्क्रिप्ट को एक Skill में पैकेज करें। Prompt में / से संदर्भ दें। और जानें\n\nBuild mode में, एजेंट इन्हें आपके लिए सेट कर सकता है।",
"agentDetail.configure.skills.toggle": "कौशल टॉगल करें",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "क्रमबद्ध विकल्प",
"roster.sort.recentlyCreated": "हाल ही में बनाए गए",
"roster.updateSuccess": "एजेंट अपडेट हो गया।",
"roster.usageStatus.draft": "ड्राफ्ट"
"roster.usageStatus.draft": "ड्राफ्ट",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "साइडबार समेटें",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "साइडबार फैलाएं",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "कोई मेल खाती फ़ाइल नहीं.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "फ़ाइलें खोजें",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "होम",
"mainNav.integrations": "इंटीग्रेशन",
"mainNav.marketplace": "मार्केटप्लेस",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "कोई वेब ऐप नहीं मिला",
"mainNav.webApps.openApp": "{{name}} वेब ऐप खोलें",
"mainNav.webApps.searchPlaceholder": "वेब ऐप खोजें",
@@ -463,6 +464,7 @@
"operation.downloading": "डाउनलोड हो रहा है...",
"operation.duplicate": "डुप्लिकेट",
"operation.edit": "संपादित करें",
"operation.export": "निर्यात करें",
"operation.exporting": "निर्यात हो रहा है",
"operation.fill": "ऑटोफिल",
"operation.format": "फॉर्मेट",
@@ -504,6 +506,7 @@
"operation.sure": "मुझे यकीन है",
"operation.toggleFullscreen": "फ़ुलस्क्रीन टॉगल करें",
"operation.toggleMute": "म्यूट टॉगल करें",
"operation.upload": "Upload",
"operation.view": "देखना",
"operation.viewDetails": "विवरण देखें",
"operation.viewMore": "और देखें",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview menjalankan agent yang sudah selesai seperti yang akan dilihat pengguna, dengan balasan rapi dan fitur chat.",
"agentDetail.configure.rightPanel.previewTipTitle": "Pratinjau agen Anda",
"agentDetail.configure.skills.add": "Tambahkan keterampilan",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Konten detail keterampilan",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILE",
"agentDetail.configure.skills.detail.files": "File",
"agentDetail.configure.skills.empty.description": "Keterampilan memberi agen keahlian yang dapat digunakan kembali yang bisa dipanggil saat bekerja",
"agentDetail.configure.skills.empty.title": "Belum ada keterampilan",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Keterampilan",
"agentDetail.configure.skills.label": "Keterampilan",
"agentDetail.configure.skills.missing": "Keterampilan tidak ditemukan",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Hapus {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Kemas instruksi, file, dan skrip untuk tugas berulang menjadi skill. Referensikan dengan / di Prompt. <docLink>Pelajari selengkapnya</docLink>\n\nDalam mode Build, agen dapat menyiapkannya untuk Anda.",
"agentDetail.configure.skills.tip": "Kemas instruksi, file, dan skrip untuk tugas berulang menjadi skill. Referensikan dengan / di Prompt. Pelajari selengkapnya\n\nDalam mode Build, agen dapat menyiapkannya untuk Anda.",
"agentDetail.configure.skills.toggle": "Alihkan keterampilan",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opsi pengurutan",
"roster.sort.recentlyCreated": "Baru dibuat",
"roster.updateSuccess": "Agen diperbarui.",
"roster.usageStatus.draft": "Draf"
"roster.usageStatus.draft": "Draf",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Ciutkan sidebar",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Perluas sidebar",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Tidak ada file yang cocok.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Cari file",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -201,6 +201,7 @@
"mainNav.home": "Beranda",
"mainNav.integrations": "Integrasi",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Tidak ada aplikasi web ditemukan",
"mainNav.webApps.openApp": "Buka aplikasi web {{name}}",
"mainNav.webApps.searchPlaceholder": "Cari aplikasi web",
@@ -462,6 +463,7 @@
"operation.downloading": "Mengunduh...",
"operation.duplicate": "Duplikat",
"operation.edit": "Mengedit",
"operation.export": "Ekspor",
"operation.exporting": "Mengekspor",
"operation.fill": "Isi otomatis",
"operation.format": "Format",
@@ -503,6 +505,7 @@
"operation.sure": "Saya yakin",
"operation.toggleFullscreen": "Alihkan layar penuh",
"operation.toggleMute": "Alihkan bisu",
"operation.upload": "Upload",
"operation.view": "Lihat",
"operation.viewDetails": "Lihat Detail",
"operation.viewMore": "LIHAT LEBIH BANYAK",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview esegue lagente completato come lo vedranno gli utenti, con risposte pulite e funzionalità di chat.",
"agentDetail.configure.rightPanel.previewTipTitle": "Anteprima del tuo agente",
"agentDetail.configure.skills.add": "Aggiungi abilità",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Contenuto dei dettagli dellabilità",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILE",
"agentDetail.configure.skills.detail.files": "File",
"agentDetail.configure.skills.empty.description": "Le abilità forniscono allagente competenze riutilizzabili che può richiamare durante il lavoro",
"agentDetail.configure.skills.empty.title": "Nessuna abilità al momento",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Abilità",
"agentDetail.configure.skills.label": "Abilità",
"agentDetail.configure.skills.missing": "Abilità non trovata",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Rimuovi {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Raggruppa istruzioni, file e script per un'attività ricorrente in una skill. Fai riferimento con / nel Prompt. <docLink>Scopri di più</docLink>\n\nIn modalità Build, l'agente può configurarli per te.",
"agentDetail.configure.skills.tip": "Raggruppa istruzioni, file e script per un'attività ricorrente in una skill. Fai riferimento con / nel Prompt. Scopri di più\n\nIn modalità Build, l'agente può configurarli per te.",
"agentDetail.configure.skills.toggle": "Attiva/disattiva abilità",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opzioni di ordinamento",
"roster.sort.recentlyCreated": "Creati di recente",
"roster.updateSuccess": "Agente aggiornato.",
"roster.usageStatus.draft": "Bozza"
"roster.usageStatus.draft": "Bozza",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Comprimi barra laterale",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Espandi barra laterale",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Nessun file corrispondente.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Cerca file",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Home",
"mainNav.integrations": "Integrazioni",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Nessuna app web trovata",
"mainNav.webApps.openApp": "Apri l'app web {{name}}",
"mainNav.webApps.searchPlaceholder": "Cerca app web",
@@ -463,6 +464,7 @@
"operation.downloading": "Download in corso...",
"operation.duplicate": "Duplica",
"operation.edit": "Modifica",
"operation.export": "Esporta",
"operation.exporting": "Esportazione in corso",
"operation.fill": "Compilazione automatica",
"operation.format": "Formato",
@@ -504,6 +506,7 @@
"operation.sure": "Sono sicuro",
"operation.toggleFullscreen": "Attiva/disattiva schermo intero",
"operation.toggleMute": "Attiva/disattiva muto",
"operation.upload": "Upload",
"operation.view": "Vista",
"operation.viewDetails": "Visualizza dettagli",
"operation.viewMore": "SCOPRI DI PIÙ",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview は完成したエージェントをユーザーに見える形で実行し、整った返信とチャット機能を確認できます。",
"agentDetail.configure.rightPanel.previewTipTitle": "エージェントをプレビュー",
"agentDetail.configure.skills.add": "スキルを追加",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "スキル詳細コンテンツ",
"agentDetail.configure.skills.detail.fileCount": "{{count}} 個のファイル",
"agentDetail.configure.skills.detail.files": "ファイル",
"agentDetail.configure.skills.empty.description": "スキルはエージェントが作業中に呼び出せる再利用可能な専門知識を提供します",
"agentDetail.configure.skills.empty.title": "スキルはまだありません",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "スキル",
"agentDetail.configure.skills.label": "スキル",
"agentDetail.configure.skills.missing": "スキルが見つかりません",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "{{name}} を削除",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "繰り返し行うタスクに必要な指示、ファイル、スクリプトを Skill にまとめます。Prompt で / を使って参照します。<docLink>詳しく見る</docLink>\n\nBuild モードでは、エージェントがこれらを設定できます。",
"agentDetail.configure.skills.tip": "繰り返し行うタスクに必要な指示、ファイル、スクリプトを Skill にまとめます。Prompt で / を使って参照します。詳しく見る\n\nBuild モードでは、エージェントがこれらを設定できます。",
"agentDetail.configure.skills.toggle": "スキルの表示を切り替え",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "並び替えオプション",
"roster.sort.recentlyCreated": "最近作成",
"roster.updateSuccess": "エージェントを更新しました。",
"roster.usageStatus.draft": "ドラフト"
"roster.usageStatus.draft": "ドラフト",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "サイドバーを折りたたむ",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "サイドバーを展開",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "一致するファイルはありません。",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "ファイルを検索",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "ホーム",
"mainNav.integrations": "連携",
"mainNav.marketplace": "マーケットプレイス",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Webアプリが見つかりません",
"mainNav.webApps.openApp": "{{name}} のWebアプリを開く",
"mainNav.webApps.searchPlaceholder": "Webアプリを検索",
@@ -463,6 +464,7 @@
"operation.downloading": "ダウンロード中...",
"operation.duplicate": "複製",
"operation.edit": "編集",
"operation.export": "エクスポート",
"operation.exporting": "エクスポート中",
"operation.fill": "自動入力",
"operation.format": "フォーマット",
@@ -504,6 +506,7 @@
"operation.sure": "確認済み",
"operation.toggleFullscreen": "全画面表示を切り替え",
"operation.toggleMute": "ミュートを切り替え",
"operation.upload": "Upload",
"operation.view": "表示",
"operation.viewDetails": "詳細を見る",
"operation.viewMore": "さらに表示",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview는 사용자가 보게 될 방식으로 완성된 에이전트를 실행하며, 깔끔한 답변과 채팅 기능을 보여줍니다.",
"agentDetail.configure.rightPanel.previewTipTitle": "에이전트 미리보기",
"agentDetail.configure.skills.add": "스킬 추가",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "스킬 상세 내용",
"agentDetail.configure.skills.detail.fileCount": "파일 {{count}}개",
"agentDetail.configure.skills.detail.files": "파일",
"agentDetail.configure.skills.empty.description": "스킬은 에이전트가 작업하면서 호출할 수 있는 재사용 가능한 전문 능력을 제공합니다",
"agentDetail.configure.skills.empty.title": "아직 스킬이 없습니다",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "스킬",
"agentDetail.configure.skills.label": "스킬",
"agentDetail.configure.skills.missing": "스킬을 찾을 수 없음",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "{{name}} 제거",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "반복 작업에 필요한 지침, 파일, 스크립트를 Skill로 패키징하세요. Prompt에서 /로 참조하세요. <docLink>자세히 알아보기</docLink>\n\nBuild mode에서는 에이전트가 이를 대신 설정할 수 있습니다.",
"agentDetail.configure.skills.tip": "반복 작업에 필요한 지침, 파일, 스크립트를 Skill로 패키징하세요. Prompt에서 /로 참조하세요. 자세히 알아보기\n\nBuild mode에서는 에이전트가 이를 대신 설정할 수 있습니다.",
"agentDetail.configure.skills.toggle": "스킬 전환",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "정렬 옵션",
"roster.sort.recentlyCreated": "최근 생성",
"roster.updateSuccess": "에이전트가 업데이트되었습니다.",
"roster.usageStatus.draft": "초안"
"roster.usageStatus.draft": "초안",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "사이드바 접기",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "사이드바 펼치기",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "일치하는 파일이 없습니다.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "파일 검색",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "홈",
"mainNav.integrations": "연동",
"mainNav.marketplace": "마켓플레이스",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "웹 앱을 찾을 수 없습니다",
"mainNav.webApps.openApp": "{{name}} 웹 앱 열기",
"mainNav.webApps.searchPlaceholder": "웹 앱 검색",
@@ -463,6 +464,7 @@
"operation.downloading": "다운로드 중...",
"operation.duplicate": "중복",
"operation.edit": "편집",
"operation.export": "내보내기",
"operation.exporting": "내보내는 중",
"operation.fill": "자동 입력",
"operation.format": "형식",
@@ -504,6 +506,7 @@
"operation.sure": "확인",
"operation.toggleFullscreen": "전체 화면 전환",
"operation.toggleMute": "음소거 전환",
"operation.upload": "Upload",
"operation.view": "보기",
"operation.viewDetails": "세부 정보보기",
"operation.viewMore": "더보기",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview voert de voltooide agent uit zoals je gebruikers die zien, met duidelijke antwoorden en chatfuncties.",
"agentDetail.configure.rightPanel.previewTipTitle": "Voorbeeld van je agent",
"agentDetail.configure.skills.add": "Skill toevoegen",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Skill-detailinhoud",
"agentDetail.configure.skills.detail.fileCount": "{{count}} BESTANDEN",
"agentDetail.configure.skills.detail.files": "Bestanden",
"agentDetail.configure.skills.empty.description": "Skills geven de agent herbruikbare expertise die hij tijdens het werken kan inzetten",
"agentDetail.configure.skills.empty.title": "Nog geen skills",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Skill",
"agentDetail.configure.skills.label": "Skills",
"agentDetail.configure.skills.missing": "Skill niet gevonden",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "{{name}} verwijderen",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Bundel de instructies, bestanden en scripts voor een terugkerende taak in een skill. Verwijs ernaar met / in de Prompt. <docLink>Meer informatie</docLink>\n\nIn de Build-modus kan de agent dit voor u instellen.",
"agentDetail.configure.skills.tip": "Bundel de instructies, bestanden en scripts voor een terugkerende taak in een skill. Verwijs ernaar met / in de Prompt. Meer informatie\n\nIn de Build-modus kan de agent dit voor u instellen.",
"agentDetail.configure.skills.toggle": "Skills in/uit",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Sorteeropties",
"roster.sort.recentlyCreated": "Recent aangemaakt",
"roster.updateSuccess": "Agent bijgewerkt.",
"roster.usageStatus.draft": "Concept"
"roster.usageStatus.draft": "Concept",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Zijbalk inklappen",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Zijbalk uitklappen",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Geen overeenkomende bestanden.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Bestanden zoeken",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -201,6 +201,7 @@
"mainNav.home": "Start",
"mainNav.integrations": "Integraties",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Geen webapps gevonden",
"mainNav.webApps.openApp": "Webapp {{name}} openen",
"mainNav.webApps.searchPlaceholder": "Webapps zoeken",
@@ -462,6 +463,7 @@
"operation.downloading": "Downloaden...",
"operation.duplicate": "Duplicate",
"operation.edit": "Edit",
"operation.export": "Exporteren",
"operation.exporting": "Exporteren",
"operation.fill": "Automatisch invullen",
"operation.format": "Format",
@@ -503,6 +505,7 @@
"operation.sure": "I'm sure",
"operation.toggleFullscreen": "Volledig scherm schakelen",
"operation.toggleMute": "Dempen schakelen",
"operation.upload": "Upload",
"operation.view": "View",
"operation.viewDetails": "View Details",
"operation.viewMore": "VIEW MORE",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview uruchamia gotowego agenta tak, jak zobaczą go użytkownicy, z przejrzystymi odpowiedziami i funkcjami czatu.",
"agentDetail.configure.rightPanel.previewTipTitle": "Podgląd agenta",
"agentDetail.configure.skills.add": "Dodaj umiejętność",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Zawartość szczegółów umiejętności",
"agentDetail.configure.skills.detail.fileCount": "{{count}} PLIKÓW",
"agentDetail.configure.skills.detail.files": "Pliki",
"agentDetail.configure.skills.empty.description": "Umiejętności dają agentowi reużywalną wiedzę, którą może wywołać podczas pracy",
"agentDetail.configure.skills.empty.title": "Brak umiejętności",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Umiejętność",
"agentDetail.configure.skills.label": "Umiejętności",
"agentDetail.configure.skills.missing": "Nie znaleziono umiejętności",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Usuń {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Spakuj instrukcje, pliki i skrypty dla powtarzalnego zadania w skill. Odwołuj się za pomocą / w Prompt. <docLink>Dowiedz się więcej</docLink>\n\nW trybie Build agent może skonfigurować je za Ciebie.",
"agentDetail.configure.skills.tip": "Spakuj instrukcje, pliki i skrypty dla powtarzalnego zadania w skill. Odwołuj się za pomocą / w Prompt. Dowiedz się więcej\n\nW trybie Build agent może skonfigurować je za Ciebie.",
"agentDetail.configure.skills.toggle": "Przełącz umiejętności",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opcje sortowania",
"roster.sort.recentlyCreated": "Ostatnio utworzone",
"roster.updateSuccess": "Agent zaktualizowany.",
"roster.usageStatus.draft": "Wersja robocza"
"roster.usageStatus.draft": "Wersja robocza",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Zwiń pasek boczny",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Rozwiń pasek boczny",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Brak pasujących plików.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Szukaj plików",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Strona główna",
"mainNav.integrations": "Integracje",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Nie znaleziono aplikacji webowych",
"mainNav.webApps.openApp": "Otwórz aplikację webową {{name}}",
"mainNav.webApps.searchPlaceholder": "Szukaj aplikacji webowych",
@@ -463,6 +464,7 @@
"operation.downloading": "Pobieranie...",
"operation.duplicate": "Duplikuj",
"operation.edit": "Edytuj",
"operation.export": "Eksportuj",
"operation.exporting": "Eksportowanie",
"operation.fill": "Autouzupełnianie",
"operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "Jestem pewien",
"operation.toggleFullscreen": "Przełącz pełny ekran",
"operation.toggleMute": "Przełącz wyciszenie",
"operation.upload": "Upload",
"operation.view": "Widok",
"operation.viewDetails": "Wyświetl szczegóły",
"operation.viewMore": "ZOBACZ WIĘCEJ",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview executa o agente finalizado como seus usuários o verão, com respostas claras e recursos de chat.",
"agentDetail.configure.rightPanel.previewTipTitle": "Pré-visualize seu agente",
"agentDetail.configure.skills.add": "Adicionar habilidade",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Conteúdo dos detalhes da habilidade",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ARQUIVOS",
"agentDetail.configure.skills.detail.files": "Arquivos",
"agentDetail.configure.skills.empty.description": "As habilidades dão ao agente expertise reutilizável que ele pode invocar enquanto trabalha",
"agentDetail.configure.skills.empty.title": "Ainda não há habilidades",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Habilidade",
"agentDetail.configure.skills.label": "Habilidades",
"agentDetail.configure.skills.missing": "Habilidade não encontrada",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Remover {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Empacote as instruções, arquivos e scripts de uma tarefa recorrente em uma skill. Referencie com / no Prompt. <docLink>Saiba mais</docLink>\n\nNo modo Build, o agente pode configurar isso para você.",
"agentDetail.configure.skills.tip": "Empacote as instruções, arquivos e scripts de uma tarefa recorrente em uma skill. Referencie com / no Prompt. Saiba mais\n\nNo modo Build, o agente pode configurar isso para você.",
"agentDetail.configure.skills.toggle": "Alternar habilidades",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opções de ordenação",
"roster.sort.recentlyCreated": "Criados recentemente",
"roster.updateSuccess": "Agente atualizado.",
"roster.usageStatus.draft": "Rascunho"
"roster.usageStatus.draft": "Rascunho",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Recolher barra lateral",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Expandir barra lateral",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Nenhum arquivo correspondente.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Buscar arquivos",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Início",
"mainNav.integrations": "Integrações",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Nenhum app web encontrado",
"mainNav.webApps.openApp": "Abrir o app web {{name}}",
"mainNav.webApps.searchPlaceholder": "Pesquisar apps web",
@@ -463,6 +464,7 @@
"operation.downloading": "Baixando...",
"operation.duplicate": "Duplicada",
"operation.edit": "Editar",
"operation.export": "Exportar",
"operation.exporting": "Exportando",
"operation.fill": "Preenchimento automático",
"operation.format": "Formato",
@@ -504,6 +506,7 @@
"operation.sure": "Tenho certeza",
"operation.toggleFullscreen": "Alternar tela cheia",
"operation.toggleMute": "Alternar mudo",
"operation.upload": "Upload",
"operation.view": "Vista",
"operation.viewDetails": "Ver detalhes",
"operation.viewMore": "VER MAIS",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview rulează agentul finalizat așa cum îl vor vedea utilizatorii, cu răspunsuri clare și funcții de chat.",
"agentDetail.configure.rightPanel.previewTipTitle": "Previzualizează agentul",
"agentDetail.configure.skills.add": "Adaugă abilitate",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Conținutul detaliilor abilității",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FIȘIERE",
"agentDetail.configure.skills.detail.files": "Fișiere",
"agentDetail.configure.skills.empty.description": "Abilitățile oferă agentului expertiză reutilizabilă pe care o poate apela în timpul lucrului",
"agentDetail.configure.skills.empty.title": "Nicio abilitate încă",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Abilitate",
"agentDetail.configure.skills.label": "Abilități",
"agentDetail.configure.skills.missing": "Abilitatea nu a fost găsită",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Elimină {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Împachetați instrucțiunile, fișierele și scripturile pentru o sarcină recurentă într-un skill. Faceți referire cu / în Prompt. <docLink>Aflați mai multe</docLink>\n\nÎn modul Build, agentul le poate configura pentru dvs.",
"agentDetail.configure.skills.tip": "Împachetați instrucțiunile, fișierele și scripturile pentru o sarcină recurentă într-un skill. Faceți referire cu / în Prompt. Aflați mai multe\n\nÎn modul Build, agentul le poate configura pentru dvs.",
"agentDetail.configure.skills.toggle": "Comută abilitățile",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opțiuni de sortare",
"roster.sort.recentlyCreated": "Create recent",
"roster.updateSuccess": "Agent actualizat.",
"roster.usageStatus.draft": "Ciornă"
"roster.usageStatus.draft": "Ciornă",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Restrânge bara laterală",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Extinde bara laterală",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Nu există fișiere potrivite.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Caută fișiere",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Acasă",
"mainNav.integrations": "Integrări",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Nu s-au găsit aplicații web",
"mainNav.webApps.openApp": "Deschide aplicația web {{name}}",
"mainNav.webApps.searchPlaceholder": "Caută aplicații web",
@@ -463,6 +464,7 @@
"operation.downloading": "Se descarcă...",
"operation.duplicate": "Duplică",
"operation.edit": "Editează",
"operation.export": "Exportă",
"operation.exporting": "Se exportă",
"operation.fill": "Completare automată",
"operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "Sunt sigur",
"operation.toggleFullscreen": "Comută ecran complet",
"operation.toggleMute": "Comută sunetul",
"operation.upload": "Upload",
"operation.view": "Vedere",
"operation.viewDetails": "Vezi detalii",
"operation.viewMore": "VEZI MAI MULT",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview запускает готового агента так, как его увидят пользователи, с чистыми ответами и функциями чата.",
"agentDetail.configure.rightPanel.previewTipTitle": "Предпросмотр агента",
"agentDetail.configure.skills.add": "Добавить навык",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Содержимое деталей навыка",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ФАЙЛОВ",
"agentDetail.configure.skills.detail.files": "Файлы",
"agentDetail.configure.skills.empty.description": "Навыки дают агенту переиспользуемую экспертизу, которую он может вызывать в работе",
"agentDetail.configure.skills.empty.title": "Пока нет навыков",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Навык",
"agentDetail.configure.skills.label": "Навыки",
"agentDetail.configure.skills.missing": "Навык не найден",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Удалить {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Упакуйте инструкции, файлы и скрипты для повторяющейся задачи в Skill. Ссылайтесь на него через / в Prompt. <docLink>Подробнее</docLink>\n\nВ режиме Build агент может настроить это за вас.",
"agentDetail.configure.skills.tip": "Упакуйте инструкции, файлы и скрипты для повторяющейся задачи в Skill. Ссылайтесь на него через / в Prompt. Подробнее\n\nВ режиме Build агент может настроить это за вас.",
"agentDetail.configure.skills.toggle": "Переключить навыки",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Параметры сортировки",
"roster.sort.recentlyCreated": "Недавно созданные",
"roster.updateSuccess": "Агент обновлён.",
"roster.usageStatus.draft": "Черновик"
"roster.usageStatus.draft": "Черновик",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Свернуть боковую панель",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Развернуть боковую панель",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Нет подходящих файлов.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Поиск файлов",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -202,6 +202,7 @@
"mainNav.home": "Главная",
"mainNav.integrations": "Интеграции",
"mainNav.marketplace": "Маркетплейс",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Веб-приложения не найдены",
"mainNav.webApps.openApp": "Открыть веб-приложение {{name}}",
"mainNav.webApps.searchPlaceholder": "Поиск веб-приложений",
@@ -463,6 +464,7 @@
"operation.downloading": "Скачивание...",
"operation.duplicate": "Дублировать",
"operation.edit": "Редактировать",
"operation.export": "Экспорт",
"operation.exporting": "Экспорт",
"operation.fill": "Автозаполнение",
"operation.format": "Формат",
@@ -504,6 +506,7 @@
"operation.sure": "Я уверен",
"operation.toggleFullscreen": "Переключить полноэкранный режим",
"operation.toggleMute": "Переключить звук",
"operation.upload": "Upload",
"operation.view": "Вид",
"operation.viewDetails": "Подробнее",
"operation.viewMore": "ПОДРОБНЕЕ",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview zažene dokončanega agenta tako, kot ga bodo videli uporabniki, z jasnimi odgovori in funkcijami klepeta.",
"agentDetail.configure.rightPanel.previewTipTitle": "Predogled agenta",
"agentDetail.configure.skills.add": "Dodaj veščino",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "Vsebina podrobnosti veščine",
"agentDetail.configure.skills.detail.fileCount": "{{count}} DATOTEK",
"agentDetail.configure.skills.detail.files": "Datoteke",
"agentDetail.configure.skills.empty.description": "Veščine agentu dajejo ponovno uporabno strokovnost, ki jo lahko kliče med delom",
"agentDetail.configure.skills.empty.title": "Še ni veščin",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Veščina",
"agentDetail.configure.skills.label": "Veščine",
"agentDetail.configure.skills.missing": "Veščina ni bila najdena",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "Odstrani {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Navodila, datoteke in skripte za ponavljajočo se nalogo zapakirajte v skill. Sklicujte se z / v Promptu. <docLink>Več informacij</docLink>\n\nV načinu Build jih lahko agent nastavi namesto vas.",
"agentDetail.configure.skills.tip": "Navodila, datoteke in skripte za ponavljajočo se nalogo zapakirajte v skill. Sklicujte se z / v Promptu. Več informacij\n\nV načinu Build jih lahko agent nastavi namesto vas.",
"agentDetail.configure.skills.toggle": "Preklopi veščine",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Možnosti razvrščanja",
"roster.sort.recentlyCreated": "Nedavno ustvarjeno",
"roster.updateSuccess": "Agent posodobljen.",
"roster.usageStatus.draft": "Osnutek"
"roster.usageStatus.draft": "Osnutek",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "Strni stransko vrstico",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "Razširi stransko vrstico",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "Ni ujemajočih se datotek.",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "Išči datoteke",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}
+3
View File
@@ -201,6 +201,7 @@
"mainNav.home": "Domov",
"mainNav.integrations": "Integracije",
"mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Ni najdenih spletnih aplikacij",
"mainNav.webApps.openApp": "Odpri spletno aplikacijo {{name}}",
"mainNav.webApps.searchPlaceholder": "Išči spletne aplikacije",
@@ -462,6 +463,7 @@
"operation.downloading": "Prenašanje...",
"operation.duplicate": "Podvoji",
"operation.edit": "Uredi",
"operation.export": "Izvozi",
"operation.exporting": "Izvažanje",
"operation.fill": "Samodejno izpolni",
"operation.format": "Format",
@@ -503,6 +505,7 @@
"operation.sure": "Prepričan sem",
"operation.toggleFullscreen": "Preklopi celozaslonski način",
"operation.toggleMute": "Preklopi utišanje",
"operation.upload": "Upload",
"operation.view": "Pogled",
"operation.viewDetails": "Poglej podrobnosti",
"operation.viewMore": "POGLEJ VEČ",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview เรียกใช้เอเจนต์ที่เสร็จแล้วในแบบที่ผู้ใช้จะเห็น พร้อมคำตอบที่ชัดเจนและฟีเจอร์แชท",
"agentDetail.configure.rightPanel.previewTipTitle": "ดูตัวอย่างเอเจนต์",
"agentDetail.configure.skills.add": "เพิ่มทักษะ",
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
"agentDetail.configure.skills.detail.contentRegion": "เนื้อหารายละเอียดทักษะ",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ไฟล์",
"agentDetail.configure.skills.detail.files": "ไฟล์",
"agentDetail.configure.skills.empty.description": "ทักษะให้ตัวแทนมีความเชี่ยวชาญที่นำกลับมาใช้ใหม่ได้ซึ่งสามารถเรียกใช้ได้ขณะทำงาน",
"agentDetail.configure.skills.empty.title": "ยังไม่มีทักษะ",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "ทักษะ",
"agentDetail.configure.skills.label": "ทักษะ",
"agentDetail.configure.skills.missing": "ไม่พบทักษะ",
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
"agentDetail.configure.skills.remove": "ลบ {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "แพ็กเกจคำสั่ง ไฟล์ และสคริปต์สำหรับงานที่ทำซ้ำเป็น Skill อ้างอิงด้วย / ใน Prompt <docLink>เรียนรู้เพิ่มเติม</docLink>\n\nในโหมด Build agent สามารถตั้งค่าสิ่งเหล่านี้ให้คุณได้",
"agentDetail.configure.skills.tip": "แพ็กเกจคำสั่ง ไฟล์ และสคริปต์สำหรับงานที่ทำซ้ำเป็น Skill อ้างอิงด้วย / ใน Prompt เรียนรู้เพิ่มเติม\n\nในโหมด Build agent สามารถตั้งค่าสิ่งเหล่านี้ให้คุณได้",
"agentDetail.configure.skills.toggle": "สลับทักษะ",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "ตัวเลือกการเรียง",
"roster.sort.recentlyCreated": "สร้างล่าสุด",
"roster.updateSuccess": "อัปเดตตัวแทนแล้ว",
"roster.usageStatus.draft": "ฉบับร่าง"
"roster.usageStatus.draft": "ฉบับร่าง",
"skillManagement.clearTags": "Clear tags",
"skillManagement.create": "Create",
"skillManagement.createFailed": "Failed to create skill.",
"skillManagement.createSuccess": "Skill created.",
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
"skillManagement.deleteDialog.title": "Delete {{name}}?",
"skillManagement.deleteFailed": "Failed to delete skill.",
"skillManagement.deleteSuccess": "Skill deleted.",
"skillManagement.detail.addMetadata": "Add metadata",
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
"skillManagement.detail.addTag": "Add tag",
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
"skillManagement.detail.addTagSuccess": "Tag added.",
"skillManagement.detail.back": "Back to Skills",
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
"skillManagement.detail.closeFileTab": "Close {{name}}",
"skillManagement.detail.collapseSidebar": "ยุบแถบด้านข้าง",
"skillManagement.detail.createFile": "New file",
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
"skillManagement.detail.createFileMenu": "New file...",
"skillManagement.detail.createFileSuccess": "File created.",
"skillManagement.detail.createFolder": "New folder",
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
"skillManagement.detail.createFolderMenu": "New folder...",
"skillManagement.detail.createFolderSuccess": "Folder created.",
"skillManagement.detail.createdBy": "Created by {{name}}",
"skillManagement.detail.currentDraft": "Current draft",
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
"skillManagement.detail.deleteFileSuccess": "File deleted.",
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
"skillManagement.detail.downloadFile": "Download file",
"skillManagement.detail.draft": "Draft",
"skillManagement.detail.expandSidebar": "ขยายแถบด้านข้าง",
"skillManagement.detail.fileCount": "{{count}} FILES",
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
"skillManagement.detail.fileMissing": "File not found.",
"skillManagement.detail.fileOperationFailed": "File operation failed.",
"skillManagement.detail.files": "Files",
"skillManagement.detail.latest": "Latest",
"skillManagement.detail.loadFailed": "Failed to load skill.",
"skillManagement.detail.markdownLiveMode": "Live preview",
"skillManagement.detail.markdownSourceMode": "Source editor",
"skillManagement.detail.metadataKey": "name",
"skillManagement.detail.metadataValue": "value",
"skillManagement.detail.moveFileSuccess": "File moved.",
"skillManagement.detail.moveFilesSuccess": "Files moved.",
"skillManagement.detail.noFileSelected": "Select a file to preview.",
"skillManagement.detail.noFiles": "No files.",
"skillManagement.detail.noSearchResults": "ไม่พบไฟล์ที่ตรงกัน",
"skillManagement.detail.noVersions": "No published versions yet.",
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
"skillManagement.detail.publish": "Publish",
"skillManagement.detail.publishFailed": "Failed to publish skill.",
"skillManagement.detail.publishSuccess": "Skill published.",
"skillManagement.detail.published": "Published",
"skillManagement.detail.readonly": "Read only",
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
"skillManagement.detail.referenceFiles.empty": "No files available.",
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
"skillManagement.detail.referenceFiles.title": "Reference files",
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
"skillManagement.detail.removeTag": "Remove {{tag}}",
"skillManagement.detail.removeTagSuccess": "Tag removed.",
"skillManagement.detail.renameFile": "Rename file",
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
"skillManagement.detail.renameFileSuccess": "File renamed.",
"skillManagement.detail.renameVersion": "Rename",
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
"skillManagement.detail.renameVersionPrompt": "Version name",
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
"skillManagement.detail.restoreVersion": "Restore",
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
"skillManagement.detail.save": "Save",
"skillManagement.detail.saveFailed": "Failed to save file.",
"skillManagement.detail.saveSuccess": "File saved.",
"skillManagement.detail.saved": "Saved",
"skillManagement.detail.savedAt": "Saved {{time}}",
"skillManagement.detail.saving": "Saving...",
"skillManagement.detail.searchFiles": "ค้นหาไฟล์",
"skillManagement.detail.unsavedChanges": "Unsaved changes",
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
"skillManagement.detail.uploadFile": "Upload file",
"skillManagement.detail.uploadFileFailed": "File upload failed.",
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
"skillManagement.detail.uploadFilesMenu": "Upload files...",
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
"skillManagement.detail.versions": "Versions",
"skillManagement.draft": "Draft",
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
"skillManagement.duplicateSuccess": "Skill duplicated.",
"skillManagement.editedAt": "Edited {{time}}",
"skillManagement.empty": "No skills yet",
"skillManagement.emptySearch": "No skills found",
"skillManagement.import": "Import",
"skillManagement.importFailed": "Failed to import skill.",
"skillManagement.importSuccess": "Skill imported.",
"skillManagement.listLabel": "Workspace skills",
"skillManagement.loadingError": "Failed to load skills",
"skillManagement.moreActions": "More actions for {{name}}",
"skillManagement.noTags": "No tags",
"skillManagement.publishedAt": "Published {{time}}",
"skillManagement.referenceCount": "{{count}} Refs",
"skillManagement.searchLabel": "Search skills",
"skillManagement.searchPlaceholder": "Search",
"skillManagement.tags": "Tags",
"skillManagement.title": "Skills"
}

Some files were not shown because too many files have changed in this diff Show More