Compare commits

...
84 Commits
Author SHA1 Message Date
fatelei 2bb01984ed fix: fix conflict 2026-07-28 19:03:11 +08:00
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
fatelei 5c4f4fd1ef Merge branch 'skill' into deploy/dev 2026-07-28 14:44:57 +08:00
fatelei 54843971ac fix: fix model not change 2026-07-28 14:43:05 +08:00
zxhlyh 5ec5d3aeb9 fix(web): prevent duplicate skill autosaves 2026-07-28 14:30:17 +08:00
fatelei d661b53e49 fix: fix model not update 2026-07-28 14:10:49 +08:00
zxhlyh fd1e777f85 Merge branch 'codex/fix-skill-autosave-conflict' into deploy/dev 2026-07-28 14:04:25 +08:00
zxhlyh e5f77ce185 fix(web): prevent duplicate skill autosaves 2026-07-28 14:02:18 +08:00
fatelei 99929d1c16 fix: fix conflict 2026-07-27 08:12:58 +08:00
fatelei c699aa11db fix: fix failed test 2026-07-26 23:42:50 +08:00
fatelei 06cd0b56ac fix: fix skill not in agent 2026-07-26 21:00:38 +08:00
fatelei 241a9e1fec fix: fix frontend issue and let draft check relax 2026-07-26 21:00:38 +08:00
fatelei 3d79689fb5 fix: fix ci failed 2026-07-26 20:59:56 +08:00
fatelei 9f050f7957 fix: fix ci failed 2026-07-26 20:59:56 +08:00
fatelei cb691d47d3 fix: fix scroll and live mode can not enter 2026-07-26 20:57:29 +08:00
fatelei 04f5ee58d0 fix: fix some bugs 2026-07-26 20:57:29 +08:00
fatelei f5cff724f4 chore: adjust skill entrance 2026-07-26 20:57:29 +08:00
fatelei 1f4ccafdea fix: fix delete skill ref 2026-07-26 20:57:29 +08:00
fatelei 163049f6ca feat: support agent skill 2026-07-26 20:54:32 +08:00
fatelei 65e7507ca3 Merge branch 'skill' into deploy/dev 2026-07-24 13:38:15 +08:00
fatelei ba157f9604 fix: fix skill not in agent 2026-07-24 13:37:19 +08:00
fatelei 0dc913630e fix: fix conflict 2026-07-24 13:18:31 +08:00
fatelei 875cd30b1f fix: fix frontend issue and let draft check relax 2026-07-24 13:11:18 +08:00
fatelei aa4a32ae84 fix: fix ci failed 2026-07-24 11:13:44 +08:00
fatelei 8573e14777 fix: fix ci failed 2026-07-24 11:13:44 +08:00
fatelei 1855be234c fix: fix scroll and live mode can not enter 2026-07-24 11:13:44 +08:00
fatelei 9bb960ff12 fix: fix some bugs 2026-07-24 11:13:44 +08:00
fatelei 1c14c7d467 chore: adjust skill entrance 2026-07-24 11:13:44 +08:00
fatelei 61faec16ca fix: fix delete skill ref 2026-07-24 11:13:44 +08:00
fatelei 57c836e692 feat: support agent skill 2026-07-24 11:13:43 +08:00
samzong 626cc282b1 fix(web): echo OAuth state on authorize redirect (#39459)
Signed-off-by: samzong <samzong.lu@gmail.com>
2026-07-23 06:36:58 -04:00
fatelei 52624d54e3 fix: fix db migration 2026-07-23 16:28:27 +08:00
fatelei 5ce038ef92 fix: fix db migration 2026-07-23 16:25:33 +08:00
fatelei 30f4d4c0c6 fix: fix db migration 2026-07-23 16:04:09 +08:00
fatelei 510679a7d1 fix: fix db migration 2026-07-23 15:38:05 +08:00
fatelei 9237f2a14a fix: fix conflict 2026-07-23 15:23:09 +08:00
fatelei bd178c7b29 Merge branch 'skill' into deploy/dev 2026-07-23 14:38:04 +08:00
fatelei d80947aa72 fix: fix scroll and live mode can not enter 2026-07-23 14:36:44 +08:00
fatelei 1618c37d26 Merge branch 'skill' into deploy/dev 2026-07-23 14:17:52 +08:00
fatelei 701ab64462 fix: fix some bugs 2026-07-23 14:14:28 +08:00
fatelei b3298800e9 chore: adjust skill entrance 2026-07-22 16:30:38 +08:00
fatelei 0f1c6b3f78 chore: remove agent v2 feature 2026-07-22 16:26:00 +08:00
林玮 (Jade Lin) 9b4b246aad refactor(api): simplify batch cleanup refresh state
(cherry picked from commit 1ded32d949)
2026-07-22 14:49:11 +08:00
林玮 (Jade Lin) 1bd654a289 refactor(api): simplify vector cache refresh metric
(cherry picked from commit fa585cf134)
2026-07-22 14:49:11 +08:00
林玮 (Jade Lin) fc70329bdb refactor(api): derive cleanup tenant from dataset
(cherry picked from commit b124ec86fc)
2026-07-22 14:49:11 +08:00
林玮 (Jade Lin) cd8a82fbd4 fix(api): invalidate vector space cache after cleanup
(cherry picked from commit a07e5c6eb4)
2026-07-22 14:49:11 +08:00
fatelei 550cb7eff5 fix: fix db migration 2026-07-22 14:42:11 +08:00
fatelei 2e748c16e9 Merge branch 'skill' into deploy/dev 2026-07-22 14:17:08 +08:00
fatelei 577012b66d fix: fix delete skill ref 2026-07-22 14:11:55 +08:00
fatelei 251c324180 Merge branch 'skill' into deploy/dev 2026-07-22 12:59:22 +08:00
fatelei 865a618fd5 Revert "feat: support agent skill"
This reverts commit 991116990a.
2026-07-22 12:48:29 +08:00
fatelei 991116990a feat: support agent skill 2026-07-22 11:45:50 +08:00
fatelei eb5d1da0e8 feat: support agent skill 2026-07-22 10:25:25 +08:00
fatelei dce3b7a7fc feat: use celery task 2026-07-10 15:20:06 +08:00
fatelei be386aba3b chore: add switch 2026-07-10 15:19:56 +08:00
JzoNg 02e51e7d7c Merge branch 'main' into deploy/dev 2026-07-10 10:28:07 +08:00
Xiyuan ChenandGitHub 96b6d4f2c0 Merge branch 'fix/socketio-user-connect-app-context' into deploy/dev 2026-07-08 22:02:43 -07:00
GareArc 4c84c5957d fix: run user_connect authorization inside Flask app context
Socket.IO event handlers run in plain gevent greenlets without a Flask
application context, so the db.session() call added in #38227 raises
RuntimeError: Working outside of application context, and every
user_connect is rejected as unauthorized. Wrap the authorization call in
sio.app.app_context(), matching the existing connect handler.
2026-07-08 21:57:07 -07:00
CodingOnStar 34613ecdc5 fix(app): update app creation logic to use database session and ensure RBAC access initialization 2026-07-09 12:00:16 +08:00
CodingOnStar 6a14245401 Merge remote-tracking branch 'origin/main' into deploy/dev 2026-07-09 11:49:16 +08:00
CodingOnStar a758ca2aef fix(amplitude): update API key for amplitude initialization 2026-07-09 11:37:28 +08:00
CodingOnStar 9e60d4e213 Merge branch 'fix/report-slug' into deploy/dev 2026-07-09 11:29:04 +08:00
CodingOnStar ef29c8442c refactor(amplitude): comment out amplitude initialization logic for future implementation 2026-07-09 11:26:59 +08:00
CodingOnStar a1b45415ac fix(web): preserve attribution from auth redirect 2026-07-09 11:00:37 +08:00
fatelei 7fc46d75bd chore: add switch 2026-07-08 18:08:02 +08:00
fatelei 953a4ef0ca feat: create app sync scope and workspace user 2026-07-08 16:12:40 +08:00
114 changed files with 23953 additions and 922 deletions
+2
View File
@@ -144,6 +144,7 @@ from .workspace import (
models, models,
plugin, plugin,
rbac, rbac,
skills,
snippets, snippets,
tool_providers, tool_providers,
trigger_providers, trigger_providers,
@@ -225,6 +226,7 @@ __all__ = [
"saved_message", "saved_message",
"setup", "setup",
"site", "site",
"skills",
"snippet_workflow", "snippet_workflow",
"snippet_workflow_draft_variable", "snippet_workflow_draft_variable",
"snippets", "snippets",
+1 -1
View File
@@ -59,7 +59,7 @@ class TagBindingRemovePayload(BaseModel):
class TagListQueryParam(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") 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_config as _agent_config
from .plugin import agent_drive as _agent_drive from .plugin import agent_drive as _agent_drive
from .plugin import plugin as _plugin from .plugin import plugin as _plugin
from .plugin import skills as _skills
from .workspace import workspace as _workspace from .workspace import workspace as _workspace
api.add_namespace(inner_api_ns) api.add_namespace(inner_api_ns)
@@ -36,6 +37,7 @@ __all__ = [
"_mail", "_mail",
"_plugin", "_plugin",
"_runtime_credentials", "_runtime_credentials",
"_skills",
"_workspace", "_workspace",
"api", "api",
"bp", "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_config_layer_config,
build_knowledge_layer_config, build_knowledge_layer_config,
build_shell_layer_config, build_shell_layer_config,
load_runtime_agent_skill_configs,
) )
from models.agent_config_entities import AgentSoulConfig, AgentSoulToolsConfig from models.agent_config_entities import AgentSoulConfig, AgentSoulToolsConfig
from models.provider_ids import ModelProviderID from models.provider_ids import ModelProviderID
@@ -125,14 +126,22 @@ class AgentAppRuntimeRequestBuilder:
"cli_tool_count": len(agent_soul.tools.cli_tools), "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( config_layer_config, config_warnings = build_config_layer_config(
agent_soul, agent_soul,
agent_id=context.agent_id, agent_id=context.agent_id,
config_version_id=context.agent_config_snapshot_id, config_version_id=context.agent_config_snapshot_id,
config_version_kind=context.agent_config_version_kind, config_version_kind=context.agent_config_version_kind,
runtime_config_skills=runtime_config_skills,
) )
append_runtime_warnings(metadata, config_warnings) 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) knowledge_config = build_knowledge_layer_config(agent_soul)
request = self._request_builder.build_for_agent_app( 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 dify_agent.protocol import CreateRunRequest, DeferredToolResultsPayload
from pydantic import BaseModel, ValidationError from pydantic import BaseModel, ValidationError
from sqlalchemy.exc import OperationalError
from clients.agent_backend import ( from clients.agent_backend import (
AgentBackendModelConfig, AgentBackendModelConfig,
@@ -206,14 +207,22 @@ class WorkflowAgentRuntimeRequestBuilder:
"cli_tool_count": len(agent_soul.tools.cli_tools), "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( config_layer_config, config_warnings = build_config_layer_config(
agent_soul, agent_soul,
agent_id=context.agent.id, agent_id=context.agent.id,
config_version_id=context.snapshot.id, config_version_id=context.snapshot.id,
config_version_kind="snapshot", config_version_kind="snapshot",
runtime_config_skills=runtime_config_skills,
) )
append_runtime_warnings(metadata, config_warnings) 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() soul_prompt = expand_prompt_mentions(agent_soul.prompt.system_prompt, soul_prompt_resolver).strip()
knowledge_config = build_knowledge_layer_config(agent_soul) 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) 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.""" """Resolve config skill/file mentions and delegate the rest to Agent Soul."""
base_resolver = build_soul_mention_resolver(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 = {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} file_names = {item.name for item in agent_soul.config_files if not item.is_missing}
def _resolve(mention: object) -> str | None: def _resolve(mention: object) -> str | None:
@@ -905,12 +919,34 @@ def build_config_aware_soul_mention_resolver(agent_soul: AgentSoulConfig):
return _resolve 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( def build_config_layer_config(
agent_soul: AgentSoulConfig, agent_soul: AgentSoulConfig,
*, *,
agent_id: str | None = None, agent_id: str | None = None,
config_version_id: str | None = None, config_version_id: str | None = None,
config_version_kind: Literal["snapshot", "draft", "build_draft"] = "snapshot", config_version_kind: Literal["snapshot", "draft", "build_draft"] = "snapshot",
runtime_config_skills: Sequence[DifyConfigSkillConfig] = (),
) -> tuple[DifyConfigLayerConfig, list[dict[str, str]]]: ) -> tuple[DifyConfigLayerConfig, list[dict[str, str]]]:
"""Build the always-present Agent config layer from Agent Soul state. """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] 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] 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} file_names = {file_ref.name for file_ref in available_files}
warnings: list[dict[str, str]] = [ warnings: list[dict[str, str]] = [
{ {
@@ -965,15 +1016,7 @@ def build_config_layer_config(
kind=config_version_kind, kind=config_version_kind,
writable=config_version_kind == "build_draft", writable=config_version_kind == "build_draft",
), ),
skills=[ skills=skill_configs,
DifyConfigSkillConfig(
name=skill.name,
description=skill.description,
size=skill.size,
mime_type=skill.mime_type,
)
for skill in available_skills
],
files=[ files=[
DifyConfigFileConfig( DifyConfigFileConfig(
name=file_ref.name, 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")
@@ -18,31 +18,109 @@ branch_labels = None
depends_on = None depends_on = None
def _is_pg(conn) -> bool:
return conn.dialect.name == "postgresql"
def _uuid_column(name: str, *, nullable: bool = False, primary_key: bool = False) -> sa.Column:
kwargs = {"nullable": nullable, "primary_key": primary_key}
if primary_key and _is_pg(op.get_bind()):
kwargs["server_default"] = sa.text("uuidv7()")
return sa.Column(name, models.types.StringUUID(), **kwargs)
def _has_table(table_name: str) -> bool:
return sa.inspect(op.get_bind()).has_table(table_name)
def _has_column(table_name: str, column_name: str) -> bool:
return any(
column["name"] == column_name for column in sa.inspect(op.get_bind()).get_columns(table_name)
)
def _has_unique_constraint(table_name: str, constraint_name: str) -> bool:
return any(
constraint["name"] == constraint_name
for constraint in sa.inspect(op.get_bind()).get_unique_constraints(table_name)
)
def upgrade(): def upgrade():
if not _has_table("agent_debug_conversations"):
op.create_table(
"agent_debug_conversations",
_uuid_column("id", primary_key=True),
sa.Column("tenant_id", models.types.StringUUID(), nullable=False),
sa.Column("agent_id", models.types.StringUUID(), nullable=False),
sa.Column("app_id", models.types.StringUUID(), nullable=False),
sa.Column("account_id", models.types.StringUUID(), nullable=False),
sa.Column("conversation_id", models.types.StringUUID(), nullable=False),
sa.Column(
"draft_type",
sa.String(length=32),
nullable=False,
server_default=sa.text("'debug_build'"),
),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.current_timestamp(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.current_timestamp(), nullable=False),
sa.PrimaryKeyConstraint("id", name=op.f("agent_debug_conversation_pkey")),
sa.UniqueConstraint(
"tenant_id",
"agent_id",
"account_id",
"draft_type",
name=op.f("agent_debug_conversation_agent_account_draft_type_unique"),
),
)
op.create_index(
"agent_debug_conversation_conversation_idx",
"agent_debug_conversations",
["conversation_id"],
)
op.create_index(
"agent_debug_conversation_account_idx",
"agent_debug_conversations",
["tenant_id", "account_id"],
)
return
# Existing pointers have always represented Build chat because the Agent # Existing pointers have always represented Build chat because the Agent
# detail API exposes them as ``debug_conversation_id`` for that surface. # detail API exposes them as ``debug_conversation_id`` for that surface.
op.add_column( if not _has_column("agent_debug_conversations", "draft_type"):
op.add_column(
"agent_debug_conversations",
sa.Column(
"draft_type",
sa.String(length=32),
nullable=False,
server_default=sa.text("'debug_build'"),
),
)
if _has_unique_constraint(
"agent_debug_conversations", "agent_debug_conversations",
sa.Column(
"draft_type",
sa.String(length=32),
nullable=False,
server_default=sa.text("'debug_build'"),
),
)
op.drop_constraint(
"agent_debug_conversation_agent_account_unique", "agent_debug_conversation_agent_account_unique",
):
op.drop_constraint(
"agent_debug_conversation_agent_account_unique",
"agent_debug_conversations",
type_="unique",
)
if not _has_unique_constraint(
"agent_debug_conversations", "agent_debug_conversations",
type_="unique",
)
op.create_unique_constraint(
"agent_debug_conversation_agent_account_draft_type_unique", "agent_debug_conversation_agent_account_draft_type_unique",
"agent_debug_conversations", ):
["tenant_id", "agent_id", "account_id", "draft_type"], op.create_unique_constraint(
) "agent_debug_conversation_agent_account_draft_type_unique",
"agent_debug_conversations",
["tenant_id", "agent_id", "account_id", "draft_type"],
)
def downgrade(): def downgrade():
if not _has_table("agent_debug_conversations"):
return
debug_conversations = sa.table( debug_conversations = sa.table(
"agent_debug_conversations", "agent_debug_conversations",
sa.column("tenant_id", models.types.StringUUID()), sa.column("tenant_id", models.types.StringUUID()),
@@ -64,14 +142,23 @@ def downgrade():
), ),
) )
) )
op.drop_constraint( if _has_unique_constraint(
"agent_debug_conversations",
"agent_debug_conversation_agent_account_draft_type_unique", "agent_debug_conversation_agent_account_draft_type_unique",
):
op.drop_constraint(
"agent_debug_conversation_agent_account_draft_type_unique",
"agent_debug_conversations",
type_="unique",
)
if not _has_unique_constraint(
"agent_debug_conversations", "agent_debug_conversations",
type_="unique",
)
op.create_unique_constraint(
"agent_debug_conversation_agent_account_unique", "agent_debug_conversation_agent_account_unique",
"agent_debug_conversations", ):
["tenant_id", "agent_id", "account_id"], op.create_unique_constraint(
) "agent_debug_conversation_agent_account_unique",
op.drop_column("agent_debug_conversations", "draft_type") "agent_debug_conversations",
["tenant_id", "agent_id", "account_id"],
)
if _has_column("agent_debug_conversations", "draft_type"):
op.drop_column("agent_debug_conversations", "draft_type")
@@ -0,0 +1,21 @@
"""merge skill and agent debug conversation heads
Revision ID: e9f4a1b2c3d5
Revises: a4f8d2c9e1b0, d2825e7b9c10
Create Date: 2026-07-23 15:00:00.000000
"""
# revision identifiers, used by Alembic.
revision = "e9f4a1b2c3d5"
down_revision = ("a4f8d2c9e1b0", "d2825e7b9c10")
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass
+7
View File
@@ -113,6 +113,7 @@ from .provider import (
TenantDefaultModel, TenantDefaultModel,
TenantPreferredModelProvider, TenantPreferredModelProvider,
) )
from .skill import AgentSkillBinding, Skill, SkillDraftFile, SkillFileKind, SkillFileStorage, SkillVersion
from .snippet import CustomizedSnippet, SnippetType from .snippet import CustomizedSnippet, SnippetType
from .source import DataSourceApiKeyAuthBinding, DataSourceOauthBinding from .source import DataSourceApiKeyAuthBinding, DataSourceOauthBinding
from .task import CeleryTask, CeleryTaskSet from .task import CeleryTask, CeleryTaskSet
@@ -173,6 +174,7 @@ __all__ = [
"AgentRuntimeSessionOwnerType", "AgentRuntimeSessionOwnerType",
"AgentRuntimeSessionStatus", "AgentRuntimeSessionStatus",
"AgentScope", "AgentScope",
"AgentSkillBinding",
"AgentSource", "AgentSource",
"AgentStatus", "AgentStatus",
"ApiRequest", "ApiRequest",
@@ -246,6 +248,11 @@ __all__ = [
"RecommendedApp", "RecommendedApp",
"SavedMessage", "SavedMessage",
"Site", "Site",
"Skill",
"SkillDraftFile",
"SkillFileKind",
"SkillFileStorage",
"SkillVersion",
"SnippetType", "SnippetType",
"Tag", "Tag",
"TagBinding", "TagBinding",
+1
View File
@@ -249,6 +249,7 @@ class TagType(StrEnum):
KNOWLEDGE = "knowledge" KNOWLEDGE = "knowledge"
APP = "app" APP = "app"
SNIPPET = "snippet" SNIPPET = "snippet"
SKILL = "skill"
class DatasetMetadataType(StrEnum): class DatasetMetadataType(StrEnum):
+1 -1
View File
@@ -2667,7 +2667,7 @@ class Tag(TypeBase):
sa.Index("tag_name_idx", "name"), sa.Index("tag_name_idx", "name"),
) )
TAG_TYPE_LIST = ["knowledge", "app", "snippet"] TAG_TYPE_LIST = ["knowledge", "app", "snippet", "skill"]
id: Mapped[str] = mapped_column( id: Mapped[str] = mapped_column(
StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False 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 | | Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ | | ---- | ---------- | ----------- | -------- | ------ |
| keyword | query | Search keyword | No | string | | 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 #### Responses
@@ -10087,6 +10087,38 @@ Get list of available agent providers
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Success | **application/json**: [AgentProviderListResponse](#agentproviderlistresponse)<br> | | 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 ### [GET] /workspaces/current/customized-snippets
**List customized snippets with pagination and search** **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> | | 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 ### [GET] /workspaces/current/tool-labels
#### Responses #### Responses
@@ -14709,6 +15076,37 @@ Visibility and lifecycle scope of an Agent record.
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| result | string | | Yes | | 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 #### AgentSkillRefConfig
| Name | Type | Description | Required | | Name | Type | Description | Required |
@@ -21665,6 +22063,186 @@ Simple provider entity response.
| title | string | | Yes | | title | string | | Yes |
| use_icon_as_answer_icon | boolean | | 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 #### SkillManifest
Validated metadata extracted from a Skill package. Validated metadata extracted from a Skill package.
@@ -21678,6 +22256,91 @@ Validated metadata extracted from a Skill package.
| name | string | | Yes | | name | string | | Yes |
| size | integer | | 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 #### SkillToolInferenceResult
| Name | Type | Description | Required | | Name | Type | Description | Required |
@@ -21686,6 +22349,60 @@ Validated metadata extracted from a Skill package.
| inferable | boolean | | Yes | | inferable | boolean | | Yes |
| reason | string | | No | | 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 #### SnippetDependencyCheckResponse
| Name | Type | Description | Required | | Name | Type | Description | Required |
@@ -22153,7 +22870,7 @@ Non-sensitive bootstrap snapshot exposed before Console or Web authentication.
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| keyword | string | Search keyword | No | | 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 #### TagListResponse
@@ -24339,6 +25056,15 @@ Workflow tool configuration
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| permission_keys | [ string ] | | No | | 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 #### WorkspaceTenantResultResponse
| Name | Type | Description | Required | | Name | Type | Description | Required |
+3 -1
View File
@@ -1599,7 +1599,9 @@ class TenantService:
return updated_accounts return updated_accounts
@staticmethod @staticmethod
def iter_member_account_id_batches(tenant_id: str, batch_size: int, *, session: Session) -> Iterator[list[str]]: def iter_member_account_id_batches(
tenant_id: str, batch_size: int, *, session: Session
) -> Iterator[list[str]]:
"""Yield workspace member account ids in bounded, ordered batches.""" """Yield workspace member account ids in bounded, ordered batches."""
offset = 0 offset = 0
while True: while True:
+46 -20
View File
@@ -19,12 +19,11 @@ from __future__ import annotations
import hashlib import hashlib
import io import io
import posixpath import posixpath
import re
import zipfile import zipfile
import zlib import zlib
import yaml 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. # Bounds — generous but finite so a hostile upload can't exhaust memory/disk.
_MAX_ARCHIVE_BYTES = 50 * 1024 * 1024 _MAX_ARCHIVE_BYTES = 50 * 1024 * 1024
@@ -33,7 +32,8 @@ _MAX_SKILL_MD_BYTES = 1 * 1024 * 1024
_MAX_ENTRIES = 5000 _MAX_ENTRIES = 5000
_ALLOWED_EXTENSIONS = (".zip", ".skill") _ALLOWED_EXTENSIONS = (".zip", ".skill")
_SKILL_MD_NAME = "SKILL.md" _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): class SkillPackageError(Exception):
@@ -53,13 +53,18 @@ class SkillPackageError(Exception):
class SkillManifest(BaseModel): class SkillManifest(BaseModel):
"""Validated metadata extracted from a Skill package.""" """Validated metadata extracted from a Skill package."""
name: str name: str = Field(min_length=1, max_length=64, pattern=_SKILL_NAME_PATTERN)
description: str description: str = Field(min_length=1, max_length=_MAX_SKILL_DESCRIPTION_LENGTH)
entry_path: str # path of SKILL.md inside the archive entry_path: str # path of SKILL.md inside the archive
files: list[str] # all (safe) file paths inside the archive files: list[str] # all (safe) file paths inside the archive
size: int # total uncompressed bytes size: int # total uncompressed bytes
hash: str # sha256 of the archive 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): class NormalizedSkillPackage(BaseModel):
"""Canonical skill package bytes and metadata ready to store in agent drive.""" """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()) normalized_size = sum(max(info.file_size, 0) for info in normalized_members.values())
name, description = self._parse_skill_md(skill_md) name, description = self._parse_skill_md(skill_md)
manifest = SkillManifest( try:
name=name, manifest = SkillManifest(
description=description, name=name,
entry_path=_SKILL_MD_NAME, description=description,
files=sorted(normalized_members), entry_path=_SKILL_MD_NAME,
size=normalized_size, files=sorted(normalized_members),
hash=hashlib.sha256(normalized_archive_bytes).hexdigest(), size=normalized_size,
) hash=hashlib.sha256(normalized_archive_bytes).hexdigest(),
)
except ValidationError as exc:
raise self._manifest_validation_error(exc) from exc
return NormalizedSkillPackage( return NormalizedSkillPackage(
manifest=manifest, manifest=manifest,
archive_bytes=normalized_archive_bytes, archive_bytes=normalized_archive_bytes,
@@ -123,6 +131,31 @@ class SkillPackageService:
strip_prefix=strip_prefix, 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: def _open_archive(self, *, content: bytes, filename: str) -> zipfile.ZipFile:
self._check_extension(filename) self._check_extension(filename)
if not content: if not content:
@@ -280,13 +313,6 @@ class SkillPackageService:
frontmatter = cls._parse_frontmatter(content) frontmatter = cls._parse_frontmatter(content)
name = str(frontmatter.get("name") or "").strip() name = str(frontmatter.get("name") or "").strip()
description = str(frontmatter.get("description") 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 return name, description
@staticmethod @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.config_skill_normalize_service import ConfigSkillNormalizeService
from services.agent.skill_package_service import SkillPackageError from services.agent.skill_package_service import SkillPackageError
from services.agent_drive_service import DriveFileRef from services.agent_drive_service import DriveFileRef
from services.skill_management_service import SkillManagementService, SkillManagementServiceError
class AgentConfigVersionKind(StrEnum): class AgentConfigVersionKind(StrEnum):
@@ -98,6 +99,7 @@ class ConfigPushPayload(BaseModel):
@dataclass(slots=True) @dataclass(slots=True)
class AgentConfigTarget: class AgentConfigTarget:
tenant_id: str
agent_id: str agent_id: str
version_id: str version_id: str
kind: AgentConfigVersionKind kind: AgentConfigVersionKind
@@ -146,6 +148,7 @@ class AgentConfigService:
user_id=user_id, user_id=user_id,
) )
return AgentConfigTarget( return AgentConfigTarget(
tenant_id=tenant_id,
agent_id=target.agent_id, agent_id=target.agent_id,
version_id=target.version_id, version_id=target.version_id,
kind=target.kind, kind=target.kind,
@@ -191,7 +194,7 @@ class AgentConfigService:
return { return {
"agent_id": target.agent_id, "agent_id": target.agent_id,
"config_version": self._config_version_payload(target), "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( def list_files(
@@ -233,10 +236,27 @@ class AgentConfigService:
config_version_kind=config_version_kind, config_version_kind=config_version_kind,
user_id=user_id, user_id=user_id,
) )
skill = self._require_skill(target.agent_soul, name=name) try:
file_id = self._available_skill_file_id(skill) skill = self._require_skill(target.agent_soul, name=name)
payload, mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id) file_id = self._available_skill_file_id(skill)
return ConfigDownload(filename=f"{skill.name}.zip", mime_type=mime_type or "application/zip", payload=payload) 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( def download_skill_url(
self, self,
@@ -279,9 +299,45 @@ class AgentConfigService:
config_version_kind=config_version_kind, config_version_kind=config_version_kind,
user_id=user_id, user_id=user_id,
) )
skill = self._require_skill(target.agent_soul, name=name) try:
file_id = self._available_skill_file_id(skill) skill = self._require_skill(target.agent_soul, name=name)
archive_bytes, _mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id) 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: try:
archive_items, skill_md = self._inspect_skill_archive(archive_bytes) archive_items, skill_md = self._inspect_skill_archive(archive_bytes)
except (OSError, ValueError, zipfile.BadZipFile) as exc: except (OSError, ValueError, zipfile.BadZipFile) as exc:
@@ -291,7 +347,7 @@ class AgentConfigService:
status_code=500, status_code=500,
) from exc ) from exc
return { return {
**self._serialize_skill_item(skill), **skill_item,
"source": "config_skill_zip", "source": "config_skill_zip",
"files": archive_items, "files": archive_items,
"skill_md": skill_md, "skill_md": skill_md,
@@ -839,6 +895,7 @@ class AgentConfigService:
status_code=404, status_code=404,
) )
return AgentConfigTarget( return AgentConfigTarget(
tenant_id=tenant_id,
agent_id=agent_id, agent_id=agent_id,
version_id=version.id, version_id=version.id,
kind=config_version_kind, kind=config_version_kind,
@@ -1133,9 +1190,7 @@ class AgentConfigService:
return { return {
"agent_id": target.agent_id, "agent_id": target.agent_id,
"config_version": AgentConfigService._config_version_payload(target), "config_version": AgentConfigService._config_version_payload(target),
"skills": { "skills": {"items": AgentConfigService._skill_items_for_target(target)},
"items": [AgentConfigService._serialize_skill_item(skill) for skill in target.agent_soul.config_skills]
},
"files": { "files": {
"items": [ "items": [
AgentConfigService._serialize_file_item(file_ref) for file_ref in target.agent_soul.config_files 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, "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 @staticmethod
def _config_version_payload(target: AgentConfigTarget) -> dict[str, object]: def _config_version_payload(target: AgentConfigTarget) -> dict[str, object]:
return { return {
File diff suppressed because it is too large Load Diff
+9
View File
@@ -12,6 +12,7 @@ from werkzeug.exceptions import NotFound
from models.dataset import Dataset from models.dataset import Dataset
from models.enums import TagType from models.enums import TagType
from models.model import App, Tag, TagBinding from models.model import App, Tag, TagBinding
from models.skill import Skill
from models.snippet import CustomizedSnippet from models.snippet import CustomizedSnippet
type _TagTypeLike = TagType | str type _TagTypeLike = TagType | str
@@ -282,5 +283,13 @@ class TagService:
) )
if not snippet: if not snippet:
raise NotFound("Snippet not found") 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: else:
raise NotFound("Invalid binding type") 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 from typing import Any
import pytest import pytest
from dify_agent.layers.config import DifyConfigSkillConfig
from dify_agent.layers.dify_core_tools import DifyCoreToolConfig, DifyCoreToolsLayerConfig from dify_agent.layers.dify_core_tools import DifyCoreToolConfig, DifyCoreToolsLayerConfig
from dify_agent.layers.dify_plugin import DifyPluginToolConfig, DifyPluginToolsLayerConfig from dify_agent.layers.dify_plugin import DifyPluginToolConfig, DifyPluginToolsLayerConfig
from dify_agent.layers.execution_context import DifyExecutionContextLayerConfig 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 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: def _exec_ctx() -> DifyExecutionContextLayerConfig:
return DifyExecutionContextLayerConfig( return DifyExecutionContextLayerConfig(
tenant_id="tenant-1", tenant_id="tenant-1",
@@ -514,6 +523,33 @@ class TestAgentAppConfigLayer:
"mentioned_file_names": [], "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( @pytest.mark.parametrize(
("system_prompt", "expected_prefix"), ("system_prompt", "expected_prefix"),
[ [
@@ -5,6 +5,7 @@ from typing import cast
import pytest import pytest
from agenton.compositor import CompositorSessionSnapshot 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_core_tools import DifyCoreToolConfig, DifyCoreToolsLayerConfig
from dify_agent.layers.dify_plugin import DifyPluginToolConfig, DifyPluginToolsLayerConfig from dify_agent.layers.dify_plugin import DifyPluginToolConfig, DifyPluginToolsLayerConfig
from dify_agent.protocol import DIFY_AGENT_HISTORY_LAYER_ID, DIFY_AGENT_MODEL_LAYER_ID 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: class FakeCredentialsProvider:
def fetch(self, provider_name: str, model_name: str) -> dict[str, object]: def fetch(self, provider_name: str, model_name: str) -> dict[str, object]:
assert provider_name == "openai" assert provider_name == "openai"
@@ -1420,6 +1429,30 @@ def test_build_config_layer_config_includes_soul_context_and_mentions():
assert warnings == [] 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(): 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 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) 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(): def test_workflow_runtime_expands_config_mentions_in_agent_soul_prompt():
context = _context() context = _context()
context.snapshot.config_snapshot = _soul_with_config_assets() 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 from services.agent.skill_package_service import NormalizedSkillPackage, SkillPackageError, SkillPackageService
_SKILL_MD = """--- _SKILL_MD = """---
name: PDF Toolkit name: pdf-toolkit
description: Tools for working with PDF files. description: Tools for working with PDF files.
--- ---
@@ -43,7 +43,7 @@ def _archive_members(content: bytes) -> list[str]:
def test_valid_skill_normalizes_manifest(): def test_valid_skill_normalizes_manifest():
manifest = _normalize({"SKILL.md": _SKILL_MD.encode(), "scripts/run.py": b"print('hi')\n"}).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.description == "Tools for working with PDF files."
assert manifest.entry_path == "SKILL.md" assert manifest.entry_path == "SKILL.md"
assert set(manifest.files) == {"SKILL.md", "scripts/run.py"} assert set(manifest.files) == {"SKILL.md", "scripts/run.py"}
@@ -51,10 +51,10 @@ def test_valid_skill_normalizes_manifest():
assert len(manifest.hash) == 64 assert len(manifest.hash) == 64
def test_name_falls_back_to_heading_without_frontmatter(): def test_name_and_description_are_required_in_frontmatter():
manifest = _normalize({"SKILL.md": b"# Heading Name\n\nbody"}).manifest with pytest.raises(SkillPackageError) as exc_info:
assert manifest.name == "Heading Name" _normalize({"SKILL.md": b"# heading-name\n\nbody"})
assert manifest.description == "" assert exc_info.value.code == "missing_skill_name"
def test_shallowest_skill_md_preferred_during_normalization(): 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"), ({"README.md": b"x"}, "skill.zip", "missing_skill_md"),
({"SKILL.md": _SKILL_MD.encode()}, "skill.tar", "unsupported_extension"), ({"SKILL.md": _SKILL_MD.encode()}, "skill.tar", "unsupported_extension"),
({"SKILL.md": b""}, "skill.zip", "empty_skill_md"), ({"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"), ({"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" assert exc_info.value.code == "invalid_frontmatter"
def test_unterminated_frontmatter_falls_back_to_heading(): def test_unterminated_frontmatter_rejected():
# leading '---' with no closing fence -> no frontmatter, use the heading with pytest.raises(SkillPackageError) as exc_info:
manifest = _normalize({"SKILL.md": b"---\n# Heading Wins\nbody"}).manifest _normalize({"SKILL.md": b"---\n# heading-wins\nbody"})
assert manifest.name == "Heading Wins" assert exc_info.value.code == "missing_skill_name"
def test_validate_and_normalize_rejects_files_outside_selected_skill_root(): 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" _USER_ID = "33333333-3333-3333-3333-333333333333"
_SKILL_MD = b"""--- _SKILL_MD = b"""---
name: PDF Toolkit name: pdf-toolkit
description: Work with PDFs. 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.is_skill is True
assert skill_row.skill_metadata is not None assert skill_row.skill_metadata is not None
skill_metadata = DriveSkillMetadata.model_validate_json(skill_row.skill_metadata) 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 skill_metadata.manifest_files == ["SKILL.md", "scripts/run.py"]
assert archive_row.file_kind == AgentDriveFileKind.TOOL_FILE assert archive_row.file_kind == AgentDriveFileKind.TOOL_FILE
assert archive_row.file_id == archive_tool_file.id 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. # The returned upload response carries only the drive-derived fields the UI needs.
skill = result["skill"] skill = result["skill"]
assert skill["path"] == "pdf-toolkit" 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["archive_key"] == "pdf-toolkit/.DIFY-SKILL-FULL.zip"
assert skill["skill_md_key"] == "pdf-toolkit/SKILL.md" assert skill["skill_md_key"] == "pdf-toolkit/SKILL.md"
assert result["manifest"]["entry_path"] == "SKILL.md" assert result["manifest"]["entry_path"] == "SKILL.md"
@@ -63,6 +63,7 @@ def _target(
) -> AgentConfigTarget: ) -> AgentConfigTarget:
agent_soul = soul or _soul() agent_soul = soul or _soul()
return AgentConfigTarget( return AgentConfigTarget(
tenant_id=TENANT,
agent_id=AGENT, agent_id=AGENT,
version_id=version_id, version_id=version_id,
kind=kind, 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 == { assert manifest == {
"agent_id": AGENT, "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) target = _target(kind=AgentConfigVersionKind.DRAFT, writable=False, soul=soul)
service = AgentConfigService() 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["skills"]["items"][0]["is_missing"] is True # type: ignore[index]
assert manifest["files"]["items"][0]["is_missing"] is True # type: ignore[index] assert manifest["files"]["items"][0]["is_missing"] is True # type: ignore[index]
@@ -606,6 +611,44 @@ 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: def test_preview_skill_file_returns_text_preview() -> None:
service = AgentConfigService() service = AgentConfigService()
target = _target( 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. * Validated metadata extracted from a Skill package.
*/ */
export const zSkillManifest = z.object({ export const zSkillManifest = z.object({
description: z.string(), description: z.string().min(1).max(1024),
entry_path: z.string(), entry_path: z.string(),
files: z.array(z.string()), files: z.array(z.string()),
hash: 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(), size: z.int(),
}) })
@@ -1373,11 +1373,15 @@ export const zAgentLogMetaResponse = z.object({
* Validated metadata extracted from a Skill package. * Validated metadata extracted from a Skill package.
*/ */
export const zSkillManifest = z.object({ export const zSkillManifest = z.object({
description: z.string(), description: z.string().min(1).max(1024),
entry_path: z.string(), entry_path: z.string(),
files: z.array(z.string()), files: z.array(z.string()),
hash: 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(), size: z.int(),
}) })
@@ -20,7 +20,7 @@ export type TagBindingRemovePayload = {
type: TagType type: TagType
} }
export type TagType = 'app' | 'knowledge' | 'snippet' export type TagType = 'app' | 'knowledge' | 'skill' | 'snippet'
export type PostTagBindingsData = { export type PostTagBindingsData = {
body: TagBindingPayload body: TagBindingPayload
@@ -14,7 +14,7 @@ export const zSimpleResultResponse = z.object({
* *
* Tag type * Tag type
*/ */
export const zTagType = z.enum(['app', 'knowledge', 'snippet']) export const zTagType = z.enum(['app', 'knowledge', 'skill', 'snippet'])
/** /**
* TagBindingPayload * TagBindingPayload
@@ -22,14 +22,14 @@ export type TagUpdateRequestPayload = {
name: string name: string
} }
export type TagType = 'app' | 'knowledge' | 'snippet' export type TagType = 'app' | 'knowledge' | 'skill' | 'snippet'
export type GetTagsData = { export type GetTagsData = {
body?: never body?: never
path?: never path?: never
query?: { query?: {
keyword?: string keyword?: string
type?: '' | 'app' | 'knowledge' | 'snippet' type?: '' | 'app' | 'knowledge' | 'skill' | 'snippet'
} }
url: '/tags' url: '/tags'
} }
@@ -29,7 +29,7 @@ export const zTagUpdateRequestPayload = z.object({
* *
* Tag type * Tag type
*/ */
export const zTagType = z.enum(['app', 'knowledge', 'snippet']) export const zTagType = z.enum(['app', 'knowledge', 'skill', 'snippet'])
/** /**
* TagBasePayload * TagBasePayload
@@ -41,7 +41,7 @@ export const zTagBasePayload = z.object({
export const zGetTagsQuery = z.object({ export const zGetTagsQuery = z.object({
keyword: z.string().optional(), keyword: z.string().optional(),
type: z.enum(['', 'app', 'knowledge', 'snippet']).optional().default(''), type: z.enum(['', 'app', 'knowledge', 'skill', 'snippet']).optional().default(''),
}) })
/** /**
File diff suppressed because it is too large Load Diff
@@ -32,6 +32,16 @@ export type AgentProviderListResponse = Array<{
[key: string]: unknown [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 = { export type SnippetPaginationResponse = {
data: Array<SnippetListItemResponse> data: Array<SnippetListItemResponse>
has_more: boolean has_more: boolean
@@ -635,6 +645,194 @@ export type WorkspaceAccessMatrix = {
pagination?: Pagination | null 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 SkillAssistModelPayload = {
model: string
model_settings?: { [key: string]: unknown } | null
plugin_id?: string | null
provider: string
}
export type SkillAssistAttachmentPayload = {
mime_type?: string | null
name: string
size?: number | null
tool_file_id: string
}
export type SkillAssistMessagePayload = {
attachments?: Array<SkillAssistAttachmentPayload>
message: string
model?: SkillAssistModelPayload | null
target_path?: string | 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 ToolLabelListResponse = Array<ToolLabel>
export type ApiToolProviderAddPayload = { export type ApiToolProviderAddPayload = {
@@ -1042,6 +1240,21 @@ export type WorkspaceCustomConfigResponse = {
replace_webapp_logo?: string | null 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 = { export type SnippetListItemResponse = {
author_name: string | null author_name: string | null
created_at: number created_at: number
@@ -1502,6 +1715,60 @@ export type AccessPolicyRole = {
role_tag?: string 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 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_id: string
agent_icon?: string | null
agent_icon_background?: string | null
agent_icon_type?: string | null
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 = { export type ToolLabel = {
icon: string icon: string
label: I18nObject label: I18nObject
@@ -1978,6 +2245,10 @@ export type PermissionCatalogItem = {
name: string name: string
} }
export type SkillFileKind = 'directory' | 'file'
export type SkillFileStorage = 'text' | 'tool_file'
export type ToolParameter = { export type ToolParameter = {
auto_generate?: PluginParameterAutoGenerate | null auto_generate?: PluginParameterAutoGenerate | null
default?: default?:
@@ -2473,6 +2744,38 @@ export type GetWorkspacesCurrentAgentProvidersResponses = {
export type GetWorkspacesCurrentAgentProvidersResponse = export type GetWorkspacesCurrentAgentProvidersResponse =
GetWorkspacesCurrentAgentProvidersResponses[keyof GetWorkspacesCurrentAgentProvidersResponses] 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 = { export type GetWorkspacesCurrentCustomizedSnippetsData = {
body?: never body?: never
path?: never path?: never
@@ -4625,6 +4928,369 @@ export type GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses = {
export type GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponse = export type GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponse =
GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses[keyof GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses] 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 = { export type GetWorkspacesCurrentToolLabelsData = {
body?: never body?: never
path?: 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())) 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 * SnippetImportPayload
* *
@@ -436,6 +443,155 @@ export const zReplaceBindingsRequest = z.object({
role_ids: z.array(z.string()).optional(), 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 * ApiToolProviderDeletePayload
*/ */
@@ -656,6 +812,33 @@ export const zSwitchWorkspaceResponse = z.object({
result: z.string(), 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 * IconInfo
* *
@@ -1244,6 +1427,164 @@ export const zWorkspaceAccessMatrix = z.object({
pagination: zPagination.nullish(), 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(),
target_path: z.string().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_id: z.string(),
agent_icon: z.string().nullish(),
agent_icon_background: z.string().nullish(),
agent_icon_type: z.string().nullish(),
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 * ToolEmojiIcon
*/ */
@@ -2096,6 +2437,42 @@ export const zPermissionCatalogResponse = z.object({
groups: z.array(zPermissionCatalogGroup).optional(), 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 * Option
*/ */
@@ -3421,6 +3798,26 @@ export const zGetWorkspacesCurrentAgentProviderByProviderNameResponse = zAgentPr
*/ */
export const zGetWorkspacesCurrentAgentProvidersResponse = zAgentProviderListResponse 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({ export const zGetWorkspacesCurrentCustomizedSnippetsQuery = z.object({
creators: z.array(z.string()).optional(), creators: z.array(z.string()).optional(),
is_published: z.boolean().optional(), is_published: z.boolean().optional(),
@@ -4694,6 +5091,234 @@ export const zGetWorkspacesCurrentRbacWorkspaceDatasetsAccessPoliciesByPolicyIdR
*/ */
export const zGetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponse = zWorkspaceAccessMatrix 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 * 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` | | 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/icon` | Plugin asset MIME type | `BinaryFileResponse` |
| console | GET | `/workspaces/current/plugin/asset` | `application/octet-stream` | `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/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/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` | | 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) await user.click(processToggle)
expect(processToggle).toHaveAttribute('aria-expanded', 'true') expect(processToggle).toHaveAttribute('aria-expanded', 'true')
await waitFor(() => { await waitFor(
expect(screen.getByText('history answer')).toBeInTheDocument() () => {
}) expect(screen.getByText('history answer')).toBeInTheDocument()
},
{ timeout: 5000 },
)
expect(screen.queryByText('internal thought should not render')).not.toBeInTheDocument() expect(screen.queryByText('internal thought should not render')).not.toBeInTheDocument()
}) })
@@ -122,9 +125,12 @@ describe('AgentRosterResponseContent', () => {
render(<AgentRosterResponseContent item={item} />) render(<AgentRosterResponseContent item={item} />)
await user.click(screen.getByRole('button', { name: 'Thinking' })) await user.click(screen.getByRole('button', { name: 'Thinking' }))
await waitFor(() => { await waitFor(
expect(screen.getByText('const answer = 42').tagName).toBe('CODE') () => {
}) expect(screen.getByText('const answer = 42').tagName).toBe('CODE')
},
{ timeout: 5000 },
)
}) })
it('should keep one collapsible thinking timeline while response parts interleave', async () => { 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' import ModelParameterModal from '../index'
let parameterRules: Array<Record<string, unknown>> | undefined = [ const mocks = vi.hoisted(() => ({
{ openIntegrationsSetting: vi.fn(),
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'],
},
],
},
]
vi.mock('@/context/provider-context', () => ({ vi.mock('@/app/components/header/account-setting/use-integrations-setting', () => ({
useProviderContext: () => ({ useIntegrationsSetting: () => mocks.openIntegrationsSetting,
isAPIKeySet: true,
}),
})) }))
vi.mock('@/service/use-common', () => ({ vi.mock('@/service/use-common', () => ({
useModelParameterRules: () => ({ useModelParameterRules: () => ({
data: { data: {
data: parameterRules, data: [],
}, },
isLoading: isRulesLoading, isLoading: false,
isPending: isRulesPending,
}), }),
})) }))
vi.mock('../../hooks', () => ({ vi.mock('../../hooks', () => ({
useTextGenerationCurrentProviderAndModelAndModelList: () => ({ useTextGenerationCurrentProviderAndModelAndModelList: () => ({
currentProvider, activeTextGenerationModelList: [],
currentModel, currentModel: undefined,
activeTextGenerationModelList, currentProvider: undefined,
}), }),
})) }))
vi.mock('../parameter-item', () => ({ vi.mock('../../model-selector', () => ({
default: ({ default: ({ onConfigureEmptyState }: { onConfigureEmptyState?: () => void }) => (
parameterRule, <button type="button" onClick={onConfigureEmptyState}>
onChange, configure-empty-model
onSwitch, </button>
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', () => ({ vi.mock('@/app/components/base/loading', () => ({
default: ({ default: () => <div>loading</div>,
onHide, }))
onSelect,
}: { vi.mock('../parameter-item', () => ({
onHide: () => void default: () => <div>parameter-item</div>,
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('../presets-parameter', () => ({ vi.mock('../presets-parameter', () => ({
default: ({ default: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
onSelect,
supportedParameterNames,
}: {
onSelect: (id: number) => void
supportedParameterNames?: string[]
}) => {
if (supportedParameterNames && !supportedParameterNames.includes('temperature')) return null
return <button onClick={() => onSelect(1)}>Preset 1</button>
},
})) }))
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', () => { 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(() => { beforeEach(() => {
vi.clearAllMocks() 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', () => { it('opens provider settings from the model selector empty state', () => {
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' } }]
render( render(
<ModelParameterModal <ModelParameterModal
{...defaultProps} isAdvancedMode
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=""
modelId="" modelId=""
isInWorkflow provider=""
renderTrigger={({ open }) => <span>{open ? 'Custom Open' : 'Custom Closed'}</span>} 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(mocks.openIntegrationsSetting).toHaveBeenCalledWith({
expect(screen.getByTestId('model-selector')).toBeInTheDocument() payload: 'provider',
fireEvent.click(screen.getByText('hide'))
await waitFor(() => {
expect(screen.queryByTestId('model-selector')).not.toBeInTheDocument()
}) })
}) })
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 { 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 { ParameterValue } from './parameter-item'
import type { TriggerProps } from './types' import type { TriggerProps } from './types'
import type { Node, NodeOutPutVar } from '@/app/components/workflow/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 { Popover, PopoverClose, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
import Loading from '@/app/components/base/loading' 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 { PROVIDER_WITH_PRESET_TONE, STOP_PARAMETER_RULE } from '@/config'
import { useModelParameterRules } from '@/service/use-common' import { useModelParameterRules } from '@/service/use-common'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '../hooks' import { useTextGenerationCurrentProviderAndModelAndModelList } from '../hooks'
@@ -34,8 +35,10 @@ export type ModelParameterModalProps = {
debugWithMultipleModel?: boolean debugWithMultipleModel?: boolean
onDebugWithMultipleModelChange?: () => void onDebugWithMultipleModelChange?: () => void
renderTrigger?: (v: TriggerProps) => ReactNode renderTrigger?: (v: TriggerProps) => ReactNode
triggerContainerClassName?: string
readonly?: boolean readonly?: boolean
isInWorkflow?: boolean isInWorkflow?: boolean
modelList?: Model[]
scope?: string scope?: string
nodesOutputVars?: NodeOutPutVar[] nodesOutputVars?: NodeOutPutVar[]
availableNodes?: Node[] availableNodes?: Node[]
@@ -53,17 +56,25 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
debugWithMultipleModel, debugWithMultipleModel,
onDebugWithMultipleModelChange, onDebugWithMultipleModelChange,
renderTrigger, renderTrigger,
triggerContainerClassName,
readonly, readonly,
isInWorkflow, isInWorkflow,
modelList,
nodesOutputVars, nodesOutputVars,
availableNodes, availableNodes,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const openIntegrationsSetting = useIntegrationsSetting()
const { data: parameterRulesData, isLoading } = useModelParameterRules(provider, modelId) const { data: parameterRulesData, isLoading } = useModelParameterRules(provider, modelId)
const isRulesLoading = !!provider && !!modelId && isLoading const isRulesLoading = !!provider && !!modelId && isLoading
const { currentProvider, currentModel, activeTextGenerationModelList } = const { currentProvider, currentModel, activeTextGenerationModelList } =
useTextGenerationCurrentProviderAndModelAndModelList({ provider, model: modelId }) 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(() => { const parameterRules: ModelParameterRule[] = useMemo(() => {
return parameterRulesData?.data || [] return parameterRulesData?.data || []
@@ -71,6 +82,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
const supportedPresetParameterNames = useMemo(() => { const supportedPresetParameterNames = useMemo(() => {
return parameterRules.map((parameterRule) => parameterRule.name) return parameterRules.map((parameterRule) => parameterRule.name)
}, [parameterRules]) }, [parameterRules])
const hasSelectedModel = !!provider && !!modelId
const handleParamChange = (key: string, value: ParameterValue) => { const handleParamChange = (key: string, value: ParameterValue) => {
onCompletionParamsChange({ onCompletionParamsChange({
@@ -80,10 +92,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
} }
const handleChangeModel = ({ provider, model }: DefaultModel) => { const handleChangeModel = ({ provider, model }: DefaultModel) => {
const targetProvider = activeTextGenerationModelList.find( const targetProvider = availableTextGenerationModelList.find(
(modelItem) => modelItem.provider === provider, (modelItem) => modelItem.provider === provider,
) )
const targetModelItem = targetProvider?.models.find((modelItem) => modelItem.model === model) const targetModelItem = targetProvider?.models?.find((modelItem) => modelItem.model === model)
setModel({ setModel({
modelId: model, modelId: model,
provider, provider,
@@ -91,6 +103,15 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
features: targetModelItem?.features || [], 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) => { const handleSwitch = (key: string, value: boolean, assignValue: ParameterValue) => {
if (!value) { if (!value) {
@@ -114,8 +135,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
}) })
} }
const hasSelectedModel = !!provider && !!modelId
return ( return (
<Popover <Popover
open={open} open={open}
@@ -133,8 +152,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
> >
{renderTrigger({ {renderTrigger({
open, open,
currentProvider, currentProvider: selectedProvider,
currentModel, currentModel: selectedModel,
providerName: provider, providerName: provider,
modelId, 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"> <div className="min-w-0 flex-1">
<ModelSelector <ModelSelector
defaultModel={provider || modelId ? { provider, model: modelId } : undefined} defaultModel={provider || modelId ? { provider, model: modelId } : undefined}
modelList={activeTextGenerationModelList} modelList={availableTextGenerationModelList}
readonly={readonly} readonly={readonly}
triggerClassName={cn( triggerClassName={cn(
'h-8! w-full rounded-r-none!', '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', 'border border-workflow-block-parma-bg bg-workflow-block-parma-bg hover:bg-workflow-block-parma-bg',
)} )}
onSelect={handleChangeModel} onSelect={handleChangeModel}
onConfigureEmptyState={handleConfigureEmptyState}
onOpenProviderSettings={handleOpenModelSettings}
/> />
</div> </div>
<PopoverTrigger <PopoverTrigger
@@ -187,8 +213,9 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
<div className="px-4 pt-2 pb-4"> <div className="px-4 pt-2 pb-4">
<ModelSelector <ModelSelector
defaultModel={hasSelectedModel ? { provider, model: modelId } : undefined} defaultModel={hasSelectedModel ? { provider, model: modelId } : undefined}
modelList={activeTextGenerationModelList} modelList={availableTextGenerationModelList}
onSelect={handleChangeModel} onSelect={handleChangeModel}
onOpenProviderSettings={handleOpenModelSettings}
onHide={() => setOpen(false)} onHide={() => setOpen(false)}
/> />
</div> </div>
@@ -249,7 +276,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
{debugWithMultipleModel {debugWithMultipleModel
? t(($) => $.debugAsSingleModel, { ns: 'appDebug' }) ? t(($) => $.debugAsSingleModel, { ns: 'appDebug' })
: t(($) => $.debugAsMultipleModel, { 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> </div>
)} )}
</PopoverContent> </PopoverContent>
@@ -33,6 +33,7 @@ type ModelSelectorProps = {
hideProviderSettingsFooter?: boolean hideProviderSettingsFooter?: boolean
onConfigureEmptyState?: () => void onConfigureEmptyState?: () => void
onOpenMarketplace?: () => void onOpenMarketplace?: () => void
onOpenProviderSettings?: () => void
providerSettingsSource?: 'agent' providerSettingsSource?: 'agent'
showModelMeta?: boolean showModelMeta?: boolean
modelPredicate?: ModelSelectorModelPredicate modelPredicate?: ModelSelectorModelPredicate
@@ -52,6 +53,7 @@ function ModelSelector({
hideProviderSettingsFooter, hideProviderSettingsFooter,
onConfigureEmptyState, onConfigureEmptyState,
onOpenMarketplace, onOpenMarketplace,
onOpenProviderSettings,
providerSettingsSource, providerSettingsSource,
showModelMeta, showModelMeta,
modelPredicate, modelPredicate,
@@ -180,6 +182,7 @@ function ModelSelector({
modelSuggestionPredicate={modelSuggestionPredicate} modelSuggestionPredicate={modelSuggestionPredicate}
onConfigureEmptyState={onConfigureEmptyState ? handleConfigureEmptyState : undefined} onConfigureEmptyState={onConfigureEmptyState ? handleConfigureEmptyState : undefined}
onOpenMarketplace={onOpenMarketplace} onOpenMarketplace={onOpenMarketplace}
onOpenProviderSettings={onOpenProviderSettings}
onInputValueChange={setInputValue} onInputValueChange={setInputValue}
onHide={handleHide} onHide={handleHide}
/> />
@@ -9,7 +9,6 @@ import { StatusDot } from '@langgenius/dify-ui/status-dot'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
import { useModalContext } from '@/context/modal-context' import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { useCredentialPermissions } from '@/hooks/use-credential-permissions' import { useCredentialPermissions } from '@/hooks/use-credential-permissions'
@@ -57,7 +56,8 @@ function PopupItem({
const updateModelProviders = useUpdateModelProviders() const updateModelProviders = useUpdateModelProviders()
const currentProvider = modelProviders.find((provider) => provider.provider === model.provider) const currentProvider = modelProviders.find((provider) => provider.provider === model.provider)
const { canUseCredential, canCreateCredential, canManageCredential } = useCredentialPermissions() const { canUseCredential, canCreateCredential, canManageCredential } = useCredentialPermissions()
const canOpenCredentialDropdown = canUseCredential || canCreateCredential || canManageCredential const canOpenCredentialDropdown =
!!currentProvider && (canUseCredential || canCreateCredential || canManageCredential)
const handleOpenModelModal = () => { const handleOpenModelModal = () => {
if (!canCreateCredential) return 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 { isChangingPriority, handleChangePriority } = useChangeProviderPriority(currentProvider)
const groupItems = useMemo( const groupItems = useMemo(
() => () =>
@@ -90,10 +91,11 @@ function PopupItem({
[model.models, model.provider], [model.models, model.provider],
) )
const isUsingCredits = state.priority === 'credits' const isUsingCredits = credentialPanelState.priority === 'credits'
const hasCredits = !state.isCreditsExhausted const hasCredits = !credentialPanelState.isCreditsExhausted
const isApiKeyActive = state.variant === 'api-active' || state.variant === 'api-fallback' const isApiKeyActive =
const { credentialName } = state credentialPanelState.variant === 'api-active' || credentialPanelState.variant === 'api-fallback'
const { credentialName } = credentialPanelState
const handleCloseDropdown = useCallback(() => { const handleCloseDropdown = useCallback(() => {
setDropdownOpen(false) setDropdownOpen(false)
@@ -129,7 +131,10 @@ function PopupItem({
{isUsingCredits ? ( {isUsingCredits ? (
hasCredits ? ( hasCredits ? (
<> <>
<CreditsCoin className="size-3" /> <span
aria-hidden
className="i-custom-vender-line-financeandecommerce-credits-coin size-3"
/>
<span className="ml-1 truncate"> <span className="ml-1 truncate">
{t(($) => $['modelProvider.selector.aiCredits'], { ns: 'common' })} {t(($) => $['modelProvider.selector.aiCredits'], { ns: 'common' })}
</span> </span>
@@ -161,15 +166,17 @@ function PopupItem({
</button> </button>
} }
/> />
<PopoverContent placement="bottom-end"> {currentProvider && (
<DropdownContent <PopoverContent placement="bottom-end">
provider={currentProvider} <DropdownContent
state={state} provider={currentProvider}
isChangingPriority={isChangingPriority} state={credentialPanelState}
onChangePriority={handleChangePriority} isChangingPriority={isChangingPriority}
onClose={handleCloseDropdown} onChangePriority={handleChangePriority}
/> onClose={handleCloseDropdown}
</PopoverContent> />
</PopoverContent>
)}
</Popover> </Popover>
</div> </div>
{!collapsed && {!collapsed &&
@@ -215,7 +222,7 @@ function PopupItem({
</ModelName> </ModelName>
</div> </div>
{defaultModel?.model === modelItem.model && {defaultModel?.model === modelItem.model &&
defaultModel.provider === currentProvider.provider && ( defaultModel.provider === model.provider && (
<ComboboxItemIndicator className="shrink-0 text-text-accent"> <ComboboxItemIndicator className="shrink-0 text-text-accent">
<span <span
className="i-custom-vender-line-general-check size-4" className="i-custom-vender-line-general-check size-4"
@@ -69,6 +69,7 @@ export type PopupProps = {
onConfigureEmptyState?: () => void onConfigureEmptyState?: () => void
onInputValueChange: (value: string) => void onInputValueChange: (value: string) => void
onOpenMarketplace?: () => void onOpenMarketplace?: () => void
onOpenProviderSettings?: () => void
onHide: () => void onHide: () => void
} }
function Popup({ function Popup({
@@ -83,6 +84,7 @@ function Popup({
onConfigureEmptyState, onConfigureEmptyState,
onInputValueChange, onInputValueChange,
onOpenMarketplace, onOpenMarketplace,
onOpenProviderSettings,
onHide, onHide,
}: PopupProps) { }: PopupProps) {
const { t } = useTranslation() const { t } = useTranslation()
@@ -255,11 +257,16 @@ function Popup({
const handleOpenSettings = useCallback(() => { const handleOpenSettings = useCallback(() => {
onHide() onHide()
if (onOpenProviderSettings) {
onOpenProviderSettings()
return
}
openIntegrationsSetting({ openIntegrationsSetting({
payload: ACCOUNT_SETTING_TAB.PROVIDER, payload: ACCOUNT_SETTING_TAB.PROVIDER,
source: providerSettingsSource, source: providerSettingsSource,
}) })
}, [onHide, openIntegrationsSetting, providerSettingsSource]) }, [onHide, onOpenProviderSettings, openIntegrationsSetting, providerSettingsSource])
const handleClosePreviewCard = useCallback(() => { const handleClosePreviewCard = useCallback(() => {
previewCardHandle.close() previewCardHandle.close()
}, [previewCardHandle]) }, [previewCardHandle])
@@ -564,6 +564,10 @@ describe('MainNav', () => {
expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps') 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/ })).toHaveAttribute('href', '/agents')
expect(screen.getByRole('link', { name: /Agents common.menus.status/ })).toBeInTheDocument() 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( expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveAttribute(
'href', 'href',
'/datasets', '/datasets',
@@ -584,6 +588,10 @@ describe('MainNav', () => {
renderMainNav() renderMainNav()
expect(screen.queryByRole('link', { name: /Agents/ })).not.toBeInTheDocument() 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', () => { 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.mainNav.home/ })).toHaveAttribute('href', '/')
expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps') expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps')
expect(screen.queryByRole('link', { name: /Agents/ })).not.toBeInTheDocument() 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( expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveAttribute(
'href', 'href',
'/datasets', '/datasets',
+11
View File
@@ -32,6 +32,8 @@ export type DetailSidebarVisibilityOptions = Pick<
const VISIBLE_TO_ALL: MainNavRouteVisibility = () => true const VISIBLE_TO_ALL: MainNavRouteVisibility = () => true
const CAN_MANAGE_AGENTS: MainNavRouteVisibility = (options) => options.canManageAgents const CAN_MANAGE_AGENTS: MainNavRouteVisibility = (options) => options.canManageAgents
const CAN_USE_APP_DEPLOY: MainNavRouteVisibility = (options) => options.canUseAppDeploy const CAN_USE_APP_DEPLOY: MainNavRouteVisibility = (options) => options.canUseAppDeploy
const NOT_DATASET_OPERATOR: MainNavRouteVisibility = (options) =>
!options.isCurrentWorkspaceDatasetOperator
function isPathUnderRoute(pathname: string, route: string) { function isPathUnderRoute(pathname: string, route: string) {
return pathname === route || pathname.startsWith(`${route}/`) return pathname === route || pathname.startsWith(`${route}/`)
@@ -69,6 +71,15 @@ export const MAIN_NAV_ROUTES = [
visibility: CAN_MANAGE_AGENTS, visibility: CAN_MANAGE_AGENTS,
feature: 'agentV2', 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', key: 'datasets',
href: '/datasets', href: '/datasets',
@@ -119,8 +119,10 @@ describe('ConversationVariableModal', () => {
}) })
expect(screen.getAllByText('session_state')).toHaveLength(2) expect(screen.getAllByText('session_state')).toHaveLength(2)
expect(screen.getByText((content) => content.includes('formatted-100'))).toBeInTheDocument() expect(
expect(screen.getByTestId('conversation-code-editor')).toHaveTextContent('{"latest":1}') 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')) await user.click(screen.getByText('summary'))
expect(screen.getByText('latest text')).toBeInTheDocument() expect(screen.getByText('latest text')).toBeInTheDocument()
+4
View File
@@ -16,6 +16,10 @@ vi.mock('@/next/navigation', () => ({
useRouter: () => ({ push: mockPush, replace: mockReplace }), useRouter: () => ({ push: mockPush, replace: mockReplace }),
})) }))
vi.mock('@/hooks/use-document-title', () => ({
default: vi.fn(),
}))
vi.mock('@/service/common', () => ({ vi.mock('@/service/common', () => ({
fetchSetupStatus: vi.fn(), fetchSetupStatus: vi.fn(),
fetchInitValidateStatus: vi.fn(), fetchInitValidateStatus: vi.fn(),
+22
View File
@@ -27,6 +27,23 @@ export const viewport: Viewport = {
viewportFit: 'cover', 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 }) { export default async function RootLayout({ children }: { children: React.ReactNode }) {
const datasetMap = getDatasetMap() const datasetMap = getDatasetMap()
const queryClient = getQueryClientServer() const queryClient = getQueryClientServer()
@@ -43,6 +60,11 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<html lang={locale ?? 'en'} className="h-full" suppressHydrationWarning> <html lang={locale ?? 'en'} className="h-full" suppressHydrationWarning>
<head> <head>
<link rel="manifest" href="/manifest.json" /> <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="theme-color" content="#1C64F2" />
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-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' } from './add-actions-context'
import { useCallback, useMemo, useState } from 'react' import { useCallback, useMemo, useState } from 'react'
import { AgentOrchestrateAddActionsContext } from './add-actions-context' import { AgentOrchestrateAddActionsContext } from './add-actions-context'
import { useAgentOrchestrateReadOnly } from './read-only-context' import { useAgentOrchestrateViewingVersion } from './read-only-context'
export function AgentOrchestrateAddActionsProvider({ children }: { children: ReactNode }) { export function AgentOrchestrateAddActionsProvider({ children }: { children: ReactNode }) {
const readOnly = useAgentOrchestrateReadOnly() const isViewingVersion = useAgentOrchestrateViewingVersion()
const [actions, setActions] = useState<AgentOrchestrateAddActions>({}) const [actions, setActions] = useState<AgentOrchestrateAddActions>({})
const registerAction = useCallback( const registerAction = useCallback(
(key: AgentOrchestrateAddActionKey, action: AgentOrchestrateAddAction) => { (key: AgentOrchestrateAddActionKey, action: AgentOrchestrateAddAction) => {
if (readOnly) return () => undefined if (isViewingVersion) return () => undefined
setActions((currentActions) => { setActions((currentActions) => {
if (currentActions[key] === action) return currentActions if (currentActions[key] === action) return currentActions
@@ -37,15 +37,15 @@ export function AgentOrchestrateAddActionsProvider({ children }: { children: Rea
}) })
} }
}, },
[readOnly], [isViewingVersion],
) )
const value = useMemo( const value = useMemo(
() => ({ () => ({
actions: readOnly ? {} : actions, actions: isViewingVersion ? {} : actions,
registerAction, registerAction,
}), }),
[actions, readOnly, registerAction], [actions, isViewingVersion, registerAction],
) )
return ( return (
@@ -4,7 +4,7 @@ import type { ButtonProps } from '@langgenius/dify-ui/button'
import { Button } from '@langgenius/dify-ui/button' import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn' import { cn } from '@langgenius/dify-ui/cn'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useAgentOrchestrateReadOnly } from '../read-only-context' import { useAgentOrchestrateViewingVersion } from '../read-only-context'
type ConfigureSectionAddButtonProps = Omit< type ConfigureSectionAddButtonProps = Omit<
ButtonProps, ButtonProps,
@@ -19,9 +19,9 @@ export function ConfigureSectionAddButton({
...props ...props
}: ConfigureSectionAddButtonProps) { }: ConfigureSectionAddButtonProps) {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const readOnly = useAgentOrchestrateReadOnly() const isViewingVersion = useAgentOrchestrateViewingVersion()
if (readOnly) return null if (isViewingVersion) return null
return ( return (
<Button <Button
@@ -13,7 +13,10 @@ import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store
import { QueryClientTestProvider } from '@/test/console/query-provider' import { QueryClientTestProvider } from '@/test/console/query-provider'
import { createSystemFeaturesFixture } from '@/test/console/system-features' import { createSystemFeaturesFixture } from '@/test/console/system-features'
import { AgentConfigApiContextProvider } from '../../config-context' import { AgentConfigApiContextProvider } from '../../config-context'
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context' import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from '../../read-only-context'
import { AgentFiles } from '../index' import { AgentFiles } from '../index'
type ConfigFileQueryOptionsInput = { type ConfigFileQueryOptionsInput = {
@@ -171,10 +174,12 @@ function renderAgentFiles({
initialDraft = createInitialDraft(), initialDraft = createInitialDraft(),
apiContext = { agentId: 'agent-1', draftType: 'draft' } satisfies AgentConfigApiContext, apiContext = { agentId: 'agent-1', draftType: 'draft' } satisfies AgentConfigApiContext,
readOnly = false, readOnly = false,
viewingVersion = false,
}: { }: {
initialDraft?: AgentSoulConfigFormState initialDraft?: AgentSoulConfigFormState
apiContext?: AgentConfigApiContext apiContext?: AgentConfigApiContext
readOnly?: boolean readOnly?: boolean
viewingVersion?: boolean
} = {}) { } = {}) {
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -191,10 +196,12 @@ function renderAgentFiles({
<QueryClientTestProvider queryClient={queryClient}> <QueryClientTestProvider queryClient={queryClient}>
<AgentConfigApiContextProvider value={apiContext}> <AgentConfigApiContextProvider value={apiContext}>
<AgentComposerProvider initialDraft={initialDraft}> <AgentComposerProvider initialDraft={initialDraft}>
<AgentOrchestrateReadOnlyContext value={readOnly}> <AgentOrchestrateViewingVersionContext value={viewingVersion}>
<AgentFiles /> <AgentOrchestrateReadOnlyContext value={readOnly}>
<ConfigSnapshotProbe /> <AgentFiles />
</AgentOrchestrateReadOnlyContext> <ConfigSnapshotProbe />
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
</AgentComposerProvider> </AgentComposerProvider>
</AgentConfigApiContextProvider> </AgentConfigApiContextProvider>
</QueryClientTestProvider>, </QueryClientTestProvider>,
@@ -642,8 +649,8 @@ describe('AgentFiles', () => {
expect(snapshot.config_note).toBe('') expect(snapshot.config_note).toBe('')
}) })
it('should keep flat config files visible without drive-prefix filtering and disable add in read-only mode', () => { it('should keep flat config files visible without drive-prefix filtering and disable add when viewing a version', () => {
renderAgentFiles({ readOnly: true }) renderAgentFiles({ readOnly: true, viewingVersion: true })
expect(screen.getByText('diagram.png')).toBeInTheDocument() expect(screen.getByText('diagram.png')).toBeInTheDocument()
expect(screen.getByText('brief.md')).toBeInTheDocument() expect(screen.getByText('brief.md')).toBeInTheDocument()
@@ -651,4 +658,12 @@ describe('AgentFiles', () => {
screen.queryByRole('button', { name: /agentV2\.agentDetail\.configure\.files\.add/i }), screen.queryByRole('button', { name: /agentV2\.agentDetail\.configure\.files\.add/i }),
).not.toBeInTheDocument() ).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 { AgentModelField } from './model-config/field'
import { AgentPromptEditor } from './prompt-editor' import { AgentPromptEditor } from './prompt-editor'
import { AgentConfigurePublishBar } from './publish-bar' import { AgentConfigurePublishBar } from './publish-bar'
import { AgentOrchestrateReadOnlyContext } from './read-only-context' import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from './read-only-context'
import { AgentSkills } from './skills' import { AgentSkills } from './skills'
import { AgentTools } from './tools' import { AgentTools } from './tools'
@@ -132,41 +135,43 @@ export function AgentOrchestratePanel({
/> />
)} )}
<AgentOrchestrateReadOnlyContext value={readOnly}> <AgentOrchestrateViewingVersionContext value={!!selectedVersionSnapshot}>
<div aria-readonly={readOnly} className="flex min-h-0 flex-1 flex-col"> <AgentOrchestrateReadOnlyContext value={readOnly}>
<ScrollArea <div aria-readonly={readOnly} className="flex min-h-0 flex-1 flex-col">
className="min-h-0 flex-1 overflow-hidden" <ScrollArea
label={showHeader ? undefined : orchestrateLabel} className="min-h-0 flex-1 overflow-hidden"
slotClassNames={{ label={showHeader ? undefined : orchestrateLabel}
viewport: 'overscroll-contain', slotClassNames={{
content: cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20'), viewport: 'overscroll-contain',
scrollbar: hasBottomAction ? 'z-20' : undefined, content: cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20'),
}} scrollbar: hasBottomAction ? 'z-20' : undefined,
> }}
<AgentConfigApiContextProvider value={configApiContext}> >
<AgentOrchestrateAddActionsProvider> <AgentConfigApiContextProvider value={configApiContext}>
<AgentBuildDraftChangedKeysProvider <AgentOrchestrateAddActionsProvider>
changedKeys={ <AgentBuildDraftChangedKeysProvider
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS changedKeys={
} isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
> }
<AgentModelField >
currentModel={currentModel} <AgentModelField
textGenerationModelList={textGenerationModelList} currentModel={currentModel}
onSelect={onSelectModel} textGenerationModelList={textGenerationModelList}
/> onSelect={onSelectModel}
<AgentPromptEditor /> />
<AgentSkills /> <AgentPromptEditor />
<AgentFiles /> <AgentSkills />
<AgentTools /> <AgentFiles />
<AgentKnowledgeRetrieval /> <AgentTools />
<AgentAdvancedSettings /> <AgentKnowledgeRetrieval />
</AgentBuildDraftChangedKeysProvider> <AgentAdvancedSettings />
</AgentOrchestrateAddActionsProvider> </AgentBuildDraftChangedKeysProvider>
</AgentConfigApiContextProvider> </AgentOrchestrateAddActionsProvider>
</ScrollArea> </AgentConfigApiContextProvider>
</div> </ScrollArea>
</AgentOrchestrateReadOnlyContext> </div>
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
{orchestrateBottomAction ? ( {orchestrateBottomAction ? (
<AgentOrchestrateBottomActions shrinkOnOpen={!bottomAction}> <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 { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
import { RerankingModeEnum } from '@/models/datasets' import { RerankingModeEnum } from '@/models/datasets'
import { renderWithConsoleQuery as render } from '@/test/console/query-data' 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' import { AgentKnowledgeRetrieval } from '../index'
vi.mock('@/context/workspace-state', async () => { vi.mock('@/context/workspace-state', async () => {
@@ -107,17 +110,21 @@ function ConfigSnapshotPreview() {
function renderKnowledgeRetrieval({ function renderKnowledgeRetrieval({
initialDraft = agentKnowledgeDraft, initialDraft = agentKnowledgeDraft,
readOnly = false, readOnly = false,
viewingVersion = false,
showConfigSnapshot = false, showConfigSnapshot = false,
}: { }: {
initialDraft?: AgentSoulConfigFormState initialDraft?: AgentSoulConfigFormState
readOnly?: boolean readOnly?: boolean
viewingVersion?: boolean
showConfigSnapshot?: boolean showConfigSnapshot?: boolean
} = {}) { } = {}) {
return render( return render(
<AgentComposerProvider initialDraft={initialDraft}> <AgentComposerProvider initialDraft={initialDraft}>
<AgentOrchestrateReadOnlyContext value={readOnly}> <AgentOrchestrateViewingVersionContext value={viewingVersion}>
<AgentKnowledgeRetrieval /> <AgentOrchestrateReadOnlyContext value={readOnly}>
</AgentOrchestrateReadOnlyContext> <AgentKnowledgeRetrieval />
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
{showConfigSnapshot && <ConfigSnapshotPreview />} {showConfigSnapshot && <ConfigSnapshotPreview />}
</AgentComposerProvider>, </AgentComposerProvider>,
) )
@@ -154,8 +161,8 @@ describe('AgentKnowledgeRetrieval', () => {
).not.toBeInTheDocument() ).not.toBeInTheDocument()
}) })
it('should hide add, edit, and remove actions when readonly', () => { it('should hide add, edit, and remove actions when viewing a version', () => {
renderKnowledgeRetrieval({ readOnly: true }) renderKnowledgeRetrieval({ readOnly: true, viewingVersion: true })
expect( expect(
screen.getByText('agentV2.agentDetail.configure.knowledgeRetrieval.retrievalOne'), screen.getByText('agentV2.agentDetail.configure.knowledgeRetrieval.retrievalOne'),
@@ -176,6 +183,16 @@ describe('AgentKnowledgeRetrieval', () => {
}), }),
).not.toBeInTheDocument() ).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', () => { describe('User Interactions', () => {
@@ -1,7 +1,12 @@
import { createContext, use } from 'react' import { createContext, use } from 'react'
export const AgentOrchestrateReadOnlyContext = createContext(false) export const AgentOrchestrateReadOnlyContext = createContext(false)
export const AgentOrchestrateViewingVersionContext = createContext(false)
export function useAgentOrchestrateReadOnly() { export function useAgentOrchestrateReadOnly() {
return use(AgentOrchestrateReadOnlyContext) 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 { AgentConfigApiContext } from '../../config-context'
import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state' import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
import { toast } from '@langgenius/dify-ui/toast' 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 { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider'
import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store' import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
import { AgentConfigApiContextProvider } from '../../config-context' import { AgentConfigApiContextProvider } from '../../config-context'
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context' import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from '../../read-only-context'
import { AgentSkills } from '../index' import { AgentSkills } from '../index'
type ConfigSkillInspectQueryOptionsInput = { type ConfigSkillInspectQueryOptionsInput = {
@@ -39,10 +43,16 @@ type ConfigSkillDownloadQueryOptionsInput = {
} }
const mocks = vi.hoisted(() => ({ const mocks = vi.hoisted(() => ({
agentSkillBindingsKey: vi.fn((_options: unknown): unknown[] => ['workspace-agent-skills']),
agentSkillBindingsQueryOptions: vi.fn((_options: unknown) => ({})),
deleteSkillMutationFn: vi.fn(async (_input: unknown) => ({ deleteSkillMutationFn: vi.fn(async (_input: unknown) => ({
removed_names: ['Tender Analyzer'], removed_names: ['Tender Analyzer'],
result: 'success', 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) => ({ uploadSkillMutationFn: vi.fn(async (_input: unknown) => ({
config_version: { id: 'draft-1', kind: 'draft', writable: true }, config_version: { id: 'draft-1', kind: 'draft', writable: true },
skill: { skill: {
@@ -59,6 +69,8 @@ const mocks = vi.hoisted(() => ({
inspectQueryOptions: vi.fn((_options: ConfigSkillInspectQueryOptionsInput) => ({})), inspectQueryOptions: vi.fn((_options: ConfigSkillInspectQueryOptionsInput) => ({})),
previewQueryOptions: vi.fn((_options: ConfigSkillFileQueryOptionsInput) => ({})), previewQueryOptions: vi.fn((_options: ConfigSkillFileQueryOptionsInput) => ({})),
downloadQueryOptions: vi.fn((_options: ConfigSkillFileQueryOptionsInput) => ({})), downloadQueryOptions: vi.fn((_options: ConfigSkillFileQueryOptionsInput) => ({})),
workspaceSkillsQueryOptions: vi.fn((_options: unknown) => ({})),
workspaceSkillsInfiniteOptions: vi.fn((_options: unknown) => ({})),
downloadBlob: vi.fn(), downloadBlob: vi.fn(),
downloadUrl: vi.fn(), downloadUrl: vi.fn(),
fetch: 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() { function ConfigSnapshotProbe() {
const draft = useAtomValue(agentComposerDraftAtom) const draft = useAtomValue(agentComposerDraftAtom)
const configSnapshot = formStateToAgentSoulConfig({ formState: draft }) const configSnapshot = formStateToAgentSoulConfig({ formState: draft })
@@ -175,6 +221,23 @@ function ConfigSnapshotProbe() {
return <pre aria-label="config snapshot">{JSON.stringify(configSnapshot)}</pre> 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({ function renderAgentSkills({
initialDraft = { initialDraft = {
...defaultAgentSoulConfigFormState, ...defaultAgentSoulConfigFormState,
@@ -189,10 +252,12 @@ function renderAgentSkills({
}, },
apiContext = { agentId: 'agent-1', draftType: 'draft' } satisfies AgentConfigApiContext, apiContext = { agentId: 'agent-1', draftType: 'draft' } satisfies AgentConfigApiContext,
readOnly = false, readOnly = false,
viewingVersion = false,
}: { }: {
initialDraft?: AgentSoulConfigFormState initialDraft?: AgentSoulConfigFormState
apiContext?: AgentConfigApiContext apiContext?: AgentConfigApiContext
readOnly?: boolean readOnly?: boolean
viewingVersion?: boolean
} = {}) { } = {}) {
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -205,10 +270,12 @@ function renderAgentSkills({
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<AgentConfigApiContextProvider value={apiContext}> <AgentConfigApiContextProvider value={apiContext}>
<AgentComposerProvider initialDraft={initialDraft}> <AgentComposerProvider initialDraft={initialDraft}>
<AgentOrchestrateReadOnlyContext value={readOnly}> <AgentOrchestrateViewingVersionContext value={viewingVersion}>
<AgentSkills /> <AgentOrchestrateReadOnlyContext value={readOnly}>
<ConfigSnapshotProbe /> <AgentSkills />
</AgentOrchestrateReadOnlyContext> <ConfigSnapshotProbe />
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
</AgentComposerProvider> </AgentComposerProvider>
</AgentConfigApiContextProvider> </AgentConfigApiContextProvider>
</QueryClientProvider>, </QueryClientProvider>,
@@ -225,6 +292,22 @@ describe('AgentSkills', () => {
headers: { 'Content-Type': 'application/octet-stream' }, 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 }) => ({ mocks.inspectQueryOptions.mockImplementation(({ input }) => ({
queryKey: ['inspect-skill', input], queryKey: ['inspect-skill', input],
queryFn: async () => ({ queryFn: async () => ({
@@ -293,6 +376,38 @@ describe('AgentSkills', () => {
url: `https://example.com/${input.params.name}.skill`, 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(() => { afterEach(() => {
@@ -372,9 +487,7 @@ describe('AgentSkills', () => {
const user = userEvent.setup() const user = userEvent.setup()
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState }) renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click( await openUploadSkillDialog(user)
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
const input = await waitFor(() => { const input = await waitFor(() => {
const element = document.querySelector('input[type="file"]') const element = document.querySelector('input[type="file"]')
@@ -418,13 +531,375 @@ describe('AgentSkills', () => {
expect(toast.success).toHaveBeenCalled() 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() 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 }) renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click( await user.click(
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }), 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( expect(
screen.queryByText('agentV2.agentDetail.configure.skills.upload.warning.specification'), screen.queryByText('agentV2.agentDetail.configure.skills.upload.warning.specification'),
@@ -438,9 +913,7 @@ describe('AgentSkills', () => {
.mockImplementationOnce(() => new Promise<never>(() => undefined)) .mockImplementationOnce(() => new Promise<never>(() => undefined))
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState }) renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click( await openUploadSkillDialog(user)
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
const input = await waitFor(() => { const input = await waitFor(() => {
const element = document.querySelector('input[type="file"]') const element = document.querySelector('input[type="file"]')
expect(element).not.toBeNull() expect(element).not.toBeNull()
@@ -477,9 +950,7 @@ describe('AgentSkills', () => {
mocks.uploadSkillMutationFn.mockRejectedValueOnce(new Error('Backend upload error')) mocks.uploadSkillMutationFn.mockRejectedValueOnce(new Error('Backend upload error'))
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState }) renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
await user.click( await openUploadSkillDialog(user)
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
const input = await waitFor(() => { const input = await waitFor(() => {
const element = document.querySelector('input[type="file"]') const element = document.querySelector('input[type="file"]')
@@ -515,9 +986,7 @@ describe('AgentSkills', () => {
}, },
}) })
await user.click( await openUploadSkillDialog(user)
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
)
const input = await waitFor(() => { const input = await waitFor(() => {
const element = document.querySelector('input[type="file"]') const element = document.querySelector('input[type="file"]')
expect(element).not.toBeNull() expect(element).not.toBeNull()
@@ -869,12 +1338,87 @@ describe('AgentSkills', () => {
) )
}) })
it('should disable add and remove actions when the section is read only', () => { it('should disable add and remove actions when viewing a version', async () => {
const { container } = renderAgentSkills({ readOnly: true }) 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( expect(
screen.queryByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }), screen.queryByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
).not.toBeInTheDocument() ).not.toBeInTheDocument()
expect(container.querySelector('[data-agent-skill-remove-button]')).toBeNull() 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' '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 { AgentOrchestrateAddActionOptions } from '../add-actions-context'
import type { AgentSkill } from '@/features/agent-v2/agent-composer/form-state' 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 { useAtomValue, useSetAtom } from 'jotai'
import { useCallback, useRef, useState } from 'react' import { useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { SearchInput } from '@/app/components/base/search-input'
import { SkeletonRectangle } from '@/app/components/base/skeleton'
import { import {
agentComposerSkillsAtom, agentComposerSkillsAtom,
removeAgentSkillAtom, removeAgentSkillAtom,
upsertAgentSkillAtom, upsertAgentSkillAtom,
} from '@/features/agent-v2/agent-composer/store-modules/skills' } from '@/features/agent-v2/agent-composer/store-modules/skills'
import Link from '@/next/link'
import { consoleQuery } from '@/service/client' import { consoleQuery } from '@/service/client'
import { useRegisterAgentOrchestrateAddAction } from '../add-actions-context' import { useRegisterAgentOrchestrateAddAction } from '../add-actions-context'
import { ConfigureSectionAddButton } from '../common/add-button'
import { ConfigureSectionEmpty } from '../common/empty' import { ConfigureSectionEmpty } from '../common/empty'
import { ConfigureSection } from '../common/section' import { ConfigureSection } from '../common/section'
import { AgentConfigureTipContent } from '../common/tip-content' import { AgentConfigureTipContent } from '../common/tip-content'
import { useAgentConfigApiContext } from '../config-context' import { useAgentConfigApiContext } from '../config-context'
import {
useAgentOrchestrateReadOnly,
useAgentOrchestrateViewingVersion,
} from '../read-only-context'
import { AgentSkillItem } from './item' import { AgentSkillItem } from './item'
import { AgentSkillUploadDialog } from './upload-dialog' 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() { export function AgentSkills() {
const { t } = useTranslation('agentV2') const { t } = useTranslation('agentV2')
const { t: tCommon } = useTranslation('common')
const skillsTip = t(($) => $['agentDetail.configure.skills.tip']) const skillsTip = t(($) => $['agentDetail.configure.skills.tip'])
const skillsListId = 'agent-configure-skills-list' 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 [isUploadOpen, setIsUploadOpen] = useState(false)
const promptAddCallbackRef = useRef<AgentOrchestrateAddActionOptions['onAdded']>(undefined) const promptAddCallbackRef = useRef<AgentOrchestrateAddActionOptions['onAdded']>(undefined)
const apiContext = useAgentConfigApiContext() const apiContext = useAgentConfigApiContext()
@@ -37,6 +398,68 @@ export function AgentSkills() {
const { mutate: deleteAppSkill } = useMutation( const { mutate: deleteAppSkill } = useMutation(
consoleQuery.apps.byAppId.agent.config.skills.byName.delete.mutationOptions(), 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) => { const handleOpenUpload = useCallback((options?: AgentOrchestrateAddActionOptions) => {
promptAddCallbackRef.current = options?.onAdded promptAddCallbackRef.current = options?.onAdded
@@ -44,6 +467,20 @@ export function AgentSkills() {
}, []) }, [])
useRegisterAgentOrchestrateAddAction('skills', handleOpenUpload) 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( const handleUploaded = useCallback(
(skill: AgentSkill) => { (skill: AgentSkill) => {
upsertAgentSkill(skill) upsertAgentSkill(skill)
@@ -53,11 +490,36 @@ export function AgentSkills() {
[upsertAgentSkill], [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) => { const handleUploadOpenChange = useCallback((open: boolean) => {
if (!open) promptAddCallbackRef.current = undefined if (!open) promptAddCallbackRef.current = undefined
setIsUploadOpen(open) 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( const handleRemoveSkill = useCallback(
(skillId: string) => { (skillId: string) => {
const skill = skills.find((item) => item.id === skillId) const skill = skills.find((item) => item.id === skillId)
@@ -113,26 +575,91 @@ export function AgentSkills() {
rootClassName="border-b border-divider-subtle pt-4" rootClassName="border-b border-divider-subtle pt-4"
panelContentClassName="flex flex-col gap-1 pb-4" panelContentClassName="flex flex-col gap-1 pb-4"
actions={ actions={
<ConfigureSectionAddButton !isViewingVersion && (
ariaLabel={t(($) => $['agentDetail.configure.skills.add'])} <Popover open={addMenuOpen} onOpenChange={handleAddMenuOpenChange}>
onClick={() => handleOpenUpload()} <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 <ConfigureSectionEmpty
title={t(($) => $['agentDetail.configure.skills.empty.title'])} title={t(($) => $['agentDetail.configure.skills.empty.title'])}
description={t(($) => $['agentDetail.configure.skills.empty.description'])} description={t(($) => $['agentDetail.configure.skills.empty.description'])}
/> />
) : ( ) : (
skills.map((skill) => ( <>
<AgentSkillItem {workspaceSkills.length > 0 && (
key={skill.id} <div className="px-1 pt-1 pb-0.5 system-xs-medium-uppercase text-text-tertiary">
apiContext={apiContext} {t(($) => $['agentDetail.configure.skills.fromSkillLibrary'])}
skill={skill} </div>
onRemove={handleRemoveSkill} )}
/> {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> </ConfigureSection>
<AgentSkillUploadDialog <AgentSkillUploadDialog
@@ -15,7 +15,10 @@ import {
agentComposerSavedDraftAtom, agentComposerSavedDraftAtom,
isAgentComposerDirtyAtom, isAgentComposerDirtyAtom,
} from '@/features/agent-v2/agent-composer/store' } from '@/features/agent-v2/agent-composer/store'
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context' import {
AgentOrchestrateReadOnlyContext,
AgentOrchestrateViewingVersionContext,
} from '../../read-only-context'
import { AgentTools } from '../index' import { AgentTools } from '../index'
const toolProviderState = vi.hoisted(() => ({ 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({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
@@ -352,9 +361,11 @@ function renderReadonlyAgentTools(initialDraft: AgentSoulConfigFormState = agent
return render( return render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<AgentComposerProvider initialDraft={initialDraft}> <AgentComposerProvider initialDraft={initialDraft}>
<AgentOrchestrateReadOnlyContext value> <AgentOrchestrateViewingVersionContext value={viewingVersion}>
<AgentTools /> <AgentOrchestrateReadOnlyContext value>
</AgentOrchestrateReadOnlyContext> <AgentTools />
</AgentOrchestrateReadOnlyContext>
</AgentOrchestrateViewingVersionContext>
</AgentComposerProvider> </AgentComposerProvider>
</QueryClientProvider>, </QueryClientProvider>,
) )
@@ -441,9 +452,9 @@ describe('AgentTools', () => {
).toBeInTheDocument() ).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() const user = userEvent.setup()
renderReadonlyAgentTools() renderReadonlyAgentTools({ viewingVersion: true })
expect( expect(
screen.queryByRole('button', { screen.queryByRole('button', {
@@ -484,6 +495,16 @@ describe('AgentTools', () => {
).not.toBeInTheDocument() ).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', () => { it('should hide CLI tool rows while CLI tools are disabled', () => {
renderAgentTools() renderAgentTools()
@@ -36,7 +36,7 @@ import { ConfigureSectionAddButton } from '../common/add-button'
import { ConfigureSectionEmpty } from '../common/empty' import { ConfigureSectionEmpty } from '../common/empty'
import { ConfigureSection } from '../common/section' import { ConfigureSection } from '../common/section'
import { AgentConfigureTipContent } from '../common/tip-content' 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 { CliToolDialog } from './cli-tool/dialog'
import { AgentCliToolItem } from './cli-tool/item' import { AgentCliToolItem } from './cli-tool/item'
import { import {
@@ -400,7 +400,7 @@ function AddToolMenu({
export function AgentTools() { export function AgentTools() {
const { t } = useTranslation('agentV2') const { t } = useTranslation('agentV2')
const readOnly = useAgentOrchestrateReadOnly() const isViewingVersion = useAgentOrchestrateViewingVersion()
const setProviderToolCredential = useSetAtom(setProviderToolCredentialAtom) const setProviderToolCredential = useSetAtom(setProviderToolCredentialAtom)
const providerById = useAgentToolProviderMap() const providerById = useAgentToolProviderMap()
const tools = useAtomValue(agentComposerToolsAtom) const tools = useAtomValue(agentComposerToolsAtom)
@@ -533,7 +533,7 @@ export function AgentTools() {
rootClassName="border-b border-divider-subtle pt-4" rootClassName="border-b border-divider-subtle pt-4"
panelContentClassName="flex flex-col gap-1 pb-4" panelContentClassName="flex flex-col gap-1 pb-4"
actions={ actions={
!readOnly ? ( !isViewingVersion ? (
<AddToolMenu <AddToolMenu
onAddCliTool={openCliToolDialog} onAddCliTool={openCliToolDialog}
onAddTools={addTools} onAddTools={addTools}
@@ -2,6 +2,7 @@
import type { AgentAppPartial, AgentIconType } from '@dify/contracts/api/console/agent/types.gen' import type { AgentAppPartial, AgentIconType } from '@dify/contracts/api/console/agent/types.gen'
import { Button } from '@langgenius/dify-ui/button' import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -224,7 +225,12 @@ function AgentRosterItem({ agent }: { agent: AgentAppPartial }) {
</div> </div>
</div> </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}> <DropdownMenu modal={false}>
<DropdownMenuTrigger <DropdownMenuTrigger
aria-label={t(($) => $['roster.moreActions'], { name: agent.name })} 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
+488
View File
@@ -0,0 +1,488 @@
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 })]
mocks.skillPages = [mocks.skills]
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.previewTipBody": "يشغل Preview الوكيل المكتمل كما سيراه المستخدمون، مع ردود واضحة وميزات الدردشة.",
"agentDetail.configure.rightPanel.previewTipTitle": "عاين وكيلك", "agentDetail.configure.rightPanel.previewTipTitle": "عاين وكيلك",
"agentDetail.configure.skills.add": "إضافة مهارة", "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.contentRegion": "محتوى تفاصيل المهارة",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ملفات", "agentDetail.configure.skills.detail.fileCount": "{{count}} ملفات",
"agentDetail.configure.skills.detail.files": "الملفات", "agentDetail.configure.skills.detail.files": "الملفات",
"agentDetail.configure.skills.empty.description": "تمنح المهارات الوكيل خبرة قابلة لإعادة الاستخدام يمكنه استدعاؤها أثناء العمل", "agentDetail.configure.skills.empty.description": "تمنح المهارات الوكيل خبرة قابلة لإعادة الاستخدام يمكنه استدعاؤها أثناء العمل",
"agentDetail.configure.skills.empty.title": "لا توجد مهارات بعد", "agentDetail.configure.skills.empty.title": "لا توجد مهارات بعد",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "مهارة", "agentDetail.configure.skills.itemType": "مهارة",
"agentDetail.configure.skills.label": "المهارات", "agentDetail.configure.skills.label": "المهارات",
"agentDetail.configure.skills.missing": "المهارة غير موجودة", "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.remove": "إزالة {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "اجمع التعليمات والملفات والبرامج النصية لمهمة متكررة في Skill. أشر إليها باستخدام / في Prompt. <docLink>معرفة المزيد</docLink>\n\nفي وضع Build، يمكن للوكيل إعدادها لك.", "agentDetail.configure.skills.richTip": "اجمع التعليمات والملفات والبرامج النصية لمهمة متكررة في Skill. أشر إليها باستخدام / في Prompt. <docLink>معرفة المزيد</docLink>\n\nفي وضع Build، يمكن للوكيل إعدادها لك.",
"agentDetail.configure.skills.tip": "اجمع التعليمات والملفات والبرامج النصية لمهمة متكررة في Skill. أشر إليها باستخدام / في Prompt. معرفة المزيد\n\nفي وضع Build، يمكن للوكيل إعدادها لك.", "agentDetail.configure.skills.tip": "اجمع التعليمات والملفات والبرامج النصية لمهمة متكررة في Skill. أشر إليها باستخدام / في Prompt. معرفة المزيد\n\nفي وضع Build، يمكن للوكيل إعدادها لك.",
"agentDetail.configure.skills.toggle": "تبديل المهارات", "agentDetail.configure.skills.toggle": "تبديل المهارات",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "خيارات الفرز", "roster.sort.optionsLabel": "خيارات الفرز",
"roster.sort.recentlyCreated": "الأحدث إنشاءً", "roster.sort.recentlyCreated": "الأحدث إنشاءً",
"roster.updateSuccess": "تم تحديث الوكيل.", "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.home": "الرئيسية",
"mainNav.integrations": "التكاملات", "mainNav.integrations": "التكاملات",
"mainNav.marketplace": "سوق الإضافات", "mainNav.marketplace": "سوق الإضافات",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "لم يتم العثور على تطبيقات ويب", "mainNav.webApps.noResults": "لم يتم العثور على تطبيقات ويب",
"mainNav.webApps.openApp": "فتح تطبيق الويب {{name}}", "mainNav.webApps.openApp": "فتح تطبيق الويب {{name}}",
"mainNav.webApps.searchPlaceholder": "البحث في تطبيقات الويب", "mainNav.webApps.searchPlaceholder": "البحث في تطبيقات الويب",
@@ -462,6 +463,7 @@
"operation.downloading": "جارٍ التنزيل...", "operation.downloading": "جارٍ التنزيل...",
"operation.duplicate": "تكرار", "operation.duplicate": "تكرار",
"operation.edit": "تعديل", "operation.edit": "تعديل",
"operation.export": "تصدير",
"operation.exporting": "جارٍ التصدير", "operation.exporting": "جارٍ التصدير",
"operation.fill": "ملء تلقائي", "operation.fill": "ملء تلقائي",
"operation.format": "تنسيق", "operation.format": "تنسيق",
@@ -503,6 +505,7 @@
"operation.sure": "أنا متأكد", "operation.sure": "أنا متأكد",
"operation.toggleFullscreen": "تبديل ملء الشاشة", "operation.toggleFullscreen": "تبديل ملء الشاشة",
"operation.toggleMute": "تبديل كتم الصوت", "operation.toggleMute": "تبديل كتم الصوت",
"operation.upload": "Upload",
"operation.view": "عرض", "operation.view": "عرض",
"operation.viewDetails": "عرض التفاصيل", "operation.viewDetails": "عرض التفاصيل",
"operation.viewMore": "عرض المزيد", "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.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.rightPanel.previewTipTitle": "Agenten vorschauen",
"agentDetail.configure.skills.add": "Skill hinzufügen", "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.contentRegion": "Skill-Detailinhalt",
"agentDetail.configure.skills.detail.fileCount": "{{count}} DATEIEN", "agentDetail.configure.skills.detail.fileCount": "{{count}} DATEIEN",
"agentDetail.configure.skills.detail.files": "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.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.empty.title": "Noch keine Skills",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Skill", "agentDetail.configure.skills.itemType": "Skill",
"agentDetail.configure.skills.label": "Skills", "agentDetail.configure.skills.label": "Skills",
"agentDetail.configure.skills.missing": "Skill nicht gefunden", "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.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.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.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", "agentDetail.configure.skills.toggle": "Skills umschalten",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Sortieroptionen", "roster.sort.optionsLabel": "Sortieroptionen",
"roster.sort.recentlyCreated": "Zuletzt erstellt", "roster.sort.recentlyCreated": "Zuletzt erstellt",
"roster.updateSuccess": "Agent aktualisiert.", "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.home": "Startseite",
"mainNav.integrations": "Integrationen", "mainNav.integrations": "Integrationen",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Keine Web-Apps gefunden", "mainNav.webApps.noResults": "Keine Web-Apps gefunden",
"mainNav.webApps.openApp": "Web-App {{name}} öffnen", "mainNav.webApps.openApp": "Web-App {{name}} öffnen",
"mainNav.webApps.searchPlaceholder": "Web-Apps suchen", "mainNav.webApps.searchPlaceholder": "Web-Apps suchen",
@@ -463,6 +464,7 @@
"operation.downloading": "Wird heruntergeladen...", "operation.downloading": "Wird heruntergeladen...",
"operation.duplicate": "Duplikat", "operation.duplicate": "Duplikat",
"operation.edit": "Bearbeiten", "operation.edit": "Bearbeiten",
"operation.export": "Exportieren",
"operation.exporting": "Exportiere", "operation.exporting": "Exportiere",
"operation.fill": "Automatisch ausfüllen", "operation.fill": "Automatisch ausfüllen",
"operation.format": "Format", "operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "Ich bin sicher", "operation.sure": "Ich bin sicher",
"operation.toggleFullscreen": "Vollbild umschalten", "operation.toggleFullscreen": "Vollbild umschalten",
"operation.toggleMute": "Stummschaltung umschalten", "operation.toggleMute": "Stummschaltung umschalten",
"operation.upload": "Upload",
"operation.view": "Ansehen", "operation.view": "Ansehen",
"operation.viewDetails": "Details anzeigen", "operation.viewDetails": "Details anzeigen",
"operation.viewMore": "MEHR SEHEN", "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.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.rightPanel.previewTipTitle": "Preview your agent",
"agentDetail.configure.skills.add": "Add skill", "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.contentRegion": "Skill detail content",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILES", "agentDetail.configure.skills.detail.fileCount": "{{count}} FILES",
"agentDetail.configure.skills.detail.files": "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.description": "Skills give the agent reusable expertise it can call while working",
"agentDetail.configure.skills.empty.title": "No skills yet", "agentDetail.configure.skills.empty.title": "No skills yet",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Skill", "agentDetail.configure.skills.itemType": "Skill",
"agentDetail.configure.skills.label": "Skills", "agentDetail.configure.skills.label": "Skills",
"agentDetail.configure.skills.missing": "Skill not found", "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.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.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.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", "agentDetail.configure.skills.toggle": "Toggle skills",
@@ -226,6 +235,15 @@
"agentDetail.configure.skills.upload.title": "Upload skill", "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.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.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.switchToPreviewConfirm.title": "Switch to Preview mode?",
"agentDetail.configure.title": "Configure", "agentDetail.configure.title": "Configure",
"agentDetail.configure.tools.add": "Add tool", "agentDetail.configure.tools.add": "Add tool",
@@ -421,5 +439,182 @@
"roster.sort.optionsLabel": "Sort options", "roster.sort.optionsLabel": "Sort options",
"roster.sort.recentlyCreated": "Recently created", "roster.sort.recentlyCreated": "Recently created",
"roster.updateSuccess": "Agent updated.", "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.home": "Home",
"mainNav.integrations": "Integrations", "mainNav.integrations": "Integrations",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "No web apps found", "mainNav.webApps.noResults": "No web apps found",
"mainNav.webApps.openApp": "Open {{name}} web app", "mainNav.webApps.openApp": "Open {{name}} web app",
"mainNav.webApps.searchPlaceholder": "Search web apps", "mainNav.webApps.searchPlaceholder": "Search web apps",
@@ -463,6 +464,7 @@
"operation.downloading": "Downloading", "operation.downloading": "Downloading",
"operation.duplicate": "Duplicate", "operation.duplicate": "Duplicate",
"operation.edit": "Edit", "operation.edit": "Edit",
"operation.export": "Export",
"operation.exporting": "Exporting", "operation.exporting": "Exporting",
"operation.fill": "Autofill", "operation.fill": "Autofill",
"operation.format": "Format", "operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "I'm sure", "operation.sure": "I'm sure",
"operation.toggleFullscreen": "Toggle fullscreen", "operation.toggleFullscreen": "Toggle fullscreen",
"operation.toggleMute": "Toggle mute", "operation.toggleMute": "Toggle mute",
"operation.upload": "Upload",
"operation.view": "View", "operation.view": "View",
"operation.viewDetails": "View Details", "operation.viewDetails": "View Details",
"operation.viewMore": "VIEW MORE", "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.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.rightPanel.previewTipTitle": "Previsualiza tu agente",
"agentDetail.configure.skills.add": "Agregar habilidad", "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.contentRegion": "Contenido de los detalles de la habilidad",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ARCHIVOS", "agentDetail.configure.skills.detail.fileCount": "{{count}} ARCHIVOS",
"agentDetail.configure.skills.detail.files": "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.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.empty.title": "Aún no hay habilidades",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Habilidad", "agentDetail.configure.skills.itemType": "Habilidad",
"agentDetail.configure.skills.label": "Habilidades", "agentDetail.configure.skills.label": "Habilidades",
"agentDetail.configure.skills.missing": "Habilidad no encontrada", "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.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.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.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", "agentDetail.configure.skills.toggle": "Alternar habilidades",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opciones de ordenación", "roster.sort.optionsLabel": "Opciones de ordenación",
"roster.sort.recentlyCreated": "Creados recientemente", "roster.sort.recentlyCreated": "Creados recientemente",
"roster.updateSuccess": "Agente actualizado.", "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.home": "Inicio",
"mainNav.integrations": "Integraciones", "mainNav.integrations": "Integraciones",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "No se encontraron aplicaciones web", "mainNav.webApps.noResults": "No se encontraron aplicaciones web",
"mainNav.webApps.openApp": "Abrir la aplicación web {{name}}", "mainNav.webApps.openApp": "Abrir la aplicación web {{name}}",
"mainNav.webApps.searchPlaceholder": "Buscar aplicaciones web", "mainNav.webApps.searchPlaceholder": "Buscar aplicaciones web",
@@ -463,6 +464,7 @@
"operation.downloading": "Descargando...", "operation.downloading": "Descargando...",
"operation.duplicate": "Duplicar", "operation.duplicate": "Duplicar",
"operation.edit": "Editar", "operation.edit": "Editar",
"operation.export": "Exportar",
"operation.exporting": "Exportando", "operation.exporting": "Exportando",
"operation.fill": "Autocompletar", "operation.fill": "Autocompletar",
"operation.format": "Formato", "operation.format": "Formato",
@@ -504,6 +506,7 @@
"operation.sure": "Estoy seguro", "operation.sure": "Estoy seguro",
"operation.toggleFullscreen": "Alternar pantalla completa", "operation.toggleFullscreen": "Alternar pantalla completa",
"operation.toggleMute": "Alternar silencio", "operation.toggleMute": "Alternar silencio",
"operation.upload": "Upload",
"operation.view": "Vista", "operation.view": "Vista",
"operation.viewDetails": "Ver detalles", "operation.viewDetails": "Ver detalles",
"operation.viewMore": "VER MÁS", "operation.viewMore": "VER MÁS",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview عامل تکمیل‌شده را همان‌طور که کاربران می‌بینند اجرا می‌کند، با پاسخ‌های تمیز و قابلیت‌های گفتگو.", "agentDetail.configure.rightPanel.previewTipBody": "Preview عامل تکمیل‌شده را همان‌طور که کاربران می‌بینند اجرا می‌کند، با پاسخ‌های تمیز و قابلیت‌های گفتگو.",
"agentDetail.configure.rightPanel.previewTipTitle": "پیش‌نمایش عامل", "agentDetail.configure.rightPanel.previewTipTitle": "پیش‌نمایش عامل",
"agentDetail.configure.skills.add": "افزودن مهارت", "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.contentRegion": "محتوای جزئیات مهارت",
"agentDetail.configure.skills.detail.fileCount": "{{count}} فایل", "agentDetail.configure.skills.detail.fileCount": "{{count}} فایل",
"agentDetail.configure.skills.detail.files": "فایل‌ها", "agentDetail.configure.skills.detail.files": "فایل‌ها",
"agentDetail.configure.skills.empty.description": "مهارت‌ها به عامل تخصص قابل استفاده مجدد می‌دهند که هنگام کار می‌تواند فراخوانی کند", "agentDetail.configure.skills.empty.description": "مهارت‌ها به عامل تخصص قابل استفاده مجدد می‌دهند که هنگام کار می‌تواند فراخوانی کند",
"agentDetail.configure.skills.empty.title": "هنوز مهارتی وجود ندارد", "agentDetail.configure.skills.empty.title": "هنوز مهارتی وجود ندارد",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "مهارت", "agentDetail.configure.skills.itemType": "مهارت",
"agentDetail.configure.skills.label": "مهارت‌ها", "agentDetail.configure.skills.label": "مهارت‌ها",
"agentDetail.configure.skills.missing": "مهارت یافت نشد", "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.remove": "حذف {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "دستورالعمل‌ها، فایل‌ها و اسکریپت‌های یک کار تکرارشونده را در یک Skill بسته‌بندی کنید. در Prompt با / به آن ارجاع دهید. <docLink>بیشتر بدانید</docLink>\n\nدر حالت Build، عامل می‌تواند این موارد را برای شما تنظیم کند.", "agentDetail.configure.skills.richTip": "دستورالعمل‌ها، فایل‌ها و اسکریپت‌های یک کار تکرارشونده را در یک Skill بسته‌بندی کنید. در Prompt با / به آن ارجاع دهید. <docLink>بیشتر بدانید</docLink>\n\nدر حالت Build، عامل می‌تواند این موارد را برای شما تنظیم کند.",
"agentDetail.configure.skills.tip": "دستورالعمل‌ها، فایل‌ها و اسکریپت‌های یک کار تکرارشونده را در یک Skill بسته‌بندی کنید. در Prompt با / به آن ارجاع دهید. بیشتر بدانید\n\nدر حالت Build، عامل می‌تواند این موارد را برای شما تنظیم کند.", "agentDetail.configure.skills.tip": "دستورالعمل‌ها، فایل‌ها و اسکریپت‌های یک کار تکرارشونده را در یک Skill بسته‌بندی کنید. در Prompt با / به آن ارجاع دهید. بیشتر بدانید\n\nدر حالت Build، عامل می‌تواند این موارد را برای شما تنظیم کند.",
"agentDetail.configure.skills.toggle": "تغییر مهارت‌ها", "agentDetail.configure.skills.toggle": "تغییر مهارت‌ها",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "گزینه‌های مرتب‌سازی", "roster.sort.optionsLabel": "گزینه‌های مرتب‌سازی",
"roster.sort.recentlyCreated": "تازه‌ترین ایجاد", "roster.sort.recentlyCreated": "تازه‌ترین ایجاد",
"roster.updateSuccess": "عامل به‌روزرسانی شد.", "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.home": "خانه",
"mainNav.integrations": "یکپارچه‌سازی‌ها", "mainNav.integrations": "یکپارچه‌سازی‌ها",
"mainNav.marketplace": "بازارچه", "mainNav.marketplace": "بازارچه",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "هیچ برنامه وبی یافت نشد", "mainNav.webApps.noResults": "هیچ برنامه وبی یافت نشد",
"mainNav.webApps.openApp": "باز کردن برنامه وب {{name}}", "mainNav.webApps.openApp": "باز کردن برنامه وب {{name}}",
"mainNav.webApps.searchPlaceholder": "جستجوی برنامه‌های وب", "mainNav.webApps.searchPlaceholder": "جستجوی برنامه‌های وب",
@@ -463,6 +464,7 @@
"operation.downloading": "در حال دانلود...", "operation.downloading": "در حال دانلود...",
"operation.duplicate": "تکرار", "operation.duplicate": "تکرار",
"operation.edit": "ویرایش", "operation.edit": "ویرایش",
"operation.export": "خروجی گرفتن",
"operation.exporting": "در حال خروجی گرفتن", "operation.exporting": "در حال خروجی گرفتن",
"operation.fill": "پر کردن خودکار", "operation.fill": "پر کردن خودکار",
"operation.format": "قالب", "operation.format": "قالب",
@@ -504,6 +506,7 @@
"operation.sure": "مطمئن هستم", "operation.sure": "مطمئن هستم",
"operation.toggleFullscreen": "تغییر حالت تمام‌صفحه", "operation.toggleFullscreen": "تغییر حالت تمام‌صفحه",
"operation.toggleMute": "تغییر حالت بی‌صدا", "operation.toggleMute": "تغییر حالت بی‌صدا",
"operation.upload": "Upload",
"operation.view": "مشاهده", "operation.view": "مشاهده",
"operation.viewDetails": "دیدن جزئیات", "operation.viewDetails": "دیدن جزئیات",
"operation.viewMore": "بیشتر ببینید", "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.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.rightPanel.previewTipTitle": "Prévisualiser votre agent",
"agentDetail.configure.skills.add": "Ajouter une compétence", "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.contentRegion": "Contenu des détails de la compétence",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FICHIERS", "agentDetail.configure.skills.detail.fileCount": "{{count}} FICHIERS",
"agentDetail.configure.skills.detail.files": "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.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.empty.title": "Pas encore de compétences",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Compétence", "agentDetail.configure.skills.itemType": "Compétence",
"agentDetail.configure.skills.label": "Compétences", "agentDetail.configure.skills.label": "Compétences",
"agentDetail.configure.skills.missing": "Compétence introuvable", "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.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.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.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", "agentDetail.configure.skills.toggle": "Basculer les compétences",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Options de tri", "roster.sort.optionsLabel": "Options de tri",
"roster.sort.recentlyCreated": "Récemment créés", "roster.sort.recentlyCreated": "Récemment créés",
"roster.updateSuccess": "Agent mis à jour.", "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.home": "Accueil",
"mainNav.integrations": "Intégrations", "mainNav.integrations": "Intégrations",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Aucune application web trouvée", "mainNav.webApps.noResults": "Aucune application web trouvée",
"mainNav.webApps.openApp": "Ouvrir lapplication web {{name}}", "mainNav.webApps.openApp": "Ouvrir lapplication web {{name}}",
"mainNav.webApps.searchPlaceholder": "Rechercher des applications web", "mainNav.webApps.searchPlaceholder": "Rechercher des applications web",
@@ -463,6 +464,7 @@
"operation.downloading": "Téléchargement...", "operation.downloading": "Téléchargement...",
"operation.duplicate": "Dupliquer", "operation.duplicate": "Dupliquer",
"operation.edit": "Modifier", "operation.edit": "Modifier",
"operation.export": "Exporter",
"operation.exporting": "Exportation", "operation.exporting": "Exportation",
"operation.fill": "Remplissage auto", "operation.fill": "Remplissage auto",
"operation.format": "Format", "operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "Je suis sûr", "operation.sure": "Je suis sûr",
"operation.toggleFullscreen": "Basculer en plein écran", "operation.toggleFullscreen": "Basculer en plein écran",
"operation.toggleMute": "Activer/désactiver le son", "operation.toggleMute": "Activer/désactiver le son",
"operation.upload": "Upload",
"operation.view": "Vue", "operation.view": "Vue",
"operation.viewDetails": "Voir les détails", "operation.viewDetails": "Voir les détails",
"operation.viewMore": "VOIR PLUS", "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.previewTipBody": "Preview तैयार agent को वैसे चलाता है जैसे आपके users उसे देखेंगे, साफ replies और chat features के साथ।",
"agentDetail.configure.rightPanel.previewTipTitle": "अपने agent का preview करें", "agentDetail.configure.rightPanel.previewTipTitle": "अपने agent का preview करें",
"agentDetail.configure.skills.add": "कौशल जोड़ें", "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.contentRegion": "कौशल विवरण सामग्री",
"agentDetail.configure.skills.detail.fileCount": "{{count}} फ़ाइलें", "agentDetail.configure.skills.detail.fileCount": "{{count}} फ़ाइलें",
"agentDetail.configure.skills.detail.files": "फ़ाइलें", "agentDetail.configure.skills.detail.files": "फ़ाइलें",
"agentDetail.configure.skills.empty.description": "कौशल एजेंट को पुनः उपयोग योग्य विशेषज्ञता देते हैं जिसे वह काम करते समय कॉल कर सकता है", "agentDetail.configure.skills.empty.description": "कौशल एजेंट को पुनः उपयोग योग्य विशेषज्ञता देते हैं जिसे वह काम करते समय कॉल कर सकता है",
"agentDetail.configure.skills.empty.title": "अभी तक कोई कौशल नहीं", "agentDetail.configure.skills.empty.title": "अभी तक कोई कौशल नहीं",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "कौशल", "agentDetail.configure.skills.itemType": "कौशल",
"agentDetail.configure.skills.label": "कौशल", "agentDetail.configure.skills.label": "कौशल",
"agentDetail.configure.skills.missing": "कौशल नहीं मिला", "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.remove": "{{name}} हटाएँ",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "किसी दोहराए जाने वाले कार्य के निर्देशों, फ़ाइलों और स्क्रिप्ट को एक Skill में पैकेज करें। Prompt में / से संदर्भ दें। <docLink>और जानें</docLink>\n\nBuild mode में, एजेंट इन्हें आपके लिए सेट कर सकता है।", "agentDetail.configure.skills.richTip": "किसी दोहराए जाने वाले कार्य के निर्देशों, फ़ाइलों और स्क्रिप्ट को एक Skill में पैकेज करें। Prompt में / से संदर्भ दें। <docLink>और जानें</docLink>\n\nBuild mode में, एजेंट इन्हें आपके लिए सेट कर सकता है।",
"agentDetail.configure.skills.tip": "किसी दोहराए जाने वाले कार्य के निर्देशों, फ़ाइलों और स्क्रिप्ट को एक Skill में पैकेज करें। Prompt में / से संदर्भ दें। और जानें\n\nBuild mode में, एजेंट इन्हें आपके लिए सेट कर सकता है।", "agentDetail.configure.skills.tip": "किसी दोहराए जाने वाले कार्य के निर्देशों, फ़ाइलों और स्क्रिप्ट को एक Skill में पैकेज करें। Prompt में / से संदर्भ दें। और जानें\n\nBuild mode में, एजेंट इन्हें आपके लिए सेट कर सकता है।",
"agentDetail.configure.skills.toggle": "कौशल टॉगल करें", "agentDetail.configure.skills.toggle": "कौशल टॉगल करें",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "क्रमबद्ध विकल्प", "roster.sort.optionsLabel": "क्रमबद्ध विकल्प",
"roster.sort.recentlyCreated": "हाल ही में बनाए गए", "roster.sort.recentlyCreated": "हाल ही में बनाए गए",
"roster.updateSuccess": "एजेंट अपडेट हो गया।", "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.home": "होम",
"mainNav.integrations": "इंटीग्रेशन", "mainNav.integrations": "इंटीग्रेशन",
"mainNav.marketplace": "मार्केटप्लेस", "mainNav.marketplace": "मार्केटप्लेस",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "कोई वेब ऐप नहीं मिला", "mainNav.webApps.noResults": "कोई वेब ऐप नहीं मिला",
"mainNav.webApps.openApp": "{{name}} वेब ऐप खोलें", "mainNav.webApps.openApp": "{{name}} वेब ऐप खोलें",
"mainNav.webApps.searchPlaceholder": "वेब ऐप खोजें", "mainNav.webApps.searchPlaceholder": "वेब ऐप खोजें",
@@ -463,6 +464,7 @@
"operation.downloading": "डाउनलोड हो रहा है...", "operation.downloading": "डाउनलोड हो रहा है...",
"operation.duplicate": "डुप्लिकेट", "operation.duplicate": "डुप्लिकेट",
"operation.edit": "संपादित करें", "operation.edit": "संपादित करें",
"operation.export": "निर्यात करें",
"operation.exporting": "निर्यात हो रहा है", "operation.exporting": "निर्यात हो रहा है",
"operation.fill": "ऑटोफिल", "operation.fill": "ऑटोफिल",
"operation.format": "फॉर्मेट", "operation.format": "फॉर्मेट",
@@ -504,6 +506,7 @@
"operation.sure": "मुझे यकीन है", "operation.sure": "मुझे यकीन है",
"operation.toggleFullscreen": "फ़ुलस्क्रीन टॉगल करें", "operation.toggleFullscreen": "फ़ुलस्क्रीन टॉगल करें",
"operation.toggleMute": "म्यूट टॉगल करें", "operation.toggleMute": "म्यूट टॉगल करें",
"operation.upload": "Upload",
"operation.view": "देखना", "operation.view": "देखना",
"operation.viewDetails": "विवरण देखें", "operation.viewDetails": "विवरण देखें",
"operation.viewMore": "और देखें", "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.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.rightPanel.previewTipTitle": "Pratinjau agen Anda",
"agentDetail.configure.skills.add": "Tambahkan keterampilan", "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.contentRegion": "Konten detail keterampilan",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILE", "agentDetail.configure.skills.detail.fileCount": "{{count}} FILE",
"agentDetail.configure.skills.detail.files": "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.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.empty.title": "Belum ada keterampilan",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Keterampilan", "agentDetail.configure.skills.itemType": "Keterampilan",
"agentDetail.configure.skills.label": "Keterampilan", "agentDetail.configure.skills.label": "Keterampilan",
"agentDetail.configure.skills.missing": "Keterampilan tidak ditemukan", "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.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.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.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", "agentDetail.configure.skills.toggle": "Alihkan keterampilan",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opsi pengurutan", "roster.sort.optionsLabel": "Opsi pengurutan",
"roster.sort.recentlyCreated": "Baru dibuat", "roster.sort.recentlyCreated": "Baru dibuat",
"roster.updateSuccess": "Agen diperbarui.", "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.home": "Beranda",
"mainNav.integrations": "Integrasi", "mainNav.integrations": "Integrasi",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Tidak ada aplikasi web ditemukan", "mainNav.webApps.noResults": "Tidak ada aplikasi web ditemukan",
"mainNav.webApps.openApp": "Buka aplikasi web {{name}}", "mainNav.webApps.openApp": "Buka aplikasi web {{name}}",
"mainNav.webApps.searchPlaceholder": "Cari aplikasi web", "mainNav.webApps.searchPlaceholder": "Cari aplikasi web",
@@ -462,6 +463,7 @@
"operation.downloading": "Mengunduh...", "operation.downloading": "Mengunduh...",
"operation.duplicate": "Duplikat", "operation.duplicate": "Duplikat",
"operation.edit": "Mengedit", "operation.edit": "Mengedit",
"operation.export": "Ekspor",
"operation.exporting": "Mengekspor", "operation.exporting": "Mengekspor",
"operation.fill": "Isi otomatis", "operation.fill": "Isi otomatis",
"operation.format": "Format", "operation.format": "Format",
@@ -503,6 +505,7 @@
"operation.sure": "Saya yakin", "operation.sure": "Saya yakin",
"operation.toggleFullscreen": "Alihkan layar penuh", "operation.toggleFullscreen": "Alihkan layar penuh",
"operation.toggleMute": "Alihkan bisu", "operation.toggleMute": "Alihkan bisu",
"operation.upload": "Upload",
"operation.view": "Lihat", "operation.view": "Lihat",
"operation.viewDetails": "Lihat Detail", "operation.viewDetails": "Lihat Detail",
"operation.viewMore": "LIHAT LEBIH BANYAK", "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.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.rightPanel.previewTipTitle": "Anteprima del tuo agente",
"agentDetail.configure.skills.add": "Aggiungi abilità", "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.contentRegion": "Contenuto dei dettagli dellabilità",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILE", "agentDetail.configure.skills.detail.fileCount": "{{count}} FILE",
"agentDetail.configure.skills.detail.files": "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.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.empty.title": "Nessuna abilità al momento",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Abilità", "agentDetail.configure.skills.itemType": "Abilità",
"agentDetail.configure.skills.label": "Abilità", "agentDetail.configure.skills.label": "Abilità",
"agentDetail.configure.skills.missing": "Abilità non trovata", "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.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.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.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à", "agentDetail.configure.skills.toggle": "Attiva/disattiva abilità",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opzioni di ordinamento", "roster.sort.optionsLabel": "Opzioni di ordinamento",
"roster.sort.recentlyCreated": "Creati di recente", "roster.sort.recentlyCreated": "Creati di recente",
"roster.updateSuccess": "Agente aggiornato.", "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.home": "Home",
"mainNav.integrations": "Integrazioni", "mainNav.integrations": "Integrazioni",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Nessuna app web trovata", "mainNav.webApps.noResults": "Nessuna app web trovata",
"mainNav.webApps.openApp": "Apri l'app web {{name}}", "mainNav.webApps.openApp": "Apri l'app web {{name}}",
"mainNav.webApps.searchPlaceholder": "Cerca app web", "mainNav.webApps.searchPlaceholder": "Cerca app web",
@@ -463,6 +464,7 @@
"operation.downloading": "Download in corso...", "operation.downloading": "Download in corso...",
"operation.duplicate": "Duplica", "operation.duplicate": "Duplica",
"operation.edit": "Modifica", "operation.edit": "Modifica",
"operation.export": "Esporta",
"operation.exporting": "Esportazione in corso", "operation.exporting": "Esportazione in corso",
"operation.fill": "Compilazione automatica", "operation.fill": "Compilazione automatica",
"operation.format": "Formato", "operation.format": "Formato",
@@ -504,6 +506,7 @@
"operation.sure": "Sono sicuro", "operation.sure": "Sono sicuro",
"operation.toggleFullscreen": "Attiva/disattiva schermo intero", "operation.toggleFullscreen": "Attiva/disattiva schermo intero",
"operation.toggleMute": "Attiva/disattiva muto", "operation.toggleMute": "Attiva/disattiva muto",
"operation.upload": "Upload",
"operation.view": "Vista", "operation.view": "Vista",
"operation.viewDetails": "Visualizza dettagli", "operation.viewDetails": "Visualizza dettagli",
"operation.viewMore": "SCOPRI DI PIÙ", "operation.viewMore": "SCOPRI DI PIÙ",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview は完成したエージェントをユーザーに見える形で実行し、整った返信とチャット機能を確認できます。", "agentDetail.configure.rightPanel.previewTipBody": "Preview は完成したエージェントをユーザーに見える形で実行し、整った返信とチャット機能を確認できます。",
"agentDetail.configure.rightPanel.previewTipTitle": "エージェントをプレビュー", "agentDetail.configure.rightPanel.previewTipTitle": "エージェントをプレビュー",
"agentDetail.configure.skills.add": "スキルを追加", "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.contentRegion": "スキル詳細コンテンツ",
"agentDetail.configure.skills.detail.fileCount": "{{count}} 個のファイル", "agentDetail.configure.skills.detail.fileCount": "{{count}} 個のファイル",
"agentDetail.configure.skills.detail.files": "ファイル", "agentDetail.configure.skills.detail.files": "ファイル",
"agentDetail.configure.skills.empty.description": "スキルはエージェントが作業中に呼び出せる再利用可能な専門知識を提供します", "agentDetail.configure.skills.empty.description": "スキルはエージェントが作業中に呼び出せる再利用可能な専門知識を提供します",
"agentDetail.configure.skills.empty.title": "スキルはまだありません", "agentDetail.configure.skills.empty.title": "スキルはまだありません",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "スキル", "agentDetail.configure.skills.itemType": "スキル",
"agentDetail.configure.skills.label": "スキル", "agentDetail.configure.skills.label": "スキル",
"agentDetail.configure.skills.missing": "スキルが見つかりません", "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.remove": "{{name}} を削除",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "繰り返し行うタスクに必要な指示、ファイル、スクリプトを Skill にまとめます。Prompt で / を使って参照します。<docLink>詳しく見る</docLink>\n\nBuild モードでは、エージェントがこれらを設定できます。", "agentDetail.configure.skills.richTip": "繰り返し行うタスクに必要な指示、ファイル、スクリプトを Skill にまとめます。Prompt で / を使って参照します。<docLink>詳しく見る</docLink>\n\nBuild モードでは、エージェントがこれらを設定できます。",
"agentDetail.configure.skills.tip": "繰り返し行うタスクに必要な指示、ファイル、スクリプトを Skill にまとめます。Prompt で / を使って参照します。詳しく見る\n\nBuild モードでは、エージェントがこれらを設定できます。", "agentDetail.configure.skills.tip": "繰り返し行うタスクに必要な指示、ファイル、スクリプトを Skill にまとめます。Prompt で / を使って参照します。詳しく見る\n\nBuild モードでは、エージェントがこれらを設定できます。",
"agentDetail.configure.skills.toggle": "スキルの表示を切り替え", "agentDetail.configure.skills.toggle": "スキルの表示を切り替え",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "並び替えオプション", "roster.sort.optionsLabel": "並び替えオプション",
"roster.sort.recentlyCreated": "最近作成", "roster.sort.recentlyCreated": "最近作成",
"roster.updateSuccess": "エージェントを更新しました。", "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.home": "ホーム",
"mainNav.integrations": "連携", "mainNav.integrations": "連携",
"mainNav.marketplace": "マーケットプレイス", "mainNav.marketplace": "マーケットプレイス",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Webアプリが見つかりません", "mainNav.webApps.noResults": "Webアプリが見つかりません",
"mainNav.webApps.openApp": "{{name}} のWebアプリを開く", "mainNav.webApps.openApp": "{{name}} のWebアプリを開く",
"mainNav.webApps.searchPlaceholder": "Webアプリを検索", "mainNav.webApps.searchPlaceholder": "Webアプリを検索",
@@ -463,6 +464,7 @@
"operation.downloading": "ダウンロード中...", "operation.downloading": "ダウンロード中...",
"operation.duplicate": "複製", "operation.duplicate": "複製",
"operation.edit": "編集", "operation.edit": "編集",
"operation.export": "エクスポート",
"operation.exporting": "エクスポート中", "operation.exporting": "エクスポート中",
"operation.fill": "自動入力", "operation.fill": "自動入力",
"operation.format": "フォーマット", "operation.format": "フォーマット",
@@ -504,6 +506,7 @@
"operation.sure": "確認済み", "operation.sure": "確認済み",
"operation.toggleFullscreen": "全画面表示を切り替え", "operation.toggleFullscreen": "全画面表示を切り替え",
"operation.toggleMute": "ミュートを切り替え", "operation.toggleMute": "ミュートを切り替え",
"operation.upload": "Upload",
"operation.view": "表示", "operation.view": "表示",
"operation.viewDetails": "詳細を見る", "operation.viewDetails": "詳細を見る",
"operation.viewMore": "さらに表示", "operation.viewMore": "さらに表示",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview는 사용자가 보게 될 방식으로 완성된 에이전트를 실행하며, 깔끔한 답변과 채팅 기능을 보여줍니다.", "agentDetail.configure.rightPanel.previewTipBody": "Preview는 사용자가 보게 될 방식으로 완성된 에이전트를 실행하며, 깔끔한 답변과 채팅 기능을 보여줍니다.",
"agentDetail.configure.rightPanel.previewTipTitle": "에이전트 미리보기", "agentDetail.configure.rightPanel.previewTipTitle": "에이전트 미리보기",
"agentDetail.configure.skills.add": "스킬 추가", "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.contentRegion": "스킬 상세 내용",
"agentDetail.configure.skills.detail.fileCount": "파일 {{count}}개", "agentDetail.configure.skills.detail.fileCount": "파일 {{count}}개",
"agentDetail.configure.skills.detail.files": "파일", "agentDetail.configure.skills.detail.files": "파일",
"agentDetail.configure.skills.empty.description": "스킬은 에이전트가 작업하면서 호출할 수 있는 재사용 가능한 전문 능력을 제공합니다", "agentDetail.configure.skills.empty.description": "스킬은 에이전트가 작업하면서 호출할 수 있는 재사용 가능한 전문 능력을 제공합니다",
"agentDetail.configure.skills.empty.title": "아직 스킬이 없습니다", "agentDetail.configure.skills.empty.title": "아직 스킬이 없습니다",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "스킬", "agentDetail.configure.skills.itemType": "스킬",
"agentDetail.configure.skills.label": "스킬", "agentDetail.configure.skills.label": "스킬",
"agentDetail.configure.skills.missing": "스킬을 찾을 수 없음", "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.remove": "{{name}} 제거",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "반복 작업에 필요한 지침, 파일, 스크립트를 Skill로 패키징하세요. Prompt에서 /로 참조하세요. <docLink>자세히 알아보기</docLink>\n\nBuild mode에서는 에이전트가 이를 대신 설정할 수 있습니다.", "agentDetail.configure.skills.richTip": "반복 작업에 필요한 지침, 파일, 스크립트를 Skill로 패키징하세요. Prompt에서 /로 참조하세요. <docLink>자세히 알아보기</docLink>\n\nBuild mode에서는 에이전트가 이를 대신 설정할 수 있습니다.",
"agentDetail.configure.skills.tip": "반복 작업에 필요한 지침, 파일, 스크립트를 Skill로 패키징하세요. Prompt에서 /로 참조하세요. 자세히 알아보기\n\nBuild mode에서는 에이전트가 이를 대신 설정할 수 있습니다.", "agentDetail.configure.skills.tip": "반복 작업에 필요한 지침, 파일, 스크립트를 Skill로 패키징하세요. Prompt에서 /로 참조하세요. 자세히 알아보기\n\nBuild mode에서는 에이전트가 이를 대신 설정할 수 있습니다.",
"agentDetail.configure.skills.toggle": "스킬 전환", "agentDetail.configure.skills.toggle": "스킬 전환",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "정렬 옵션", "roster.sort.optionsLabel": "정렬 옵션",
"roster.sort.recentlyCreated": "최근 생성", "roster.sort.recentlyCreated": "최근 생성",
"roster.updateSuccess": "에이전트가 업데이트되었습니다.", "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.home": "홈",
"mainNav.integrations": "연동", "mainNav.integrations": "연동",
"mainNav.marketplace": "마켓플레이스", "mainNav.marketplace": "마켓플레이스",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "웹 앱을 찾을 수 없습니다", "mainNav.webApps.noResults": "웹 앱을 찾을 수 없습니다",
"mainNav.webApps.openApp": "{{name}} 웹 앱 열기", "mainNav.webApps.openApp": "{{name}} 웹 앱 열기",
"mainNav.webApps.searchPlaceholder": "웹 앱 검색", "mainNav.webApps.searchPlaceholder": "웹 앱 검색",
@@ -463,6 +464,7 @@
"operation.downloading": "다운로드 중...", "operation.downloading": "다운로드 중...",
"operation.duplicate": "중복", "operation.duplicate": "중복",
"operation.edit": "편집", "operation.edit": "편집",
"operation.export": "내보내기",
"operation.exporting": "내보내는 중", "operation.exporting": "내보내는 중",
"operation.fill": "자동 입력", "operation.fill": "자동 입력",
"operation.format": "형식", "operation.format": "형식",
@@ -504,6 +506,7 @@
"operation.sure": "확인", "operation.sure": "확인",
"operation.toggleFullscreen": "전체 화면 전환", "operation.toggleFullscreen": "전체 화면 전환",
"operation.toggleMute": "음소거 전환", "operation.toggleMute": "음소거 전환",
"operation.upload": "Upload",
"operation.view": "보기", "operation.view": "보기",
"operation.viewDetails": "세부 정보보기", "operation.viewDetails": "세부 정보보기",
"operation.viewMore": "더보기", "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.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.rightPanel.previewTipTitle": "Voorbeeld van je agent",
"agentDetail.configure.skills.add": "Skill toevoegen", "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.contentRegion": "Skill-detailinhoud",
"agentDetail.configure.skills.detail.fileCount": "{{count}} BESTANDEN", "agentDetail.configure.skills.detail.fileCount": "{{count}} BESTANDEN",
"agentDetail.configure.skills.detail.files": "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.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.empty.title": "Nog geen skills",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Skill", "agentDetail.configure.skills.itemType": "Skill",
"agentDetail.configure.skills.label": "Skills", "agentDetail.configure.skills.label": "Skills",
"agentDetail.configure.skills.missing": "Skill niet gevonden", "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.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.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.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", "agentDetail.configure.skills.toggle": "Skills in/uit",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Sorteeropties", "roster.sort.optionsLabel": "Sorteeropties",
"roster.sort.recentlyCreated": "Recent aangemaakt", "roster.sort.recentlyCreated": "Recent aangemaakt",
"roster.updateSuccess": "Agent bijgewerkt.", "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.home": "Start",
"mainNav.integrations": "Integraties", "mainNav.integrations": "Integraties",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Geen webapps gevonden", "mainNav.webApps.noResults": "Geen webapps gevonden",
"mainNav.webApps.openApp": "Webapp {{name}} openen", "mainNav.webApps.openApp": "Webapp {{name}} openen",
"mainNav.webApps.searchPlaceholder": "Webapps zoeken", "mainNav.webApps.searchPlaceholder": "Webapps zoeken",
@@ -462,6 +463,7 @@
"operation.downloading": "Downloaden...", "operation.downloading": "Downloaden...",
"operation.duplicate": "Duplicate", "operation.duplicate": "Duplicate",
"operation.edit": "Edit", "operation.edit": "Edit",
"operation.export": "Exporteren",
"operation.exporting": "Exporteren", "operation.exporting": "Exporteren",
"operation.fill": "Automatisch invullen", "operation.fill": "Automatisch invullen",
"operation.format": "Format", "operation.format": "Format",
@@ -503,6 +505,7 @@
"operation.sure": "I'm sure", "operation.sure": "I'm sure",
"operation.toggleFullscreen": "Volledig scherm schakelen", "operation.toggleFullscreen": "Volledig scherm schakelen",
"operation.toggleMute": "Dempen schakelen", "operation.toggleMute": "Dempen schakelen",
"operation.upload": "Upload",
"operation.view": "View", "operation.view": "View",
"operation.viewDetails": "View Details", "operation.viewDetails": "View Details",
"operation.viewMore": "VIEW MORE", "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.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.rightPanel.previewTipTitle": "Podgląd agenta",
"agentDetail.configure.skills.add": "Dodaj umiejętność", "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.contentRegion": "Zawartość szczegółów umiejętności",
"agentDetail.configure.skills.detail.fileCount": "{{count}} PLIKÓW", "agentDetail.configure.skills.detail.fileCount": "{{count}} PLIKÓW",
"agentDetail.configure.skills.detail.files": "Pliki", "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.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.empty.title": "Brak umiejętności",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Umiejętność", "agentDetail.configure.skills.itemType": "Umiejętność",
"agentDetail.configure.skills.label": "Umiejętności", "agentDetail.configure.skills.label": "Umiejętności",
"agentDetail.configure.skills.missing": "Nie znaleziono 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.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.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.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", "agentDetail.configure.skills.toggle": "Przełącz umiejętności",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opcje sortowania", "roster.sort.optionsLabel": "Opcje sortowania",
"roster.sort.recentlyCreated": "Ostatnio utworzone", "roster.sort.recentlyCreated": "Ostatnio utworzone",
"roster.updateSuccess": "Agent zaktualizowany.", "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.home": "Strona główna",
"mainNav.integrations": "Integracje", "mainNav.integrations": "Integracje",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Nie znaleziono aplikacji webowych", "mainNav.webApps.noResults": "Nie znaleziono aplikacji webowych",
"mainNav.webApps.openApp": "Otwórz aplikację webową {{name}}", "mainNav.webApps.openApp": "Otwórz aplikację webową {{name}}",
"mainNav.webApps.searchPlaceholder": "Szukaj aplikacji webowych", "mainNav.webApps.searchPlaceholder": "Szukaj aplikacji webowych",
@@ -463,6 +464,7 @@
"operation.downloading": "Pobieranie...", "operation.downloading": "Pobieranie...",
"operation.duplicate": "Duplikuj", "operation.duplicate": "Duplikuj",
"operation.edit": "Edytuj", "operation.edit": "Edytuj",
"operation.export": "Eksportuj",
"operation.exporting": "Eksportowanie", "operation.exporting": "Eksportowanie",
"operation.fill": "Autouzupełnianie", "operation.fill": "Autouzupełnianie",
"operation.format": "Format", "operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "Jestem pewien", "operation.sure": "Jestem pewien",
"operation.toggleFullscreen": "Przełącz pełny ekran", "operation.toggleFullscreen": "Przełącz pełny ekran",
"operation.toggleMute": "Przełącz wyciszenie", "operation.toggleMute": "Przełącz wyciszenie",
"operation.upload": "Upload",
"operation.view": "Widok", "operation.view": "Widok",
"operation.viewDetails": "Wyświetl szczegóły", "operation.viewDetails": "Wyświetl szczegóły",
"operation.viewMore": "ZOBACZ WIĘCEJ", "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.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.rightPanel.previewTipTitle": "Pré-visualize seu agente",
"agentDetail.configure.skills.add": "Adicionar habilidade", "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.contentRegion": "Conteúdo dos detalhes da habilidade",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ARQUIVOS", "agentDetail.configure.skills.detail.fileCount": "{{count}} ARQUIVOS",
"agentDetail.configure.skills.detail.files": "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.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.empty.title": "Ainda não há habilidades",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Habilidade", "agentDetail.configure.skills.itemType": "Habilidade",
"agentDetail.configure.skills.label": "Habilidades", "agentDetail.configure.skills.label": "Habilidades",
"agentDetail.configure.skills.missing": "Habilidade não encontrada", "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.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.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.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", "agentDetail.configure.skills.toggle": "Alternar habilidades",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opções de ordenação", "roster.sort.optionsLabel": "Opções de ordenação",
"roster.sort.recentlyCreated": "Criados recentemente", "roster.sort.recentlyCreated": "Criados recentemente",
"roster.updateSuccess": "Agente atualizado.", "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.home": "Início",
"mainNav.integrations": "Integrações", "mainNav.integrations": "Integrações",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Nenhum app web encontrado", "mainNav.webApps.noResults": "Nenhum app web encontrado",
"mainNav.webApps.openApp": "Abrir o app web {{name}}", "mainNav.webApps.openApp": "Abrir o app web {{name}}",
"mainNav.webApps.searchPlaceholder": "Pesquisar apps web", "mainNav.webApps.searchPlaceholder": "Pesquisar apps web",
@@ -463,6 +464,7 @@
"operation.downloading": "Baixando...", "operation.downloading": "Baixando...",
"operation.duplicate": "Duplicada", "operation.duplicate": "Duplicada",
"operation.edit": "Editar", "operation.edit": "Editar",
"operation.export": "Exportar",
"operation.exporting": "Exportando", "operation.exporting": "Exportando",
"operation.fill": "Preenchimento automático", "operation.fill": "Preenchimento automático",
"operation.format": "Formato", "operation.format": "Formato",
@@ -504,6 +506,7 @@
"operation.sure": "Tenho certeza", "operation.sure": "Tenho certeza",
"operation.toggleFullscreen": "Alternar tela cheia", "operation.toggleFullscreen": "Alternar tela cheia",
"operation.toggleMute": "Alternar mudo", "operation.toggleMute": "Alternar mudo",
"operation.upload": "Upload",
"operation.view": "Vista", "operation.view": "Vista",
"operation.viewDetails": "Ver detalhes", "operation.viewDetails": "Ver detalhes",
"operation.viewMore": "VER MAIS", "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.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.rightPanel.previewTipTitle": "Previzualizează agentul",
"agentDetail.configure.skills.add": "Adaugă abilitate", "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.contentRegion": "Conținutul detaliilor abilității",
"agentDetail.configure.skills.detail.fileCount": "{{count}} FIȘIERE", "agentDetail.configure.skills.detail.fileCount": "{{count}} FIȘIERE",
"agentDetail.configure.skills.detail.files": "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.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.empty.title": "Nicio abilitate încă",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Abilitate", "agentDetail.configure.skills.itemType": "Abilitate",
"agentDetail.configure.skills.label": "Abilități", "agentDetail.configure.skills.label": "Abilități",
"agentDetail.configure.skills.missing": "Abilitatea nu a fost găsită", "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.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.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.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", "agentDetail.configure.skills.toggle": "Comută abilitățile",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Opțiuni de sortare", "roster.sort.optionsLabel": "Opțiuni de sortare",
"roster.sort.recentlyCreated": "Create recent", "roster.sort.recentlyCreated": "Create recent",
"roster.updateSuccess": "Agent actualizat.", "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.home": "Acasă",
"mainNav.integrations": "Integrări", "mainNav.integrations": "Integrări",
"mainNav.marketplace": "Marketplace", "mainNav.marketplace": "Marketplace",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Nu s-au găsit aplicații web", "mainNav.webApps.noResults": "Nu s-au găsit aplicații web",
"mainNav.webApps.openApp": "Deschide aplicația web {{name}}", "mainNav.webApps.openApp": "Deschide aplicația web {{name}}",
"mainNav.webApps.searchPlaceholder": "Caută aplicații web", "mainNav.webApps.searchPlaceholder": "Caută aplicații web",
@@ -463,6 +464,7 @@
"operation.downloading": "Se descarcă...", "operation.downloading": "Se descarcă...",
"operation.duplicate": "Duplică", "operation.duplicate": "Duplică",
"operation.edit": "Editează", "operation.edit": "Editează",
"operation.export": "Exportă",
"operation.exporting": "Se exportă", "operation.exporting": "Se exportă",
"operation.fill": "Completare automată", "operation.fill": "Completare automată",
"operation.format": "Format", "operation.format": "Format",
@@ -504,6 +506,7 @@
"operation.sure": "Sunt sigur", "operation.sure": "Sunt sigur",
"operation.toggleFullscreen": "Comută ecran complet", "operation.toggleFullscreen": "Comută ecran complet",
"operation.toggleMute": "Comută sunetul", "operation.toggleMute": "Comută sunetul",
"operation.upload": "Upload",
"operation.view": "Vedere", "operation.view": "Vedere",
"operation.viewDetails": "Vezi detalii", "operation.viewDetails": "Vezi detalii",
"operation.viewMore": "VEZI MAI MULT", "operation.viewMore": "VEZI MAI MULT",
+120 -1
View File
@@ -203,15 +203,22 @@
"agentDetail.configure.rightPanel.previewTipBody": "Preview запускает готового агента так, как его увидят пользователи, с чистыми ответами и функциями чата.", "agentDetail.configure.rightPanel.previewTipBody": "Preview запускает готового агента так, как его увидят пользователи, с чистыми ответами и функциями чата.",
"agentDetail.configure.rightPanel.previewTipTitle": "Предпросмотр агента", "agentDetail.configure.rightPanel.previewTipTitle": "Предпросмотр агента",
"agentDetail.configure.skills.add": "Добавить навык", "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.contentRegion": "Содержимое деталей навыка",
"agentDetail.configure.skills.detail.fileCount": "{{count}} ФАЙЛОВ", "agentDetail.configure.skills.detail.fileCount": "{{count}} ФАЙЛОВ",
"agentDetail.configure.skills.detail.files": "Файлы", "agentDetail.configure.skills.detail.files": "Файлы",
"agentDetail.configure.skills.empty.description": "Навыки дают агенту переиспользуемую экспертизу, которую он может вызывать в работе", "agentDetail.configure.skills.empty.description": "Навыки дают агенту переиспользуемую экспертизу, которую он может вызывать в работе",
"agentDetail.configure.skills.empty.title": "Пока нет навыков", "agentDetail.configure.skills.empty.title": "Пока нет навыков",
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
"agentDetail.configure.skills.itemType": "Навык", "agentDetail.configure.skills.itemType": "Навык",
"agentDetail.configure.skills.label": "Навыки", "agentDetail.configure.skills.label": "Навыки",
"agentDetail.configure.skills.missing": "Навык не найден", "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.remove": "Удалить {{name}}",
"agentDetail.configure.skills.removeAction": "Remove",
"agentDetail.configure.skills.richTip": "Упакуйте инструкции, файлы и скрипты для повторяющейся задачи в Skill. Ссылайтесь на него через / в Prompt. <docLink>Подробнее</docLink>\n\nВ режиме Build агент может настроить это за вас.", "agentDetail.configure.skills.richTip": "Упакуйте инструкции, файлы и скрипты для повторяющейся задачи в Skill. Ссылайтесь на него через / в Prompt. <docLink>Подробнее</docLink>\n\nВ режиме Build агент может настроить это за вас.",
"agentDetail.configure.skills.tip": "Упакуйте инструкции, файлы и скрипты для повторяющейся задачи в Skill. Ссылайтесь на него через / в Prompt. Подробнее\n\nВ режиме Build агент может настроить это за вас.", "agentDetail.configure.skills.tip": "Упакуйте инструкции, файлы и скрипты для повторяющейся задачи в Skill. Ссылайтесь на него через / в Prompt. Подробнее\n\nВ режиме Build агент может настроить это за вас.",
"agentDetail.configure.skills.toggle": "Переключить навыки", "agentDetail.configure.skills.toggle": "Переключить навыки",
@@ -421,5 +428,117 @@
"roster.sort.optionsLabel": "Параметры сортировки", "roster.sort.optionsLabel": "Параметры сортировки",
"roster.sort.recentlyCreated": "Недавно созданные", "roster.sort.recentlyCreated": "Недавно созданные",
"roster.updateSuccess": "Агент обновлён.", "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.home": "Главная",
"mainNav.integrations": "Интеграции", "mainNav.integrations": "Интеграции",
"mainNav.marketplace": "Маркетплейс", "mainNav.marketplace": "Маркетплейс",
"mainNav.skills": "Skills",
"mainNav.webApps.noResults": "Веб-приложения не найдены", "mainNav.webApps.noResults": "Веб-приложения не найдены",
"mainNav.webApps.openApp": "Открыть веб-приложение {{name}}", "mainNav.webApps.openApp": "Открыть веб-приложение {{name}}",
"mainNav.webApps.searchPlaceholder": "Поиск веб-приложений", "mainNav.webApps.searchPlaceholder": "Поиск веб-приложений",
@@ -463,6 +464,7 @@
"operation.downloading": "Скачивание...", "operation.downloading": "Скачивание...",
"operation.duplicate": "Дублировать", "operation.duplicate": "Дублировать",
"operation.edit": "Редактировать", "operation.edit": "Редактировать",
"operation.export": "Экспорт",
"operation.exporting": "Экспорт", "operation.exporting": "Экспорт",
"operation.fill": "Автозаполнение", "operation.fill": "Автозаполнение",
"operation.format": "Формат", "operation.format": "Формат",
@@ -504,6 +506,7 @@
"operation.sure": "Я уверен", "operation.sure": "Я уверен",
"operation.toggleFullscreen": "Переключить полноэкранный режим", "operation.toggleFullscreen": "Переключить полноэкранный режим",
"operation.toggleMute": "Переключить звук", "operation.toggleMute": "Переключить звук",
"operation.upload": "Upload",
"operation.view": "Вид", "operation.view": "Вид",
"operation.viewDetails": "Подробнее", "operation.viewDetails": "Подробнее",
"operation.viewMore": "ПОДРОБНЕЕ", "operation.viewMore": "ПОДРОБНЕЕ",

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