Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77e41a37ee | ||
|
|
79229b7ede | ||
|
|
36ae648a63 | ||
|
|
c409d531da | ||
|
|
511ca69e4e | ||
|
|
4c7e60d7e7 | ||
|
|
a9e163403d | ||
|
|
bc136b89f9 | ||
|
|
b1bd4f9a8b | ||
|
|
06d3927f05 | ||
|
|
5c3516cae8 | ||
|
|
1c18d8ddbd | ||
|
|
253dfe9351 | ||
|
|
2d3999e984 | ||
|
|
a431cc726d | ||
|
|
08493d2429 | ||
|
|
8dd0969006 | ||
|
|
2d80d3c35c | ||
|
|
4da764904c |
@@ -144,6 +144,7 @@ from .workspace import (
|
||||
models,
|
||||
plugin,
|
||||
rbac,
|
||||
skills,
|
||||
snippets,
|
||||
tool_providers,
|
||||
trigger_providers,
|
||||
@@ -225,6 +226,7 @@ __all__ = [
|
||||
"saved_message",
|
||||
"setup",
|
||||
"site",
|
||||
"skills",
|
||||
"snippet_workflow",
|
||||
"snippet_workflow_draft_variable",
|
||||
"snippets",
|
||||
|
||||
@@ -59,7 +59,7 @@ class TagBindingRemovePayload(BaseModel):
|
||||
|
||||
|
||||
class TagListQueryParam(BaseModel):
|
||||
type: Literal["knowledge", "app", "snippet", ""] = Field("", description="Tag type filter")
|
||||
type: TagType | Literal[""] = Field("", description="Tag type filter")
|
||||
keyword: str | None = Field(None, description="Search keyword")
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -23,6 +23,7 @@ from .knowledge import retrieval as _knowledge_retrieval
|
||||
from .plugin import agent_config as _agent_config
|
||||
from .plugin import agent_drive as _agent_drive
|
||||
from .plugin import plugin as _plugin
|
||||
from .plugin import skills as _skills
|
||||
from .workspace import workspace as _workspace
|
||||
|
||||
api.add_namespace(inner_api_ns)
|
||||
@@ -36,6 +37,7 @@ __all__ = [
|
||||
"_mail",
|
||||
"_plugin",
|
||||
"_runtime_credentials",
|
||||
"_skills",
|
||||
"_workspace",
|
||||
"api",
|
||||
"bp",
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Inner API for published workspace Skills.
|
||||
|
||||
These endpoints are called by trusted runtime services. They expose only
|
||||
published Skill artifacts, never draft files or editable metadata.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
from flask import request, send_file
|
||||
from flask_restx import Resource
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from controllers.console.wraps import setup_required
|
||||
from controllers.inner_api import inner_api_ns
|
||||
from controllers.inner_api.wraps import plugin_inner_api_only
|
||||
from services.skill_management_service import SkillManagementService, SkillManagementServiceError
|
||||
|
||||
|
||||
class _SkillTargetQuery(BaseModel):
|
||||
tenant_id: str
|
||||
|
||||
|
||||
def _target_query_from_request() -> _SkillTargetQuery:
|
||||
return _SkillTargetQuery.model_validate({"tenant_id": request.args.get("tenant_id")})
|
||||
|
||||
|
||||
def _error_response(exc: SkillManagementServiceError) -> tuple[dict[str, str], int]:
|
||||
return {"code": exc.code, "message": exc.message}, exc.status_code
|
||||
|
||||
|
||||
@inner_api_ns.route("/skills/<string:skill_id>/pull")
|
||||
class PublishedSkillPullApi(Resource):
|
||||
@setup_required
|
||||
@plugin_inner_api_only
|
||||
@inner_api_ns.doc("published_skill_pull")
|
||||
def get(self, skill_id: str):
|
||||
try:
|
||||
query = _target_query_from_request()
|
||||
result = SkillManagementService().pull_published_archive(tenant_id=query.tenant_id, skill_id=skill_id)
|
||||
return send_file(
|
||||
io.BytesIO(result.payload),
|
||||
mimetype=result.mime_type,
|
||||
as_attachment=True,
|
||||
download_name=result.filename,
|
||||
)
|
||||
except ValidationError as exc:
|
||||
return {"code": "invalid_request", "message": str(exc)}, 400
|
||||
except SkillManagementServiceError as exc:
|
||||
return _error_response(exc)
|
||||
|
||||
|
||||
__all__ = ["PublishedSkillPullApi"]
|
||||
@@ -43,6 +43,7 @@ from core.workflow.nodes.agent_v2.runtime_request_builder import (
|
||||
build_config_layer_config,
|
||||
build_knowledge_layer_config,
|
||||
build_shell_layer_config,
|
||||
load_runtime_agent_skill_configs,
|
||||
)
|
||||
from models.agent_config_entities import AgentSoulConfig, AgentSoulToolsConfig
|
||||
from models.provider_ids import ModelProviderID
|
||||
@@ -125,14 +126,22 @@ class AgentAppRuntimeRequestBuilder:
|
||||
"cli_tool_count": len(agent_soul.tools.cli_tools),
|
||||
}
|
||||
|
||||
runtime_config_skills = load_runtime_agent_skill_configs(
|
||||
tenant_id=context.dify_context.tenant_id,
|
||||
agent_id=context.agent_id,
|
||||
)
|
||||
config_layer_config, config_warnings = build_config_layer_config(
|
||||
agent_soul,
|
||||
agent_id=context.agent_id,
|
||||
config_version_id=context.agent_config_snapshot_id,
|
||||
config_version_kind=context.agent_config_version_kind,
|
||||
runtime_config_skills=runtime_config_skills,
|
||||
)
|
||||
append_runtime_warnings(metadata, config_warnings)
|
||||
soul_prompt_resolver = build_config_aware_soul_mention_resolver(agent_soul)
|
||||
soul_prompt_resolver = build_config_aware_soul_mention_resolver(
|
||||
agent_soul,
|
||||
runtime_config_skills=runtime_config_skills,
|
||||
)
|
||||
knowledge_config = build_knowledge_layer_config(agent_soul)
|
||||
|
||||
request = self._request_builder.build_for_agent_app(
|
||||
|
||||
@@ -38,6 +38,7 @@ from dify_agent.layers.shell import (
|
||||
)
|
||||
from dify_agent.protocol import CreateRunRequest, DeferredToolResultsPayload
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from sqlalchemy.exc import OperationalError
|
||||
|
||||
from clients.agent_backend import (
|
||||
AgentBackendModelConfig,
|
||||
@@ -206,14 +207,22 @@ class WorkflowAgentRuntimeRequestBuilder:
|
||||
"cli_tool_count": len(agent_soul.tools.cli_tools),
|
||||
}
|
||||
|
||||
runtime_config_skills = load_runtime_agent_skill_configs(
|
||||
tenant_id=context.dify_context.tenant_id,
|
||||
agent_id=context.agent.id,
|
||||
)
|
||||
config_layer_config, config_warnings = build_config_layer_config(
|
||||
agent_soul,
|
||||
agent_id=context.agent.id,
|
||||
config_version_id=context.snapshot.id,
|
||||
config_version_kind="snapshot",
|
||||
runtime_config_skills=runtime_config_skills,
|
||||
)
|
||||
append_runtime_warnings(metadata, config_warnings)
|
||||
soul_prompt_resolver = build_config_aware_soul_mention_resolver(agent_soul)
|
||||
soul_prompt_resolver = build_config_aware_soul_mention_resolver(
|
||||
agent_soul,
|
||||
runtime_config_skills=runtime_config_skills,
|
||||
)
|
||||
soul_prompt = expand_prompt_mentions(agent_soul.prompt.system_prompt, soul_prompt_resolver).strip()
|
||||
knowledge_config = build_knowledge_layer_config(agent_soul)
|
||||
|
||||
@@ -883,11 +892,16 @@ def append_runtime_warnings(metadata: dict[str, Any], warnings: list[dict[str, s
|
||||
existing.extend(warnings)
|
||||
|
||||
|
||||
def build_config_aware_soul_mention_resolver(agent_soul: AgentSoulConfig):
|
||||
def build_config_aware_soul_mention_resolver(
|
||||
agent_soul: AgentSoulConfig,
|
||||
*,
|
||||
runtime_config_skills: Sequence[DifyConfigSkillConfig] = (),
|
||||
):
|
||||
"""Resolve config skill/file mentions and delegate the rest to Agent Soul."""
|
||||
|
||||
base_resolver = build_soul_mention_resolver(agent_soul)
|
||||
skill_names = {item.name for item in agent_soul.config_skills if not item.is_missing}
|
||||
skill_names.update(item.name for item in runtime_config_skills)
|
||||
file_names = {item.name for item in agent_soul.config_files if not item.is_missing}
|
||||
|
||||
def _resolve(mention: object) -> str | None:
|
||||
@@ -905,12 +919,34 @@ def build_config_aware_soul_mention_resolver(agent_soul: AgentSoulConfig):
|
||||
return _resolve
|
||||
|
||||
|
||||
def load_runtime_agent_skill_configs(*, tenant_id: str, agent_id: str) -> list[DifyConfigSkillConfig]:
|
||||
"""Return workspace-bound Skills as prompt-safe runtime config skills."""
|
||||
from services.skill_management_service import SkillManagementService
|
||||
|
||||
try:
|
||||
runtime_skills = SkillManagementService().list_runtime_agent_skills(tenant_id=tenant_id, agent_id=agent_id)
|
||||
except OperationalError as exc:
|
||||
if "no such table: agent_skill_bindings" not in str(exc.orig):
|
||||
raise
|
||||
runtime_skills = []
|
||||
return [
|
||||
DifyConfigSkillConfig(
|
||||
name=str(item["name"]),
|
||||
description=str(item.get("description") or ""),
|
||||
size=cast(int | None, item.get("size")),
|
||||
mime_type=cast(str | None, item.get("mime_type")),
|
||||
)
|
||||
for item in runtime_skills
|
||||
]
|
||||
|
||||
|
||||
def build_config_layer_config(
|
||||
agent_soul: AgentSoulConfig,
|
||||
*,
|
||||
agent_id: str | None = None,
|
||||
config_version_id: str | None = None,
|
||||
config_version_kind: Literal["snapshot", "draft", "build_draft"] = "snapshot",
|
||||
runtime_config_skills: Sequence[DifyConfigSkillConfig] = (),
|
||||
) -> tuple[DifyConfigLayerConfig, list[dict[str, str]]]:
|
||||
"""Build the always-present Agent config layer from Agent Soul state.
|
||||
|
||||
@@ -927,8 +963,23 @@ def build_config_layer_config(
|
||||
)
|
||||
)
|
||||
available_skills = [skill for skill in agent_soul.config_skills if not skill.is_missing]
|
||||
skill_configs = [
|
||||
DifyConfigSkillConfig(
|
||||
name=skill.name,
|
||||
description=skill.description,
|
||||
size=skill.size,
|
||||
mime_type=skill.mime_type,
|
||||
)
|
||||
for skill in available_skills
|
||||
]
|
||||
seen_skill_names = {skill.name for skill in skill_configs}
|
||||
for skill in runtime_config_skills:
|
||||
if skill.name in seen_skill_names:
|
||||
continue
|
||||
seen_skill_names.add(skill.name)
|
||||
skill_configs.append(skill)
|
||||
available_files = [file_ref for file_ref in agent_soul.config_files if not file_ref.is_missing]
|
||||
skill_names = {skill.name for skill in available_skills}
|
||||
skill_names = {skill.name for skill in skill_configs}
|
||||
file_names = {file_ref.name for file_ref in available_files}
|
||||
warnings: list[dict[str, str]] = [
|
||||
{
|
||||
@@ -965,15 +1016,7 @@ def build_config_layer_config(
|
||||
kind=config_version_kind,
|
||||
writable=config_version_kind == "build_draft",
|
||||
),
|
||||
skills=[
|
||||
DifyConfigSkillConfig(
|
||||
name=skill.name,
|
||||
description=skill.description,
|
||||
size=skill.size,
|
||||
mime_type=skill.mime_type,
|
||||
)
|
||||
for skill in available_skills
|
||||
],
|
||||
skills=skill_configs,
|
||||
files=[
|
||||
DifyConfigFileConfig(
|
||||
name=file_ref.name,
|
||||
|
||||
+114
@@ -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")
|
||||
@@ -113,6 +113,7 @@ from .provider import (
|
||||
TenantDefaultModel,
|
||||
TenantPreferredModelProvider,
|
||||
)
|
||||
from .skill import AgentSkillBinding, Skill, SkillDraftFile, SkillFileKind, SkillFileStorage, SkillVersion
|
||||
from .snippet import CustomizedSnippet, SnippetType
|
||||
from .source import DataSourceApiKeyAuthBinding, DataSourceOauthBinding
|
||||
from .task import CeleryTask, CeleryTaskSet
|
||||
@@ -173,6 +174,7 @@ __all__ = [
|
||||
"AgentRuntimeSessionOwnerType",
|
||||
"AgentRuntimeSessionStatus",
|
||||
"AgentScope",
|
||||
"AgentSkillBinding",
|
||||
"AgentSource",
|
||||
"AgentStatus",
|
||||
"ApiRequest",
|
||||
@@ -246,6 +248,11 @@ __all__ = [
|
||||
"RecommendedApp",
|
||||
"SavedMessage",
|
||||
"Site",
|
||||
"Skill",
|
||||
"SkillDraftFile",
|
||||
"SkillFileKind",
|
||||
"SkillFileStorage",
|
||||
"SkillVersion",
|
||||
"SnippetType",
|
||||
"Tag",
|
||||
"TagBinding",
|
||||
|
||||
@@ -249,6 +249,7 @@ class TagType(StrEnum):
|
||||
KNOWLEDGE = "knowledge"
|
||||
APP = "app"
|
||||
SNIPPET = "snippet"
|
||||
SKILL = "skill"
|
||||
|
||||
|
||||
class DatasetMetadataType(StrEnum):
|
||||
|
||||
+1
-1
@@ -2667,7 +2667,7 @@ class Tag(TypeBase):
|
||||
sa.Index("tag_name_idx", "name"),
|
||||
)
|
||||
|
||||
TAG_TYPE_LIST = ["knowledge", "app", "snippet"]
|
||||
TAG_TYPE_LIST = ["knowledge", "app", "snippet", "skill"]
|
||||
|
||||
id: Mapped[str] = mapped_column(
|
||||
StringUUID, insert_default=lambda: str(uuid4()), default_factory=lambda: str(uuid4()), init=False
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
@@ -9551,7 +9551,7 @@ Remove one or more tag bindings from a target.
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| keyword | query | Search keyword | No | string |
|
||||
| type | query | Tag type filter | No | string, <br>**Available values:** "", "app", "knowledge", "snippet" |
|
||||
| type | query | Tag type filter | No | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
@@ -10087,6 +10087,38 @@ Get list of available agent providers
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [AgentProviderListResponse](#agentproviderlistresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/agents/{agent_id}/skills
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| agent_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Agent Skill bindings | **application/json**: [AgentSkillBindingsResponse](#agentskillbindingsresponse)<br> |
|
||||
|
||||
### [PUT] /workspaces/current/agents/{agent_id}/skills
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| agent_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [AgentSkillBindingsPayload](#agentskillbindingspayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Agent Skill bindings replaced | **application/json**: [AgentSkillBindingsResponse](#agentskillbindingsresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/customized-snippets
|
||||
**List customized snippets with pagination and search**
|
||||
|
||||
@@ -11988,6 +12020,341 @@ Returns permission flags that control workspace features like member invitations
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Success | **application/json**: [WorkspaceAccessMatrix](#workspaceaccessmatrix)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| keyword | query | Search keyword matching skill name or description. | No | string |
|
||||
| limit | query | Number of items per page. | No | integer, <br>**Default:** 20 |
|
||||
| page | query | Page number. | No | integer, <br>**Default:** 1 |
|
||||
| tag | query | Skill tag filters. Repeat the parameter for multiple tags. | No | [ string ] |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Workspace skills | **application/json**: [SkillListResponse](#skilllistresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/skills
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillCreatePayload](#skillcreatepayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 201 | Skill created | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/skills/files/upload
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **multipart/form-data**: { **"file"**: binary }<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 201 | Skill draft file uploaded | **application/json**: [SkillFileUploadResponse](#skillfileuploadresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/skills/import
|
||||
Import a Skill zip package from multipart form field `file`.
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 201 | Skill imported | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills/tags
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Workspace Skill tags | **application/json**: [SkillTagListResponse](#skilltaglistresponse)<br> |
|
||||
|
||||
### [DELETE] /workspaces/current/skills/{skill_id}
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillDeletePayload](#skilldeletepayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill deleted | **application/json**: [SkillDeleteResponse](#skilldeleteresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills/{skill_id}
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill detail | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
|
||||
|
||||
### [PATCH] /workspaces/current/skills/{skill_id}
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillMetadataPayload](#skillmetadatapayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill updated | **application/json**: [SkillResponse](#skillresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/skills/{skill_id}/assist/messages
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillAssistMessagePayload](#skillassistmessagepayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description |
|
||||
| ---- | ----------- |
|
||||
| 200 | Skill Authoring assistant event stream |
|
||||
|
||||
### [POST] /workspaces/current/skills/{skill_id}/duplicate
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 201 | Skill duplicated | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills/{skill_id}/export
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description |
|
||||
| ---- | ----------- |
|
||||
| 200 | Published Skill zip archive |
|
||||
|
||||
### [PATCH] /workspaces/current/skills/{skill_id}/files
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillDraftFileOperationPayload](#skilldraftfileoperationpayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Draft file operation applied | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
|
||||
|
||||
### [PUT] /workspaces/current/skills/{skill_id}/files
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillDraftTreePayload](#skilldrafttreepayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Draft files replaced | **application/json**: [SkillDetailResponse](#skilldetailresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills/{skill_id}/files/content
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| download | query | Return as an attachment when 1. | No | string |
|
||||
| path | query | Skill file path relative to the Skill root. | Yes | string |
|
||||
| version_id | query | Optional published version ID. Omit for current draft. | No | string |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill file content | **application/json**: [BinaryFileResponse](#binaryfileresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills/{skill_id}/files/preview
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| path | query | Skill file path relative to the Skill root. | Yes | string |
|
||||
| version_id | query | Optional published version ID. Omit for current draft. | No | string |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill file text preview | **application/json**: [SkillFilePreviewResponse](#skillfilepreviewresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/skills/{skill_id}/publish
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillPublishPayload](#skillpublishpayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill published | **application/json**: [SkillVersionResponse](#skillversionresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills/{skill_id}/references
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill references | **application/json**: [SkillReferenceListResponse](#skillreferencelistresponse)<br> |
|
||||
|
||||
### [POST] /workspaces/current/skills/{skill_id}/restore
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillRestorePayload](#skillrestorepayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill version restored | **application/json**: [SkillVersionResponse](#skillversionresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills/{skill_id}/versions
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill versions | **application/json**: [SkillVersionListResponse](#skillversionlistresponse)<br> |
|
||||
|
||||
### [DELETE] /workspaces/current/skills/{skill_id}/versions/{version_id}
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
| version_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill version deleted | **application/json**: [SkillVersionDeleteResponse](#skillversiondeleteresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/skills/{skill_id}/versions/{version_id}
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
| version_id | path | | Yes | string |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill version detail | **application/json**: [SkillVersionDetailResponse](#skillversiondetailresponse)<br> |
|
||||
|
||||
### [PATCH] /workspaces/current/skills/{skill_id}/versions/{version_id}
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| skill_id | path | | Yes | string |
|
||||
| version_id | path | | Yes | string |
|
||||
|
||||
#### Request Body
|
||||
|
||||
| Required | Schema |
|
||||
| -------- | ------ |
|
||||
| Yes | **application/json**: [SkillVersionUpdatePayload](#skillversionupdatepayload)<br> |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Skill version updated | **application/json**: [SkillVersionResponse](#skillversionresponse)<br> |
|
||||
|
||||
### [GET] /workspaces/current/tool-labels
|
||||
#### Responses
|
||||
|
||||
@@ -14709,6 +15076,37 @@ Visibility and lifecycle scope of an Agent record.
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| result | string | | Yes |
|
||||
|
||||
#### AgentSkillBindingItemResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| description | string | | Yes |
|
||||
| display_name | string | | Yes |
|
||||
| file_count | integer | | Yes |
|
||||
| icon | string | | Yes |
|
||||
| id | string | | Yes |
|
||||
| latest_published_at | integer | | No |
|
||||
| latest_published_version_id | string | | No |
|
||||
| name | string | | Yes |
|
||||
| priority | integer | | Yes |
|
||||
| status | string | | Yes |
|
||||
| tags | [ string ] | | No |
|
||||
| updated_at | integer | | Yes |
|
||||
|
||||
#### AgentSkillBindingsPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| skill_ids | [ string ] | Ordered Skill IDs bound to the Agent. | No |
|
||||
|
||||
#### AgentSkillBindingsResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| agent_id | string | | Yes |
|
||||
| data | [ [AgentSkillBindingItemResponse](#agentskillbindingitemresponse) ] | | No |
|
||||
| skill_ids | [ string ] | | No |
|
||||
|
||||
#### AgentSkillRefConfig
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -21665,6 +22063,186 @@ Simple provider entity response.
|
||||
| title | string | | Yes |
|
||||
| use_icon_as_answer_icon | boolean | | Yes |
|
||||
|
||||
#### SkillAssistAttachmentPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| mime_type | string | | No |
|
||||
| name | string | | Yes |
|
||||
| size | integer | | No |
|
||||
| tool_file_id | string | | Yes |
|
||||
|
||||
#### SkillAssistMessagePayload
|
||||
|
||||
One user message and optional uploaded context for the read-only Skill Authoring assistant.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| attachments | [ [SkillAssistAttachmentPayload](#skillassistattachmentpayload) ] | | No |
|
||||
| message | string | | Yes |
|
||||
| model | [SkillAssistModelPayload](#skillassistmodelpayload) | | No |
|
||||
|
||||
#### SkillAssistModelPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| model | string | | Yes |
|
||||
| model_settings | object | | No |
|
||||
| plugin_id | string | | No |
|
||||
| provider | string | | Yes |
|
||||
|
||||
#### SkillCreatePayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| description | string | | No |
|
||||
| display_name | string | | No |
|
||||
| icon | string, <br>**Default:** 📄 | | No |
|
||||
| name | string | | No |
|
||||
| tags | [ string ] | | No |
|
||||
|
||||
#### SkillDeletePayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| confirmation_name | string | Required when deleting a referenced Skill. Must match the Skill name. | No |
|
||||
|
||||
#### SkillDeleteResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| deleted | boolean | | Yes |
|
||||
| id | string | | Yes |
|
||||
|
||||
#### SkillDetailResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| created_at | integer | | Yes |
|
||||
| created_by | string | | No |
|
||||
| created_by_name | string | | No |
|
||||
| description | string | | Yes |
|
||||
| display_name | string | | Yes |
|
||||
| files | [ [SkillFileResponse](#skillfileresponse) ] | | No |
|
||||
| icon | string | | Yes |
|
||||
| id | string | | Yes |
|
||||
| latest_published_version_id | string | | No |
|
||||
| name | string | | Yes |
|
||||
| name_manually_edited | boolean | | No |
|
||||
| reference_count | integer | | No |
|
||||
| tags | [ string ] | | No |
|
||||
| updated_at | integer | | Yes |
|
||||
| updated_by | string | | No |
|
||||
| updated_by_name | string | | No |
|
||||
| visibility | string | | Yes |
|
||||
|
||||
#### SkillDraftFileOperation
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| SkillDraftFileOperation | string | | |
|
||||
|
||||
#### SkillDraftFileOperationPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| content | string | | No |
|
||||
| expected_updated_at | integer | | No |
|
||||
| hash | string | | No |
|
||||
| mime_type | string | | No |
|
||||
| operation | [SkillDraftFileOperation](#skilldraftfileoperation) | | Yes |
|
||||
| path | string | | Yes |
|
||||
| size | integer | | No |
|
||||
| target_path | string | | No |
|
||||
| tool_file_id | string | | No |
|
||||
|
||||
#### SkillDraftTreeItemPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| content | string | | No |
|
||||
| hash | string | | No |
|
||||
| kind | [SkillFileKind](#skillfilekind) | | No |
|
||||
| mime_type | string | | No |
|
||||
| path | string | | Yes |
|
||||
| size | integer | | No |
|
||||
| storage | [SkillFileStorage](#skillfilestorage) | | No |
|
||||
| tool_file_id | string | | No |
|
||||
|
||||
#### SkillDraftTreePayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| expected_updated_at | integer | | No |
|
||||
| files | [ [SkillDraftTreeItemPayload](#skilldrafttreeitempayload) ] | | No |
|
||||
|
||||
#### SkillFileKind
|
||||
|
||||
Draft file entry kind.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| SkillFileKind | string | Draft file entry kind. | |
|
||||
|
||||
#### SkillFilePreviewResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| content | string | | Yes |
|
||||
| hash | string | | Yes |
|
||||
| mime_type | string | | Yes |
|
||||
| path | string | | Yes |
|
||||
| size | integer | | Yes |
|
||||
|
||||
#### SkillFileQuery
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| path | string | Skill file path relative to the Skill root. | Yes |
|
||||
| version_id | string | Optional published version ID. Omit for current draft. | No |
|
||||
|
||||
#### SkillFileResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| content | string | | No |
|
||||
| hash | string | | No |
|
||||
| id | string | | No |
|
||||
| kind | string | | Yes |
|
||||
| mime_type | string | | No |
|
||||
| path | string | | Yes |
|
||||
| size | integer | | No |
|
||||
| storage | string | | No |
|
||||
| tool_file_id | string | | No |
|
||||
|
||||
#### SkillFileStorage
|
||||
|
||||
How a draft file's content is stored.
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| SkillFileStorage | string | How a draft file's content is stored. | |
|
||||
|
||||
#### SkillFileUploadResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| hash | string | | Yes |
|
||||
| id | string | | Yes |
|
||||
| mime_type | string | | Yes |
|
||||
| name | string | | Yes |
|
||||
| size | integer | | Yes |
|
||||
|
||||
#### SkillListResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| data | [ [SkillResponse](#skillresponse) ] | | No |
|
||||
| has_more | boolean | | No |
|
||||
| limit | integer, <br>**Default:** 20 | | No |
|
||||
| page | integer, <br>**Default:** 1 | | No |
|
||||
| total | integer | | No |
|
||||
|
||||
#### SkillManifest
|
||||
|
||||
Validated metadata extracted from a Skill package.
|
||||
@@ -21678,6 +22256,91 @@ Validated metadata extracted from a Skill package.
|
||||
| name | string | | Yes |
|
||||
| size | integer | | Yes |
|
||||
|
||||
#### SkillMetadataPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| display_name | string | | No |
|
||||
| expected_updated_at | integer | | No |
|
||||
| icon | string | | No |
|
||||
| tags | [ string ] | | No |
|
||||
|
||||
#### SkillPublishPayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| publish_note | string | | No |
|
||||
| version_name | string | | No |
|
||||
|
||||
#### SkillReferenceListResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| data | [ [SkillReferenceResponse](#skillreferenceresponse) ] | | No |
|
||||
|
||||
#### SkillReferenceResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| agent_icon | string | | No |
|
||||
| agent_icon_background | string | | No |
|
||||
| agent_icon_type | string | | No |
|
||||
| agent_id | string | | Yes |
|
||||
| app_id | string | | No |
|
||||
| display_name | string | | Yes |
|
||||
| name | string | | Yes |
|
||||
| node_id | string | | No |
|
||||
| node_name | string | | No |
|
||||
| type | string | | Yes |
|
||||
| workflow_icon | string | | No |
|
||||
| workflow_icon_background | string | | No |
|
||||
| workflow_icon_type | string | | No |
|
||||
| workflow_id | string | | No |
|
||||
| workflow_name | string | | No |
|
||||
| workflow_version | string | | No |
|
||||
|
||||
#### SkillResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| created_at | integer | | Yes |
|
||||
| created_by | string | | No |
|
||||
| created_by_name | string | | No |
|
||||
| description | string | | Yes |
|
||||
| display_name | string | | Yes |
|
||||
| icon | string | | Yes |
|
||||
| id | string | | Yes |
|
||||
| latest_published_version_id | string | | No |
|
||||
| name | string | | Yes |
|
||||
| name_manually_edited | boolean | | No |
|
||||
| reference_count | integer | | No |
|
||||
| tags | [ string ] | | No |
|
||||
| updated_at | integer | | Yes |
|
||||
| updated_by | string | | No |
|
||||
| updated_by_name | string | | No |
|
||||
| visibility | string | | Yes |
|
||||
|
||||
#### SkillRestorePayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| publish_note | string | | No |
|
||||
| version_id | string | | Yes |
|
||||
| version_name | string | | No |
|
||||
|
||||
#### SkillTagListResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| data | [ [SkillTagResponse](#skilltagresponse) ] | | No |
|
||||
|
||||
#### SkillTagResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| count | integer | | Yes |
|
||||
| tag | string | | Yes |
|
||||
|
||||
#### SkillToolInferenceResult
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -21686,6 +22349,60 @@ Validated metadata extracted from a Skill package.
|
||||
| inferable | boolean | | Yes |
|
||||
| reason | string | | No |
|
||||
|
||||
#### SkillVersionDeleteResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| deleted | boolean | | Yes |
|
||||
| id | string | | Yes |
|
||||
| latest_published_version_id | string | | No |
|
||||
|
||||
#### SkillVersionDetailResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| archive_size | integer | | Yes |
|
||||
| created_at | integer | | Yes |
|
||||
| files | [ [SkillFileResponse](#skillfileresponse) ] | | No |
|
||||
| hash_code | string | | Yes |
|
||||
| id | string | | Yes |
|
||||
| is_latest | boolean | | No |
|
||||
| publish_note | string | | Yes |
|
||||
| published_by | string | | No |
|
||||
| published_by_name | string | | No |
|
||||
| skill_id | string | | Yes |
|
||||
| version_name | string | | Yes |
|
||||
| version_number | integer | | Yes |
|
||||
|
||||
#### SkillVersionListResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| data | [ [SkillVersionResponse](#skillversionresponse) ] | | No |
|
||||
|
||||
#### SkillVersionResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| archive_size | integer | | Yes |
|
||||
| created_at | integer | | Yes |
|
||||
| hash_code | string | | Yes |
|
||||
| id | string | | Yes |
|
||||
| is_latest | boolean | | No |
|
||||
| publish_note | string | | Yes |
|
||||
| published_by | string | | No |
|
||||
| published_by_name | string | | No |
|
||||
| skill_id | string | | Yes |
|
||||
| version_name | string | | Yes |
|
||||
| version_number | integer | | Yes |
|
||||
|
||||
#### SkillVersionUpdatePayload
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| publish_note | string | | No |
|
||||
| version_name | string | | No |
|
||||
|
||||
#### SnippetDependencyCheckResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
@@ -22153,7 +22870,7 @@ Non-sensitive bootstrap snapshot exposed before Console or Web authentication.
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| keyword | string | Search keyword | No |
|
||||
| type | string, <br>**Available values:** "", "app", "knowledge", "snippet" | Tag type filter<br>*Enum:* `""`, `"app"`, `"knowledge"`, `"snippet"` | No |
|
||||
| type | [TagType](#tagtype)<br>string | Tag type filter | No |
|
||||
|
||||
#### TagListResponse
|
||||
|
||||
@@ -24339,6 +25056,15 @@ Workflow tool configuration
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| permission_keys | [ string ] | | No |
|
||||
|
||||
#### WorkspaceSkillsQuery
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| keyword | string | Search keyword matching skill name or description. | No |
|
||||
| limit | integer, <br>**Default:** 20 | Number of items per page. | No |
|
||||
| page | integer, <br>**Default:** 1 | Page number. | No |
|
||||
| tag | [ string ] | Skill tag filters. Repeat the parameter for multiple tags. | No |
|
||||
|
||||
#### WorkspaceTenantResultResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|
||||
@@ -19,12 +19,11 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import io
|
||||
import posixpath
|
||||
import re
|
||||
import zipfile
|
||||
import zlib
|
||||
|
||||
import yaml
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field, ValidationError, field_validator
|
||||
|
||||
# Bounds — generous but finite so a hostile upload can't exhaust memory/disk.
|
||||
_MAX_ARCHIVE_BYTES = 50 * 1024 * 1024
|
||||
@@ -33,7 +32,8 @@ _MAX_SKILL_MD_BYTES = 1 * 1024 * 1024
|
||||
_MAX_ENTRIES = 5000
|
||||
_ALLOWED_EXTENSIONS = (".zip", ".skill")
|
||||
_SKILL_MD_NAME = "SKILL.md"
|
||||
_HEADING_RE = re.compile(r"^\s*#\s+(.+?)\s*$", re.MULTILINE)
|
||||
_SKILL_NAME_PATTERN = r"^[a-z0-9]+(?:-[a-z0-9]+)*$"
|
||||
_MAX_SKILL_DESCRIPTION_LENGTH = 1024
|
||||
|
||||
|
||||
class SkillPackageError(Exception):
|
||||
@@ -53,13 +53,18 @@ class SkillPackageError(Exception):
|
||||
class SkillManifest(BaseModel):
|
||||
"""Validated metadata extracted from a Skill package."""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
name: str = Field(min_length=1, max_length=64, pattern=_SKILL_NAME_PATTERN)
|
||||
description: str = Field(min_length=1, max_length=_MAX_SKILL_DESCRIPTION_LENGTH)
|
||||
entry_path: str # path of SKILL.md inside the archive
|
||||
files: list[str] # all (safe) file paths inside the archive
|
||||
size: int # total uncompressed bytes
|
||||
hash: str # sha256 of the archive bytes
|
||||
|
||||
@field_validator("name", "description", mode="before")
|
||||
@classmethod
|
||||
def _strip_required_string(cls, value: object) -> object:
|
||||
return value.strip() if isinstance(value, str) else value
|
||||
|
||||
|
||||
class NormalizedSkillPackage(BaseModel):
|
||||
"""Canonical skill package bytes and metadata ready to store in agent drive."""
|
||||
@@ -108,14 +113,17 @@ class SkillPackageService:
|
||||
normalized_size = sum(max(info.file_size, 0) for info in normalized_members.values())
|
||||
|
||||
name, description = self._parse_skill_md(skill_md)
|
||||
manifest = SkillManifest(
|
||||
name=name,
|
||||
description=description,
|
||||
entry_path=_SKILL_MD_NAME,
|
||||
files=sorted(normalized_members),
|
||||
size=normalized_size,
|
||||
hash=hashlib.sha256(normalized_archive_bytes).hexdigest(),
|
||||
)
|
||||
try:
|
||||
manifest = SkillManifest(
|
||||
name=name,
|
||||
description=description,
|
||||
entry_path=_SKILL_MD_NAME,
|
||||
files=sorted(normalized_members),
|
||||
size=normalized_size,
|
||||
hash=hashlib.sha256(normalized_archive_bytes).hexdigest(),
|
||||
)
|
||||
except ValidationError as exc:
|
||||
raise self._manifest_validation_error(exc) from exc
|
||||
return NormalizedSkillPackage(
|
||||
manifest=manifest,
|
||||
archive_bytes=normalized_archive_bytes,
|
||||
@@ -123,6 +131,31 @@ class SkillPackageService:
|
||||
strip_prefix=strip_prefix,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _manifest_validation_error(exc: ValidationError) -> SkillPackageError:
|
||||
first_error = exc.errors()[0]
|
||||
loc = first_error["loc"]
|
||||
field = loc[0] if loc else "manifest"
|
||||
error_type = first_error["type"]
|
||||
if field == "name":
|
||||
code = "missing_skill_name" if error_type == "string_too_short" else "invalid_skill_name"
|
||||
message = (
|
||||
"SKILL.md frontmatter name is required"
|
||||
if code == "missing_skill_name"
|
||||
else "SKILL.md frontmatter name must be lowercase letters, numbers, and hyphens only, "
|
||||
"must not start or end with a hyphen, and must be at most 64 characters"
|
||||
)
|
||||
return SkillPackageError(code, message, status_code=400)
|
||||
if field == "description":
|
||||
code = "missing_skill_description" if error_type == "string_too_short" else "invalid_skill_description"
|
||||
message = (
|
||||
"SKILL.md frontmatter description is required"
|
||||
if code == "missing_skill_description"
|
||||
else f"SKILL.md frontmatter description must be at most {_MAX_SKILL_DESCRIPTION_LENGTH} characters"
|
||||
)
|
||||
return SkillPackageError(code, message, status_code=400)
|
||||
return SkillPackageError("invalid_skill_manifest", "SKILL.md frontmatter is invalid", status_code=400)
|
||||
|
||||
def _open_archive(self, *, content: bytes, filename: str) -> zipfile.ZipFile:
|
||||
self._check_extension(filename)
|
||||
if not content:
|
||||
@@ -280,13 +313,6 @@ class SkillPackageService:
|
||||
frontmatter = cls._parse_frontmatter(content)
|
||||
name = str(frontmatter.get("name") or "").strip()
|
||||
description = str(frontmatter.get("description") or "").strip()
|
||||
if not name:
|
||||
heading = _HEADING_RE.search(content)
|
||||
name = heading.group(1).strip() if heading else ""
|
||||
if not name:
|
||||
raise SkillPackageError(
|
||||
"missing_skill_name", "SKILL.md must declare a name (frontmatter or top heading)", status_code=400
|
||||
)
|
||||
return name, description
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -46,6 +46,7 @@ from models.tools import ToolFile
|
||||
from services.agent.config_skill_normalize_service import ConfigSkillNormalizeService
|
||||
from services.agent.skill_package_service import SkillPackageError
|
||||
from services.agent_drive_service import DriveFileRef
|
||||
from services.skill_management_service import SkillManagementService, SkillManagementServiceError
|
||||
|
||||
|
||||
class AgentConfigVersionKind(StrEnum):
|
||||
@@ -98,6 +99,7 @@ class ConfigPushPayload(BaseModel):
|
||||
|
||||
@dataclass(slots=True)
|
||||
class AgentConfigTarget:
|
||||
tenant_id: str
|
||||
agent_id: str
|
||||
version_id: str
|
||||
kind: AgentConfigVersionKind
|
||||
@@ -146,6 +148,7 @@ class AgentConfigService:
|
||||
user_id=user_id,
|
||||
)
|
||||
return AgentConfigTarget(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=target.agent_id,
|
||||
version_id=target.version_id,
|
||||
kind=target.kind,
|
||||
@@ -191,7 +194,7 @@ class AgentConfigService:
|
||||
return {
|
||||
"agent_id": target.agent_id,
|
||||
"config_version": self._config_version_payload(target),
|
||||
"items": [self._serialize_skill_item(skill) for skill in target.agent_soul.config_skills],
|
||||
"items": self._skill_items_for_target(target),
|
||||
}
|
||||
|
||||
def list_files(
|
||||
@@ -233,10 +236,27 @@ class AgentConfigService:
|
||||
config_version_kind=config_version_kind,
|
||||
user_id=user_id,
|
||||
)
|
||||
skill = self._require_skill(target.agent_soul, name=name)
|
||||
file_id = self._available_skill_file_id(skill)
|
||||
payload, mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id)
|
||||
return ConfigDownload(filename=f"{skill.name}.zip", mime_type=mime_type or "application/zip", payload=payload)
|
||||
try:
|
||||
skill = self._require_skill(target.agent_soul, name=name)
|
||||
file_id = self._available_skill_file_id(skill)
|
||||
payload, mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id)
|
||||
return ConfigDownload(
|
||||
filename=f"{skill.name}.zip",
|
||||
mime_type=mime_type or "application/zip",
|
||||
payload=payload,
|
||||
)
|
||||
except AgentConfigServiceError as exc:
|
||||
if exc.code != "config_skill_not_found":
|
||||
raise
|
||||
try:
|
||||
result = SkillManagementService().pull_runtime_agent_skill(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=agent_id,
|
||||
name=name,
|
||||
)
|
||||
return ConfigDownload(filename=result.filename, mime_type=result.mime_type, payload=result.payload)
|
||||
except SkillManagementServiceError as exc:
|
||||
raise AgentConfigServiceError("config_skill_not_found", "config skill not found", status_code=404) from exc
|
||||
|
||||
def download_skill_url(
|
||||
self,
|
||||
@@ -279,9 +299,45 @@ class AgentConfigService:
|
||||
config_version_kind=config_version_kind,
|
||||
user_id=user_id,
|
||||
)
|
||||
skill = self._require_skill(target.agent_soul, name=name)
|
||||
file_id = self._available_skill_file_id(skill)
|
||||
archive_bytes, _mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id)
|
||||
try:
|
||||
skill = self._require_skill(target.agent_soul, name=name)
|
||||
file_id = self._available_skill_file_id(skill)
|
||||
archive_bytes, _mime_type = self._load_tool_file_bytes(tenant_id=tenant_id, file_id=file_id)
|
||||
skill_item = self._serialize_skill_item(skill)
|
||||
except AgentConfigServiceError as exc:
|
||||
if exc.code != "config_skill_not_found":
|
||||
raise
|
||||
try:
|
||||
workspace_archive = SkillManagementService().pull_runtime_agent_skill(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=agent_id,
|
||||
name=name,
|
||||
)
|
||||
except SkillManagementServiceError as skill_exc:
|
||||
raise AgentConfigServiceError(
|
||||
"config_skill_not_found",
|
||||
"config skill not found",
|
||||
status_code=404,
|
||||
) from skill_exc
|
||||
archive_bytes = workspace_archive.payload
|
||||
skill_item = next(
|
||||
(
|
||||
item
|
||||
for item in SkillManagementService().list_runtime_agent_skills(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=agent_id,
|
||||
)
|
||||
if item["name"] == name
|
||||
),
|
||||
{
|
||||
"id": name,
|
||||
"name": name,
|
||||
"description": "",
|
||||
"size": None,
|
||||
"hash": None,
|
||||
"mime_type": "application/zip",
|
||||
},
|
||||
)
|
||||
try:
|
||||
archive_items, skill_md = self._inspect_skill_archive(archive_bytes)
|
||||
except (OSError, ValueError, zipfile.BadZipFile) as exc:
|
||||
@@ -291,7 +347,7 @@ class AgentConfigService:
|
||||
status_code=500,
|
||||
) from exc
|
||||
return {
|
||||
**self._serialize_skill_item(skill),
|
||||
**skill_item,
|
||||
"source": "config_skill_zip",
|
||||
"files": archive_items,
|
||||
"skill_md": skill_md,
|
||||
@@ -839,6 +895,7 @@ class AgentConfigService:
|
||||
status_code=404,
|
||||
)
|
||||
return AgentConfigTarget(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=agent_id,
|
||||
version_id=version.id,
|
||||
kind=config_version_kind,
|
||||
@@ -1133,9 +1190,7 @@ class AgentConfigService:
|
||||
return {
|
||||
"agent_id": target.agent_id,
|
||||
"config_version": AgentConfigService._config_version_payload(target),
|
||||
"skills": {
|
||||
"items": [AgentConfigService._serialize_skill_item(skill) for skill in target.agent_soul.config_skills]
|
||||
},
|
||||
"skills": {"items": AgentConfigService._skill_items_for_target(target)},
|
||||
"files": {
|
||||
"items": [
|
||||
AgentConfigService._serialize_file_item(file_ref) for file_ref in target.agent_soul.config_files
|
||||
@@ -1145,6 +1200,20 @@ class AgentConfigService:
|
||||
"note": target.agent_soul.config_note,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _skill_items_for_target(target: AgentConfigTarget) -> list[dict[str, object]]:
|
||||
items = [AgentConfigService._serialize_skill_item(skill) for skill in target.agent_soul.config_skills]
|
||||
seen_names = {str(item["name"]) for item in items}
|
||||
for item in SkillManagementService().list_runtime_agent_skills(
|
||||
tenant_id=target.tenant_id,
|
||||
agent_id=target.agent_id,
|
||||
):
|
||||
if item["name"] in seen_names:
|
||||
continue
|
||||
seen_names.add(str(item["name"]))
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
@staticmethod
|
||||
def _config_version_payload(target: AgentConfigTarget) -> dict[str, object]:
|
||||
return {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ from werkzeug.exceptions import NotFound
|
||||
from models.dataset import Dataset
|
||||
from models.enums import TagType
|
||||
from models.model import App, Tag, TagBinding
|
||||
from models.skill import Skill
|
||||
from models.snippet import CustomizedSnippet
|
||||
|
||||
type _TagTypeLike = TagType | str
|
||||
@@ -282,5 +283,11 @@ class TagService:
|
||||
)
|
||||
if not snippet:
|
||||
raise NotFound("Snippet not found")
|
||||
elif type == "skill":
|
||||
skill = session.scalar(
|
||||
select(Skill).where(Skill.tenant_id == current_user.current_tenant_id, Skill.id == target_id).limit(1)
|
||||
)
|
||||
if not skill:
|
||||
raise NotFound("Skill not found")
|
||||
else:
|
||||
raise NotFound("Invalid binding type")
|
||||
|
||||
@@ -0,0 +1,394 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from inspect import unwrap
|
||||
from unittest.mock import MagicMock, PropertyMock, patch
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
|
||||
from controllers.console import console_ns
|
||||
from controllers.console.workspace.skills import (
|
||||
WorkspaceAgentSkillBindingsApi,
|
||||
WorkspaceSkillAssistMessageApi,
|
||||
WorkspaceSkillFilesApi,
|
||||
WorkspaceSkillsApi,
|
||||
WorkspaceSkillTagsApi,
|
||||
WorkspaceSkillVersionApi,
|
||||
)
|
||||
from models.account import Account
|
||||
from services.skill_management_service import SkillAssistAttachmentPayload, SkillManagementServiceError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app() -> Flask:
|
||||
flask_app = Flask("test_workspace_skills")
|
||||
flask_app.config["TESTING"] = True
|
||||
return flask_app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def current_user() -> Account:
|
||||
user = Account(name="Test User", email="test@example.com")
|
||||
user.id = "user-1"
|
||||
return user
|
||||
|
||||
|
||||
def _skill_detail() -> dict:
|
||||
return {
|
||||
"id": "skill-1",
|
||||
"name": "finance-sop",
|
||||
"display_name": "Finance SOP",
|
||||
"icon": "📄",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"name_manually_edited": False,
|
||||
"visibility": "workspace",
|
||||
"latest_published_version_id": None,
|
||||
"reference_count": 0,
|
||||
"created_by": "user-1",
|
||||
"created_by_name": "Test User",
|
||||
"updated_by": "user-1",
|
||||
"updated_by_name": "Test User",
|
||||
"created_at": 1,
|
||||
"updated_at": 1,
|
||||
"files": [
|
||||
{
|
||||
"id": "file-1",
|
||||
"path": "SKILL.md",
|
||||
"kind": "file",
|
||||
"storage": "text",
|
||||
"mime_type": "text/markdown",
|
||||
"content": "---\nname: finance-sop\n---\n# Body",
|
||||
"tool_file_id": None,
|
||||
"size": 32,
|
||||
"hash": "hash",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_create_skill_validates_payload_and_returns_detail(app: Flask, current_user: Account) -> None:
|
||||
api = WorkspaceSkillsApi()
|
||||
method = unwrap(api.post)
|
||||
service = MagicMock()
|
||||
service.create_skill.return_value = _skill_detail()
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="POST"),
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value={}),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload, status = method(api, "tenant-1", current_user)
|
||||
|
||||
assert status == 201
|
||||
assert payload["id"] == "skill-1"
|
||||
assert payload["files"][0]["path"] == "SKILL.md"
|
||||
service.create_skill.assert_called_once()
|
||||
assert service.create_skill.call_args.kwargs["tenant_id"] == "tenant-1"
|
||||
assert service.create_skill.call_args.kwargs["user_id"] == "user-1"
|
||||
|
||||
|
||||
def test_list_skills_uses_default_pagination_when_query_omits_page_and_limit(app: Flask) -> None:
|
||||
api = WorkspaceSkillsApi()
|
||||
method = unwrap(api.get)
|
||||
service = MagicMock()
|
||||
service.list_skills.return_value = {
|
||||
"data": [],
|
||||
"has_more": False,
|
||||
"limit": 20,
|
||||
"page": 1,
|
||||
"total": 0,
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/?keyword=finance&tag=ops&tag=", method="GET"),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload = method(api, "tenant-1")
|
||||
|
||||
assert payload == {
|
||||
"data": [],
|
||||
"has_more": False,
|
||||
"limit": 20,
|
||||
"page": 1,
|
||||
"total": 0,
|
||||
}
|
||||
service.list_skills.assert_called_once_with(
|
||||
tenant_id="tenant-1",
|
||||
keyword="finance",
|
||||
page=1,
|
||||
limit=20,
|
||||
tags=["ops"],
|
||||
)
|
||||
|
||||
|
||||
def test_get_agent_skill_bindings_returns_card_data(app: Flask) -> None:
|
||||
api = WorkspaceAgentSkillBindingsApi()
|
||||
method = unwrap(api.get)
|
||||
service = MagicMock()
|
||||
service.list_agent_bindings.return_value = {
|
||||
"agent_id": "agent-1",
|
||||
"skill_ids": ["skill-1"],
|
||||
"data": [
|
||||
{
|
||||
"id": "skill-1",
|
||||
"priority": 0,
|
||||
"name": "finance-sop",
|
||||
"display_name": "Finance SOP",
|
||||
"icon": "📄",
|
||||
"description": "Handle finance.",
|
||||
"tags": ["Finance"],
|
||||
"status": "published",
|
||||
"file_count": 2,
|
||||
"latest_published_version_id": "version-1",
|
||||
"latest_published_at": 123,
|
||||
"updated_at": 124,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="GET"),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload = method(api, "tenant-1", "agent-1")
|
||||
|
||||
assert payload["skill_ids"] == ["skill-1"]
|
||||
assert payload["data"][0]["display_name"] == "Finance SOP"
|
||||
assert payload["data"][0]["file_count"] == 2
|
||||
service.list_agent_bindings.assert_called_once_with(tenant_id="tenant-1", agent_id="agent-1")
|
||||
|
||||
|
||||
def test_patch_skill_file_operation_validates_payload_and_returns_detail(app: Flask, current_user: Account) -> None:
|
||||
api = WorkspaceSkillFilesApi()
|
||||
method = unwrap(api.patch)
|
||||
service = MagicMock()
|
||||
service.apply_draft_file_operation.return_value = _skill_detail()
|
||||
request_payload = {
|
||||
"operation": "upsert_text",
|
||||
"path": "references/policy.md",
|
||||
"content": "Policy",
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="PATCH"),
|
||||
patch.object(type(console_ns), "payload", new_callable=PropertyMock, return_value=request_payload),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload = method(api, "tenant-1", current_user, "skill-1")
|
||||
|
||||
assert payload["id"] == "skill-1"
|
||||
service.apply_draft_file_operation.assert_called_once()
|
||||
call = service.apply_draft_file_operation.call_args.kwargs
|
||||
assert call["tenant_id"] == "tenant-1"
|
||||
assert call["user_id"] == "user-1"
|
||||
assert call["skill_id"] == "skill-1"
|
||||
assert call["payload"].operation == "upsert_text"
|
||||
|
||||
|
||||
def test_patch_skill_file_operation_returns_error_details(app: Flask, current_user: Account) -> None:
|
||||
api = WorkspaceSkillFilesApi()
|
||||
method = unwrap(api.patch)
|
||||
service = MagicMock()
|
||||
service.apply_draft_file_operation.side_effect = SkillManagementServiceError(
|
||||
"missing_skill_name",
|
||||
"SKILL.md frontmatter name is required",
|
||||
details={"path": "SKILL.md", "field": "name", "line": 2},
|
||||
)
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="PATCH"),
|
||||
patch.object(
|
||||
type(console_ns),
|
||||
"payload",
|
||||
new_callable=PropertyMock,
|
||||
return_value={"operation": "delete", "path": "SKILL.md"},
|
||||
),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload, status = method(api, "tenant-1", current_user, "skill-1")
|
||||
|
||||
assert status == 400
|
||||
assert payload == {
|
||||
"code": "missing_skill_name",
|
||||
"message": "SKILL.md frontmatter name is required",
|
||||
"details": {"path": "SKILL.md", "field": "name", "line": 2},
|
||||
}
|
||||
|
||||
|
||||
def test_list_skill_tags_returns_filter_options(app: Flask) -> None:
|
||||
api = WorkspaceSkillTagsApi()
|
||||
method = unwrap(api.get)
|
||||
service = MagicMock()
|
||||
service.list_tags.return_value = {"data": [{"tag": "finance", "count": 2}]}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="GET"),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload = method(api, "tenant-1")
|
||||
|
||||
assert payload == {"data": [{"tag": "finance", "count": 2}]}
|
||||
service.list_tags.assert_called_once_with(tenant_id="tenant-1")
|
||||
|
||||
|
||||
def test_get_skill_version_returns_version_detail(app: Flask) -> None:
|
||||
api = WorkspaceSkillVersionApi()
|
||||
method = unwrap(api.get)
|
||||
service = MagicMock()
|
||||
service.get_version.return_value = {
|
||||
"id": "version-1",
|
||||
"skill_id": "skill-1",
|
||||
"version_number": 1,
|
||||
"version_name": "Initial finance policy",
|
||||
"publish_note": "Initial finance policy",
|
||||
"hash_code": "hash-code",
|
||||
"archive_size": 123,
|
||||
"published_by": "user-1",
|
||||
"published_by_name": "Li Wei",
|
||||
"is_latest": True,
|
||||
"created_at": 1,
|
||||
"files": [
|
||||
{
|
||||
"id": None,
|
||||
"path": "SKILL.md",
|
||||
"kind": "file",
|
||||
"storage": "text",
|
||||
"mime_type": "text/markdown",
|
||||
"content": "# Version",
|
||||
"tool_file_id": None,
|
||||
"size": 9,
|
||||
"hash": "file-hash",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="GET"),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload = method(api, "tenant-1", "skill-1", "version-1")
|
||||
|
||||
assert payload["files"][0]["content"] == "# Version"
|
||||
service.get_version.assert_called_once_with(
|
||||
tenant_id="tenant-1",
|
||||
skill_id="skill-1",
|
||||
version_id="version-1",
|
||||
)
|
||||
|
||||
|
||||
def test_patch_skill_version_renames_version(app: Flask) -> None:
|
||||
api = WorkspaceSkillVersionApi()
|
||||
method = unwrap(api.patch)
|
||||
service = MagicMock()
|
||||
service.update_version.return_value = {
|
||||
"id": "version-1",
|
||||
"skill_id": "skill-1",
|
||||
"version_number": 1,
|
||||
"version_name": "Approval threshold",
|
||||
"publish_note": "",
|
||||
"hash_code": "hash-code",
|
||||
"archive_size": 123,
|
||||
"published_by": "user-1",
|
||||
"published_by_name": "Li Wei",
|
||||
"is_latest": True,
|
||||
"created_at": 1,
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="PATCH"),
|
||||
patch.object(
|
||||
type(console_ns),
|
||||
"payload",
|
||||
new_callable=PropertyMock,
|
||||
return_value={"version_name": "Approval threshold"},
|
||||
),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload = method(api, "tenant-1", "skill-1", "version-1")
|
||||
|
||||
assert payload["version_name"] == "Approval threshold"
|
||||
service.update_version.assert_called_once()
|
||||
assert service.update_version.call_args.kwargs["payload"].version_name == "Approval threshold"
|
||||
|
||||
|
||||
def test_delete_skill_version_returns_new_latest(app: Flask, current_user: Account) -> None:
|
||||
api = WorkspaceSkillVersionApi()
|
||||
method = unwrap(api.delete)
|
||||
service = MagicMock()
|
||||
service.delete_version.return_value = {
|
||||
"id": "version-2",
|
||||
"deleted": True,
|
||||
"latest_published_version_id": "version-1",
|
||||
}
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="DELETE"),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
):
|
||||
payload = method(api, "tenant-1", current_user, "skill-1", "version-2")
|
||||
|
||||
assert payload == {"id": "version-2", "deleted": True, "latest_published_version_id": "version-1"}
|
||||
service.delete_version.assert_called_once_with(
|
||||
tenant_id="tenant-1",
|
||||
user_id="user-1",
|
||||
skill_id="skill-1",
|
||||
version_id="version-2",
|
||||
)
|
||||
|
||||
|
||||
def test_skill_assistant_runs_agent_app_stream(app: Flask, current_user: Account) -> None:
|
||||
api = WorkspaceSkillAssistMessageApi()
|
||||
method = unwrap(api.post)
|
||||
service = MagicMock()
|
||||
assistant_app = MagicMock()
|
||||
assistant_app.id = "assistant-app-1"
|
||||
service.get_or_create_assistant_app.return_value = (assistant_app, "<skill_draft>draft</skill_draft>")
|
||||
app_model = MagicMock()
|
||||
app_response = MagicMock()
|
||||
compact_response = MagicMock()
|
||||
|
||||
with (
|
||||
app.test_request_context("/", method="POST"),
|
||||
patch.object(
|
||||
type(console_ns),
|
||||
"payload",
|
||||
new_callable=PropertyMock,
|
||||
return_value={
|
||||
"attachments": [
|
||||
{
|
||||
"tool_file_id": "tool-file-1",
|
||||
"name": "requirements.md",
|
||||
"mime_type": "text/markdown",
|
||||
"size": 128,
|
||||
}
|
||||
],
|
||||
"message": "Create an approval checklist.",
|
||||
},
|
||||
),
|
||||
patch("controllers.console.workspace.skills.SkillManagementService", return_value=service),
|
||||
patch(
|
||||
"controllers.console.workspace.skills.db.session",
|
||||
return_value=MagicMock(get=MagicMock(return_value=app_model)),
|
||||
),
|
||||
patch("controllers.console.workspace.skills.AppGenerateService.generate", return_value=app_response),
|
||||
patch("controllers.console.workspace.skills.helper.compact_generate_response", return_value=compact_response),
|
||||
):
|
||||
response = method(api, "tenant-1", current_user, "skill-1")
|
||||
|
||||
assert response is compact_response
|
||||
service.get_or_create_assistant_app.assert_called_once_with(
|
||||
tenant_id="tenant-1",
|
||||
skill_id="skill-1",
|
||||
user_id="user-1",
|
||||
attachments=[
|
||||
SkillAssistAttachmentPayload(
|
||||
tool_file_id="tool-file-1",
|
||||
name="requirements.md",
|
||||
mime_type="text/markdown",
|
||||
size=128,
|
||||
)
|
||||
],
|
||||
message="Create an approval checklist.",
|
||||
model_payload=None,
|
||||
)
|
||||
@@ -7,6 +7,7 @@ from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from dify_agent.layers.config import DifyConfigSkillConfig
|
||||
from dify_agent.layers.dify_core_tools import DifyCoreToolConfig, DifyCoreToolsLayerConfig
|
||||
from dify_agent.layers.dify_plugin import DifyPluginToolConfig, DifyPluginToolsLayerConfig
|
||||
from dify_agent.layers.execution_context import DifyExecutionContextLayerConfig
|
||||
@@ -29,6 +30,14 @@ from core.app.entities.app_invoke_entities import InvokeFrom, UserFrom
|
||||
from models.agent_config_entities import AgentSoulConfig
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_runtime_agent_skills(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setattr(
|
||||
"core.app.apps.agent_app.runtime_request_builder.load_runtime_agent_skill_configs",
|
||||
lambda *, tenant_id, agent_id: [],
|
||||
)
|
||||
|
||||
|
||||
def _exec_ctx() -> DifyExecutionContextLayerConfig:
|
||||
return DifyExecutionContextLayerConfig(
|
||||
tenant_id="tenant-1",
|
||||
@@ -514,6 +523,33 @@ class TestAgentAppConfigLayer:
|
||||
"mentioned_file_names": [],
|
||||
}
|
||||
|
||||
def test_config_layer_includes_bound_workspace_skills(self, monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setattr(
|
||||
"core.app.apps.agent_app.runtime_request_builder.load_runtime_agent_skill_configs",
|
||||
lambda *, tenant_id, agent_id: [
|
||||
DifyConfigSkillConfig(
|
||||
name="workspace-skill",
|
||||
description="Bound workspace skill.",
|
||||
size=123,
|
||||
mime_type="application/zip",
|
||||
)
|
||||
],
|
||||
)
|
||||
soul = _soul_with_model()
|
||||
soul.prompt.system_prompt = "Use [§skill:workspace-skill:Workspace Skill§]."
|
||||
builder = AgentAppRuntimeRequestBuilder(
|
||||
credentials_provider=_FakeCredentialsProvider(),
|
||||
dify_tools_builder=_NoToolsBuilder(), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
result = builder.build(_ctx(soul))
|
||||
|
||||
config = next(layer for layer in result.request.composition.layers if layer.name == DIFY_CONFIG_LAYER_ID)
|
||||
assert [skill.name for skill in config.config.skills] == ["workspace-skill"]
|
||||
assert config.config.mentioned_skill_names == ["workspace-skill"]
|
||||
prompt_layer = next(layer for layer in result.request.composition.layers if layer.name == "agent_soul_prompt")
|
||||
assert prompt_layer.config.prefix == "Use workspace-skill."
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("system_prompt", "expected_prefix"),
|
||||
[
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import cast
|
||||
|
||||
import pytest
|
||||
from agenton.compositor import CompositorSessionSnapshot
|
||||
from dify_agent.layers.config import DifyConfigSkillConfig
|
||||
from dify_agent.layers.dify_core_tools import DifyCoreToolConfig, DifyCoreToolsLayerConfig
|
||||
from dify_agent.layers.dify_plugin import DifyPluginToolConfig, DifyPluginToolsLayerConfig
|
||||
from dify_agent.protocol import DIFY_AGENT_HISTORY_LAYER_ID, DIFY_AGENT_MODEL_LAYER_ID
|
||||
@@ -40,6 +41,14 @@ from models.agent_config_entities import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _no_runtime_agent_skills(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setattr(
|
||||
"core.workflow.nodes.agent_v2.runtime_request_builder.load_runtime_agent_skill_configs",
|
||||
lambda *, tenant_id, agent_id: [],
|
||||
)
|
||||
|
||||
|
||||
class FakeCredentialsProvider:
|
||||
def fetch(self, provider_name: str, model_name: str) -> dict[str, object]:
|
||||
assert provider_name == "openai"
|
||||
@@ -1420,6 +1429,30 @@ def test_build_config_layer_config_includes_soul_context_and_mentions():
|
||||
assert warnings == []
|
||||
|
||||
|
||||
def test_build_config_layer_config_includes_runtime_agent_skills():
|
||||
from core.workflow.nodes.agent_v2.runtime_request_builder import build_config_layer_config
|
||||
|
||||
soul = AgentSoulConfig(
|
||||
prompt={"system_prompt": "Use [§skill:workspace-skill:Workspace Skill§]."},
|
||||
model=AgentSoulModelConfig(plugin_id="langgenius/openai", model_provider="openai", model="gpt-test"),
|
||||
)
|
||||
config, warnings = build_config_layer_config(
|
||||
soul,
|
||||
runtime_config_skills=[
|
||||
DifyConfigSkillConfig(
|
||||
name="workspace-skill",
|
||||
description="Bound workspace skill.",
|
||||
size=123,
|
||||
mime_type="application/zip",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
assert [skill.name for skill in config.skills] == ["workspace-skill"]
|
||||
assert config.mentioned_skill_names == ["workspace-skill"]
|
||||
assert warnings == []
|
||||
|
||||
|
||||
def test_build_config_layer_config_returns_empty_config_for_empty_agent_soul():
|
||||
from core.workflow.nodes.agent_v2.runtime_request_builder import build_config_layer_config
|
||||
|
||||
@@ -1505,6 +1538,33 @@ def test_workflow_run_request_contains_config_layer():
|
||||
assert any(spec.name == DIFY_CONFIG_LAYER_ID and spec.type == "dify.config" for spec in specs)
|
||||
|
||||
|
||||
def test_workflow_run_request_includes_bound_workspace_skills(monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setattr(
|
||||
"core.workflow.nodes.agent_v2.runtime_request_builder.load_runtime_agent_skill_configs",
|
||||
lambda *, tenant_id, agent_id: [
|
||||
DifyConfigSkillConfig(
|
||||
name="workspace-skill",
|
||||
description="Bound workspace skill.",
|
||||
size=123,
|
||||
mime_type="application/zip",
|
||||
)
|
||||
],
|
||||
)
|
||||
context = _context()
|
||||
context.snapshot.config_snapshot = AgentSoulConfig(
|
||||
prompt={"system_prompt": "Use [§skill:workspace-skill:Workspace Skill§]."},
|
||||
model=AgentSoulModelConfig(plugin_id="langgenius/openai", model_provider="openai", model="gpt-test"),
|
||||
)
|
||||
|
||||
result = WorkflowAgentRuntimeRequestBuilder(credentials_provider=FakeCredentialsProvider()).build(context)
|
||||
|
||||
config = next(layer for layer in result.request.composition.layers if layer.name == DIFY_CONFIG_LAYER_ID)
|
||||
assert [skill.name for skill in config.config.skills] == ["workspace-skill"]
|
||||
assert config.config.mentioned_skill_names == ["workspace-skill"]
|
||||
soul_prompt = next(layer for layer in result.request.composition.layers if layer.name == "agent_soul_prompt")
|
||||
assert soul_prompt.config.prefix == "Use workspace-skill."
|
||||
|
||||
|
||||
def test_workflow_runtime_expands_config_mentions_in_agent_soul_prompt():
|
||||
context = _context()
|
||||
context.snapshot.config_snapshot = _soul_with_config_assets()
|
||||
|
||||
@@ -13,7 +13,7 @@ from services.agent import skill_package_service as skill_package_service_module
|
||||
from services.agent.skill_package_service import NormalizedSkillPackage, SkillPackageError, SkillPackageService
|
||||
|
||||
_SKILL_MD = """---
|
||||
name: PDF Toolkit
|
||||
name: pdf-toolkit
|
||||
description: Tools for working with PDF files.
|
||||
---
|
||||
|
||||
@@ -43,7 +43,7 @@ def _archive_members(content: bytes) -> list[str]:
|
||||
def test_valid_skill_normalizes_manifest():
|
||||
manifest = _normalize({"SKILL.md": _SKILL_MD.encode(), "scripts/run.py": b"print('hi')\n"}).manifest
|
||||
|
||||
assert manifest.name == "PDF Toolkit"
|
||||
assert manifest.name == "pdf-toolkit"
|
||||
assert manifest.description == "Tools for working with PDF files."
|
||||
assert manifest.entry_path == "SKILL.md"
|
||||
assert set(manifest.files) == {"SKILL.md", "scripts/run.py"}
|
||||
@@ -51,10 +51,10 @@ def test_valid_skill_normalizes_manifest():
|
||||
assert len(manifest.hash) == 64
|
||||
|
||||
|
||||
def test_name_falls_back_to_heading_without_frontmatter():
|
||||
manifest = _normalize({"SKILL.md": b"# Heading Name\n\nbody"}).manifest
|
||||
assert manifest.name == "Heading Name"
|
||||
assert manifest.description == ""
|
||||
def test_name_and_description_are_required_in_frontmatter():
|
||||
with pytest.raises(SkillPackageError) as exc_info:
|
||||
_normalize({"SKILL.md": b"# heading-name\n\nbody"})
|
||||
assert exc_info.value.code == "missing_skill_name"
|
||||
|
||||
|
||||
def test_shallowest_skill_md_preferred_during_normalization():
|
||||
@@ -155,7 +155,18 @@ def test_validate_and_normalize_strips_deeper_selected_skill_root():
|
||||
({"README.md": b"x"}, "skill.zip", "missing_skill_md"),
|
||||
({"SKILL.md": _SKILL_MD.encode()}, "skill.tar", "unsupported_extension"),
|
||||
({"SKILL.md": b""}, "skill.zip", "empty_skill_md"),
|
||||
({"SKILL.md": b"no name here"}, "skill.zip", "missing_skill_name"),
|
||||
({"SKILL.md": b"---\ndescription: valid\n---\n# no name here"}, "skill.zip", "missing_skill_name"),
|
||||
({"SKILL.md": b"---\nname: pdf-toolkit\n---\n# no description"}, "skill.zip", "missing_skill_description"),
|
||||
(
|
||||
{"SKILL.md": b"---\nname: PDF Toolkit\ndescription: valid\n---\n# invalid name"},
|
||||
"skill.zip",
|
||||
"invalid_skill_name",
|
||||
),
|
||||
(
|
||||
{"SKILL.md": f"---\nname: pdf-toolkit\ndescription: {'x' * 1025}\n---\n# long".encode()},
|
||||
"skill.zip",
|
||||
"invalid_skill_description",
|
||||
),
|
||||
({"SKILL.md": b"\xff\xfenot utf8"}, "skill.zip", "skill_md_not_utf8"),
|
||||
],
|
||||
)
|
||||
@@ -224,10 +235,10 @@ def test_bad_frontmatter_yaml_rejected():
|
||||
assert exc_info.value.code == "invalid_frontmatter"
|
||||
|
||||
|
||||
def test_unterminated_frontmatter_falls_back_to_heading():
|
||||
# leading '---' with no closing fence -> no frontmatter, use the heading
|
||||
manifest = _normalize({"SKILL.md": b"---\n# Heading Wins\nbody"}).manifest
|
||||
assert manifest.name == "Heading Wins"
|
||||
def test_unterminated_frontmatter_rejected():
|
||||
with pytest.raises(SkillPackageError) as exc_info:
|
||||
_normalize({"SKILL.md": b"---\n# heading-wins\nbody"})
|
||||
assert exc_info.value.code == "missing_skill_name"
|
||||
|
||||
|
||||
def test_validate_and_normalize_rejects_files_outside_selected_skill_root():
|
||||
|
||||
@@ -20,7 +20,7 @@ _AGENT_ID = "22222222-2222-2222-2222-222222222222"
|
||||
_USER_ID = "33333333-3333-3333-3333-333333333333"
|
||||
|
||||
_SKILL_MD = b"""---
|
||||
name: PDF Toolkit
|
||||
name: pdf-toolkit
|
||||
description: Work with PDFs.
|
||||
---
|
||||
|
||||
@@ -121,7 +121,7 @@ def test_standardize_creates_drive_owned_toolfiles_and_commits_archive_manifest(
|
||||
assert skill_row.is_skill is True
|
||||
assert skill_row.skill_metadata is not None
|
||||
skill_metadata = DriveSkillMetadata.model_validate_json(skill_row.skill_metadata)
|
||||
assert skill_metadata.name == "PDF Toolkit"
|
||||
assert skill_metadata.name == "pdf-toolkit"
|
||||
assert skill_metadata.manifest_files == ["SKILL.md", "scripts/run.py"]
|
||||
assert archive_row.file_kind == AgentDriveFileKind.TOOL_FILE
|
||||
assert archive_row.file_id == archive_tool_file.id
|
||||
@@ -132,7 +132,7 @@ def test_standardize_creates_drive_owned_toolfiles_and_commits_archive_manifest(
|
||||
# The returned upload response carries only the drive-derived fields the UI needs.
|
||||
skill = result["skill"]
|
||||
assert skill["path"] == "pdf-toolkit"
|
||||
assert skill["name"] == "PDF Toolkit"
|
||||
assert skill["name"] == "pdf-toolkit"
|
||||
assert skill["archive_key"] == "pdf-toolkit/.DIFY-SKILL-FULL.zip"
|
||||
assert skill["skill_md_key"] == "pdf-toolkit/SKILL.md"
|
||||
assert result["manifest"]["entry_path"] == "SKILL.md"
|
||||
|
||||
@@ -63,6 +63,7 @@ def _target(
|
||||
) -> AgentConfigTarget:
|
||||
agent_soul = soul or _soul()
|
||||
return AgentConfigTarget(
|
||||
tenant_id=TENANT,
|
||||
agent_id=AGENT,
|
||||
version_id=version_id,
|
||||
kind=kind,
|
||||
@@ -508,7 +509,9 @@ def test_manifest_uses_items_shape_without_download_urls() -> None:
|
||||
),
|
||||
)
|
||||
|
||||
manifest = AgentConfigService._manifest_for_target(target)
|
||||
with patch(f"{MODULE}.SkillManagementService") as skill_management_service:
|
||||
skill_management_service.return_value.list_runtime_agent_skills.return_value = []
|
||||
manifest = AgentConfigService._manifest_for_target(target)
|
||||
|
||||
assert manifest == {
|
||||
"agent_id": AGENT,
|
||||
@@ -557,7 +560,9 @@ def test_manifest_preserves_missing_config_assets_and_pull_rejects_them() -> Non
|
||||
target = _target(kind=AgentConfigVersionKind.DRAFT, writable=False, soul=soul)
|
||||
service = AgentConfigService()
|
||||
|
||||
manifest = service._manifest_for_target(target)
|
||||
with patch(f"{MODULE}.SkillManagementService") as skill_management_service:
|
||||
skill_management_service.return_value.list_runtime_agent_skills.return_value = []
|
||||
manifest = service._manifest_for_target(target)
|
||||
|
||||
assert manifest["skills"]["items"][0]["is_missing"] is True # type: ignore[index]
|
||||
assert manifest["files"]["items"][0]["is_missing"] is True # type: ignore[index]
|
||||
@@ -606,6 +611,42 @@ def test_config_asset_refs_require_file_id_unless_marked_missing() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_manifest_appends_published_workspace_skills() -> None:
|
||||
target = _target(
|
||||
kind=AgentConfigVersionKind.DRAFT,
|
||||
writable=False,
|
||||
soul=_soul(
|
||||
config_skills=[AgentConfigSkillRefConfig(name="alpha", description="Alpha skill", file_id="tool-file-1")]
|
||||
),
|
||||
)
|
||||
|
||||
with patch(f"{MODULE}.SkillManagementService") as skill_management_service:
|
||||
skill_management_service.return_value.list_runtime_agent_skills.return_value = [
|
||||
{
|
||||
"id": "workspace-skill-id",
|
||||
"name": "beta",
|
||||
"file_id": "tool-file-2",
|
||||
"description": "Beta workspace skill",
|
||||
"size": 123,
|
||||
"hash": "sha256:beta",
|
||||
"mime_type": "application/zip",
|
||||
},
|
||||
{
|
||||
"id": "duplicate",
|
||||
"name": "alpha",
|
||||
"file_id": "tool-file-ignored",
|
||||
"description": "Duplicate workspace skill",
|
||||
"size": 456,
|
||||
"hash": "sha256:ignored",
|
||||
"mime_type": "application/zip",
|
||||
},
|
||||
]
|
||||
manifest = AgentConfigService._manifest_for_target(target)
|
||||
|
||||
assert [item["name"] for item in manifest["skills"]["items"]] == ["alpha", "beta"]
|
||||
assert manifest["skills"]["items"][1]["file_id"] == "tool-file-2"
|
||||
|
||||
|
||||
def test_preview_skill_file_returns_text_preview() -> None:
|
||||
service = AgentConfigService()
|
||||
target = _target(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -997,11 +997,15 @@ export const zSandboxListResponse = z.object({
|
||||
* Validated metadata extracted from a Skill package.
|
||||
*/
|
||||
export const zSkillManifest = z.object({
|
||||
description: z.string(),
|
||||
description: z.string().min(1).max(1024),
|
||||
entry_path: z.string(),
|
||||
files: z.array(z.string()),
|
||||
hash: z.string(),
|
||||
name: z.string(),
|
||||
name: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(64)
|
||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
||||
size: z.int(),
|
||||
})
|
||||
|
||||
|
||||
@@ -1373,11 +1373,15 @@ export const zAgentLogMetaResponse = z.object({
|
||||
* Validated metadata extracted from a Skill package.
|
||||
*/
|
||||
export const zSkillManifest = z.object({
|
||||
description: z.string(),
|
||||
description: z.string().min(1).max(1024),
|
||||
entry_path: z.string(),
|
||||
files: z.array(z.string()),
|
||||
hash: z.string(),
|
||||
name: z.string(),
|
||||
name: z
|
||||
.string()
|
||||
.min(1)
|
||||
.max(64)
|
||||
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
|
||||
size: z.int(),
|
||||
})
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export type TagBindingRemovePayload = {
|
||||
type: TagType
|
||||
}
|
||||
|
||||
export type TagType = 'app' | 'knowledge' | 'snippet'
|
||||
export type TagType = 'app' | 'knowledge' | 'skill' | 'snippet'
|
||||
|
||||
export type PostTagBindingsData = {
|
||||
body: TagBindingPayload
|
||||
|
||||
@@ -14,7 +14,7 @@ export const zSimpleResultResponse = z.object({
|
||||
*
|
||||
* Tag type
|
||||
*/
|
||||
export const zTagType = z.enum(['app', 'knowledge', 'snippet'])
|
||||
export const zTagType = z.enum(['app', 'knowledge', 'skill', 'snippet'])
|
||||
|
||||
/**
|
||||
* TagBindingPayload
|
||||
|
||||
@@ -22,14 +22,14 @@ export type TagUpdateRequestPayload = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type TagType = 'app' | 'knowledge' | 'snippet'
|
||||
export type TagType = 'app' | 'knowledge' | 'skill' | 'snippet'
|
||||
|
||||
export type GetTagsData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
keyword?: string
|
||||
type?: '' | 'app' | 'knowledge' | 'snippet'
|
||||
type?: string
|
||||
}
|
||||
url: '/tags'
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export const zTagUpdateRequestPayload = z.object({
|
||||
*
|
||||
* Tag type
|
||||
*/
|
||||
export const zTagType = z.enum(['app', 'knowledge', 'snippet'])
|
||||
export const zTagType = z.enum(['app', 'knowledge', 'skill', 'snippet'])
|
||||
|
||||
/**
|
||||
* TagBasePayload
|
||||
@@ -41,7 +41,7 @@ export const zTagBasePayload = z.object({
|
||||
|
||||
export const zGetTagsQuery = z.object({
|
||||
keyword: z.string().optional(),
|
||||
type: z.enum(['', 'app', 'knowledge', 'snippet']).optional().default(''),
|
||||
type: z.string().optional().default(''),
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,6 +32,16 @@ export type AgentProviderListResponse = Array<{
|
||||
[key: string]: unknown
|
||||
}>
|
||||
|
||||
export type AgentSkillBindingsResponse = {
|
||||
agent_id: string
|
||||
data?: Array<AgentSkillBindingItemResponse>
|
||||
skill_ids?: Array<string>
|
||||
}
|
||||
|
||||
export type AgentSkillBindingsPayload = {
|
||||
skill_ids?: Array<string>
|
||||
}
|
||||
|
||||
export type SnippetPaginationResponse = {
|
||||
data: Array<SnippetListItemResponse>
|
||||
has_more: boolean
|
||||
@@ -635,6 +645,179 @@ export type WorkspaceAccessMatrix = {
|
||||
pagination?: Pagination | null
|
||||
}
|
||||
|
||||
export type SkillListResponse = {
|
||||
data?: Array<SkillResponse>
|
||||
has_more?: boolean
|
||||
limit?: number
|
||||
page?: number
|
||||
total?: number
|
||||
}
|
||||
|
||||
export type SkillCreatePayload = {
|
||||
description?: string
|
||||
display_name?: string | null
|
||||
icon?: string
|
||||
name?: string | null
|
||||
tags?: Array<string>
|
||||
}
|
||||
|
||||
export type SkillDetailResponse = {
|
||||
created_at: number
|
||||
created_by?: string | null
|
||||
created_by_name?: string | null
|
||||
description: string
|
||||
display_name: string
|
||||
files?: Array<SkillFileResponse>
|
||||
icon: string
|
||||
id: string
|
||||
latest_published_version_id?: string | null
|
||||
name: string
|
||||
name_manually_edited?: boolean
|
||||
reference_count?: number
|
||||
tags?: Array<string>
|
||||
updated_at: number
|
||||
updated_by?: string | null
|
||||
updated_by_name?: string | null
|
||||
visibility: string
|
||||
}
|
||||
|
||||
export type SkillFileUploadResponse = {
|
||||
hash: string
|
||||
id: string
|
||||
mime_type: string
|
||||
name: string
|
||||
size: number
|
||||
}
|
||||
|
||||
export type SkillTagListResponse = {
|
||||
data?: Array<SkillTagResponse>
|
||||
}
|
||||
|
||||
export type SkillDeletePayload = {
|
||||
confirmation_name?: string | null
|
||||
}
|
||||
|
||||
export type SkillDeleteResponse = {
|
||||
deleted: boolean
|
||||
id: string
|
||||
}
|
||||
|
||||
export type SkillMetadataPayload = {
|
||||
display_name?: string | null
|
||||
expected_updated_at?: number | null
|
||||
icon?: string | null
|
||||
tags?: Array<string> | null
|
||||
}
|
||||
|
||||
export type SkillResponse = {
|
||||
created_at: number
|
||||
created_by?: string | null
|
||||
created_by_name?: string | null
|
||||
description: string
|
||||
display_name: string
|
||||
icon: string
|
||||
id: string
|
||||
latest_published_version_id?: string | null
|
||||
name: string
|
||||
name_manually_edited?: boolean
|
||||
reference_count?: number
|
||||
tags?: Array<string>
|
||||
updated_at: number
|
||||
updated_by?: string | null
|
||||
updated_by_name?: string | null
|
||||
visibility: string
|
||||
}
|
||||
|
||||
export type SkillAssistMessagePayload = {
|
||||
attachments?: Array<SkillAssistAttachmentPayload>
|
||||
message: string
|
||||
model?: SkillAssistModelPayload | null
|
||||
}
|
||||
|
||||
export type SkillDraftFileOperationPayload = {
|
||||
content?: string | null
|
||||
expected_updated_at?: number | null
|
||||
hash?: string | null
|
||||
mime_type?: string | null
|
||||
operation: SkillDraftFileOperation
|
||||
path: string
|
||||
size?: number | null
|
||||
target_path?: string | null
|
||||
tool_file_id?: string | null
|
||||
}
|
||||
|
||||
export type SkillDraftTreePayload = {
|
||||
expected_updated_at?: number | null
|
||||
files?: Array<SkillDraftTreeItemPayload>
|
||||
}
|
||||
|
||||
export type SkillFilePreviewResponse = {
|
||||
content: string
|
||||
hash: string
|
||||
mime_type: string
|
||||
path: string
|
||||
size: number
|
||||
}
|
||||
|
||||
export type SkillPublishPayload = {
|
||||
publish_note?: string
|
||||
version_name?: string | null
|
||||
}
|
||||
|
||||
export type SkillVersionResponse = {
|
||||
archive_size: number
|
||||
created_at: number
|
||||
hash_code: string
|
||||
id: string
|
||||
is_latest?: boolean
|
||||
publish_note: string
|
||||
published_by?: string | null
|
||||
published_by_name?: string | null
|
||||
skill_id: string
|
||||
version_name: string
|
||||
version_number: number
|
||||
}
|
||||
|
||||
export type SkillReferenceListResponse = {
|
||||
data?: Array<SkillReferenceResponse>
|
||||
}
|
||||
|
||||
export type SkillRestorePayload = {
|
||||
publish_note?: string
|
||||
version_id: string
|
||||
version_name?: string | null
|
||||
}
|
||||
|
||||
export type SkillVersionListResponse = {
|
||||
data?: Array<SkillVersionResponse>
|
||||
}
|
||||
|
||||
export type SkillVersionDeleteResponse = {
|
||||
deleted: boolean
|
||||
id: string
|
||||
latest_published_version_id?: string | null
|
||||
}
|
||||
|
||||
export type SkillVersionDetailResponse = {
|
||||
archive_size: number
|
||||
created_at: number
|
||||
files?: Array<SkillFileResponse>
|
||||
hash_code: string
|
||||
id: string
|
||||
is_latest?: boolean
|
||||
publish_note: string
|
||||
published_by?: string | null
|
||||
published_by_name?: string | null
|
||||
skill_id: string
|
||||
version_name: string
|
||||
version_number: number
|
||||
}
|
||||
|
||||
export type SkillVersionUpdatePayload = {
|
||||
publish_note?: string
|
||||
version_name?: string | null
|
||||
}
|
||||
|
||||
export type ToolLabelListResponse = Array<ToolLabel>
|
||||
|
||||
export type ApiToolProviderAddPayload = {
|
||||
@@ -1042,6 +1225,21 @@ export type WorkspaceCustomConfigResponse = {
|
||||
replace_webapp_logo?: string | null
|
||||
}
|
||||
|
||||
export type AgentSkillBindingItemResponse = {
|
||||
description: string
|
||||
display_name: string
|
||||
file_count: number
|
||||
icon: string
|
||||
id: string
|
||||
latest_published_at?: number | null
|
||||
latest_published_version_id?: string | null
|
||||
name: string
|
||||
priority: number
|
||||
status: string
|
||||
tags?: Array<string>
|
||||
updated_at: number
|
||||
}
|
||||
|
||||
export type SnippetListItemResponse = {
|
||||
author_name: string | null
|
||||
created_at: number
|
||||
@@ -1502,6 +1700,76 @@ export type AccessPolicyRole = {
|
||||
role_tag?: string
|
||||
}
|
||||
|
||||
export type SkillFileResponse = {
|
||||
content?: string | null
|
||||
hash?: string | null
|
||||
id?: string | null
|
||||
kind: string
|
||||
mime_type?: string | null
|
||||
path: string
|
||||
size?: number | null
|
||||
storage?: string | null
|
||||
tool_file_id?: string | null
|
||||
}
|
||||
|
||||
export type SkillTagResponse = {
|
||||
count: number
|
||||
tag: string
|
||||
}
|
||||
|
||||
export type SkillAssistAttachmentPayload = {
|
||||
mime_type?: string | null
|
||||
name: string
|
||||
size?: number | null
|
||||
tool_file_id: string
|
||||
}
|
||||
|
||||
export type SkillAssistModelPayload = {
|
||||
model: string
|
||||
model_settings?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
plugin_id?: string | null
|
||||
provider: string
|
||||
}
|
||||
|
||||
export type SkillDraftFileOperation =
|
||||
| 'delete'
|
||||
| 'mkdir'
|
||||
| 'rename'
|
||||
| 'upsert_text'
|
||||
| 'upsert_tool_file'
|
||||
|
||||
export type SkillDraftTreeItemPayload = {
|
||||
content?: string | null
|
||||
hash?: string | null
|
||||
kind?: SkillFileKind
|
||||
mime_type?: string | null
|
||||
path: string
|
||||
size?: number | null
|
||||
storage?: SkillFileStorage | null
|
||||
tool_file_id?: string | null
|
||||
}
|
||||
|
||||
export type SkillReferenceResponse = {
|
||||
agent_icon?: string | null
|
||||
agent_icon_background?: string | null
|
||||
agent_icon_type?: string | null
|
||||
agent_id: string
|
||||
app_id?: string | null
|
||||
display_name: string
|
||||
name: string
|
||||
node_id?: string | null
|
||||
node_name?: string | null
|
||||
type: string
|
||||
workflow_icon?: string | null
|
||||
workflow_icon_background?: string | null
|
||||
workflow_icon_type?: string | null
|
||||
workflow_id?: string | null
|
||||
workflow_name?: string | null
|
||||
workflow_version?: string | null
|
||||
}
|
||||
|
||||
export type ToolLabel = {
|
||||
icon: string
|
||||
label: I18nObject
|
||||
@@ -1978,6 +2246,10 @@ export type PermissionCatalogItem = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type SkillFileKind = 'directory' | 'file'
|
||||
|
||||
export type SkillFileStorage = 'text' | 'tool_file'
|
||||
|
||||
export type ToolParameter = {
|
||||
auto_generate?: PluginParameterAutoGenerate | null
|
||||
default?:
|
||||
@@ -2473,6 +2745,38 @@ export type GetWorkspacesCurrentAgentProvidersResponses = {
|
||||
export type GetWorkspacesCurrentAgentProvidersResponse =
|
||||
GetWorkspacesCurrentAgentProvidersResponses[keyof GetWorkspacesCurrentAgentProvidersResponses]
|
||||
|
||||
export type GetWorkspacesCurrentAgentsByAgentIdSkillsData = {
|
||||
body?: never
|
||||
path: {
|
||||
agent_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/agents/{agent_id}/skills'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentAgentsByAgentIdSkillsResponses = {
|
||||
200: AgentSkillBindingsResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentAgentsByAgentIdSkillsResponse =
|
||||
GetWorkspacesCurrentAgentsByAgentIdSkillsResponses[keyof GetWorkspacesCurrentAgentsByAgentIdSkillsResponses]
|
||||
|
||||
export type PutWorkspacesCurrentAgentsByAgentIdSkillsData = {
|
||||
body: AgentSkillBindingsPayload
|
||||
path: {
|
||||
agent_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/agents/{agent_id}/skills'
|
||||
}
|
||||
|
||||
export type PutWorkspacesCurrentAgentsByAgentIdSkillsResponses = {
|
||||
200: AgentSkillBindingsResponse
|
||||
}
|
||||
|
||||
export type PutWorkspacesCurrentAgentsByAgentIdSkillsResponse =
|
||||
PutWorkspacesCurrentAgentsByAgentIdSkillsResponses[keyof PutWorkspacesCurrentAgentsByAgentIdSkillsResponses]
|
||||
|
||||
export type GetWorkspacesCurrentCustomizedSnippetsData = {
|
||||
body?: never
|
||||
path?: never
|
||||
@@ -4625,6 +4929,369 @@ export type GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses = {
|
||||
export type GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponse =
|
||||
GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses[keyof GetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
keyword?: string
|
||||
limit?: number
|
||||
page?: number
|
||||
tag?: Array<string>
|
||||
}
|
||||
url: '/workspaces/current/skills'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsResponses = {
|
||||
200: SkillListResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsResponse =
|
||||
GetWorkspacesCurrentSkillsResponses[keyof GetWorkspacesCurrentSkillsResponses]
|
||||
|
||||
export type PostWorkspacesCurrentSkillsData = {
|
||||
body: SkillCreatePayload
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/skills'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsResponses = {
|
||||
201: SkillDetailResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsResponse =
|
||||
PostWorkspacesCurrentSkillsResponses[keyof PostWorkspacesCurrentSkillsResponses]
|
||||
|
||||
export type PostWorkspacesCurrentSkillsFilesUploadData = {
|
||||
body: {
|
||||
file: Blob | File
|
||||
}
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/files/upload'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsFilesUploadResponses = {
|
||||
201: SkillFileUploadResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsFilesUploadResponse =
|
||||
PostWorkspacesCurrentSkillsFilesUploadResponses[keyof PostWorkspacesCurrentSkillsFilesUploadResponses]
|
||||
|
||||
export type PostWorkspacesCurrentSkillsImportData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/import'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsImportResponses = {
|
||||
201: SkillDetailResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsImportResponse =
|
||||
PostWorkspacesCurrentSkillsImportResponses[keyof PostWorkspacesCurrentSkillsImportResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsTagsData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/tags'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsTagsResponses = {
|
||||
200: SkillTagListResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsTagsResponse =
|
||||
GetWorkspacesCurrentSkillsTagsResponses[keyof GetWorkspacesCurrentSkillsTagsResponses]
|
||||
|
||||
export type DeleteWorkspacesCurrentSkillsBySkillIdData = {
|
||||
body: SkillDeletePayload
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}'
|
||||
}
|
||||
|
||||
export type DeleteWorkspacesCurrentSkillsBySkillIdResponses = {
|
||||
200: SkillDeleteResponse
|
||||
}
|
||||
|
||||
export type DeleteWorkspacesCurrentSkillsBySkillIdResponse =
|
||||
DeleteWorkspacesCurrentSkillsBySkillIdResponses[keyof DeleteWorkspacesCurrentSkillsBySkillIdResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdResponses = {
|
||||
200: SkillDetailResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdResponse =
|
||||
GetWorkspacesCurrentSkillsBySkillIdResponses[keyof GetWorkspacesCurrentSkillsBySkillIdResponses]
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdData = {
|
||||
body: SkillMetadataPayload
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}'
|
||||
}
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdResponses = {
|
||||
200: SkillResponse
|
||||
}
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdResponse =
|
||||
PatchWorkspacesCurrentSkillsBySkillIdResponses[keyof PatchWorkspacesCurrentSkillsBySkillIdResponses]
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdAssistMessagesData = {
|
||||
body: SkillAssistMessagePayload
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/assist/messages'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponses = {
|
||||
200: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponse =
|
||||
PostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponses[keyof PostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponses]
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdDuplicateData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/duplicate'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdDuplicateResponses = {
|
||||
201: SkillDetailResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdDuplicateResponse =
|
||||
PostWorkspacesCurrentSkillsBySkillIdDuplicateResponses[keyof PostWorkspacesCurrentSkillsBySkillIdDuplicateResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdExportData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/export'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdExportResponses = {
|
||||
200: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdExportResponse =
|
||||
GetWorkspacesCurrentSkillsBySkillIdExportResponses[keyof GetWorkspacesCurrentSkillsBySkillIdExportResponses]
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdFilesData = {
|
||||
body: SkillDraftFileOperationPayload
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/files'
|
||||
}
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdFilesResponses = {
|
||||
200: SkillDetailResponse
|
||||
}
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdFilesResponse =
|
||||
PatchWorkspacesCurrentSkillsBySkillIdFilesResponses[keyof PatchWorkspacesCurrentSkillsBySkillIdFilesResponses]
|
||||
|
||||
export type PutWorkspacesCurrentSkillsBySkillIdFilesData = {
|
||||
body: SkillDraftTreePayload
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/files'
|
||||
}
|
||||
|
||||
export type PutWorkspacesCurrentSkillsBySkillIdFilesResponses = {
|
||||
200: SkillDetailResponse
|
||||
}
|
||||
|
||||
export type PutWorkspacesCurrentSkillsBySkillIdFilesResponse =
|
||||
PutWorkspacesCurrentSkillsBySkillIdFilesResponses[keyof PutWorkspacesCurrentSkillsBySkillIdFilesResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdFilesContentData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query: {
|
||||
download?: string
|
||||
path: string
|
||||
version_id?: string
|
||||
}
|
||||
url: '/workspaces/current/skills/{skill_id}/files/content'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdFilesContentResponses = {
|
||||
200: BinaryFileResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdFilesContentResponse =
|
||||
GetWorkspacesCurrentSkillsBySkillIdFilesContentResponses[keyof GetWorkspacesCurrentSkillsBySkillIdFilesContentResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdFilesPreviewData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query: {
|
||||
path: string
|
||||
version_id?: string
|
||||
}
|
||||
url: '/workspaces/current/skills/{skill_id}/files/preview'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponses = {
|
||||
200: SkillFilePreviewResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponse =
|
||||
GetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponses[keyof GetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponses]
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdPublishData = {
|
||||
body: SkillPublishPayload
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/publish'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdPublishResponses = {
|
||||
200: SkillVersionResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdPublishResponse =
|
||||
PostWorkspacesCurrentSkillsBySkillIdPublishResponses[keyof PostWorkspacesCurrentSkillsBySkillIdPublishResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdReferencesData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/references'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdReferencesResponses = {
|
||||
200: SkillReferenceListResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdReferencesResponse =
|
||||
GetWorkspacesCurrentSkillsBySkillIdReferencesResponses[keyof GetWorkspacesCurrentSkillsBySkillIdReferencesResponses]
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdRestoreData = {
|
||||
body: SkillRestorePayload
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/restore'
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdRestoreResponses = {
|
||||
200: SkillVersionResponse
|
||||
}
|
||||
|
||||
export type PostWorkspacesCurrentSkillsBySkillIdRestoreResponse =
|
||||
PostWorkspacesCurrentSkillsBySkillIdRestoreResponses[keyof PostWorkspacesCurrentSkillsBySkillIdRestoreResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdVersionsData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/versions'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdVersionsResponses = {
|
||||
200: SkillVersionListResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdVersionsResponse =
|
||||
GetWorkspacesCurrentSkillsBySkillIdVersionsResponses[keyof GetWorkspacesCurrentSkillsBySkillIdVersionsResponses]
|
||||
|
||||
export type DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
version_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/versions/{version_id}'
|
||||
}
|
||||
|
||||
export type DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses = {
|
||||
200: SkillVersionDeleteResponse
|
||||
}
|
||||
|
||||
export type DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
|
||||
DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses[keyof DeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses]
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdData = {
|
||||
body?: never
|
||||
path: {
|
||||
skill_id: string
|
||||
version_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/versions/{version_id}'
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses = {
|
||||
200: SkillVersionDetailResponse
|
||||
}
|
||||
|
||||
export type GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
|
||||
GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses[keyof GetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses]
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdData = {
|
||||
body: SkillVersionUpdatePayload
|
||||
path: {
|
||||
skill_id: string
|
||||
version_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/workspaces/current/skills/{skill_id}/versions/{version_id}'
|
||||
}
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses = {
|
||||
200: SkillVersionResponse
|
||||
}
|
||||
|
||||
export type PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
|
||||
PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses[keyof PatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponses]
|
||||
|
||||
export type GetWorkspacesCurrentToolLabelsData = {
|
||||
body?: never
|
||||
path?: never
|
||||
|
||||
@@ -12,6 +12,13 @@ export const zAgentProviderResponse = z.record(z.string(), z.unknown())
|
||||
*/
|
||||
export const zAgentProviderListResponse = z.array(z.record(z.string(), z.unknown()))
|
||||
|
||||
/**
|
||||
* AgentSkillBindingsPayload
|
||||
*/
|
||||
export const zAgentSkillBindingsPayload = z.object({
|
||||
skill_ids: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SnippetImportPayload
|
||||
*
|
||||
@@ -436,6 +443,155 @@ export const zReplaceBindingsRequest = z.object({
|
||||
role_ids: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillCreatePayload
|
||||
*/
|
||||
export const zSkillCreatePayload = z.object({
|
||||
description: z.string().optional().default(''),
|
||||
display_name: z.string().nullish(),
|
||||
icon: z.string().optional().default('📄'),
|
||||
name: z.string().nullish(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillFileUploadResponse
|
||||
*/
|
||||
export const zSkillFileUploadResponse = z.object({
|
||||
hash: z.string(),
|
||||
id: z.string(),
|
||||
mime_type: z.string(),
|
||||
name: z.string(),
|
||||
size: z.int(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillDeletePayload
|
||||
*/
|
||||
export const zSkillDeletePayload = z.object({
|
||||
confirmation_name: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillDeleteResponse
|
||||
*/
|
||||
export const zSkillDeleteResponse = z.object({
|
||||
deleted: z.boolean(),
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillMetadataPayload
|
||||
*/
|
||||
export const zSkillMetadataPayload = z.object({
|
||||
display_name: z.string().nullish(),
|
||||
expected_updated_at: z.int().nullish(),
|
||||
icon: z.string().nullish(),
|
||||
tags: z.array(z.string()).nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillResponse
|
||||
*/
|
||||
export const zSkillResponse = z.object({
|
||||
created_at: z.int(),
|
||||
created_by: z.string().nullish(),
|
||||
created_by_name: z.string().nullish(),
|
||||
description: z.string(),
|
||||
display_name: z.string(),
|
||||
icon: z.string(),
|
||||
id: z.string(),
|
||||
latest_published_version_id: z.string().nullish(),
|
||||
name: z.string(),
|
||||
name_manually_edited: z.boolean().optional().default(false),
|
||||
reference_count: z.int().optional().default(0),
|
||||
tags: z.array(z.string()).optional(),
|
||||
updated_at: z.int(),
|
||||
updated_by: z.string().nullish(),
|
||||
updated_by_name: z.string().nullish(),
|
||||
visibility: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillListResponse
|
||||
*/
|
||||
export const zSkillListResponse = z.object({
|
||||
data: z.array(zSkillResponse).optional(),
|
||||
has_more: z.boolean().optional().default(false),
|
||||
limit: z.int().optional().default(20),
|
||||
page: z.int().optional().default(1),
|
||||
total: z.int().optional().default(0),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillFilePreviewResponse
|
||||
*/
|
||||
export const zSkillFilePreviewResponse = z.object({
|
||||
content: z.string(),
|
||||
hash: z.string(),
|
||||
mime_type: z.string(),
|
||||
path: z.string(),
|
||||
size: z.int(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillPublishPayload
|
||||
*/
|
||||
export const zSkillPublishPayload = z.object({
|
||||
publish_note: z.string().max(1024).optional().default(''),
|
||||
version_name: z.string().max(128).nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillVersionResponse
|
||||
*/
|
||||
export const zSkillVersionResponse = z.object({
|
||||
archive_size: z.int(),
|
||||
created_at: z.int(),
|
||||
hash_code: z.string(),
|
||||
id: z.string(),
|
||||
is_latest: z.boolean().optional().default(false),
|
||||
publish_note: z.string(),
|
||||
published_by: z.string().nullish(),
|
||||
published_by_name: z.string().nullish(),
|
||||
skill_id: z.string(),
|
||||
version_name: z.string(),
|
||||
version_number: z.int(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillRestorePayload
|
||||
*/
|
||||
export const zSkillRestorePayload = z.object({
|
||||
publish_note: z.string().max(1024).optional().default(''),
|
||||
version_id: z.string(),
|
||||
version_name: z.string().max(128).nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillVersionListResponse
|
||||
*/
|
||||
export const zSkillVersionListResponse = z.object({
|
||||
data: z.array(zSkillVersionResponse).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillVersionDeleteResponse
|
||||
*/
|
||||
export const zSkillVersionDeleteResponse = z.object({
|
||||
deleted: z.boolean(),
|
||||
id: z.string(),
|
||||
latest_published_version_id: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillVersionUpdatePayload
|
||||
*/
|
||||
export const zSkillVersionUpdatePayload = z.object({
|
||||
publish_note: z.string().max(1024).optional().default(''),
|
||||
version_name: z.string().max(128).nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ApiToolProviderDeletePayload
|
||||
*/
|
||||
@@ -656,6 +812,33 @@ export const zSwitchWorkspaceResponse = z.object({
|
||||
result: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* AgentSkillBindingItemResponse
|
||||
*/
|
||||
export const zAgentSkillBindingItemResponse = z.object({
|
||||
description: z.string(),
|
||||
display_name: z.string(),
|
||||
file_count: z.int(),
|
||||
icon: z.string(),
|
||||
id: z.string(),
|
||||
latest_published_at: z.int().nullish(),
|
||||
latest_published_version_id: z.string().nullish(),
|
||||
name: z.string(),
|
||||
priority: z.int(),
|
||||
status: z.string(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
updated_at: z.int(),
|
||||
})
|
||||
|
||||
/**
|
||||
* AgentSkillBindingsResponse
|
||||
*/
|
||||
export const zAgentSkillBindingsResponse = z.object({
|
||||
agent_id: z.string(),
|
||||
data: z.array(zAgentSkillBindingItemResponse).optional(),
|
||||
skill_ids: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* IconInfo
|
||||
*
|
||||
@@ -1244,6 +1427,163 @@ export const zWorkspaceAccessMatrix = z.object({
|
||||
pagination: zPagination.nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillFileResponse
|
||||
*/
|
||||
export const zSkillFileResponse = z.object({
|
||||
content: z.string().nullish(),
|
||||
hash: z.string().nullish(),
|
||||
id: z.string().nullish(),
|
||||
kind: z.string(),
|
||||
mime_type: z.string().nullish(),
|
||||
path: z.string(),
|
||||
size: z.int().nullish(),
|
||||
storage: z.string().nullish(),
|
||||
tool_file_id: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillDetailResponse
|
||||
*/
|
||||
export const zSkillDetailResponse = z.object({
|
||||
created_at: z.int(),
|
||||
created_by: z.string().nullish(),
|
||||
created_by_name: z.string().nullish(),
|
||||
description: z.string(),
|
||||
display_name: z.string(),
|
||||
files: z.array(zSkillFileResponse).optional(),
|
||||
icon: z.string(),
|
||||
id: z.string(),
|
||||
latest_published_version_id: z.string().nullish(),
|
||||
name: z.string(),
|
||||
name_manually_edited: z.boolean().optional().default(false),
|
||||
reference_count: z.int().optional().default(0),
|
||||
tags: z.array(z.string()).optional(),
|
||||
updated_at: z.int(),
|
||||
updated_by: z.string().nullish(),
|
||||
updated_by_name: z.string().nullish(),
|
||||
visibility: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillVersionDetailResponse
|
||||
*/
|
||||
export const zSkillVersionDetailResponse = z.object({
|
||||
archive_size: z.int(),
|
||||
created_at: z.int(),
|
||||
files: z.array(zSkillFileResponse).optional(),
|
||||
hash_code: z.string(),
|
||||
id: z.string(),
|
||||
is_latest: z.boolean().optional().default(false),
|
||||
publish_note: z.string(),
|
||||
published_by: z.string().nullish(),
|
||||
published_by_name: z.string().nullish(),
|
||||
skill_id: z.string(),
|
||||
version_name: z.string(),
|
||||
version_number: z.int(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillTagResponse
|
||||
*/
|
||||
export const zSkillTagResponse = z.object({
|
||||
count: z.int(),
|
||||
tag: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillTagListResponse
|
||||
*/
|
||||
export const zSkillTagListResponse = z.object({
|
||||
data: z.array(zSkillTagResponse).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillAssistAttachmentPayload
|
||||
*/
|
||||
export const zSkillAssistAttachmentPayload = z.object({
|
||||
mime_type: z.string().min(1).max(255).nullish(),
|
||||
name: z.string().min(1).max(255),
|
||||
size: z.int().gte(0).nullish(),
|
||||
tool_file_id: z.string().min(1),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillAssistModelPayload
|
||||
*/
|
||||
export const zSkillAssistModelPayload = z.object({
|
||||
model: z.string().min(1).max(255),
|
||||
model_settings: z.record(z.string(), z.unknown()).nullish(),
|
||||
plugin_id: z.string().min(1).max(255).nullish(),
|
||||
provider: z.string().min(1).max(255),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillAssistMessagePayload
|
||||
*
|
||||
* One user message and optional uploaded context for the read-only Skill Authoring assistant.
|
||||
*/
|
||||
export const zSkillAssistMessagePayload = z.object({
|
||||
attachments: z.array(zSkillAssistAttachmentPayload).max(10).optional(),
|
||||
message: z.string().min(1).max(8000),
|
||||
model: zSkillAssistModelPayload.nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillDraftFileOperation
|
||||
*/
|
||||
export const zSkillDraftFileOperation = z.enum([
|
||||
'delete',
|
||||
'mkdir',
|
||||
'rename',
|
||||
'upsert_text',
|
||||
'upsert_tool_file',
|
||||
])
|
||||
|
||||
/**
|
||||
* SkillDraftFileOperationPayload
|
||||
*/
|
||||
export const zSkillDraftFileOperationPayload = z.object({
|
||||
content: z.string().nullish(),
|
||||
expected_updated_at: z.int().nullish(),
|
||||
hash: z.string().nullish(),
|
||||
mime_type: z.string().nullish(),
|
||||
operation: zSkillDraftFileOperation,
|
||||
path: z.string(),
|
||||
size: z.int().gte(0).nullish(),
|
||||
target_path: z.string().nullish(),
|
||||
tool_file_id: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillReferenceResponse
|
||||
*/
|
||||
export const zSkillReferenceResponse = z.object({
|
||||
agent_icon: z.string().nullish(),
|
||||
agent_icon_background: z.string().nullish(),
|
||||
agent_icon_type: z.string().nullish(),
|
||||
agent_id: z.string(),
|
||||
app_id: z.string().nullish(),
|
||||
display_name: z.string(),
|
||||
name: z.string(),
|
||||
node_id: z.string().nullish(),
|
||||
node_name: z.string().nullish(),
|
||||
type: z.string(),
|
||||
workflow_icon: z.string().nullish(),
|
||||
workflow_icon_background: z.string().nullish(),
|
||||
workflow_icon_type: z.string().nullish(),
|
||||
workflow_id: z.string().nullish(),
|
||||
workflow_name: z.string().nullish(),
|
||||
workflow_version: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillReferenceListResponse
|
||||
*/
|
||||
export const zSkillReferenceListResponse = z.object({
|
||||
data: z.array(zSkillReferenceResponse).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* ToolEmojiIcon
|
||||
*/
|
||||
@@ -2096,6 +2436,42 @@ export const zPermissionCatalogResponse = z.object({
|
||||
groups: z.array(zPermissionCatalogGroup).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillFileKind
|
||||
*
|
||||
* Draft file entry kind.
|
||||
*/
|
||||
export const zSkillFileKind = z.enum(['directory', 'file'])
|
||||
|
||||
/**
|
||||
* SkillFileStorage
|
||||
*
|
||||
* How a draft file's content is stored.
|
||||
*/
|
||||
export const zSkillFileStorage = z.enum(['text', 'tool_file'])
|
||||
|
||||
/**
|
||||
* SkillDraftTreeItemPayload
|
||||
*/
|
||||
export const zSkillDraftTreeItemPayload = z.object({
|
||||
content: z.string().nullish(),
|
||||
hash: z.string().nullish(),
|
||||
kind: zSkillFileKind.optional().default('file'),
|
||||
mime_type: z.string().nullish(),
|
||||
path: z.string(),
|
||||
size: z.int().gte(0).nullish(),
|
||||
storage: zSkillFileStorage.nullish(),
|
||||
tool_file_id: z.string().nullish(),
|
||||
})
|
||||
|
||||
/**
|
||||
* SkillDraftTreePayload
|
||||
*/
|
||||
export const zSkillDraftTreePayload = z.object({
|
||||
expected_updated_at: z.int().nullish(),
|
||||
files: z.array(zSkillDraftTreeItemPayload).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Option
|
||||
*/
|
||||
@@ -3421,6 +3797,26 @@ export const zGetWorkspacesCurrentAgentProviderByProviderNameResponse = zAgentPr
|
||||
*/
|
||||
export const zGetWorkspacesCurrentAgentProvidersResponse = zAgentProviderListResponse
|
||||
|
||||
export const zGetWorkspacesCurrentAgentsByAgentIdSkillsPath = z.object({
|
||||
agent_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Agent Skill bindings
|
||||
*/
|
||||
export const zGetWorkspacesCurrentAgentsByAgentIdSkillsResponse = zAgentSkillBindingsResponse
|
||||
|
||||
export const zPutWorkspacesCurrentAgentsByAgentIdSkillsBody = zAgentSkillBindingsPayload
|
||||
|
||||
export const zPutWorkspacesCurrentAgentsByAgentIdSkillsPath = z.object({
|
||||
agent_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Agent Skill bindings replaced
|
||||
*/
|
||||
export const zPutWorkspacesCurrentAgentsByAgentIdSkillsResponse = zAgentSkillBindingsResponse
|
||||
|
||||
export const zGetWorkspacesCurrentCustomizedSnippetsQuery = z.object({
|
||||
creators: z.array(z.string()).optional(),
|
||||
is_published: z.boolean().optional(),
|
||||
@@ -4694,6 +5090,234 @@ export const zGetWorkspacesCurrentRbacWorkspaceDatasetsAccessPoliciesByPolicyIdR
|
||||
*/
|
||||
export const zGetWorkspacesCurrentRbacWorkspaceDatasetsAccessPolicyResponse = zWorkspaceAccessMatrix
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsQuery = z.object({
|
||||
keyword: z.string().optional(),
|
||||
limit: z.int().gte(1).lte(100).optional().default(20),
|
||||
page: z.int().gte(1).lte(99999).optional().default(1),
|
||||
tag: z.array(z.string()).optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Workspace skills
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsResponse = zSkillListResponse
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsBody = zSkillCreatePayload
|
||||
|
||||
/**
|
||||
* Skill created
|
||||
*/
|
||||
export const zPostWorkspacesCurrentSkillsResponse = zSkillDetailResponse
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsFilesUploadBody = z.object({
|
||||
file: z.custom<Blob | File>((value) => value instanceof Blob || value instanceof File),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill draft file uploaded
|
||||
*/
|
||||
export const zPostWorkspacesCurrentSkillsFilesUploadResponse = zSkillFileUploadResponse
|
||||
|
||||
/**
|
||||
* Skill imported
|
||||
*/
|
||||
export const zPostWorkspacesCurrentSkillsImportResponse = zSkillDetailResponse
|
||||
|
||||
/**
|
||||
* Workspace Skill tags
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsTagsResponse = zSkillTagListResponse
|
||||
|
||||
export const zDeleteWorkspacesCurrentSkillsBySkillIdBody = zSkillDeletePayload
|
||||
|
||||
export const zDeleteWorkspacesCurrentSkillsBySkillIdPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill deleted
|
||||
*/
|
||||
export const zDeleteWorkspacesCurrentSkillsBySkillIdResponse = zSkillDeleteResponse
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill detail
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdResponse = zSkillDetailResponse
|
||||
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdBody = zSkillMetadataPayload
|
||||
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill updated
|
||||
*/
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdResponse = zSkillResponse
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdAssistMessagesBody = zSkillAssistMessagePayload
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdAssistMessagesPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill Authoring assistant event stream
|
||||
*/
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdAssistMessagesResponse = z.record(
|
||||
z.string(),
|
||||
z.unknown(),
|
||||
)
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdDuplicatePath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill duplicated
|
||||
*/
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdDuplicateResponse = zSkillDetailResponse
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdExportPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Published Skill zip archive
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdExportResponse = z.record(z.string(), z.unknown())
|
||||
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdFilesBody = zSkillDraftFileOperationPayload
|
||||
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdFilesPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Draft file operation applied
|
||||
*/
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdFilesResponse = zSkillDetailResponse
|
||||
|
||||
export const zPutWorkspacesCurrentSkillsBySkillIdFilesBody = zSkillDraftTreePayload
|
||||
|
||||
export const zPutWorkspacesCurrentSkillsBySkillIdFilesPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Draft files replaced
|
||||
*/
|
||||
export const zPutWorkspacesCurrentSkillsBySkillIdFilesResponse = zSkillDetailResponse
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdFilesContentPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdFilesContentQuery = z.object({
|
||||
download: z.string().optional(),
|
||||
path: z.string(),
|
||||
version_id: z.string().optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill file content
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdFilesContentResponse = zBinaryFileResponse
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdFilesPreviewPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdFilesPreviewQuery = z.object({
|
||||
path: z.string(),
|
||||
version_id: z.string().optional(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill file text preview
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdFilesPreviewResponse = zSkillFilePreviewResponse
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdPublishBody = zSkillPublishPayload
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdPublishPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill published
|
||||
*/
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdPublishResponse = zSkillVersionResponse
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdReferencesPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill references
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdReferencesResponse = zSkillReferenceListResponse
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdRestoreBody = zSkillRestorePayload
|
||||
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdRestorePath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill version restored
|
||||
*/
|
||||
export const zPostWorkspacesCurrentSkillsBySkillIdRestoreResponse = zSkillVersionResponse
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdVersionsPath = z.object({
|
||||
skill_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill versions
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdVersionsResponse = zSkillVersionListResponse
|
||||
|
||||
export const zDeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdPath = z.object({
|
||||
skill_id: z.string(),
|
||||
version_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill version deleted
|
||||
*/
|
||||
export const zDeleteWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
|
||||
zSkillVersionDeleteResponse
|
||||
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdPath = z.object({
|
||||
skill_id: z.string(),
|
||||
version_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill version detail
|
||||
*/
|
||||
export const zGetWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
|
||||
zSkillVersionDetailResponse
|
||||
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdBody =
|
||||
zSkillVersionUpdatePayload
|
||||
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdPath = z.object({
|
||||
skill_id: z.string(),
|
||||
version_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill version updated
|
||||
*/
|
||||
export const zPatchWorkspacesCurrentSkillsBySkillIdVersionsByVersionIdResponse =
|
||||
zSkillVersionResponse
|
||||
|
||||
/**
|
||||
* Tool labels retrieved successfully
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,7 @@ The current Flask-RESTX generator still emits these response entries under `appl
|
||||
| service | GET | `/files/{file_id}/preview` | Original file MIME type, optionally attachment | `BinaryFileResponse` |
|
||||
| console | GET | `/workspaces/current/plugin/icon` | Plugin asset MIME type | `BinaryFileResponse` |
|
||||
| console | GET | `/workspaces/current/plugin/asset` | `application/octet-stream` | `BinaryFileResponse` |
|
||||
| console | GET | `/workspaces/current/skills/{skill_id}/files/content` | Skill file MIME type, optionally attachment | `BinaryFileResponse` |
|
||||
| console | GET | `/workspaces/current/tool-provider/builtin/{provider}/icon` | Tool icon MIME type | `BinaryFileResponse` |
|
||||
| console | GET | `/workspaces/current/trigger-provider/{provider}/icon` | Trigger icon response | `BinaryFileResponse` |
|
||||
| console | GET | `/workspaces/{tenant_id}/model-providers/{provider}/{icon_type}/{lang}` | Model provider icon MIME type | `BinaryFileResponse` |
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import SkillDetailPage from '@/features/skills/detail-page'
|
||||
|
||||
export default function Page() {
|
||||
return <SkillDetailPage />
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import SkillsPage from '@/features/skills/page'
|
||||
|
||||
export default function Page() {
|
||||
return <SkillsPage />
|
||||
}
|
||||
+12
-6
@@ -91,9 +91,12 @@ describe('AgentRosterResponseContent', () => {
|
||||
await user.click(processToggle)
|
||||
|
||||
expect(processToggle).toHaveAttribute('aria-expanded', 'true')
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('history answer')).toBeInTheDocument()
|
||||
})
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByText('history answer')).toBeInTheDocument()
|
||||
},
|
||||
{ timeout: 5000 },
|
||||
)
|
||||
|
||||
expect(screen.queryByText('internal thought should not render')).not.toBeInTheDocument()
|
||||
})
|
||||
@@ -122,9 +125,12 @@ describe('AgentRosterResponseContent', () => {
|
||||
render(<AgentRosterResponseContent item={item} />)
|
||||
await user.click(screen.getByRole('button', { name: 'Thinking' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('const answer = 42').tagName).toBe('CODE')
|
||||
})
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(screen.getByText('const answer = 42').tagName).toBe('CODE')
|
||||
},
|
||||
{ timeout: 5000 },
|
||||
)
|
||||
})
|
||||
|
||||
it('should keep one collapsible thinking timeline while response parts interleave', async () => {
|
||||
|
||||
+33
-376
@@ -1,416 +1,73 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import ModelParameterModal from '../index'
|
||||
|
||||
let parameterRules: Array<Record<string, unknown>> | undefined = [
|
||||
{
|
||||
name: 'temperature',
|
||||
label: { en_US: 'Temperature' },
|
||||
type: 'float',
|
||||
default: 0.7,
|
||||
min: 0,
|
||||
max: 1,
|
||||
help: { en_US: 'Control randomness' },
|
||||
},
|
||||
]
|
||||
let isRulesLoading = false
|
||||
let isRulesPending = false
|
||||
let currentProvider: Record<string, unknown> | undefined = {
|
||||
provider: 'openai',
|
||||
label: { en_US: 'OpenAI' },
|
||||
}
|
||||
let currentModel: Record<string, unknown> | undefined = {
|
||||
model: 'gpt-3.5-turbo',
|
||||
status: 'active',
|
||||
model_properties: { mode: 'chat' },
|
||||
}
|
||||
let activeTextGenerationModelList: Array<Record<string, unknown>> = [
|
||||
{
|
||||
provider: 'openai',
|
||||
models: [
|
||||
{
|
||||
model: 'gpt-3.5-turbo',
|
||||
model_properties: { mode: 'chat' },
|
||||
features: ['vision'],
|
||||
},
|
||||
{
|
||||
model: 'gpt-4.1',
|
||||
model_properties: { mode: 'chat' },
|
||||
features: ['vision', 'tool-call'],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
const mocks = vi.hoisted(() => ({
|
||||
openIntegrationsSetting: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/context/provider-context', () => ({
|
||||
useProviderContext: () => ({
|
||||
isAPIKeySet: true,
|
||||
}),
|
||||
vi.mock('@/app/components/header/account-setting/use-integrations-setting', () => ({
|
||||
useIntegrationsSetting: () => mocks.openIntegrationsSetting,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-common', () => ({
|
||||
useModelParameterRules: () => ({
|
||||
data: {
|
||||
data: parameterRules,
|
||||
data: [],
|
||||
},
|
||||
isLoading: isRulesLoading,
|
||||
isPending: isRulesPending,
|
||||
isLoading: false,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks', () => ({
|
||||
useTextGenerationCurrentProviderAndModelAndModelList: () => ({
|
||||
currentProvider,
|
||||
currentModel,
|
||||
activeTextGenerationModelList,
|
||||
activeTextGenerationModelList: [],
|
||||
currentModel: undefined,
|
||||
currentProvider: undefined,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../parameter-item', () => ({
|
||||
default: ({
|
||||
parameterRule,
|
||||
onChange,
|
||||
onSwitch,
|
||||
nodesOutputVars,
|
||||
availableNodes,
|
||||
}: {
|
||||
parameterRule: { name: string; label: { en_US: string } }
|
||||
onChange: (v: number) => void
|
||||
onSwitch: (checked: boolean, val: unknown) => void
|
||||
nodesOutputVars?: unknown[]
|
||||
availableNodes?: unknown[]
|
||||
}) => (
|
||||
<div
|
||||
data-testid={`param-${parameterRule.name}`}
|
||||
data-has-nodes-output-vars={!!nodesOutputVars}
|
||||
data-has-available-nodes={!!availableNodes}
|
||||
>
|
||||
{parameterRule.label.en_US}
|
||||
<button onClick={() => onChange(0.9)}>Change</button>
|
||||
<button onClick={() => onSwitch(false, undefined)}>Remove</button>
|
||||
<button onClick={() => onSwitch(true, 'assigned')}>Add</button>
|
||||
</div>
|
||||
vi.mock('../../model-selector', () => ({
|
||||
default: ({ onConfigureEmptyState }: { onConfigureEmptyState?: () => void }) => (
|
||||
<button type="button" onClick={onConfigureEmptyState}>
|
||||
configure-empty-model
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('../../model-selector', () => ({
|
||||
default: ({
|
||||
onHide,
|
||||
onSelect,
|
||||
}: {
|
||||
onHide: () => void
|
||||
onSelect: (value: { provider: string; model: string }) => void
|
||||
}) => (
|
||||
<div data-testid="model-selector">
|
||||
<button onClick={() => onSelect({ provider: 'openai', model: 'gpt-4.1' })}>
|
||||
Select GPT-4.1
|
||||
</button>
|
||||
<button onClick={onHide}>hide</button>
|
||||
</div>
|
||||
),
|
||||
vi.mock('@/app/components/base/loading', () => ({
|
||||
default: () => <div>loading</div>,
|
||||
}))
|
||||
|
||||
vi.mock('../parameter-item', () => ({
|
||||
default: () => <div>parameter-item</div>,
|
||||
}))
|
||||
|
||||
vi.mock('../presets-parameter', () => ({
|
||||
default: ({
|
||||
onSelect,
|
||||
supportedParameterNames,
|
||||
}: {
|
||||
onSelect: (id: number) => void
|
||||
supportedParameterNames?: string[]
|
||||
}) => {
|
||||
if (supportedParameterNames && !supportedParameterNames.includes('temperature')) return null
|
||||
|
||||
return <button onClick={() => onSelect(1)}>Preset 1</button>
|
||||
},
|
||||
default: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
|
||||
}))
|
||||
|
||||
vi.mock('../presets-parameter-utils', () => ({
|
||||
getSupportedPresetConfig: (_toneId: number, supportedParameterNames?: string[]) => {
|
||||
if (supportedParameterNames && !supportedParameterNames.includes('temperature')) return {}
|
||||
|
||||
return { temperature: 0.8 }
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../trigger', () => ({
|
||||
default: () => <button type="button">Open Settings</button>,
|
||||
}))
|
||||
|
||||
vi.mock('@/config', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/config')>()
|
||||
return {
|
||||
...actual,
|
||||
PROVIDER_WITH_PRESET_TONE: ['openai'],
|
||||
}
|
||||
})
|
||||
|
||||
describe('ModelParameterModal', () => {
|
||||
const openSettings = () =>
|
||||
fireEvent.click(screen.getByRole('button', { name: /modelProvider\.modelSettings/i }))
|
||||
const defaultProps = {
|
||||
isAdvancedMode: false,
|
||||
modelId: 'gpt-3.5-turbo',
|
||||
provider: 'openai',
|
||||
setModel: vi.fn(),
|
||||
completionParams: { temperature: 0.7 },
|
||||
onCompletionParamsChange: vi.fn(),
|
||||
hideDebugWithMultipleModel: false,
|
||||
debugWithMultipleModel: false,
|
||||
onDebugWithMultipleModelChange: vi.fn(),
|
||||
readonly: false,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
isRulesLoading = false
|
||||
isRulesPending = false
|
||||
parameterRules = [
|
||||
{
|
||||
name: 'temperature',
|
||||
label: { en_US: 'Temperature' },
|
||||
type: 'float',
|
||||
default: 0.7,
|
||||
min: 0,
|
||||
max: 1,
|
||||
help: { en_US: 'Control randomness' },
|
||||
},
|
||||
]
|
||||
currentProvider = { provider: 'openai', label: { en_US: 'OpenAI' } }
|
||||
currentModel = {
|
||||
model: 'gpt-3.5-turbo',
|
||||
status: 'active',
|
||||
model_properties: { mode: 'chat' },
|
||||
}
|
||||
activeTextGenerationModelList = [
|
||||
{
|
||||
provider: 'openai',
|
||||
models: [
|
||||
{
|
||||
model: 'gpt-3.5-turbo',
|
||||
model_properties: { mode: 'chat' },
|
||||
features: ['vision'],
|
||||
},
|
||||
{
|
||||
model: 'gpt-4.1',
|
||||
model_properties: { mode: 'chat' },
|
||||
features: ['vision', 'tool-call'],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
it('should render trigger and open modal content when trigger is clicked', () => {
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
|
||||
openSettings()
|
||||
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('param-temperature')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should keep model selection and model settings as separate actions', () => {
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
|
||||
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByText('Select GPT-4.1'))
|
||||
|
||||
expect(defaultProps.setModel).toHaveBeenCalledWith({
|
||||
modelId: 'gpt-4.1',
|
||||
provider: 'openai',
|
||||
mode: 'chat',
|
||||
features: ['vision', 'tool-call'],
|
||||
})
|
||||
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /modelProvider\.modelSettings/i }))
|
||||
|
||||
expect(screen.getByTestId('param-temperature')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should disable model settings when no model is selected', () => {
|
||||
render(<ModelParameterModal {...defaultProps} provider="" modelId="" />)
|
||||
|
||||
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /modelProvider\.modelSettings/i })).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should call onCompletionParamsChange when parameter changes and switch actions happen', () => {
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
openSettings()
|
||||
|
||||
fireEvent.click(screen.getByText('Change'))
|
||||
expect(defaultProps.onCompletionParamsChange).toHaveBeenCalledWith({
|
||||
...defaultProps.completionParams,
|
||||
temperature: 0.9,
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('Remove'))
|
||||
expect(defaultProps.onCompletionParamsChange).toHaveBeenCalledWith({})
|
||||
|
||||
fireEvent.click(screen.getByText('Add'))
|
||||
expect(defaultProps.onCompletionParamsChange).toHaveBeenCalledWith({
|
||||
...defaultProps.completionParams,
|
||||
temperature: 'assigned',
|
||||
})
|
||||
})
|
||||
|
||||
it('should call onCompletionParamsChange when preset is selected', () => {
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
openSettings()
|
||||
fireEvent.click(screen.getByText('Preset 1'))
|
||||
expect(defaultProps.onCompletionParamsChange).toHaveBeenCalledWith({
|
||||
...defaultProps.completionParams,
|
||||
temperature: 0.8,
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render preset control when visible parameters do not support preset keys', () => {
|
||||
parameterRules = [
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: { en_US: 'Max Tokens' },
|
||||
type: 'int',
|
||||
default: 256,
|
||||
min: 1,
|
||||
max: 4096,
|
||||
},
|
||||
]
|
||||
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
openSettings()
|
||||
|
||||
expect(screen.queryByText('Preset 1')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call setModel when model selector picks another model', () => {
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
openSettings()
|
||||
fireEvent.click(screen.getByText('Select GPT-4.1'))
|
||||
|
||||
expect(defaultProps.setModel).toHaveBeenCalledWith({
|
||||
modelId: 'gpt-4.1',
|
||||
provider: 'openai',
|
||||
mode: 'chat',
|
||||
features: ['vision', 'tool-call'],
|
||||
})
|
||||
})
|
||||
|
||||
it('should toggle debug mode when debug footer is clicked', () => {
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
openSettings()
|
||||
fireEvent.click(screen.getByText(/debugAsMultipleModel/i))
|
||||
expect(defaultProps.onDebugWithMultipleModelChange).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should render loading state when parameter rules are loading', () => {
|
||||
isRulesLoading = true
|
||||
isRulesPending = true
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
openSettings()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render parameter loading when model is not configured and parameter rules query is pending but disabled', () => {
|
||||
isRulesPending = true
|
||||
parameterRules = []
|
||||
|
||||
render(<ModelParameterModal {...defaultProps} provider="" modelId="" />)
|
||||
openSettings()
|
||||
|
||||
expect(screen.queryByRole('status')).not.toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not open content when readonly is true', () => {
|
||||
render(<ModelParameterModal {...defaultProps} readonly />)
|
||||
expect(screen.getByRole('button', { name: /modelProvider\.modelSettings/i })).toBeDisabled()
|
||||
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render no parameter items when rules are undefined', () => {
|
||||
parameterRules = undefined
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
openSettings()
|
||||
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass nodesOutputVars and availableNodes to ParameterItem', () => {
|
||||
const mockNodesOutputVars = [{ nodeId: 'n1', title: 'Node', vars: [] }]
|
||||
const mockAvailableNodes = [{ id: 'n1', data: { title: 'Node', type: 'llm' } }]
|
||||
|
||||
it('opens provider settings from the model selector empty state', () => {
|
||||
render(
|
||||
<ModelParameterModal
|
||||
{...defaultProps}
|
||||
isInWorkflow
|
||||
nodesOutputVars={mockNodesOutputVars as never}
|
||||
availableNodes={mockAvailableNodes as never}
|
||||
/>,
|
||||
)
|
||||
|
||||
openSettings()
|
||||
|
||||
const paramEl = screen.getByTestId('param-temperature')
|
||||
expect(paramEl).toHaveAttribute('data-has-nodes-output-vars', 'true')
|
||||
expect(paramEl).toHaveAttribute('data-has-available-nodes', 'true')
|
||||
})
|
||||
|
||||
it('should support custom triggers, workflow mode, and missing default model values', async () => {
|
||||
render(
|
||||
<ModelParameterModal
|
||||
{...defaultProps}
|
||||
provider=""
|
||||
isAdvancedMode
|
||||
modelId=""
|
||||
isInWorkflow
|
||||
renderTrigger={({ open }) => <span>{open ? 'Custom Open' : 'Custom Closed'}</span>}
|
||||
provider=""
|
||||
completionParams={{}}
|
||||
setModel={vi.fn()}
|
||||
onCompletionParamsChange={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('Custom Closed'))
|
||||
fireEvent.click(screen.getByText('configure-empty-model'))
|
||||
|
||||
expect(screen.getByText('Custom Open')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByText('hide'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('model-selector')).not.toBeInTheDocument()
|
||||
expect(mocks.openIntegrationsSetting).toHaveBeenCalledWith({
|
||||
payload: 'provider',
|
||||
})
|
||||
})
|
||||
|
||||
it('should append the stop parameter in advanced mode and show the single-model debug label', () => {
|
||||
render(<ModelParameterModal {...defaultProps} isAdvancedMode debugWithMultipleModel />)
|
||||
|
||||
openSettings()
|
||||
|
||||
expect(screen.getByTestId('param-stop')).toBeInTheDocument()
|
||||
expect(screen.getByText(/debugAsSingleModel/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render the empty loading fallback when rules resolve to an empty list', () => {
|
||||
parameterRules = []
|
||||
isRulesLoading = true
|
||||
isRulesPending = true
|
||||
|
||||
render(<ModelParameterModal {...defaultProps} />)
|
||||
openSettings()
|
||||
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('param-temperature')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should support custom trigger placement outside workflow mode', () => {
|
||||
render(
|
||||
<ModelParameterModal
|
||||
{...defaultProps}
|
||||
renderTrigger={({ open }) => <span>{open ? 'Popup Open' : 'Popup Closed'}</span>}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('Popup Closed'))
|
||||
|
||||
expect(screen.getByText('Popup Open')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-selector')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
+42
-12
@@ -1,5 +1,5 @@
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import type { DefaultModel, FormValue, ModelParameterRule } from '../declarations'
|
||||
import type { DefaultModel, FormValue, Model, ModelParameterRule } from '../declarations'
|
||||
import type { ParameterValue } from './parameter-item'
|
||||
import type { TriggerProps } from './types'
|
||||
import type { Node, NodeOutPutVar } from '@/app/components/workflow/types'
|
||||
@@ -7,8 +7,9 @@ import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ArrowNarrowLeft } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/constants'
|
||||
import { useIntegrationsSetting } from '@/app/components/header/account-setting/use-integrations-setting'
|
||||
import { PROVIDER_WITH_PRESET_TONE, STOP_PARAMETER_RULE } from '@/config'
|
||||
import { useModelParameterRules } from '@/service/use-common'
|
||||
import { useTextGenerationCurrentProviderAndModelAndModelList } from '../hooks'
|
||||
@@ -34,8 +35,10 @@ export type ModelParameterModalProps = {
|
||||
debugWithMultipleModel?: boolean
|
||||
onDebugWithMultipleModelChange?: () => void
|
||||
renderTrigger?: (v: TriggerProps) => ReactNode
|
||||
triggerContainerClassName?: string
|
||||
readonly?: boolean
|
||||
isInWorkflow?: boolean
|
||||
modelList?: Model[]
|
||||
scope?: string
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
@@ -53,17 +56,25 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
debugWithMultipleModel,
|
||||
onDebugWithMultipleModelChange,
|
||||
renderTrigger,
|
||||
triggerContainerClassName,
|
||||
readonly,
|
||||
isInWorkflow,
|
||||
modelList,
|
||||
nodesOutputVars,
|
||||
availableNodes,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const openIntegrationsSetting = useIntegrationsSetting()
|
||||
const { data: parameterRulesData, isLoading } = useModelParameterRules(provider, modelId)
|
||||
const isRulesLoading = !!provider && !!modelId && isLoading
|
||||
const { currentProvider, currentModel, activeTextGenerationModelList } =
|
||||
useTextGenerationCurrentProviderAndModelAndModelList({ provider, model: modelId })
|
||||
const availableTextGenerationModelList = modelList ?? activeTextGenerationModelList
|
||||
const selectedProvider =
|
||||
modelList?.find((modelItem) => modelItem.provider === provider) ?? currentProvider
|
||||
const selectedModel =
|
||||
selectedProvider?.models?.find((modelItem) => modelItem.model === modelId) ?? currentModel
|
||||
|
||||
const parameterRules: ModelParameterRule[] = useMemo(() => {
|
||||
return parameterRulesData?.data || []
|
||||
@@ -71,6 +82,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
const supportedPresetParameterNames = useMemo(() => {
|
||||
return parameterRules.map((parameterRule) => parameterRule.name)
|
||||
}, [parameterRules])
|
||||
const hasSelectedModel = !!provider && !!modelId
|
||||
|
||||
const handleParamChange = (key: string, value: ParameterValue) => {
|
||||
onCompletionParamsChange({
|
||||
@@ -80,10 +92,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
}
|
||||
|
||||
const handleChangeModel = ({ provider, model }: DefaultModel) => {
|
||||
const targetProvider = activeTextGenerationModelList.find(
|
||||
const targetProvider = availableTextGenerationModelList.find(
|
||||
(modelItem) => modelItem.provider === provider,
|
||||
)
|
||||
const targetModelItem = targetProvider?.models.find((modelItem) => modelItem.model === model)
|
||||
const targetModelItem = targetProvider?.models?.find((modelItem) => modelItem.model === model)
|
||||
setModel({
|
||||
modelId: model,
|
||||
provider,
|
||||
@@ -91,6 +103,15 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
features: targetModelItem?.features || [],
|
||||
})
|
||||
}
|
||||
const handleOpenModelSettings = () => {
|
||||
if (readonly || !hasSelectedModel) return
|
||||
setOpen(true)
|
||||
}
|
||||
const handleConfigureEmptyState = () => {
|
||||
if (readonly) return
|
||||
|
||||
openIntegrationsSetting({ payload: ACCOUNT_SETTING_TAB.PROVIDER })
|
||||
}
|
||||
|
||||
const handleSwitch = (key: string, value: boolean, assignValue: ParameterValue) => {
|
||||
if (!value) {
|
||||
@@ -114,8 +135,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const hasSelectedModel = !!provider && !!modelId
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
@@ -133,8 +152,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
>
|
||||
{renderTrigger({
|
||||
open,
|
||||
currentProvider,
|
||||
currentModel,
|
||||
currentProvider: selectedProvider,
|
||||
currentModel: selectedModel,
|
||||
providerName: provider,
|
||||
modelId,
|
||||
})}
|
||||
@@ -142,11 +161,16 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-8 min-w-[296px] items-center gap-px overflow-hidden rounded-lg">
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-8 min-w-[296px] items-center gap-px overflow-hidden rounded-lg',
|
||||
triggerContainerClassName,
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<ModelSelector
|
||||
defaultModel={provider || modelId ? { provider, model: modelId } : undefined}
|
||||
modelList={activeTextGenerationModelList}
|
||||
modelList={availableTextGenerationModelList}
|
||||
readonly={readonly}
|
||||
triggerClassName={cn(
|
||||
'h-8! w-full rounded-r-none!',
|
||||
@@ -154,6 +178,8 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
'border border-workflow-block-parma-bg bg-workflow-block-parma-bg hover:bg-workflow-block-parma-bg',
|
||||
)}
|
||||
onSelect={handleChangeModel}
|
||||
onConfigureEmptyState={handleConfigureEmptyState}
|
||||
onOpenProviderSettings={handleOpenModelSettings}
|
||||
/>
|
||||
</div>
|
||||
<PopoverTrigger
|
||||
@@ -187,8 +213,9 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
<div className="px-4 pt-2 pb-4">
|
||||
<ModelSelector
|
||||
defaultModel={hasSelectedModel ? { provider, model: modelId } : undefined}
|
||||
modelList={activeTextGenerationModelList}
|
||||
modelList={availableTextGenerationModelList}
|
||||
onSelect={handleChangeModel}
|
||||
onOpenProviderSettings={handleOpenModelSettings}
|
||||
onHide={() => setOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
@@ -249,7 +276,10 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
{debugWithMultipleModel
|
||||
? t(($) => $.debugAsSingleModel, { ns: 'appDebug' })
|
||||
: t(($) => $.debugAsMultipleModel, { ns: 'appDebug' })}
|
||||
<ArrowNarrowLeft className="size-3 rotate-180" />
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-custom-vender-line-arrows-arrow-narrow-left size-3 rotate-180"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
|
||||
@@ -33,6 +33,7 @@ type ModelSelectorProps = {
|
||||
hideProviderSettingsFooter?: boolean
|
||||
onConfigureEmptyState?: () => void
|
||||
onOpenMarketplace?: () => void
|
||||
onOpenProviderSettings?: () => void
|
||||
providerSettingsSource?: 'agent'
|
||||
showModelMeta?: boolean
|
||||
modelPredicate?: ModelSelectorModelPredicate
|
||||
@@ -52,6 +53,7 @@ function ModelSelector({
|
||||
hideProviderSettingsFooter,
|
||||
onConfigureEmptyState,
|
||||
onOpenMarketplace,
|
||||
onOpenProviderSettings,
|
||||
providerSettingsSource,
|
||||
showModelMeta,
|
||||
modelPredicate,
|
||||
@@ -180,6 +182,7 @@ function ModelSelector({
|
||||
modelSuggestionPredicate={modelSuggestionPredicate}
|
||||
onConfigureEmptyState={onConfigureEmptyState ? handleConfigureEmptyState : undefined}
|
||||
onOpenMarketplace={onOpenMarketplace}
|
||||
onOpenProviderSettings={onOpenProviderSettings}
|
||||
onInputValueChange={setInputValue}
|
||||
onHide={handleHide}
|
||||
/>
|
||||
|
||||
+25
-18
@@ -9,7 +9,6 @@ import { StatusDot } from '@langgenius/dify-ui/status-dot'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useCredentialPermissions } from '@/hooks/use-credential-permissions'
|
||||
@@ -57,7 +56,8 @@ function PopupItem({
|
||||
const updateModelProviders = useUpdateModelProviders()
|
||||
const currentProvider = modelProviders.find((provider) => provider.provider === model.provider)
|
||||
const { canUseCredential, canCreateCredential, canManageCredential } = useCredentialPermissions()
|
||||
const canOpenCredentialDropdown = canUseCredential || canCreateCredential || canManageCredential
|
||||
const canOpenCredentialDropdown =
|
||||
!!currentProvider && (canUseCredential || canCreateCredential || canManageCredential)
|
||||
const handleOpenModelModal = () => {
|
||||
if (!canCreateCredential) return
|
||||
|
||||
@@ -77,7 +77,8 @@ function PopupItem({
|
||||
})
|
||||
}
|
||||
|
||||
const state = useCredentialPanelState(currentProvider)
|
||||
// oxlint-disable-next-line eslint-react/use-state -- This domain hook returns credential panel state, not a React useState tuple.
|
||||
const credentialPanelState = useCredentialPanelState(currentProvider)
|
||||
const { isChangingPriority, handleChangePriority } = useChangeProviderPriority(currentProvider)
|
||||
const groupItems = useMemo(
|
||||
() =>
|
||||
@@ -90,10 +91,11 @@ function PopupItem({
|
||||
[model.models, model.provider],
|
||||
)
|
||||
|
||||
const isUsingCredits = state.priority === 'credits'
|
||||
const hasCredits = !state.isCreditsExhausted
|
||||
const isApiKeyActive = state.variant === 'api-active' || state.variant === 'api-fallback'
|
||||
const { credentialName } = state
|
||||
const isUsingCredits = credentialPanelState.priority === 'credits'
|
||||
const hasCredits = !credentialPanelState.isCreditsExhausted
|
||||
const isApiKeyActive =
|
||||
credentialPanelState.variant === 'api-active' || credentialPanelState.variant === 'api-fallback'
|
||||
const { credentialName } = credentialPanelState
|
||||
|
||||
const handleCloseDropdown = useCallback(() => {
|
||||
setDropdownOpen(false)
|
||||
@@ -129,7 +131,10 @@ function PopupItem({
|
||||
{isUsingCredits ? (
|
||||
hasCredits ? (
|
||||
<>
|
||||
<CreditsCoin className="size-3" />
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-custom-vender-line-financeandecommerce-credits-coin size-3"
|
||||
/>
|
||||
<span className="ml-1 truncate">
|
||||
{t(($) => $['modelProvider.selector.aiCredits'], { ns: 'common' })}
|
||||
</span>
|
||||
@@ -161,15 +166,17 @@ function PopupItem({
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
<PopoverContent placement="bottom-end">
|
||||
<DropdownContent
|
||||
provider={currentProvider}
|
||||
state={state}
|
||||
isChangingPriority={isChangingPriority}
|
||||
onChangePriority={handleChangePriority}
|
||||
onClose={handleCloseDropdown}
|
||||
/>
|
||||
</PopoverContent>
|
||||
{currentProvider && (
|
||||
<PopoverContent placement="bottom-end">
|
||||
<DropdownContent
|
||||
provider={currentProvider}
|
||||
state={credentialPanelState}
|
||||
isChangingPriority={isChangingPriority}
|
||||
onChangePriority={handleChangePriority}
|
||||
onClose={handleCloseDropdown}
|
||||
/>
|
||||
</PopoverContent>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
{!collapsed &&
|
||||
@@ -215,7 +222,7 @@ function PopupItem({
|
||||
</ModelName>
|
||||
</div>
|
||||
{defaultModel?.model === modelItem.model &&
|
||||
defaultModel.provider === currentProvider.provider && (
|
||||
defaultModel.provider === model.provider && (
|
||||
<ComboboxItemIndicator className="shrink-0 text-text-accent">
|
||||
<span
|
||||
className="i-custom-vender-line-general-check size-4"
|
||||
|
||||
+8
-1
@@ -69,6 +69,7 @@ export type PopupProps = {
|
||||
onConfigureEmptyState?: () => void
|
||||
onInputValueChange: (value: string) => void
|
||||
onOpenMarketplace?: () => void
|
||||
onOpenProviderSettings?: () => void
|
||||
onHide: () => void
|
||||
}
|
||||
function Popup({
|
||||
@@ -83,6 +84,7 @@ function Popup({
|
||||
onConfigureEmptyState,
|
||||
onInputValueChange,
|
||||
onOpenMarketplace,
|
||||
onOpenProviderSettings,
|
||||
onHide,
|
||||
}: PopupProps) {
|
||||
const { t } = useTranslation()
|
||||
@@ -255,11 +257,16 @@ function Popup({
|
||||
|
||||
const handleOpenSettings = useCallback(() => {
|
||||
onHide()
|
||||
if (onOpenProviderSettings) {
|
||||
onOpenProviderSettings()
|
||||
return
|
||||
}
|
||||
|
||||
openIntegrationsSetting({
|
||||
payload: ACCOUNT_SETTING_TAB.PROVIDER,
|
||||
source: providerSettingsSource,
|
||||
})
|
||||
}, [onHide, openIntegrationsSetting, providerSettingsSource])
|
||||
}, [onHide, onOpenProviderSettings, openIntegrationsSetting, providerSettingsSource])
|
||||
const handleClosePreviewCard = useCallback(() => {
|
||||
previewCardHandle.close()
|
||||
}, [previewCardHandle])
|
||||
|
||||
@@ -564,6 +564,10 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps')
|
||||
expect(screen.getByRole('link', { name: /Agents/ })).toHaveAttribute('href', '/agents')
|
||||
expect(screen.getByRole('link', { name: /Agents common.menus.status/ })).toBeInTheDocument()
|
||||
expect(screen.getByRole('link', { name: /common.mainNav.skills/ })).toHaveAttribute(
|
||||
'href',
|
||||
'/skills',
|
||||
)
|
||||
expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveAttribute(
|
||||
'href',
|
||||
'/datasets',
|
||||
@@ -584,6 +588,10 @@ describe('MainNav', () => {
|
||||
renderMainNav()
|
||||
|
||||
expect(screen.queryByRole('link', { name: /Agents/ })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('link', { name: /common.mainNav.skills/ })).toHaveAttribute(
|
||||
'href',
|
||||
'/skills',
|
||||
)
|
||||
})
|
||||
|
||||
it('hides the roster entry when the user lacks agent.manage', () => {
|
||||
@@ -743,6 +751,7 @@ describe('MainNav', () => {
|
||||
expect(screen.getByRole('link', { name: /common.mainNav.home/ })).toHaveAttribute('href', '/')
|
||||
expect(screen.getByRole('link', { name: /common.menus.apps/ })).toHaveAttribute('href', '/apps')
|
||||
expect(screen.queryByRole('link', { name: /Agents/ })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('link', { name: /common.mainNav.skills/ })).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('link', { name: /common.menus.datasets/ })).toHaveAttribute(
|
||||
'href',
|
||||
'/datasets',
|
||||
|
||||
@@ -32,6 +32,8 @@ export type DetailSidebarVisibilityOptions = Pick<
|
||||
const VISIBLE_TO_ALL: MainNavRouteVisibility = () => true
|
||||
const CAN_MANAGE_AGENTS: MainNavRouteVisibility = (options) => options.canManageAgents
|
||||
const CAN_USE_APP_DEPLOY: MainNavRouteVisibility = (options) => options.canUseAppDeploy
|
||||
const NOT_DATASET_OPERATOR: MainNavRouteVisibility = (options) =>
|
||||
!options.isCurrentWorkspaceDatasetOperator
|
||||
|
||||
function isPathUnderRoute(pathname: string, route: string) {
|
||||
return pathname === route || pathname.startsWith(`${route}/`)
|
||||
@@ -69,6 +71,15 @@ export const MAIN_NAV_ROUTES = [
|
||||
visibility: CAN_MANAGE_AGENTS,
|
||||
feature: 'agentV2',
|
||||
},
|
||||
{
|
||||
key: 'skills',
|
||||
href: '/skills',
|
||||
labelKey: 'mainNav.skills',
|
||||
active: (path: string) => isPathUnderRoute(path, '/skills'),
|
||||
icon: 'i-ri-box-3-line',
|
||||
activeIcon: 'i-ri-box-3-fill',
|
||||
visibility: NOT_DATASET_OPERATOR,
|
||||
},
|
||||
{
|
||||
key: 'datasets',
|
||||
href: '/datasets',
|
||||
|
||||
+4
-2
@@ -119,8 +119,10 @@ describe('ConversationVariableModal', () => {
|
||||
})
|
||||
|
||||
expect(screen.getAllByText('session_state')).toHaveLength(2)
|
||||
expect(screen.getByText((content) => content.includes('formatted-100'))).toBeInTheDocument()
|
||||
expect(screen.getByTestId('conversation-code-editor')).toHaveTextContent('{"latest":1}')
|
||||
expect(
|
||||
await screen.findByText((content) => content.includes('formatted-100')),
|
||||
).toBeInTheDocument()
|
||||
expect(await screen.findByTestId('conversation-code-editor')).toHaveTextContent('{"latest":1}')
|
||||
|
||||
await user.click(screen.getByText('summary'))
|
||||
expect(screen.getByText('latest text')).toBeInTheDocument()
|
||||
|
||||
@@ -16,6 +16,10 @@ vi.mock('@/next/navigation', () => ({
|
||||
useRouter: () => ({ push: mockPush, replace: mockReplace }),
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-document-title', () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/common', () => ({
|
||||
fetchSetupStatus: vi.fn(),
|
||||
fetchInitValidateStatus: vi.fn(),
|
||||
|
||||
@@ -27,6 +27,23 @@ export const viewport: Viewport = {
|
||||
viewportFit: 'cover',
|
||||
}
|
||||
|
||||
const resizeObserverErrorFilterScript = `
|
||||
(() => {
|
||||
const ignoredMessages = new Set([
|
||||
'ResizeObserver loop completed with undelivered notifications.',
|
||||
'ResizeObserver loop limit exceeded',
|
||||
]);
|
||||
const ignore = (event) => {
|
||||
const message = event?.message || event?.reason?.message;
|
||||
if (!ignoredMessages.has(message)) return;
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
};
|
||||
window.addEventListener('error', ignore, true);
|
||||
window.addEventListener('unhandledrejection', ignore, true);
|
||||
})();
|
||||
`
|
||||
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const datasetMap = getDatasetMap()
|
||||
const queryClient = getQueryClientServer()
|
||||
@@ -43,6 +60,11 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
<html lang={locale ?? 'en'} className="h-full" suppressHydrationWarning>
|
||||
<head>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<script
|
||||
nonce={nonce}
|
||||
// oxlint-disable-next-line eslint-react/dom-no-dangerously-set-innerhtml -- Static early listener must run before the dev error overlay registers.
|
||||
dangerouslySetInnerHTML={{ __html: resizeObserverErrorFilterScript }}
|
||||
/>
|
||||
<meta name="theme-color" content="#1C64F2" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
|
||||
@@ -8,15 +8,15 @@ import type {
|
||||
} from './add-actions-context'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { AgentOrchestrateAddActionsContext } from './add-actions-context'
|
||||
import { useAgentOrchestrateReadOnly } from './read-only-context'
|
||||
import { useAgentOrchestrateViewingVersion } from './read-only-context'
|
||||
|
||||
export function AgentOrchestrateAddActionsProvider({ children }: { children: ReactNode }) {
|
||||
const readOnly = useAgentOrchestrateReadOnly()
|
||||
const isViewingVersion = useAgentOrchestrateViewingVersion()
|
||||
const [actions, setActions] = useState<AgentOrchestrateAddActions>({})
|
||||
|
||||
const registerAction = useCallback(
|
||||
(key: AgentOrchestrateAddActionKey, action: AgentOrchestrateAddAction) => {
|
||||
if (readOnly) return () => undefined
|
||||
if (isViewingVersion) return () => undefined
|
||||
|
||||
setActions((currentActions) => {
|
||||
if (currentActions[key] === action) return currentActions
|
||||
@@ -37,15 +37,15 @@ export function AgentOrchestrateAddActionsProvider({ children }: { children: Rea
|
||||
})
|
||||
}
|
||||
},
|
||||
[readOnly],
|
||||
[isViewingVersion],
|
||||
)
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
actions: readOnly ? {} : actions,
|
||||
actions: isViewingVersion ? {} : actions,
|
||||
registerAction,
|
||||
}),
|
||||
[actions, readOnly, registerAction],
|
||||
[actions, isViewingVersion, registerAction],
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ import type { ButtonProps } from '@langgenius/dify-ui/button'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useAgentOrchestrateReadOnly } from '../read-only-context'
|
||||
import { useAgentOrchestrateViewingVersion } from '../read-only-context'
|
||||
|
||||
type ConfigureSectionAddButtonProps = Omit<
|
||||
ButtonProps,
|
||||
@@ -19,9 +19,9 @@ export function ConfigureSectionAddButton({
|
||||
...props
|
||||
}: ConfigureSectionAddButtonProps) {
|
||||
const { t } = useTranslation('common')
|
||||
const readOnly = useAgentOrchestrateReadOnly()
|
||||
const isViewingVersion = useAgentOrchestrateViewingVersion()
|
||||
|
||||
if (readOnly) return null
|
||||
if (isViewingVersion) return null
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
||||
+22
-7
@@ -13,7 +13,10 @@ import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store
|
||||
import { QueryClientTestProvider } from '@/test/console/query-provider'
|
||||
import { createSystemFeaturesFixture } from '@/test/console/system-features'
|
||||
import { AgentConfigApiContextProvider } from '../../config-context'
|
||||
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context'
|
||||
import {
|
||||
AgentOrchestrateReadOnlyContext,
|
||||
AgentOrchestrateViewingVersionContext,
|
||||
} from '../../read-only-context'
|
||||
import { AgentFiles } from '../index'
|
||||
|
||||
type ConfigFileQueryOptionsInput = {
|
||||
@@ -171,10 +174,12 @@ function renderAgentFiles({
|
||||
initialDraft = createInitialDraft(),
|
||||
apiContext = { agentId: 'agent-1', draftType: 'draft' } satisfies AgentConfigApiContext,
|
||||
readOnly = false,
|
||||
viewingVersion = false,
|
||||
}: {
|
||||
initialDraft?: AgentSoulConfigFormState
|
||||
apiContext?: AgentConfigApiContext
|
||||
readOnly?: boolean
|
||||
viewingVersion?: boolean
|
||||
} = {}) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -191,10 +196,12 @@ function renderAgentFiles({
|
||||
<QueryClientTestProvider queryClient={queryClient}>
|
||||
<AgentConfigApiContextProvider value={apiContext}>
|
||||
<AgentComposerProvider initialDraft={initialDraft}>
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<AgentFiles />
|
||||
<ConfigSnapshotProbe />
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
<AgentOrchestrateViewingVersionContext value={viewingVersion}>
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<AgentFiles />
|
||||
<ConfigSnapshotProbe />
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
</AgentOrchestrateViewingVersionContext>
|
||||
</AgentComposerProvider>
|
||||
</AgentConfigApiContextProvider>
|
||||
</QueryClientTestProvider>,
|
||||
@@ -642,8 +649,8 @@ describe('AgentFiles', () => {
|
||||
expect(snapshot.config_note).toBe('')
|
||||
})
|
||||
|
||||
it('should keep flat config files visible without drive-prefix filtering and disable add in read-only mode', () => {
|
||||
renderAgentFiles({ readOnly: true })
|
||||
it('should keep flat config files visible without drive-prefix filtering and disable add when viewing a version', () => {
|
||||
renderAgentFiles({ readOnly: true, viewingVersion: true })
|
||||
|
||||
expect(screen.getByText('diagram.png')).toBeInTheDocument()
|
||||
expect(screen.getByText('brief.md')).toBeInTheDocument()
|
||||
@@ -651,4 +658,12 @@ describe('AgentFiles', () => {
|
||||
screen.queryByRole('button', { name: /agentV2\.agentDetail\.configure\.files\.add/i }),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should keep add action available for build drafts', () => {
|
||||
renderAgentFiles({ readOnly: true })
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.files\.add/i }),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,7 +23,10 @@ import { AgentKnowledgeRetrieval } from './knowledge'
|
||||
import { AgentModelField } from './model-config/field'
|
||||
import { AgentPromptEditor } from './prompt-editor'
|
||||
import { AgentConfigurePublishBar } from './publish-bar'
|
||||
import { AgentOrchestrateReadOnlyContext } from './read-only-context'
|
||||
import {
|
||||
AgentOrchestrateReadOnlyContext,
|
||||
AgentOrchestrateViewingVersionContext,
|
||||
} from './read-only-context'
|
||||
import { AgentSkills } from './skills'
|
||||
import { AgentTools } from './tools'
|
||||
|
||||
@@ -132,41 +135,43 @@ export function AgentOrchestratePanel({
|
||||
/>
|
||||
)}
|
||||
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<div aria-readonly={readOnly} className="flex min-h-0 flex-1 flex-col">
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1 overflow-hidden"
|
||||
label={showHeader ? undefined : orchestrateLabel}
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20'),
|
||||
scrollbar: hasBottomAction ? 'z-20' : undefined,
|
||||
}}
|
||||
>
|
||||
<AgentConfigApiContextProvider value={configApiContext}>
|
||||
<AgentOrchestrateAddActionsProvider>
|
||||
<AgentBuildDraftChangedKeysProvider
|
||||
changedKeys={
|
||||
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
|
||||
}
|
||||
>
|
||||
<AgentModelField
|
||||
currentModel={currentModel}
|
||||
textGenerationModelList={textGenerationModelList}
|
||||
onSelect={onSelectModel}
|
||||
/>
|
||||
<AgentPromptEditor />
|
||||
<AgentSkills />
|
||||
<AgentFiles />
|
||||
<AgentTools />
|
||||
<AgentKnowledgeRetrieval />
|
||||
<AgentAdvancedSettings />
|
||||
</AgentBuildDraftChangedKeysProvider>
|
||||
</AgentOrchestrateAddActionsProvider>
|
||||
</AgentConfigApiContextProvider>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
<AgentOrchestrateViewingVersionContext value={!!selectedVersionSnapshot}>
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<div aria-readonly={readOnly} className="flex min-h-0 flex-1 flex-col">
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1 overflow-hidden"
|
||||
label={showHeader ? undefined : orchestrateLabel}
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20'),
|
||||
scrollbar: hasBottomAction ? 'z-20' : undefined,
|
||||
}}
|
||||
>
|
||||
<AgentConfigApiContextProvider value={configApiContext}>
|
||||
<AgentOrchestrateAddActionsProvider>
|
||||
<AgentBuildDraftChangedKeysProvider
|
||||
changedKeys={
|
||||
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
|
||||
}
|
||||
>
|
||||
<AgentModelField
|
||||
currentModel={currentModel}
|
||||
textGenerationModelList={textGenerationModelList}
|
||||
onSelect={onSelectModel}
|
||||
/>
|
||||
<AgentPromptEditor />
|
||||
<AgentSkills />
|
||||
<AgentFiles />
|
||||
<AgentTools />
|
||||
<AgentKnowledgeRetrieval />
|
||||
<AgentAdvancedSettings />
|
||||
</AgentBuildDraftChangedKeysProvider>
|
||||
</AgentOrchestrateAddActionsProvider>
|
||||
</AgentConfigApiContextProvider>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
</AgentOrchestrateViewingVersionContext>
|
||||
|
||||
{orchestrateBottomAction ? (
|
||||
<AgentOrchestrateBottomActions shrinkOnOpen={!bottomAction}>
|
||||
|
||||
+23
-6
@@ -10,7 +10,10 @@ import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provid
|
||||
import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
|
||||
import { RerankingModeEnum } from '@/models/datasets'
|
||||
import { renderWithConsoleQuery as render } from '@/test/console/query-data'
|
||||
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context'
|
||||
import {
|
||||
AgentOrchestrateReadOnlyContext,
|
||||
AgentOrchestrateViewingVersionContext,
|
||||
} from '../../read-only-context'
|
||||
import { AgentKnowledgeRetrieval } from '../index'
|
||||
|
||||
vi.mock('@/context/workspace-state', async () => {
|
||||
@@ -107,17 +110,21 @@ function ConfigSnapshotPreview() {
|
||||
function renderKnowledgeRetrieval({
|
||||
initialDraft = agentKnowledgeDraft,
|
||||
readOnly = false,
|
||||
viewingVersion = false,
|
||||
showConfigSnapshot = false,
|
||||
}: {
|
||||
initialDraft?: AgentSoulConfigFormState
|
||||
readOnly?: boolean
|
||||
viewingVersion?: boolean
|
||||
showConfigSnapshot?: boolean
|
||||
} = {}) {
|
||||
return render(
|
||||
<AgentComposerProvider initialDraft={initialDraft}>
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<AgentKnowledgeRetrieval />
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
<AgentOrchestrateViewingVersionContext value={viewingVersion}>
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<AgentKnowledgeRetrieval />
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
</AgentOrchestrateViewingVersionContext>
|
||||
{showConfigSnapshot && <ConfigSnapshotPreview />}
|
||||
</AgentComposerProvider>,
|
||||
)
|
||||
@@ -154,8 +161,8 @@ describe('AgentKnowledgeRetrieval', () => {
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide add, edit, and remove actions when readonly', () => {
|
||||
renderKnowledgeRetrieval({ readOnly: true })
|
||||
it('should hide add, edit, and remove actions when viewing a version', () => {
|
||||
renderKnowledgeRetrieval({ readOnly: true, viewingVersion: true })
|
||||
|
||||
expect(
|
||||
screen.getByText('agentV2.agentDetail.configure.knowledgeRetrieval.retrievalOne'),
|
||||
@@ -176,6 +183,16 @@ describe('AgentKnowledgeRetrieval', () => {
|
||||
}),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should keep add action available for build drafts', () => {
|
||||
renderKnowledgeRetrieval({ readOnly: true })
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.knowledgeRetrieval.add',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
|
||||
+5
@@ -1,7 +1,12 @@
|
||||
import { createContext, use } from 'react'
|
||||
|
||||
export const AgentOrchestrateReadOnlyContext = createContext(false)
|
||||
export const AgentOrchestrateViewingVersionContext = createContext(false)
|
||||
|
||||
export function useAgentOrchestrateReadOnly() {
|
||||
return use(AgentOrchestrateReadOnlyContext)
|
||||
}
|
||||
|
||||
export function useAgentOrchestrateViewingVersion() {
|
||||
return use(AgentOrchestrateViewingVersionContext)
|
||||
}
|
||||
|
||||
+564
-20
@@ -1,3 +1,4 @@
|
||||
import type { SkillResponse } from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { AgentConfigApiContext } from '../../config-context'
|
||||
import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
@@ -11,7 +12,10 @@ import { defaultAgentSoulConfigFormState } from '@/features/agent-v2/agent-compo
|
||||
import { AgentComposerProvider } from '@/features/agent-v2/agent-composer/provider'
|
||||
import { agentComposerDraftAtom } from '@/features/agent-v2/agent-composer/store'
|
||||
import { AgentConfigApiContextProvider } from '../../config-context'
|
||||
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context'
|
||||
import {
|
||||
AgentOrchestrateReadOnlyContext,
|
||||
AgentOrchestrateViewingVersionContext,
|
||||
} from '../../read-only-context'
|
||||
import { AgentSkills } from '../index'
|
||||
|
||||
type ConfigSkillInspectQueryOptionsInput = {
|
||||
@@ -39,10 +43,16 @@ type ConfigSkillDownloadQueryOptionsInput = {
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
agentSkillBindingsKey: vi.fn((_options: unknown): unknown[] => ['workspace-agent-skills']),
|
||||
agentSkillBindingsQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
deleteSkillMutationFn: vi.fn(async (_input: unknown) => ({
|
||||
removed_names: ['Tender Analyzer'],
|
||||
result: 'success',
|
||||
})),
|
||||
replaceAgentSkillBindingsMutationFn: vi.fn(async (input: { body: { skill_ids?: string[] } }) => ({
|
||||
agent_id: 'agent-1',
|
||||
skill_ids: input.body.skill_ids ?? [],
|
||||
})),
|
||||
uploadSkillMutationFn: vi.fn(async (_input: unknown) => ({
|
||||
config_version: { id: 'draft-1', kind: 'draft', writable: true },
|
||||
skill: {
|
||||
@@ -59,6 +69,8 @@ const mocks = vi.hoisted(() => ({
|
||||
inspectQueryOptions: vi.fn((_options: ConfigSkillInspectQueryOptionsInput) => ({})),
|
||||
previewQueryOptions: vi.fn((_options: ConfigSkillFileQueryOptionsInput) => ({})),
|
||||
downloadQueryOptions: vi.fn((_options: ConfigSkillFileQueryOptionsInput) => ({})),
|
||||
workspaceSkillsQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
workspaceSkillsInfiniteOptions: vi.fn((_options: unknown) => ({})),
|
||||
downloadBlob: vi.fn(),
|
||||
downloadUrl: vi.fn(),
|
||||
fetch: vi.fn(),
|
||||
@@ -165,9 +177,43 @@ vi.mock('@/service/client', () => ({
|
||||
},
|
||||
},
|
||||
},
|
||||
workspaces: {
|
||||
current: {
|
||||
agents: {
|
||||
byAgentId: {
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.agentSkillBindingsKey,
|
||||
queryOptions: mocks.agentSkillBindingsQueryOptions,
|
||||
},
|
||||
put: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.replaceAgentSkillBindingsMutationFn }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skills: {
|
||||
get: {
|
||||
queryOptions: mocks.workspaceSkillsQueryOptions,
|
||||
infiniteOptions: mocks.workspaceSkillsInfiniteOptions,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
async function openUploadSkillDialog(user: ReturnType<typeof userEvent.setup>) {
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.upload\.label/i,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
function ConfigSnapshotProbe() {
|
||||
const draft = useAtomValue(agentComposerDraftAtom)
|
||||
const configSnapshot = formStateToAgentSoulConfig({ formState: draft })
|
||||
@@ -175,6 +221,23 @@ function ConfigSnapshotProbe() {
|
||||
return <pre aria-label="config snapshot">{JSON.stringify(configSnapshot)}</pre>
|
||||
}
|
||||
|
||||
function createWorkspaceSkill(overrides: Partial<SkillResponse> = {}): SkillResponse {
|
||||
return {
|
||||
id: 'workspace-skill-1',
|
||||
name: 'refund-approval',
|
||||
display_name: 'Refund approval',
|
||||
description: 'Handle refund requests.',
|
||||
icon: '💳',
|
||||
latest_published_version_id: 'version-1',
|
||||
reference_count: 0,
|
||||
tags: [],
|
||||
visibility: 'workspace',
|
||||
created_at: 1,
|
||||
updated_at: 1,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function renderAgentSkills({
|
||||
initialDraft = {
|
||||
...defaultAgentSoulConfigFormState,
|
||||
@@ -189,10 +252,12 @@ function renderAgentSkills({
|
||||
},
|
||||
apiContext = { agentId: 'agent-1', draftType: 'draft' } satisfies AgentConfigApiContext,
|
||||
readOnly = false,
|
||||
viewingVersion = false,
|
||||
}: {
|
||||
initialDraft?: AgentSoulConfigFormState
|
||||
apiContext?: AgentConfigApiContext
|
||||
readOnly?: boolean
|
||||
viewingVersion?: boolean
|
||||
} = {}) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -205,10 +270,12 @@ function renderAgentSkills({
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AgentConfigApiContextProvider value={apiContext}>
|
||||
<AgentComposerProvider initialDraft={initialDraft}>
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<AgentSkills />
|
||||
<ConfigSnapshotProbe />
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
<AgentOrchestrateViewingVersionContext value={viewingVersion}>
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<AgentSkills />
|
||||
<ConfigSnapshotProbe />
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
</AgentOrchestrateViewingVersionContext>
|
||||
</AgentComposerProvider>
|
||||
</AgentConfigApiContextProvider>
|
||||
</QueryClientProvider>,
|
||||
@@ -225,6 +292,22 @@ describe('AgentSkills', () => {
|
||||
headers: { 'Content-Type': 'application/octet-stream' },
|
||||
}),
|
||||
)
|
||||
mocks.agentSkillBindingsKey.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
return ['workspace-agent-skills', input]
|
||||
})
|
||||
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: [],
|
||||
data: [],
|
||||
}),
|
||||
}
|
||||
})
|
||||
mocks.inspectQueryOptions.mockImplementation(({ input }) => ({
|
||||
queryKey: ['inspect-skill', input],
|
||||
queryFn: async () => ({
|
||||
@@ -293,6 +376,38 @@ describe('AgentSkills', () => {
|
||||
url: `https://example.com/${input.params.name}.skill`,
|
||||
}),
|
||||
}))
|
||||
mocks.workspaceSkillsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { query?: { keyword?: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input],
|
||||
queryFn: async () => ({
|
||||
data: [],
|
||||
}),
|
||||
}
|
||||
})
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
|
||||
initialPageParam: number
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [],
|
||||
has_more: false,
|
||||
limit: input(pageParam).query?.limit ?? 20,
|
||||
page: pageParam,
|
||||
total: 0,
|
||||
}),
|
||||
getNextPageParam,
|
||||
initialPageParam,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -372,9 +487,7 @@ describe('AgentSkills', () => {
|
||||
const user = userEvent.setup()
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await openUploadSkillDialog(user)
|
||||
|
||||
const input = await waitFor(() => {
|
||||
const element = document.querySelector('input[type="file"]')
|
||||
@@ -418,13 +531,375 @@ describe('AgentSkills', () => {
|
||||
expect(toast.success).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should hide skill package guidance before an upload fails', async () => {
|
||||
it('should bind workspace skills without adding them to inline config skills', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
|
||||
initialPageParam: number
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [
|
||||
{
|
||||
id: 'workspace-skill-1',
|
||||
name: 'refund-approval',
|
||||
display_name: 'Refund approval',
|
||||
description: 'Handle refund requests.',
|
||||
icon: '💳',
|
||||
latest_published_version_id: 'version-1',
|
||||
reference_count: 0,
|
||||
tags: [],
|
||||
visibility: 'workspace',
|
||||
created_at: 1,
|
||||
updated_at: 1,
|
||||
},
|
||||
],
|
||||
has_more: false,
|
||||
limit: 20,
|
||||
page: pageParam,
|
||||
total: 1,
|
||||
}),
|
||||
getNextPageParam,
|
||||
initialPageParam,
|
||||
}
|
||||
})
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByRole('button', { name: /Refund approval/ }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.replaceAgentSkillBindingsMutationFn.mock.calls[0]?.[0]).toEqual({
|
||||
params: {
|
||||
agent_id: 'agent-1',
|
||||
},
|
||||
body: {
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
const snapshot = JSON.parse(screen.getByLabelText('config snapshot').textContent ?? '{}')
|
||||
expect(snapshot.config_skills).toEqual([])
|
||||
})
|
||||
|
||||
it('should allow workflow agent nodes to bind workspace skills', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
|
||||
initialPageParam: number
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [
|
||||
{
|
||||
id: 'workspace-skill-1',
|
||||
name: 'refund-approval',
|
||||
display_name: 'Refund approval',
|
||||
description: 'Handle refund requests.',
|
||||
icon: '💳',
|
||||
latest_published_version_id: 'version-1',
|
||||
reference_count: 0,
|
||||
tags: [],
|
||||
visibility: 'workspace',
|
||||
created_at: 1,
|
||||
updated_at: 1,
|
||||
},
|
||||
],
|
||||
has_more: false,
|
||||
limit: 20,
|
||||
page: pageParam,
|
||||
total: 1,
|
||||
}),
|
||||
getNextPageParam,
|
||||
initialPageParam,
|
||||
}
|
||||
})
|
||||
renderAgentSkills({
|
||||
initialDraft: defaultAgentSoulConfigFormState,
|
||||
apiContext: {
|
||||
agentId: 'workflow-agent-1',
|
||||
draftType: 'draft',
|
||||
workflow: {
|
||||
appId: 'workflow-app-1',
|
||||
nodeId: 'agent-node-1',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
const workspaceMenuItem = screen.getByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
|
||||
})
|
||||
expect(workspaceMenuItem).not.toBeDisabled()
|
||||
|
||||
await user.click(workspaceMenuItem)
|
||||
await user.click(await screen.findByRole('button', { name: /Refund approval/ }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.replaceAgentSkillBindingsMutationFn.mock.calls[0]?.[0]).toEqual({
|
||||
params: {
|
||||
agent_id: 'workflow-agent-1',
|
||||
},
|
||||
body: {
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should mark already bound workspace skills as added and prevent duplicate binding', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
data: [
|
||||
{
|
||||
...createWorkspaceSkill(),
|
||||
priority: 0,
|
||||
status: 'published',
|
||||
file_count: 1,
|
||||
latest_published_at: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
})
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
|
||||
initialPageParam: number
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data: [
|
||||
createWorkspaceSkill(),
|
||||
createWorkspaceSkill({
|
||||
id: 'draft-skill',
|
||||
name: 'draft-skill',
|
||||
display_name: 'Draft skill',
|
||||
latest_published_version_id: null,
|
||||
}),
|
||||
],
|
||||
has_more: false,
|
||||
limit: 20,
|
||||
page: pageParam,
|
||||
total: 2,
|
||||
}),
|
||||
getNextPageParam,
|
||||
initialPageParam,
|
||||
}
|
||||
})
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(
|
||||
await screen.findByText('agentV2.agentDetail.configure.skills.workspaceSelector.added'),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('agentV2.agentDetail.configure.skills.workspaceSelector.draft'),
|
||||
).toBeInTheDocument()
|
||||
|
||||
const addedSkillButton = screen
|
||||
.getByText('agentV2.agentDetail.configure.skills.workspaceSelector.added')
|
||||
.closest('button')
|
||||
const draftSkillButton = screen
|
||||
.getByText('agentV2.agentDetail.configure.skills.workspaceSelector.draft')
|
||||
.closest('button')
|
||||
expect(addedSkillButton).toBeDisabled()
|
||||
expect(draftSkillButton).toBeDisabled()
|
||||
|
||||
expect(mocks.replaceAgentSkillBindingsMutationFn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should fetch the next workspace skill page when scrolling the selector', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.workspaceSkillsInfiniteOptions.mockImplementation((options) => {
|
||||
const { input, getNextPageParam, initialPageParam } = options as {
|
||||
input: (pageParam: number) => {
|
||||
query?: { keyword?: string; limit?: number; page?: number }
|
||||
}
|
||||
getNextPageParam: (lastPage: { has_more?: boolean; page?: number }) => number | undefined
|
||||
initialPageParam: number
|
||||
}
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-skills', input(initialPageParam)],
|
||||
queryFn: async ({ pageParam = initialPageParam }: { pageParam?: number }) => ({
|
||||
data:
|
||||
pageParam === 1
|
||||
? [createWorkspaceSkill()]
|
||||
: [
|
||||
createWorkspaceSkill({
|
||||
id: 'workspace-skill-2',
|
||||
name: 'sales-follow-up',
|
||||
display_name: 'Sales follow-up',
|
||||
}),
|
||||
],
|
||||
has_more: pageParam === 1,
|
||||
limit: 20,
|
||||
page: pageParam,
|
||||
total: 2,
|
||||
}),
|
||||
getNextPageParam,
|
||||
initialPageParam,
|
||||
}
|
||||
})
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await user.click(
|
||||
screen.getByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
|
||||
}),
|
||||
)
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('Refund approval').length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
const scrollContainer = document.querySelector('.overflow-y-auto')
|
||||
expect(scrollContainer).not.toBeNull()
|
||||
Object.defineProperties(scrollContainer!, {
|
||||
clientHeight: { configurable: true, value: 100 },
|
||||
scrollHeight: { configurable: true, value: 160 },
|
||||
scrollTop: { configurable: true, value: 80 },
|
||||
})
|
||||
fireEvent.scroll(scrollContainer!)
|
||||
|
||||
expect(await screen.findByText('Sales follow-up')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should remove workspace skill bindings from the configured agent', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
data: [
|
||||
{
|
||||
...createWorkspaceSkill(),
|
||||
priority: 0,
|
||||
status: 'published',
|
||||
file_count: 1,
|
||||
latest_published_at: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
})
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByText('agentV2.agentDetail.configure.skills.removeAction'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.replaceAgentSkillBindingsMutationFn.mock.calls[0]?.[0]).toEqual({
|
||||
params: {
|
||||
agent_id: 'agent-1',
|
||||
},
|
||||
body: {
|
||||
skill_ids: [],
|
||||
},
|
||||
})
|
||||
})
|
||||
expect(toast.success).toHaveBeenCalledWith(
|
||||
'agentV2.agentDetail.configure.skills.workspaceSelector.removeSuccess',
|
||||
)
|
||||
})
|
||||
|
||||
it('should open workspace skill details in a new tab from the row menu', async () => {
|
||||
const user = userEvent.setup()
|
||||
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
|
||||
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
data: [
|
||||
{
|
||||
...createWorkspaceSkill(),
|
||||
priority: 0,
|
||||
status: 'published',
|
||||
file_count: 1,
|
||||
latest_published_at: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
})
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByText('agentV2.agentDetail.configure.skills.openInLibrary'))
|
||||
|
||||
expect(openSpy).toHaveBeenCalledWith(
|
||||
'/skills/workspace-skill-1',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
)
|
||||
})
|
||||
|
||||
it('should hide skill package guidance before an upload fails', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await openUploadSkillDialog(user)
|
||||
|
||||
expect(
|
||||
screen.queryByText('agentV2.agentDetail.configure.skills.upload.warning.specification'),
|
||||
@@ -438,9 +913,7 @@ describe('AgentSkills', () => {
|
||||
.mockImplementationOnce(() => new Promise<never>(() => undefined))
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await openUploadSkillDialog(user)
|
||||
const input = await waitFor(() => {
|
||||
const element = document.querySelector('input[type="file"]')
|
||||
expect(element).not.toBeNull()
|
||||
@@ -477,9 +950,7 @@ describe('AgentSkills', () => {
|
||||
mocks.uploadSkillMutationFn.mockRejectedValueOnce(new Error('Backend upload error'))
|
||||
renderAgentSkills({ initialDraft: defaultAgentSoulConfigFormState })
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await openUploadSkillDialog(user)
|
||||
|
||||
const input = await waitFor(() => {
|
||||
const element = document.querySelector('input[type="file"]')
|
||||
@@ -515,9 +986,7 @@ describe('AgentSkills', () => {
|
||||
},
|
||||
})
|
||||
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
)
|
||||
await openUploadSkillDialog(user)
|
||||
const input = await waitFor(() => {
|
||||
const element = document.querySelector('input[type="file"]')
|
||||
expect(element).not.toBeNull()
|
||||
@@ -869,12 +1338,87 @@ describe('AgentSkills', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should disable add and remove actions when the section is read only', () => {
|
||||
const { container } = renderAgentSkills({ readOnly: true })
|
||||
it('should disable add and remove actions when viewing a version', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.agentSkillBindingsQueryOptions.mockImplementation((options) => {
|
||||
const { input } = options as { input: { params: { agent_id: string } } }
|
||||
|
||||
return {
|
||||
queryKey: ['workspace-agent-skills', input],
|
||||
queryFn: async () => ({
|
||||
agent_id: input.params.agent_id,
|
||||
skill_ids: ['workspace-skill-1'],
|
||||
data: [
|
||||
{
|
||||
...createWorkspaceSkill(),
|
||||
priority: 0,
|
||||
status: 'published',
|
||||
file_count: 1,
|
||||
latest_published_at: 1,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}
|
||||
})
|
||||
const { container } = renderAgentSkills({
|
||||
apiContext: {
|
||||
agentId: 'agent-1',
|
||||
draftType: 'draft',
|
||||
versionId: 'version-1',
|
||||
},
|
||||
readOnly: true,
|
||||
viewingVersion: true,
|
||||
})
|
||||
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /agentV2\.agentDetail\.configure\.skills\.add/i }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(container.querySelector('[data-agent-skill-remove-button]')).toBeNull()
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.skills.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(
|
||||
screen.getByText('agentV2.agentDetail.configure.skills.openInLibrary'),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('agentV2.agentDetail.configure.skills.removeAction'),
|
||||
).not.toBeInTheDocument()
|
||||
expect(mocks.replaceAgentSkillBindingsMutationFn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should keep the add menu available for build draft skills', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderAgentSkills({
|
||||
apiContext: {
|
||||
agentId: 'agent-1',
|
||||
draftType: 'debug_build',
|
||||
},
|
||||
initialDraft: {
|
||||
...defaultAgentSoulConfigFormState,
|
||||
skills: [],
|
||||
},
|
||||
readOnly: true,
|
||||
})
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.add/i,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(
|
||||
await screen.findByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.workspace\.label/i,
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: /agentV2\.agentDetail\.configure\.skills\.addMenu\.upload\.label/i,
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
+543
-16
@@ -1,30 +1,391 @@
|
||||
'use client'
|
||||
|
||||
import type {
|
||||
AgentSkillBindingItemResponse,
|
||||
SkillResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { UIEvent } from 'react'
|
||||
import type { AgentOrchestrateAddActionOptions } from '../add-actions-context'
|
||||
import type { AgentSkill } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@langgenius/dify-ui/dropdown-menu'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import {
|
||||
keepPreviousData,
|
||||
useInfiniteQuery,
|
||||
useMutation,
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
} from '@tanstack/react-query'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SearchInput } from '@/app/components/base/search-input'
|
||||
import { SkeletonRectangle } from '@/app/components/base/skeleton'
|
||||
import {
|
||||
agentComposerSkillsAtom,
|
||||
removeAgentSkillAtom,
|
||||
upsertAgentSkillAtom,
|
||||
} from '@/features/agent-v2/agent-composer/store-modules/skills'
|
||||
import Link from '@/next/link'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { useRegisterAgentOrchestrateAddAction } from '../add-actions-context'
|
||||
import { ConfigureSectionAddButton } from '../common/add-button'
|
||||
import { ConfigureSectionEmpty } from '../common/empty'
|
||||
import { ConfigureSection } from '../common/section'
|
||||
import { AgentConfigureTipContent } from '../common/tip-content'
|
||||
import { useAgentConfigApiContext } from '../config-context'
|
||||
import {
|
||||
useAgentOrchestrateReadOnly,
|
||||
useAgentOrchestrateViewingVersion,
|
||||
} from '../read-only-context'
|
||||
import { AgentSkillItem } from './item'
|
||||
import { AgentSkillUploadDialog } from './upload-dialog'
|
||||
|
||||
const WORKSPACE_SKILLS_PAGE_SIZE = 20
|
||||
|
||||
function AgentSkillAddMenuItem({
|
||||
badge,
|
||||
description,
|
||||
disabled,
|
||||
iconClassName,
|
||||
label,
|
||||
onClick,
|
||||
}: {
|
||||
badge?: string
|
||||
description: string
|
||||
disabled?: boolean
|
||||
iconClassName: string
|
||||
label: string
|
||||
onClick: () => void
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
className="flex w-full min-w-0 items-start gap-3 rounded-lg px-2 py-2 text-left outline-hidden hover:not-disabled:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn('mt-0.5 size-4 shrink-0 text-text-tertiary', iconClassName)}
|
||||
/>
|
||||
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
|
||||
<span className="flex min-w-0 items-center gap-2">
|
||||
<span className="truncate system-sm-medium text-text-secondary">{label}</span>
|
||||
{badge && (
|
||||
<span className="shrink-0 rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1.5 py-0.5 system-2xs-medium-uppercase text-text-tertiary">
|
||||
{badge}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="line-clamp-2 system-xs-regular text-text-tertiary">{description}</span>
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function WorkspaceSkillIcon({ icon }: { icon?: string }) {
|
||||
return (
|
||||
<span className="flex size-5 shrink-0 items-center justify-center rounded-md border-[0.5px] border-divider-subtle bg-background-default-dodge">
|
||||
{icon ? (
|
||||
<span className="text-[12px] leading-none">{icon}</span>
|
||||
) : (
|
||||
<span aria-hidden className="i-ri-box-3-line size-3.5 text-text-tertiary" />
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function WorkspaceSkillRow({
|
||||
disabled,
|
||||
isAdded,
|
||||
isPending,
|
||||
onSelect,
|
||||
onPreview,
|
||||
selected,
|
||||
skill,
|
||||
}: {
|
||||
disabled: boolean
|
||||
isAdded: boolean
|
||||
isPending: boolean
|
||||
onSelect: (skill: SkillResponse) => void
|
||||
onPreview: (skill: SkillResponse) => void
|
||||
selected: boolean
|
||||
skill: SkillResponse
|
||||
}) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled || isAdded || isPending}
|
||||
onClick={() => onSelect(skill)}
|
||||
onFocus={() => onPreview(skill)}
|
||||
onMouseEnter={() => onPreview(skill)}
|
||||
className={cn(
|
||||
'flex h-12 w-full min-w-0 items-center gap-2 rounded-lg px-2 text-left outline-hidden hover:not-disabled:bg-state-base-hover focus-visible:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid disabled:cursor-default disabled:opacity-60',
|
||||
selected && 'bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<WorkspaceSkillIcon icon={skill.icon} />
|
||||
<span className="flex w-0 min-w-0 flex-1 flex-col gap-0.5">
|
||||
<span className="truncate system-sm-medium text-text-secondary">{skill.display_name}</span>
|
||||
<span className="truncate system-xs-regular text-text-tertiary">{skill.name}</span>
|
||||
</span>
|
||||
{isAdded && (
|
||||
<span className="shrink-0 system-xs-medium text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.workspaceSelector.added'])}
|
||||
</span>
|
||||
)}
|
||||
{!isAdded && disabled && (
|
||||
<span className="shrink-0 system-xs-medium text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.workspaceSelector.draft'])}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function WorkspaceSkillPreview({ skill }: { skill?: SkillResponse }) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
|
||||
if (!skill) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center px-6 text-center system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.workspaceSelector.empty'])}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-4 p-4">
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<WorkspaceSkillIcon icon={skill.icon} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate system-md-semibold text-text-primary">{skill.display_name}</div>
|
||||
<div className="mt-0.5 truncate system-xs-regular text-text-tertiary">{skill.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
{!!skill.tags?.length && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{skill.tags.slice(0, 5).map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="rounded-[5px] border border-divider-subtle bg-components-badge-bg-dimm px-1.5 py-0.5 system-2xs-medium-uppercase text-text-tertiary"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<p className="line-clamp-6 system-sm-regular text-text-secondary">{skill.description}</p>
|
||||
{(skill.updated_by_name || skill.created_by_name) && (
|
||||
<div className="mt-auto system-xs-regular text-text-tertiary">
|
||||
{skill.updated_by_name || skill.created_by_name}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WorkspaceSkillSelector({
|
||||
boundSkillIds,
|
||||
isBindingPending,
|
||||
onSelect,
|
||||
}: {
|
||||
boundSkillIds: string[]
|
||||
isBindingPending: boolean
|
||||
onSelect: (skill: SkillResponse) => void
|
||||
}) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const [keyword, setKeyword] = useState('')
|
||||
const [previewSkillId, setPreviewSkillId] = useState<string | undefined>(undefined)
|
||||
const debouncedKeyword = useDebounce(keyword.trim(), { wait: 300 })
|
||||
const skillsQuery = useInfiniteQuery({
|
||||
...consoleQuery.workspaces.current.skills.get.infiniteOptions({
|
||||
input: (pageParam) => ({
|
||||
query: {
|
||||
limit: WORKSPACE_SKILLS_PAGE_SIZE,
|
||||
page: Number(pageParam),
|
||||
...(debouncedKeyword ? { keyword: debouncedKeyword } : {}),
|
||||
},
|
||||
}),
|
||||
getNextPageParam: (lastPage) => (lastPage.has_more ? (lastPage.page ?? 1) + 1 : undefined),
|
||||
initialPageParam: 1,
|
||||
placeholderData: keepPreviousData,
|
||||
}),
|
||||
})
|
||||
const boundSkillIdSet = useMemo(() => new Set(boundSkillIds), [boundSkillIds])
|
||||
const skills = skillsQuery.data?.pages.flatMap((page) => page.data ?? []) ?? []
|
||||
const previewSkill = skills.find((skill) => skill.id === previewSkillId) ?? skills[0]
|
||||
const hasNextPage = skillsQuery.hasNextPage ?? false
|
||||
const isFetchingNextPage = skillsQuery.isFetchingNextPage
|
||||
const fetchNextPage = skillsQuery.fetchNextPage
|
||||
|
||||
const handleListScroll = useCallback(
|
||||
(event: UIEvent<HTMLDivElement>) => {
|
||||
const target = event.currentTarget
|
||||
const scrollBottom = target.scrollHeight - target.scrollTop - target.clientHeight
|
||||
if (scrollBottom < 80 && hasNextPage && !isFetchingNextPage) void fetchNextPage()
|
||||
},
|
||||
[fetchNextPage, hasNextPage, isFetchingNextPage],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex h-[520px] w-[560px] overflow-hidden rounded-xl border border-divider-regular bg-components-panel-bg shadow-lg">
|
||||
<div className="flex min-w-0 flex-1 flex-col border-r border-divider-subtle">
|
||||
<div className="border-b border-divider-subtle p-3">
|
||||
<div className="relative">
|
||||
<SearchInput
|
||||
value={keyword}
|
||||
onValueChange={setKeyword}
|
||||
placeholder={t(($) => $['agentDetail.configure.skills.workspaceSelector.search'])}
|
||||
/>
|
||||
<span
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute top-1/2 right-8 i-ri-price-tag-3-line size-4 -translate-y-1/2 text-text-tertiary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-1.5" onScroll={handleListScroll}>
|
||||
{skillsQuery.isPending && (
|
||||
<div className="space-y-2 p-1">
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
</div>
|
||||
)}
|
||||
{!skillsQuery.isPending && skills.length === 0 && (
|
||||
<div className="flex h-full items-center justify-center px-4 text-center system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.workspaceSelector.empty'])}
|
||||
</div>
|
||||
)}
|
||||
{!skillsQuery.isPending &&
|
||||
skills.map((skill) => (
|
||||
<WorkspaceSkillRow
|
||||
key={skill.id}
|
||||
disabled={!skill.latest_published_version_id}
|
||||
isAdded={boundSkillIdSet.has(skill.id)}
|
||||
isPending={isBindingPending}
|
||||
selected={previewSkill?.id === skill.id}
|
||||
skill={skill}
|
||||
onPreview={(skill) => setPreviewSkillId(skill.id)}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
{skillsQuery.isFetchingNextPage && (
|
||||
<div className="space-y-2 p-1">
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
<SkeletonRectangle className="h-10 rounded-lg" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
href="/skills"
|
||||
className="flex h-10 items-center justify-between border-t border-divider-subtle px-3 system-sm-medium text-text-secondary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
<span>{t(($) => $['agentDetail.configure.skills.workspaceSelector.manage'])}</span>
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-4 text-text-tertiary" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="w-[240px] shrink-0 bg-background-default">
|
||||
<WorkspaceSkillPreview skill={previewSkill} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WorkspaceAgentSkillItem({
|
||||
canRemove,
|
||||
skill,
|
||||
onRemove,
|
||||
}: {
|
||||
canRemove: boolean
|
||||
skill: AgentSkillBindingItemResponse
|
||||
onRemove: (skillId: string) => void
|
||||
}) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const readOnly = useAgentOrchestrateReadOnly()
|
||||
const displayName = skill.display_name || skill.name
|
||||
const handleOpenInLibrary = useCallback(() => {
|
||||
window.open(`/skills/${skill.id}`, '_blank', 'noopener,noreferrer')
|
||||
}, [skill.id])
|
||||
|
||||
return (
|
||||
<div className="group relative h-8 overflow-hidden rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs shadow-shadow-shadow-3 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm">
|
||||
<Link
|
||||
href={`/skills/${skill.id}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="flex h-full w-full min-w-0 cursor-pointer items-center gap-1 rounded-lg py-1 pr-8 pl-2 text-left outline-hidden select-none focus-visible:inset-ring-2 focus-visible:inset-ring-state-accent-solid"
|
||||
>
|
||||
<WorkspaceSkillIcon icon={skill.icon} />
|
||||
<span className="flex w-0 min-w-0 flex-1 items-center gap-1">
|
||||
<span className="min-w-0 truncate system-sm-medium text-text-secondary">
|
||||
{displayName}
|
||||
</span>
|
||||
<span
|
||||
aria-hidden
|
||||
className="i-ri-arrow-right-up-line size-3.5 shrink-0 text-text-quaternary opacity-0 group-focus-within:opacity-100 group-hover:opacity-100"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
'shrink-0 system-xs-regular text-text-tertiary',
|
||||
!readOnly && 'group-focus-within:opacity-0 group-hover:opacity-0',
|
||||
)}
|
||||
>
|
||||
{skill.name}
|
||||
</span>
|
||||
</Link>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(($) => $['agentDetail.configure.skills.moreActions'], {
|
||||
name: displayName,
|
||||
})}
|
||||
className="absolute top-1/2 right-1 z-10 flex size-6 -translate-y-1/2 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:bg-state-base-hover data-popup-open:text-text-secondary"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<span aria-hidden className="i-ri-more-fill size-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent placement="bottom-end" sideOffset={4} popupClassName="w-48">
|
||||
<DropdownMenuItem className="gap-2" onClick={handleOpenInLibrary}>
|
||||
<span aria-hidden className="i-ri-arrow-right-up-line size-4 shrink-0" />
|
||||
<span>{t(($) => $['agentDetail.configure.skills.openInLibrary'])}</span>
|
||||
</DropdownMenuItem>
|
||||
{canRemove && (
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
className="gap-2"
|
||||
onClick={() => onRemove(skill.id)}
|
||||
>
|
||||
<span aria-hidden className="i-ri-delete-bin-line size-4 shrink-0" />
|
||||
<span>{t(($) => $['agentDetail.configure.skills.removeAction'])}</span>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function AgentSkills() {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const skillsTip = t(($) => $['agentDetail.configure.skills.tip'])
|
||||
const skillsListId = 'agent-configure-skills-list'
|
||||
const queryClient = useQueryClient()
|
||||
const isViewingVersion = useAgentOrchestrateViewingVersion()
|
||||
const [addMenuOpen, setAddMenuOpen] = useState(false)
|
||||
const [addMenuView, setAddMenuView] = useState<'menu' | 'workspace-selector'>('menu')
|
||||
const [isUploadOpen, setIsUploadOpen] = useState(false)
|
||||
const promptAddCallbackRef = useRef<AgentOrchestrateAddActionOptions['onAdded']>(undefined)
|
||||
const apiContext = useAgentConfigApiContext()
|
||||
@@ -37,6 +398,68 @@ export function AgentSkills() {
|
||||
const { mutate: deleteAppSkill } = useMutation(
|
||||
consoleQuery.apps.byAppId.agent.config.skills.byName.delete.mutationOptions(),
|
||||
)
|
||||
const agentSkillBindingsQueryOptions =
|
||||
consoleQuery.workspaces.current.agents.byAgentId.skills.get.queryOptions({
|
||||
input: {
|
||||
params: {
|
||||
agent_id: apiContext.agentId,
|
||||
},
|
||||
},
|
||||
})
|
||||
const agentSkillBindingsQuery = useQuery({
|
||||
...agentSkillBindingsQueryOptions,
|
||||
})
|
||||
const { isPending: isReplacingAgentSkillBindings, mutate: replaceAgentSkillBindings } =
|
||||
useMutation(consoleQuery.workspaces.current.agents.byAgentId.skills.put.mutationOptions())
|
||||
const workspaceSkills = agentSkillBindingsQuery.data?.data ?? []
|
||||
const boundSkillIds =
|
||||
agentSkillBindingsQuery.data?.skill_ids ?? workspaceSkills.map((skill) => skill.id)
|
||||
const hasSkills = skills.length > 0 || workspaceSkills.length > 0
|
||||
const invalidateAgentSkillBindings = useCallback(() => {
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: consoleQuery.workspaces.current.agents.byAgentId.skills.get.key({
|
||||
type: 'query',
|
||||
input: {
|
||||
params: {
|
||||
agent_id: apiContext.agentId,
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
}, [apiContext.agentId, queryClient])
|
||||
|
||||
const replaceWorkspaceSkillBindings = useCallback(
|
||||
(skillIds: string[], onSuccess?: () => void) => {
|
||||
if (isViewingVersion) return
|
||||
|
||||
replaceAgentSkillBindings(
|
||||
{
|
||||
params: {
|
||||
agent_id: apiContext.agentId,
|
||||
},
|
||||
body: {
|
||||
skill_ids: skillIds,
|
||||
},
|
||||
},
|
||||
{
|
||||
onError: () => {
|
||||
toast.error(t(($) => $['agentDetail.configure.skills.workspaceSelector.saveFailed']))
|
||||
},
|
||||
onSuccess: () => {
|
||||
invalidateAgentSkillBindings()
|
||||
onSuccess?.()
|
||||
},
|
||||
},
|
||||
)
|
||||
},
|
||||
[
|
||||
apiContext.agentId,
|
||||
invalidateAgentSkillBindings,
|
||||
isViewingVersion,
|
||||
replaceAgentSkillBindings,
|
||||
t,
|
||||
],
|
||||
)
|
||||
|
||||
const handleOpenUpload = useCallback((options?: AgentOrchestrateAddActionOptions) => {
|
||||
promptAddCallbackRef.current = options?.onAdded
|
||||
@@ -44,6 +467,20 @@ export function AgentSkills() {
|
||||
}, [])
|
||||
useRegisterAgentOrchestrateAddAction('skills', handleOpenUpload)
|
||||
|
||||
const handleAddMenuOpenChange = useCallback((open: boolean) => {
|
||||
setAddMenuOpen(open)
|
||||
if (!open) setAddMenuView('menu')
|
||||
}, [])
|
||||
|
||||
const handleOpenWorkspaceSelector = useCallback(() => {
|
||||
setAddMenuView('workspace-selector')
|
||||
}, [])
|
||||
|
||||
const handleOpenUploadFromMenu = useCallback(() => {
|
||||
setAddMenuOpen(false)
|
||||
handleOpenUpload()
|
||||
}, [handleOpenUpload])
|
||||
|
||||
const handleUploaded = useCallback(
|
||||
(skill: AgentSkill) => {
|
||||
upsertAgentSkill(skill)
|
||||
@@ -53,11 +490,36 @@ export function AgentSkills() {
|
||||
[upsertAgentSkill],
|
||||
)
|
||||
|
||||
const handleSelectWorkspaceSkill = useCallback(
|
||||
(skill: SkillResponse) => {
|
||||
if (!skill.latest_published_version_id || boundSkillIds.includes(skill.id)) return
|
||||
|
||||
replaceWorkspaceSkillBindings([...boundSkillIds, skill.id], () => {
|
||||
toast.success(t(($) => $['agentDetail.configure.skills.workspaceSelector.addSuccess']))
|
||||
setAddMenuOpen(false)
|
||||
setAddMenuView('menu')
|
||||
})
|
||||
},
|
||||
[boundSkillIds, replaceWorkspaceSkillBindings, t],
|
||||
)
|
||||
|
||||
const handleUploadOpenChange = useCallback((open: boolean) => {
|
||||
if (!open) promptAddCallbackRef.current = undefined
|
||||
setIsUploadOpen(open)
|
||||
}, [])
|
||||
|
||||
const handleRemoveWorkspaceSkill = useCallback(
|
||||
(skillId: string) => {
|
||||
replaceWorkspaceSkillBindings(
|
||||
boundSkillIds.filter((item) => item !== skillId),
|
||||
() => {
|
||||
toast.success(t(($) => $['agentDetail.configure.skills.workspaceSelector.removeSuccess']))
|
||||
},
|
||||
)
|
||||
},
|
||||
[boundSkillIds, replaceWorkspaceSkillBindings, t],
|
||||
)
|
||||
|
||||
const handleRemoveSkill = useCallback(
|
||||
(skillId: string) => {
|
||||
const skill = skills.find((item) => item.id === skillId)
|
||||
@@ -113,26 +575,91 @@ export function AgentSkills() {
|
||||
rootClassName="border-b border-divider-subtle pt-4"
|
||||
panelContentClassName="flex flex-col gap-1 pb-4"
|
||||
actions={
|
||||
<ConfigureSectionAddButton
|
||||
ariaLabel={t(($) => $['agentDetail.configure.skills.add'])}
|
||||
onClick={() => handleOpenUpload()}
|
||||
/>
|
||||
!isViewingVersion && (
|
||||
<Popover open={addMenuOpen} onOpenChange={handleAddMenuOpenChange}>
|
||||
<PopoverTrigger
|
||||
render={
|
||||
<Button
|
||||
aria-label={t(($) => $['agentDetail.configure.skills.add'])}
|
||||
variant="ghost"
|
||||
size="small"
|
||||
className="shrink-0 gap-1 px-2"
|
||||
>
|
||||
<span aria-hidden className="i-ri-add-line size-3.5" />
|
||||
<span>{tCommon(($) => $['operation.add'])}</span>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<PopoverContent
|
||||
placement="bottom-end"
|
||||
sideOffset={4}
|
||||
popupClassName={
|
||||
addMenuView === 'menu'
|
||||
? 'w-[320px] bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-[5px]'
|
||||
: 'w-[560px] overflow-hidden border-none bg-transparent p-0 shadow-none'
|
||||
}
|
||||
>
|
||||
{addMenuView === 'menu' ? (
|
||||
<>
|
||||
<AgentSkillAddMenuItem
|
||||
iconClassName="i-custom-public-agent-building-blocks"
|
||||
label={t(($) => $['agentDetail.configure.skills.addMenu.workspace.label'])}
|
||||
description={t(
|
||||
($) => $['agentDetail.configure.skills.addMenu.workspace.description'],
|
||||
)}
|
||||
onClick={handleOpenWorkspaceSelector}
|
||||
/>
|
||||
<AgentSkillAddMenuItem
|
||||
badge={t(($) => $['agentDetail.configure.skills.addMenu.upload.badge'])}
|
||||
iconClassName="i-ri-upload-cloud-2-line"
|
||||
label={t(($) => $['agentDetail.configure.skills.addMenu.upload.label'])}
|
||||
description={t(
|
||||
($) => $['agentDetail.configure.skills.addMenu.upload.description'],
|
||||
)}
|
||||
onClick={handleOpenUploadFromMenu}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<WorkspaceSkillSelector
|
||||
boundSkillIds={boundSkillIds}
|
||||
isBindingPending={isReplacingAgentSkillBindings}
|
||||
onSelect={handleSelectWorkspaceSkill}
|
||||
/>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
>
|
||||
{skills.length === 0 ? (
|
||||
{!hasSkills ? (
|
||||
<ConfigureSectionEmpty
|
||||
title={t(($) => $['agentDetail.configure.skills.empty.title'])}
|
||||
description={t(($) => $['agentDetail.configure.skills.empty.description'])}
|
||||
/>
|
||||
) : (
|
||||
skills.map((skill) => (
|
||||
<AgentSkillItem
|
||||
key={skill.id}
|
||||
apiContext={apiContext}
|
||||
skill={skill}
|
||||
onRemove={handleRemoveSkill}
|
||||
/>
|
||||
))
|
||||
<>
|
||||
{workspaceSkills.length > 0 && (
|
||||
<div className="px-1 pt-1 pb-0.5 system-xs-medium-uppercase text-text-tertiary">
|
||||
{t(($) => $['agentDetail.configure.skills.fromSkillLibrary'])}
|
||||
</div>
|
||||
)}
|
||||
{workspaceSkills.map((skill) => (
|
||||
<WorkspaceAgentSkillItem
|
||||
key={skill.id}
|
||||
canRemove={!isViewingVersion}
|
||||
skill={skill}
|
||||
onRemove={handleRemoveWorkspaceSkill}
|
||||
/>
|
||||
))}
|
||||
{skills.map((skill) => (
|
||||
<AgentSkillItem
|
||||
key={skill.id}
|
||||
apiContext={apiContext}
|
||||
skill={skill}
|
||||
onRemove={handleRemoveSkill}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</ConfigureSection>
|
||||
<AgentSkillUploadDialog
|
||||
|
||||
+28
-7
@@ -15,7 +15,10 @@ import {
|
||||
agentComposerSavedDraftAtom,
|
||||
isAgentComposerDirtyAtom,
|
||||
} from '@/features/agent-v2/agent-composer/store'
|
||||
import { AgentOrchestrateReadOnlyContext } from '../../read-only-context'
|
||||
import {
|
||||
AgentOrchestrateReadOnlyContext,
|
||||
AgentOrchestrateViewingVersionContext,
|
||||
} from '../../read-only-context'
|
||||
import { AgentTools } from '../index'
|
||||
|
||||
const toolProviderState = vi.hoisted(() => ({
|
||||
@@ -340,7 +343,13 @@ function renderAgentToolsWithStore(initialDraft: AgentSoulConfigFormState = agen
|
||||
}
|
||||
}
|
||||
|
||||
function renderReadonlyAgentTools(initialDraft: AgentSoulConfigFormState = agentToolsDraft) {
|
||||
function renderReadonlyAgentTools({
|
||||
initialDraft = agentToolsDraft,
|
||||
viewingVersion = false,
|
||||
}: {
|
||||
initialDraft?: AgentSoulConfigFormState
|
||||
viewingVersion?: boolean
|
||||
} = {}) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
@@ -352,9 +361,11 @@ function renderReadonlyAgentTools(initialDraft: AgentSoulConfigFormState = agent
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AgentComposerProvider initialDraft={initialDraft}>
|
||||
<AgentOrchestrateReadOnlyContext value>
|
||||
<AgentTools />
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
<AgentOrchestrateViewingVersionContext value={viewingVersion}>
|
||||
<AgentOrchestrateReadOnlyContext value>
|
||||
<AgentTools />
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
</AgentOrchestrateViewingVersionContext>
|
||||
</AgentComposerProvider>
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
@@ -441,9 +452,9 @@ describe('AgentTools', () => {
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide add, edit, and remove actions when readonly', async () => {
|
||||
it('should hide add, edit, and remove actions when viewing a version', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderReadonlyAgentTools()
|
||||
renderReadonlyAgentTools({ viewingVersion: true })
|
||||
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
@@ -484,6 +495,16 @@ describe('AgentTools', () => {
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should keep add action available for build drafts', () => {
|
||||
renderReadonlyAgentTools()
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: 'agentV2.agentDetail.configure.tools.add',
|
||||
}),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide CLI tool rows while CLI tools are disabled', () => {
|
||||
renderAgentTools()
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ import { ConfigureSectionAddButton } from '../common/add-button'
|
||||
import { ConfigureSectionEmpty } from '../common/empty'
|
||||
import { ConfigureSection } from '../common/section'
|
||||
import { AgentConfigureTipContent } from '../common/tip-content'
|
||||
import { useAgentOrchestrateReadOnly } from '../read-only-context'
|
||||
import { useAgentOrchestrateViewingVersion } from '../read-only-context'
|
||||
import { CliToolDialog } from './cli-tool/dialog'
|
||||
import { AgentCliToolItem } from './cli-tool/item'
|
||||
import {
|
||||
@@ -400,7 +400,7 @@ function AddToolMenu({
|
||||
|
||||
export function AgentTools() {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const readOnly = useAgentOrchestrateReadOnly()
|
||||
const isViewingVersion = useAgentOrchestrateViewingVersion()
|
||||
const setProviderToolCredential = useSetAtom(setProviderToolCredentialAtom)
|
||||
const providerById = useAgentToolProviderMap()
|
||||
const tools = useAtomValue(agentComposerToolsAtom)
|
||||
@@ -533,7 +533,7 @@ export function AgentTools() {
|
||||
rootClassName="border-b border-divider-subtle pt-4"
|
||||
panelContentClassName="flex flex-col gap-1 pb-4"
|
||||
actions={
|
||||
!readOnly ? (
|
||||
!isViewingVersion ? (
|
||||
<AddToolMenu
|
||||
onAddCliTool={openCliToolDialog}
|
||||
onAddTools={addTools}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import type { AgentAppPartial, AgentIconType } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -224,7 +225,12 @@ function AgentRosterItem({ agent }: { agent: AgentAppPartial }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute top-2 right-2 z-20 flex items-center overflow-hidden rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 opacity-0 shadow-lg backdrop-blur-xs transition-opacity group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 has-data-popup-open:pointer-events-auto has-data-popup-open:opacity-100">
|
||||
<div
|
||||
className={cn(
|
||||
'pointer-events-none absolute right-2 z-20 flex items-center overflow-hidden rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 opacity-0 shadow-lg backdrop-blur-xs transition-opacity group-focus-within:pointer-events-auto group-focus-within:opacity-100 group-hover:pointer-events-auto group-hover:opacity-100 has-data-popup-open:pointer-events-auto has-data-popup-open:opacity-100',
|
||||
isDraft ? 'top-7' : 'top-2',
|
||||
)}
|
||||
>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger
|
||||
aria-label={t(($) => $['roster.moreActions'], { name: agent.name })}
|
||||
|
||||
@@ -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
@@ -0,0 +1,487 @@
|
||||
import type {
|
||||
SkillResponse,
|
||||
SkillTagResponse,
|
||||
} from '@dify/contracts/api/console/workspaces/types.gen'
|
||||
import type { ReactNode } from 'react'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import SkillsPage from '../page'
|
||||
|
||||
type SkillsInfiniteOptions = {
|
||||
getNextPageParam: (lastPage: { has_more: boolean; page: number }) => number | undefined
|
||||
initialPageParam: number
|
||||
input: (pageParam: unknown) => {
|
||||
query: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
createSkillMutationFn: vi.fn(),
|
||||
deleteSkillMutationFn: vi.fn(),
|
||||
downloadBlob: vi.fn(),
|
||||
duplicateSkillMutationFn: vi.fn(),
|
||||
exportSkillArchiveBlob: vi.fn(),
|
||||
importSkillMutationFn: vi.fn(),
|
||||
push: vi.fn(),
|
||||
queryState: {
|
||||
keyword: '',
|
||||
tag: [] as string[],
|
||||
},
|
||||
skills: [] as SkillResponse[],
|
||||
skillPages: [] as SkillResponse[][],
|
||||
skillsKey: vi.fn((_options: unknown): unknown[] => ['skills']),
|
||||
skillsQueryOptions: vi.fn((_options: SkillsInfiniteOptions) => ({})),
|
||||
tags: [] as SkillTagResponse[],
|
||||
tagsKey: vi.fn((_options: unknown): unknown[] => ['skill-tags']),
|
||||
tagsQueryOptions: vi.fn((_options: unknown) => ({})),
|
||||
}))
|
||||
|
||||
vi.mock('@langgenius/dify-ui/toast', () => ({
|
||||
toast: {
|
||||
error: vi.fn(),
|
||||
success: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('ahooks', () => ({
|
||||
useDebounce: (value: unknown) => value,
|
||||
}))
|
||||
|
||||
vi.mock('nuqs', async () => {
|
||||
const React = await import('react')
|
||||
const listeners = new Map<'keyword' | 'tag', Set<() => void>>()
|
||||
const createParser = () => ({
|
||||
withDefault: () => ({
|
||||
withOptions: () => ({}),
|
||||
}),
|
||||
})
|
||||
|
||||
return {
|
||||
debounce: () => undefined,
|
||||
parseAsArrayOf: () => ({
|
||||
withDefault: () => ({}),
|
||||
}),
|
||||
parseAsString: createParser(),
|
||||
useQueryState: (name: 'keyword' | 'tag') => {
|
||||
const [value, setValue] = React.useState(mocks.queryState[name])
|
||||
React.useEffect(() => {
|
||||
const nameListeners = listeners.get(name) ?? new Set<() => void>()
|
||||
listeners.set(name, nameListeners)
|
||||
const listener = () => setValue(mocks.queryState[name])
|
||||
nameListeners.add(listener)
|
||||
|
||||
return () => {
|
||||
nameListeners.delete(listener)
|
||||
}
|
||||
}, [name])
|
||||
const setQueryValue = (nextValue: string | string[]) => {
|
||||
mocks.queryState[name] = nextValue as never
|
||||
setValue(nextValue as never)
|
||||
listeners.get(name)?.forEach((listener) => listener())
|
||||
return Promise.resolve(new URLSearchParams())
|
||||
}
|
||||
|
||||
return [value, setQueryValue] as const
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/hooks/use-document-title', () => ({
|
||||
default: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/hooks/use-timestamp', () => ({
|
||||
default: () => ({
|
||||
formatTime: () => '2026-07-22 10:00',
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/next/link', () => ({
|
||||
default: ({ children, href, ...props }: { children: ReactNode; href: string }) => (
|
||||
<a href={href} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/next/navigation', () => ({
|
||||
useRouter: () => ({
|
||||
push: mocks.push,
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/download', () => ({
|
||||
downloadBlob: mocks.downloadBlob,
|
||||
}))
|
||||
|
||||
vi.mock('../client', () => ({
|
||||
fetchSkillArchiveBlob: mocks.exportSkillArchiveBlob,
|
||||
uploadSkillFile: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/client', () => ({
|
||||
consoleQuery: {
|
||||
workspaces: {
|
||||
current: {
|
||||
skills: {
|
||||
get: {
|
||||
key: mocks.skillsKey,
|
||||
infiniteOptions: mocks.skillsQueryOptions,
|
||||
},
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.createSkillMutationFn }),
|
||||
},
|
||||
import: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.importSkillMutationFn }),
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
get: {
|
||||
key: mocks.tagsKey,
|
||||
queryOptions: mocks.tagsQueryOptions,
|
||||
},
|
||||
},
|
||||
bySkillId: {
|
||||
delete: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.deleteSkillMutationFn }),
|
||||
},
|
||||
duplicate: {
|
||||
post: {
|
||||
mutationOptions: () => ({ mutationFn: mocks.duplicateSkillMutationFn }),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
function createSkill(overrides: Partial<SkillResponse> = {}): SkillResponse {
|
||||
return {
|
||||
id: 'skill-1',
|
||||
name: 'refund-approval',
|
||||
display_name: 'Refund approval',
|
||||
icon: '💳',
|
||||
description: 'Handle refund requests.',
|
||||
tags: ['support'],
|
||||
visibility: 'workspace',
|
||||
latest_published_version_id: 'version-1',
|
||||
reference_count: 2,
|
||||
created_at: 1784631405,
|
||||
updated_at: 1784638487,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function renderSkillsPage() {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: { retry: false },
|
||||
queries: { retry: false },
|
||||
},
|
||||
})
|
||||
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SkillsPage />
|
||||
</QueryClientProvider>,
|
||||
)
|
||||
}
|
||||
|
||||
describe('SkillsPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mocks.queryState.keyword = ''
|
||||
mocks.queryState.tag = []
|
||||
mocks.skills = [createSkill()]
|
||||
mocks.skillPages = [mocks.skills]
|
||||
mocks.tags = [
|
||||
{ count: 2, tag: 'support' },
|
||||
{ count: 1, tag: 'sales' },
|
||||
]
|
||||
mocks.skillsKey.mockImplementation((options) => ['skills', options])
|
||||
mocks.tagsKey.mockImplementation((options) => ['skill-tags', options])
|
||||
mocks.skillsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skills', options],
|
||||
queryFn: async ({ pageParam }: { pageParam: unknown }) => {
|
||||
const page = Number(pageParam)
|
||||
return {
|
||||
data: mocks.skillPages[page - 1] ?? [],
|
||||
has_more: page < mocks.skillPages.length,
|
||||
page,
|
||||
total: mocks.skillPages.flat().length,
|
||||
}
|
||||
},
|
||||
getNextPageParam: options.getNextPageParam,
|
||||
initialPageParam: options.initialPageParam,
|
||||
}))
|
||||
mocks.tagsQueryOptions.mockImplementation((options) => ({
|
||||
queryKey: ['skill-tags', options],
|
||||
queryFn: async () => ({
|
||||
data: mocks.tags,
|
||||
}),
|
||||
}))
|
||||
mocks.createSkillMutationFn.mockResolvedValue(createSkill({ id: 'created-skill' }))
|
||||
mocks.importSkillMutationFn.mockResolvedValue(createSkill({ id: 'imported-skill' }))
|
||||
mocks.duplicateSkillMutationFn.mockResolvedValue(createSkill({ id: 'duplicated-skill' }))
|
||||
mocks.exportSkillArchiveBlob.mockResolvedValue(new Blob(['skill archive']))
|
||||
mocks.deleteSkillMutationFn.mockResolvedValue({
|
||||
deleted: true,
|
||||
id: 'skill-1',
|
||||
})
|
||||
})
|
||||
|
||||
it('renders skills with tags, reference count, and detail links', async () => {
|
||||
renderSkillsPage()
|
||||
|
||||
const skillLink = await screen.findByRole('link', { name: /Refund approval/ })
|
||||
expect(skillLink).toHaveAttribute('href', '/skills/skill-1')
|
||||
expect(screen.getByText('refund-approval')).toBeInTheDocument()
|
||||
expect(screen.getByText('Handle refund requests.')).toBeInTheDocument()
|
||||
expect(screen.getByText('support')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByText('agentV2.skillManagement.referenceCount:{"count":2}'),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('passes keyword and selected tags to the list query', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderSkillsPage()
|
||||
|
||||
await user.type(
|
||||
await screen.findByRole('searchbox', {
|
||||
name: 'agentV2.skillManagement.searchLabel',
|
||||
}),
|
||||
'refund',
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
const queryOptions = mocks.skillsQueryOptions.mock.lastCall?.[0]
|
||||
expect(queryOptions?.input(1)).toEqual({
|
||||
query: {
|
||||
keyword: 'refund',
|
||||
limit: 20,
|
||||
page: 1,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'agentV2.skillManagement.tags' }))
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('support').length).toBeGreaterThan(1)
|
||||
})
|
||||
await user.click(screen.getAllByText('support').at(-1)!)
|
||||
|
||||
await waitFor(() => {
|
||||
const queryOptions = mocks.skillsQueryOptions.mock.lastCall?.[0]
|
||||
expect(queryOptions?.input(1)).toEqual({
|
||||
query: {
|
||||
keyword: 'refund',
|
||||
limit: 20,
|
||||
page: 1,
|
||||
tag: ['support'],
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('loads the next skill page when the list scrolls near the bottom', async () => {
|
||||
const firstPageSkills = Array.from({ length: 20 }, (_, index) =>
|
||||
createSkill({
|
||||
id: `skill-${index + 1}`,
|
||||
name: `skill-${index + 1}`,
|
||||
display_name: `Skill ${index + 1}`,
|
||||
}),
|
||||
)
|
||||
const nextPageSkill = createSkill({
|
||||
id: 'skill-21',
|
||||
name: 'skill-21',
|
||||
display_name: 'Skill 21',
|
||||
})
|
||||
mocks.skills = firstPageSkills
|
||||
mocks.skillPages = [firstPageSkills, [nextPageSkill]]
|
||||
|
||||
renderSkillsPage()
|
||||
|
||||
const skillList = await screen.findByRole('region', {
|
||||
name: 'agentV2.skillManagement.listLabel',
|
||||
})
|
||||
await screen.findByRole('heading', { name: 'Skill 1' })
|
||||
expect(within(skillList).getAllByRole('article')).toHaveLength(20)
|
||||
|
||||
const scrollViewport = skillList.parentElement?.parentElement
|
||||
expect(scrollViewport).not.toBeNull()
|
||||
Object.defineProperties(scrollViewport!, {
|
||||
clientHeight: { configurable: true, value: 600 },
|
||||
scrollHeight: { configurable: true, value: 1200 },
|
||||
scrollTop: { configurable: true, value: 560 },
|
||||
})
|
||||
fireEvent.scroll(scrollViewport!)
|
||||
|
||||
expect(await screen.findByRole('heading', { name: 'Skill 21' })).toBeInTheDocument()
|
||||
expect(within(skillList).getAllByRole('article')).toHaveLength(21)
|
||||
expect(mocks.skillsQueryOptions.mock.lastCall?.[0].input(2)).toEqual({
|
||||
query: {
|
||||
limit: 20,
|
||||
page: 2,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('creates a placeholder skill and navigates to its detail page', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderSkillsPage()
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: 'agentV2.skillManagement.create' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.createSkillMutationFn).toHaveBeenCalledWith(
|
||||
{
|
||||
body: {},
|
||||
},
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.createSuccess')
|
||||
expect(mocks.push).toHaveBeenCalledWith('/skills/created-skill')
|
||||
})
|
||||
|
||||
it('imports a package file and navigates to the imported skill', async () => {
|
||||
const user = userEvent.setup()
|
||||
const { container } = renderSkillsPage()
|
||||
|
||||
const fileInput = container.querySelector<HTMLInputElement>('input[type="file"]')
|
||||
expect(fileInput).not.toBeNull()
|
||||
const file = new File(['skill'], 'refund.skill', { type: 'application/zip' })
|
||||
|
||||
await user.upload(fileInput!, file)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.importSkillMutationFn).toHaveBeenCalledWith(
|
||||
{
|
||||
body: {
|
||||
file,
|
||||
},
|
||||
},
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.importSuccess')
|
||||
expect(mocks.push).toHaveBeenCalledWith('/skills/imported-skill')
|
||||
})
|
||||
|
||||
it('duplicates a skill from the card action menu', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderSkillsPage()
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByText('common.operation.duplicate'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.duplicateSkillMutationFn).toHaveBeenCalledWith(
|
||||
{
|
||||
params: {
|
||||
skill_id: 'skill-1',
|
||||
},
|
||||
},
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.duplicateSuccess')
|
||||
})
|
||||
|
||||
it('exports a published skill from the card action menu', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderSkillsPage()
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByText('common.operation.export'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.exportSkillArchiveBlob).toHaveBeenCalledWith('skill-1')
|
||||
})
|
||||
expect(mocks.downloadBlob).toHaveBeenCalledWith({
|
||||
data: expect.any(Blob),
|
||||
fileName: 'refund-approval.zip',
|
||||
})
|
||||
})
|
||||
|
||||
it('does not show export for an unpublished skill', async () => {
|
||||
const user = userEvent.setup()
|
||||
mocks.skills = [createSkill({ latest_published_version_id: null })]
|
||||
renderSkillsPage()
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
|
||||
expect(screen.queryByText('common.operation.export')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('confirms deletion with the skill name and refreshes list data', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderSkillsPage()
|
||||
|
||||
await user.click(
|
||||
await screen.findByRole('button', {
|
||||
name: 'agentV2.skillManagement.moreActions:{"name":"Refund approval"}',
|
||||
}),
|
||||
)
|
||||
await user.click(await screen.findByText('common.operation.delete'))
|
||||
const dialog = await screen.findByRole('alertdialog')
|
||||
|
||||
expect(
|
||||
within(dialog).getByText(
|
||||
'agentV2.skillManagement.deleteDialog.referencedDescription:{"count":2}',
|
||||
),
|
||||
).toBeInTheDocument()
|
||||
|
||||
await user.click(within(dialog).getByRole('button', { name: 'common.operation.delete' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mocks.deleteSkillMutationFn).toHaveBeenCalledWith(
|
||||
{
|
||||
body: {
|
||||
confirmation_name: 'refund-approval',
|
||||
},
|
||||
params: {
|
||||
skill_id: 'skill-1',
|
||||
},
|
||||
},
|
||||
expect.anything(),
|
||||
)
|
||||
})
|
||||
expect(toast.success).toHaveBeenCalledWith('agentV2.skillManagement.deleteSuccess')
|
||||
})
|
||||
|
||||
it('shows the empty-search state without create or import actions', async () => {
|
||||
mocks.queryState.keyword = 'missing'
|
||||
mocks.skills = []
|
||||
mocks.skillPages = [[]]
|
||||
|
||||
renderSkillsPage()
|
||||
|
||||
expect(await screen.findByText('agentV2.skillManagement.emptySearch')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('agentV2.skillManagement.emptyAction.createTitle'),
|
||||
).not.toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('agentV2.skillManagement.emptyAction.importTitle'),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -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
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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([])
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "يشغل Preview الوكيل المكتمل كما سيراه المستخدمون، مع ردود واضحة وميزات الدردشة.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "عاين وكيلك",
|
||||
"agentDetail.configure.skills.add": "إضافة مهارة",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "محتوى تفاصيل المهارة",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ملفات",
|
||||
"agentDetail.configure.skills.detail.files": "الملفات",
|
||||
"agentDetail.configure.skills.empty.description": "تمنح المهارات الوكيل خبرة قابلة لإعادة الاستخدام يمكنه استدعاؤها أثناء العمل",
|
||||
"agentDetail.configure.skills.empty.title": "لا توجد مهارات بعد",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "مهارة",
|
||||
"agentDetail.configure.skills.label": "المهارات",
|
||||
"agentDetail.configure.skills.missing": "المهارة غير موجودة",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "إزالة {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "اجمع التعليمات والملفات والبرامج النصية لمهمة متكررة في Skill. أشر إليها باستخدام / في Prompt. <docLink>معرفة المزيد</docLink>\n\nفي وضع Build، يمكن للوكيل إعدادها لك.",
|
||||
"agentDetail.configure.skills.tip": "اجمع التعليمات والملفات والبرامج النصية لمهمة متكررة في Skill. أشر إليها باستخدام / في Prompt. معرفة المزيد\n\nفي وضع Build، يمكن للوكيل إعدادها لك.",
|
||||
"agentDetail.configure.skills.toggle": "تبديل المهارات",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "خيارات الفرز",
|
||||
"roster.sort.recentlyCreated": "الأحدث إنشاءً",
|
||||
"roster.updateSuccess": "تم تحديث الوكيل.",
|
||||
"roster.usageStatus.draft": "مسودة"
|
||||
"roster.usageStatus.draft": "مسودة",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "طي الشريط الجانبي",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "توسيع الشريط الجانبي",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "لا توجد ملفات مطابقة.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "البحث عن الملفات",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
"mainNav.home": "الرئيسية",
|
||||
"mainNav.integrations": "التكاملات",
|
||||
"mainNav.marketplace": "سوق الإضافات",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "لم يتم العثور على تطبيقات ويب",
|
||||
"mainNav.webApps.openApp": "فتح تطبيق الويب {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "البحث في تطبيقات الويب",
|
||||
@@ -462,6 +463,7 @@
|
||||
"operation.downloading": "جارٍ التنزيل...",
|
||||
"operation.duplicate": "تكرار",
|
||||
"operation.edit": "تعديل",
|
||||
"operation.export": "تصدير",
|
||||
"operation.exporting": "جارٍ التصدير",
|
||||
"operation.fill": "ملء تلقائي",
|
||||
"operation.format": "تنسيق",
|
||||
@@ -503,6 +505,7 @@
|
||||
"operation.sure": "أنا متأكد",
|
||||
"operation.toggleFullscreen": "تبديل ملء الشاشة",
|
||||
"operation.toggleMute": "تبديل كتم الصوت",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "عرض",
|
||||
"operation.viewDetails": "عرض التفاصيل",
|
||||
"operation.viewMore": "عرض المزيد",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview führt den fertigen Agenten so aus, wie deine Benutzer ihn sehen, mit klaren Antworten und Chat-Funktionen.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Agenten vorschauen",
|
||||
"agentDetail.configure.skills.add": "Skill hinzufügen",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Skill-Detailinhalt",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} DATEIEN",
|
||||
"agentDetail.configure.skills.detail.files": "Dateien",
|
||||
"agentDetail.configure.skills.empty.description": "Skills geben dem Agenten wiederverwendbare Fachkenntnisse, die er bei der Arbeit nutzen kann",
|
||||
"agentDetail.configure.skills.empty.title": "Noch keine Skills",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Skill",
|
||||
"agentDetail.configure.skills.label": "Skills",
|
||||
"agentDetail.configure.skills.missing": "Skill nicht gefunden",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "{{name}} entfernen",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Bündeln Sie Anweisungen, Dateien und Skripte für eine wiederkehrende Aufgabe in einer Skill. Verweisen Sie im Prompt mit / darauf. <docLink>Mehr erfahren</docLink>\n\nIm Build-Modus kann der Agent diese Einrichtung für Sie übernehmen.",
|
||||
"agentDetail.configure.skills.tip": "Bündeln Sie Anweisungen, Dateien und Skripte für eine wiederkehrende Aufgabe in einer Skill. Verweisen Sie im Prompt mit / darauf. Mehr erfahren\n\nIm Build-Modus kann der Agent diese Einrichtung für Sie übernehmen.",
|
||||
"agentDetail.configure.skills.toggle": "Skills umschalten",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Sortieroptionen",
|
||||
"roster.sort.recentlyCreated": "Zuletzt erstellt",
|
||||
"roster.updateSuccess": "Agent aktualisiert.",
|
||||
"roster.usageStatus.draft": "Entwurf"
|
||||
"roster.usageStatus.draft": "Entwurf",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Seitenleiste einklappen",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Seitenleiste ausklappen",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Keine passenden Dateien.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Dateien suchen",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Startseite",
|
||||
"mainNav.integrations": "Integrationen",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Keine Web-Apps gefunden",
|
||||
"mainNav.webApps.openApp": "Web-App {{name}} öffnen",
|
||||
"mainNav.webApps.searchPlaceholder": "Web-Apps suchen",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Wird heruntergeladen...",
|
||||
"operation.duplicate": "Duplikat",
|
||||
"operation.edit": "Bearbeiten",
|
||||
"operation.export": "Exportieren",
|
||||
"operation.exporting": "Exportiere",
|
||||
"operation.fill": "Automatisch ausfüllen",
|
||||
"operation.format": "Format",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "Ich bin sicher",
|
||||
"operation.toggleFullscreen": "Vollbild umschalten",
|
||||
"operation.toggleMute": "Stummschaltung umschalten",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Ansehen",
|
||||
"operation.viewDetails": "Details anzeigen",
|
||||
"operation.viewMore": "MEHR SEHEN",
|
||||
|
||||
@@ -203,15 +203,24 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview runs the finished agent the way your users will see it, with clean replies and chat features.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Preview your agent",
|
||||
"agentDetail.configure.skills.add": "Add skill",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.description": "A .zip containing SKILL.md. It's embedded in this app and won't update with the library.",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.description": "Reuses a shared skill and follows its published updates.",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Skill detail content",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILES",
|
||||
"agentDetail.configure.skills.detail.files": "Files",
|
||||
"agentDetail.configure.skills.empty.description": "Skills give the agent reusable expertise it can call while working",
|
||||
"agentDetail.configure.skills.empty.title": "No skills yet",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Skill",
|
||||
"agentDetail.configure.skills.label": "Skills",
|
||||
"agentDetail.configure.skills.missing": "Skill not found",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Remove {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Package the instructions, files, and scripts for a recurring task into a skill. Reference with / in the Prompt. <docLink>Learn more</docLink>\n\nIn Build mode, the agent can set these up for you.",
|
||||
"agentDetail.configure.skills.tip": "Package the instructions, files, and scripts for a recurring task into a skill. Reference with / in the Prompt. Learn more\n\nIn Build mode, the agent can set these up for you.",
|
||||
"agentDetail.configure.skills.toggle": "Toggle skills",
|
||||
@@ -226,6 +235,15 @@
|
||||
"agentDetail.configure.skills.upload.title": "Upload skill",
|
||||
"agentDetail.configure.skills.upload.warning.files": "If you only need to use Markdown files, upload them to Files and reference them in your prompt.",
|
||||
"agentDetail.configure.skills.upload.warning.specification": "Uploaded skills must follow the <specificationLink>Agent Skills specification</specificationLink>.",
|
||||
"agentDetail.configure.skills.workspaceItemType": "Workspace",
|
||||
"agentDetail.configure.skills.workspaceSelector.addSuccess": "Workspace skill added.",
|
||||
"agentDetail.configure.skills.workspaceSelector.added": "Added",
|
||||
"agentDetail.configure.skills.workspaceSelector.draft": "Draft",
|
||||
"agentDetail.configure.skills.workspaceSelector.empty": "No workspace skills found.",
|
||||
"agentDetail.configure.skills.workspaceSelector.manage": "Manage in Skills",
|
||||
"agentDetail.configure.skills.workspaceSelector.removeSuccess": "Workspace skill removed.",
|
||||
"agentDetail.configure.skills.workspaceSelector.saveFailed": "Failed to update workspace skills.",
|
||||
"agentDetail.configure.skills.workspaceSelector.search": "Search skills...",
|
||||
"agentDetail.configure.switchToPreviewConfirm.title": "Switch to Preview mode?",
|
||||
"agentDetail.configure.title": "Configure",
|
||||
"agentDetail.configure.tools.add": "Add tool",
|
||||
@@ -421,5 +439,182 @@
|
||||
"roster.sort.optionsLabel": "Sort options",
|
||||
"roster.sort.recentlyCreated": "Recently created",
|
||||
"roster.updateSuccess": "Agent updated.",
|
||||
"roster.usageStatus.draft": "Draft"
|
||||
"roster.usageStatus.draft": "Draft",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.referencedDescription": "This Skill is referenced by {{count}} Agent, Workflow, or Chatflow item. Deleting it will remove the Skill from the workspace and clear those bindings.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.builder.applyFailed": "Failed to apply draft updates.",
|
||||
"skillManagement.detail.builder.attach": "Attach file",
|
||||
"skillManagement.detail.builder.attachFailed": "Failed to attach file.",
|
||||
"skillManagement.detail.builder.attachUnsupported": "Only text and document files can be attached.",
|
||||
"skillManagement.detail.builder.attachmentOnlyMessage": "Use the attached file to help improve this Skill.",
|
||||
"skillManagement.detail.builder.close": "Close Skill Builder",
|
||||
"skillManagement.detail.builder.compatibleModelsOnly": "Only compatible models are shown",
|
||||
"skillManagement.detail.builder.editIntro": "I've reviewed this Skill. What would you like to adjust?",
|
||||
"skillManagement.detail.builder.exampleIssueTriage": "Customer issue triage",
|
||||
"skillManagement.detail.builder.exampleOnboarding": "New hire onboarding guide",
|
||||
"skillManagement.detail.builder.exampleSalesFollowUp": "Sales lead follow-up strategy",
|
||||
"skillManagement.detail.builder.followUpDisplayName": "Use Refund approval as the display name",
|
||||
"skillManagement.detail.builder.followUpNameIcon": "Apply the suggested name and icon",
|
||||
"skillManagement.detail.builder.fromMarketplace": "From Marketplace",
|
||||
"skillManagement.detail.builder.model": "GPT-4o",
|
||||
"skillManagement.detail.builder.modelCredits.all": "All credits",
|
||||
"skillManagement.detail.builder.modelCredits.configure": "Configure required",
|
||||
"skillManagement.detail.builder.modelCredits.exhausted": "Credits exhausted",
|
||||
"skillManagement.detail.builder.modelProviderSettings": "Model Provider Settings",
|
||||
"skillManagement.detail.builder.modelSearch": "Search models...",
|
||||
"skillManagement.detail.builder.modifyPlaceholder": "Ask AI to modify this skill...",
|
||||
"skillManagement.detail.builder.open": "Open Skill Builder",
|
||||
"skillManagement.detail.builder.placeholder": "Describe the scenario...",
|
||||
"skillManagement.detail.builder.promptDescription": "Describe it and a draft appears in the editor, with steps and files included.",
|
||||
"skillManagement.detail.builder.promptTitle": "What should this Skill handle?",
|
||||
"skillManagement.detail.builder.removeAttachment": "Remove {{name}}",
|
||||
"skillManagement.detail.builder.restart": "Restart builder",
|
||||
"skillManagement.detail.builder.send": "Send message",
|
||||
"skillManagement.detail.builder.sendFailed": "Skill Builder failed to respond.",
|
||||
"skillManagement.detail.builder.title": "Skill Builder",
|
||||
"skillManagement.detail.builder.tryExample": "Try an example",
|
||||
"skillManagement.detail.builder.voice": "Voice input",
|
||||
"skillManagement.detail.builder.voiceUnavailable": "Voice input is not available yet.",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.closeVersions": "Close versions",
|
||||
"skillManagement.detail.collapseSidebar": "Collapse sidebar",
|
||||
"skillManagement.detail.copyFile": "Copy",
|
||||
"skillManagement.detail.copyFileSuccess": "File copied to clipboard.",
|
||||
"skillManagement.detail.copyVersionId": "Copy ID",
|
||||
"skillManagement.detail.copyVersionIdSuccess": "ID copied to clipboard.",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.cutFile": "Cut",
|
||||
"skillManagement.detail.cutFileSuccess": "File cut to clipboard.",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.editVersionInfo": "Edit version info",
|
||||
"skillManagement.detail.exitVersions": "Exit versions",
|
||||
"skillManagement.detail.expandSidebar": "Expand sidebar",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.nameThisVersion": "Name this version",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "No matching files.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.pasteFile": "Paste",
|
||||
"skillManagement.detail.pasteFileSuccess": "File pasted.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishReferencesDescription": "Publishing will make this draft active for {{count}} references using this Skill.",
|
||||
"skillManagement.detail.publishReferencesTitle": "Publish skill",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.publishUpdate": "Publish update",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveConflict": "This Skill changed in another tab. Refreshed the latest draft and will retry saving your changes.",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Search files",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesFailedStatus": "{{count}} file uploads failed.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.uploadFilesProgress": "Uploading {{completed}}/{{total}}",
|
||||
"skillManagement.detail.uploadFilesResult": "{{uploaded}} uploaded · {{failed}} failed",
|
||||
"skillManagement.detail.uploadFilesStatus": "Upload Status",
|
||||
"skillManagement.detail.uploadStatusDismiss": "Dismiss upload status",
|
||||
"skillManagement.detail.versionHistory": "Open version history",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versionPublishNote": "Release notes",
|
||||
"skillManagement.detail.versionPublishNotePlaceholder": "Describe this change",
|
||||
"skillManagement.detail.versionPublishedMeta": "{{time}} · {{name}}",
|
||||
"skillManagement.detail.versionTitle": "Title",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.detail.viewOnly": "View only",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptyAction.createDescription": "Describe a scenario in plain words and get a draft skill to review.",
|
||||
"skillManagement.emptyAction.createTitle": "Create with Skill Builder",
|
||||
"skillManagement.emptyAction.importDescription": "Bring a .zip with SKILL.md, in the agentskills.io format.",
|
||||
"skillManagement.emptyAction.importTitle": "Import a skill package",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Home",
|
||||
"mainNav.integrations": "Integrations",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "No web apps found",
|
||||
"mainNav.webApps.openApp": "Open {{name}} web app",
|
||||
"mainNav.webApps.searchPlaceholder": "Search web apps",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Downloading",
|
||||
"operation.duplicate": "Duplicate",
|
||||
"operation.edit": "Edit",
|
||||
"operation.export": "Export",
|
||||
"operation.exporting": "Exporting",
|
||||
"operation.fill": "Autofill",
|
||||
"operation.format": "Format",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "I'm sure",
|
||||
"operation.toggleFullscreen": "Toggle fullscreen",
|
||||
"operation.toggleMute": "Toggle mute",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "View",
|
||||
"operation.viewDetails": "View Details",
|
||||
"operation.viewMore": "VIEW MORE",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview ejecuta el agente terminado como lo verán tus usuarios, con respuestas claras y funciones de chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Previsualiza tu agente",
|
||||
"agentDetail.configure.skills.add": "Agregar habilidad",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Contenido de los detalles de la habilidad",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ARCHIVOS",
|
||||
"agentDetail.configure.skills.detail.files": "Archivos",
|
||||
"agentDetail.configure.skills.empty.description": "Las habilidades le dan al agente experiencia reutilizable que puede invocar mientras trabaja",
|
||||
"agentDetail.configure.skills.empty.title": "Aún no hay habilidades",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Habilidad",
|
||||
"agentDetail.configure.skills.label": "Habilidades",
|
||||
"agentDetail.configure.skills.missing": "Habilidad no encontrada",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Eliminar {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Empaqueta las instrucciones, archivos y scripts de una tarea recurrente en una skill. Haz referencia con / en el Prompt. <docLink>Más información</docLink>\n\nEn modo Build, el agente puede configurarlas por ti.",
|
||||
"agentDetail.configure.skills.tip": "Empaqueta las instrucciones, archivos y scripts de una tarea recurrente en una skill. Haz referencia con / en el Prompt. Más información\n\nEn modo Build, el agente puede configurarlas por ti.",
|
||||
"agentDetail.configure.skills.toggle": "Alternar habilidades",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Opciones de ordenación",
|
||||
"roster.sort.recentlyCreated": "Creados recientemente",
|
||||
"roster.updateSuccess": "Agente actualizado.",
|
||||
"roster.usageStatus.draft": "Borrador"
|
||||
"roster.usageStatus.draft": "Borrador",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Contraer barra lateral",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Expandir barra lateral",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "No hay archivos coincidentes.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Buscar archivos",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Inicio",
|
||||
"mainNav.integrations": "Integraciones",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "No se encontraron aplicaciones web",
|
||||
"mainNav.webApps.openApp": "Abrir la aplicación web {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Buscar aplicaciones web",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Descargando...",
|
||||
"operation.duplicate": "Duplicar",
|
||||
"operation.edit": "Editar",
|
||||
"operation.export": "Exportar",
|
||||
"operation.exporting": "Exportando",
|
||||
"operation.fill": "Autocompletar",
|
||||
"operation.format": "Formato",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "Estoy seguro",
|
||||
"operation.toggleFullscreen": "Alternar pantalla completa",
|
||||
"operation.toggleMute": "Alternar silencio",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Vista",
|
||||
"operation.viewDetails": "Ver detalles",
|
||||
"operation.viewMore": "VER MÁS",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview عامل تکمیلشده را همانطور که کاربران میبینند اجرا میکند، با پاسخهای تمیز و قابلیتهای گفتگو.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "پیشنمایش عامل",
|
||||
"agentDetail.configure.skills.add": "افزودن مهارت",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "محتوای جزئیات مهارت",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} فایل",
|
||||
"agentDetail.configure.skills.detail.files": "فایلها",
|
||||
"agentDetail.configure.skills.empty.description": "مهارتها به عامل تخصص قابل استفاده مجدد میدهند که هنگام کار میتواند فراخوانی کند",
|
||||
"agentDetail.configure.skills.empty.title": "هنوز مهارتی وجود ندارد",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "مهارت",
|
||||
"agentDetail.configure.skills.label": "مهارتها",
|
||||
"agentDetail.configure.skills.missing": "مهارت یافت نشد",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "حذف {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "دستورالعملها، فایلها و اسکریپتهای یک کار تکرارشونده را در یک Skill بستهبندی کنید. در Prompt با / به آن ارجاع دهید. <docLink>بیشتر بدانید</docLink>\n\nدر حالت Build، عامل میتواند این موارد را برای شما تنظیم کند.",
|
||||
"agentDetail.configure.skills.tip": "دستورالعملها، فایلها و اسکریپتهای یک کار تکرارشونده را در یک Skill بستهبندی کنید. در Prompt با / به آن ارجاع دهید. بیشتر بدانید\n\nدر حالت Build، عامل میتواند این موارد را برای شما تنظیم کند.",
|
||||
"agentDetail.configure.skills.toggle": "تغییر مهارتها",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "گزینههای مرتبسازی",
|
||||
"roster.sort.recentlyCreated": "تازهترین ایجاد",
|
||||
"roster.updateSuccess": "عامل بهروزرسانی شد.",
|
||||
"roster.usageStatus.draft": "پیشنویس"
|
||||
"roster.usageStatus.draft": "پیشنویس",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "جمع کردن نوار کناری",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "باز کردن نوار کناری",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "فایل منطبقی نیست.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "جستجوی فایلها",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "خانه",
|
||||
"mainNav.integrations": "یکپارچهسازیها",
|
||||
"mainNav.marketplace": "بازارچه",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "هیچ برنامه وبی یافت نشد",
|
||||
"mainNav.webApps.openApp": "باز کردن برنامه وب {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "جستجوی برنامههای وب",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "در حال دانلود...",
|
||||
"operation.duplicate": "تکرار",
|
||||
"operation.edit": "ویرایش",
|
||||
"operation.export": "خروجی گرفتن",
|
||||
"operation.exporting": "در حال خروجی گرفتن",
|
||||
"operation.fill": "پر کردن خودکار",
|
||||
"operation.format": "قالب",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "مطمئن هستم",
|
||||
"operation.toggleFullscreen": "تغییر حالت تمامصفحه",
|
||||
"operation.toggleMute": "تغییر حالت بیصدا",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "مشاهده",
|
||||
"operation.viewDetails": "دیدن جزئیات",
|
||||
"operation.viewMore": "بیشتر ببینید",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview exécute l’agent final comme vos utilisateurs le verront, avec des réponses claires et les fonctions de chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Prévisualiser votre agent",
|
||||
"agentDetail.configure.skills.add": "Ajouter une compétence",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Contenu des détails de la compétence",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FICHIERS",
|
||||
"agentDetail.configure.skills.detail.files": "Fichiers",
|
||||
"agentDetail.configure.skills.empty.description": "Les compétences offrent à l’agent une expertise réutilisable qu’il peut invoquer pendant son travail",
|
||||
"agentDetail.configure.skills.empty.title": "Pas encore de compétences",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Compétence",
|
||||
"agentDetail.configure.skills.label": "Compétences",
|
||||
"agentDetail.configure.skills.missing": "Compétence introuvable",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Supprimer {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Regroupez les instructions, fichiers et scripts d'une tâche récurrente dans une skill. Référencez-la avec / dans le Prompt. <docLink>En savoir plus</docLink>\n\nEn mode Build, l'agent peut les configurer pour vous.",
|
||||
"agentDetail.configure.skills.tip": "Regroupez les instructions, fichiers et scripts d'une tâche récurrente dans une skill. Référencez-la avec / dans le Prompt. En savoir plus\n\nEn mode Build, l'agent peut les configurer pour vous.",
|
||||
"agentDetail.configure.skills.toggle": "Basculer les compétences",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Options de tri",
|
||||
"roster.sort.recentlyCreated": "Récemment créés",
|
||||
"roster.updateSuccess": "Agent mis à jour.",
|
||||
"roster.usageStatus.draft": "Brouillon"
|
||||
"roster.usageStatus.draft": "Brouillon",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Réduire la barre latérale",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Développer la barre latérale",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Aucun fichier correspondant.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Rechercher des fichiers",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Accueil",
|
||||
"mainNav.integrations": "Intégrations",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Aucune application web trouvée",
|
||||
"mainNav.webApps.openApp": "Ouvrir l’application web {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Rechercher des applications web",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Téléchargement...",
|
||||
"operation.duplicate": "Dupliquer",
|
||||
"operation.edit": "Modifier",
|
||||
"operation.export": "Exporter",
|
||||
"operation.exporting": "Exportation",
|
||||
"operation.fill": "Remplissage auto",
|
||||
"operation.format": "Format",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "Je suis sûr",
|
||||
"operation.toggleFullscreen": "Basculer en plein écran",
|
||||
"operation.toggleMute": "Activer/désactiver le son",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Vue",
|
||||
"operation.viewDetails": "Voir les détails",
|
||||
"operation.viewMore": "VOIR PLUS",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview तैयार agent को वैसे चलाता है जैसे आपके users उसे देखेंगे, साफ replies और chat features के साथ।",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "अपने agent का preview करें",
|
||||
"agentDetail.configure.skills.add": "कौशल जोड़ें",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "कौशल विवरण सामग्री",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} फ़ाइलें",
|
||||
"agentDetail.configure.skills.detail.files": "फ़ाइलें",
|
||||
"agentDetail.configure.skills.empty.description": "कौशल एजेंट को पुनः उपयोग योग्य विशेषज्ञता देते हैं जिसे वह काम करते समय कॉल कर सकता है",
|
||||
"agentDetail.configure.skills.empty.title": "अभी तक कोई कौशल नहीं",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "कौशल",
|
||||
"agentDetail.configure.skills.label": "कौशल",
|
||||
"agentDetail.configure.skills.missing": "कौशल नहीं मिला",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "{{name}} हटाएँ",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "किसी दोहराए जाने वाले कार्य के निर्देशों, फ़ाइलों और स्क्रिप्ट को एक Skill में पैकेज करें। Prompt में / से संदर्भ दें। <docLink>और जानें</docLink>\n\nBuild mode में, एजेंट इन्हें आपके लिए सेट कर सकता है।",
|
||||
"agentDetail.configure.skills.tip": "किसी दोहराए जाने वाले कार्य के निर्देशों, फ़ाइलों और स्क्रिप्ट को एक Skill में पैकेज करें। Prompt में / से संदर्भ दें। और जानें\n\nBuild mode में, एजेंट इन्हें आपके लिए सेट कर सकता है।",
|
||||
"agentDetail.configure.skills.toggle": "कौशल टॉगल करें",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "क्रमबद्ध विकल्प",
|
||||
"roster.sort.recentlyCreated": "हाल ही में बनाए गए",
|
||||
"roster.updateSuccess": "एजेंट अपडेट हो गया।",
|
||||
"roster.usageStatus.draft": "ड्राफ्ट"
|
||||
"roster.usageStatus.draft": "ड्राफ्ट",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "साइडबार समेटें",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "साइडबार फैलाएं",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "कोई मेल खाती फ़ाइल नहीं.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "फ़ाइलें खोजें",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "होम",
|
||||
"mainNav.integrations": "इंटीग्रेशन",
|
||||
"mainNav.marketplace": "मार्केटप्लेस",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "कोई वेब ऐप नहीं मिला",
|
||||
"mainNav.webApps.openApp": "{{name}} वेब ऐप खोलें",
|
||||
"mainNav.webApps.searchPlaceholder": "वेब ऐप खोजें",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "डाउनलोड हो रहा है...",
|
||||
"operation.duplicate": "डुप्लिकेट",
|
||||
"operation.edit": "संपादित करें",
|
||||
"operation.export": "निर्यात करें",
|
||||
"operation.exporting": "निर्यात हो रहा है",
|
||||
"operation.fill": "ऑटोफिल",
|
||||
"operation.format": "फॉर्मेट",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "मुझे यकीन है",
|
||||
"operation.toggleFullscreen": "फ़ुलस्क्रीन टॉगल करें",
|
||||
"operation.toggleMute": "म्यूट टॉगल करें",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "देखना",
|
||||
"operation.viewDetails": "विवरण देखें",
|
||||
"operation.viewMore": "और देखें",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview menjalankan agent yang sudah selesai seperti yang akan dilihat pengguna, dengan balasan rapi dan fitur chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Pratinjau agen Anda",
|
||||
"agentDetail.configure.skills.add": "Tambahkan keterampilan",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Konten detail keterampilan",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILE",
|
||||
"agentDetail.configure.skills.detail.files": "File",
|
||||
"agentDetail.configure.skills.empty.description": "Keterampilan memberi agen keahlian yang dapat digunakan kembali yang bisa dipanggil saat bekerja",
|
||||
"agentDetail.configure.skills.empty.title": "Belum ada keterampilan",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Keterampilan",
|
||||
"agentDetail.configure.skills.label": "Keterampilan",
|
||||
"agentDetail.configure.skills.missing": "Keterampilan tidak ditemukan",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Hapus {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Kemas instruksi, file, dan skrip untuk tugas berulang menjadi skill. Referensikan dengan / di Prompt. <docLink>Pelajari selengkapnya</docLink>\n\nDalam mode Build, agen dapat menyiapkannya untuk Anda.",
|
||||
"agentDetail.configure.skills.tip": "Kemas instruksi, file, dan skrip untuk tugas berulang menjadi skill. Referensikan dengan / di Prompt. Pelajari selengkapnya\n\nDalam mode Build, agen dapat menyiapkannya untuk Anda.",
|
||||
"agentDetail.configure.skills.toggle": "Alihkan keterampilan",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Opsi pengurutan",
|
||||
"roster.sort.recentlyCreated": "Baru dibuat",
|
||||
"roster.updateSuccess": "Agen diperbarui.",
|
||||
"roster.usageStatus.draft": "Draf"
|
||||
"roster.usageStatus.draft": "Draf",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Ciutkan sidebar",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Perluas sidebar",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Tidak ada file yang cocok.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Cari file",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
"mainNav.home": "Beranda",
|
||||
"mainNav.integrations": "Integrasi",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Tidak ada aplikasi web ditemukan",
|
||||
"mainNav.webApps.openApp": "Buka aplikasi web {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Cari aplikasi web",
|
||||
@@ -462,6 +463,7 @@
|
||||
"operation.downloading": "Mengunduh...",
|
||||
"operation.duplicate": "Duplikat",
|
||||
"operation.edit": "Mengedit",
|
||||
"operation.export": "Ekspor",
|
||||
"operation.exporting": "Mengekspor",
|
||||
"operation.fill": "Isi otomatis",
|
||||
"operation.format": "Format",
|
||||
@@ -503,6 +505,7 @@
|
||||
"operation.sure": "Saya yakin",
|
||||
"operation.toggleFullscreen": "Alihkan layar penuh",
|
||||
"operation.toggleMute": "Alihkan bisu",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Lihat",
|
||||
"operation.viewDetails": "Lihat Detail",
|
||||
"operation.viewMore": "LIHAT LEBIH BANYAK",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview esegue l’agente completato come lo vedranno gli utenti, con risposte pulite e funzionalità di chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Anteprima del tuo agente",
|
||||
"agentDetail.configure.skills.add": "Aggiungi abilità",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Contenuto dei dettagli dell’abilità",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILE",
|
||||
"agentDetail.configure.skills.detail.files": "File",
|
||||
"agentDetail.configure.skills.empty.description": "Le abilità forniscono all’agente competenze riutilizzabili che può richiamare durante il lavoro",
|
||||
"agentDetail.configure.skills.empty.title": "Nessuna abilità al momento",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Abilità",
|
||||
"agentDetail.configure.skills.label": "Abilità",
|
||||
"agentDetail.configure.skills.missing": "Abilità non trovata",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Rimuovi {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Raggruppa istruzioni, file e script per un'attività ricorrente in una skill. Fai riferimento con / nel Prompt. <docLink>Scopri di più</docLink>\n\nIn modalità Build, l'agente può configurarli per te.",
|
||||
"agentDetail.configure.skills.tip": "Raggruppa istruzioni, file e script per un'attività ricorrente in una skill. Fai riferimento con / nel Prompt. Scopri di più\n\nIn modalità Build, l'agente può configurarli per te.",
|
||||
"agentDetail.configure.skills.toggle": "Attiva/disattiva abilità",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Opzioni di ordinamento",
|
||||
"roster.sort.recentlyCreated": "Creati di recente",
|
||||
"roster.updateSuccess": "Agente aggiornato.",
|
||||
"roster.usageStatus.draft": "Bozza"
|
||||
"roster.usageStatus.draft": "Bozza",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Comprimi barra laterale",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Espandi barra laterale",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Nessun file corrispondente.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Cerca file",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Home",
|
||||
"mainNav.integrations": "Integrazioni",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Nessuna app web trovata",
|
||||
"mainNav.webApps.openApp": "Apri l'app web {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Cerca app web",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Download in corso...",
|
||||
"operation.duplicate": "Duplica",
|
||||
"operation.edit": "Modifica",
|
||||
"operation.export": "Esporta",
|
||||
"operation.exporting": "Esportazione in corso",
|
||||
"operation.fill": "Compilazione automatica",
|
||||
"operation.format": "Formato",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "Sono sicuro",
|
||||
"operation.toggleFullscreen": "Attiva/disattiva schermo intero",
|
||||
"operation.toggleMute": "Attiva/disattiva muto",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Vista",
|
||||
"operation.viewDetails": "Visualizza dettagli",
|
||||
"operation.viewMore": "SCOPRI DI PIÙ",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview は完成したエージェントをユーザーに見える形で実行し、整った返信とチャット機能を確認できます。",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "エージェントをプレビュー",
|
||||
"agentDetail.configure.skills.add": "スキルを追加",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "スキル詳細コンテンツ",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} 個のファイル",
|
||||
"agentDetail.configure.skills.detail.files": "ファイル",
|
||||
"agentDetail.configure.skills.empty.description": "スキルはエージェントが作業中に呼び出せる再利用可能な専門知識を提供します",
|
||||
"agentDetail.configure.skills.empty.title": "スキルはまだありません",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "スキル",
|
||||
"agentDetail.configure.skills.label": "スキル",
|
||||
"agentDetail.configure.skills.missing": "スキルが見つかりません",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "{{name}} を削除",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "繰り返し行うタスクに必要な指示、ファイル、スクリプトを Skill にまとめます。Prompt で / を使って参照します。<docLink>詳しく見る</docLink>\n\nBuild モードでは、エージェントがこれらを設定できます。",
|
||||
"agentDetail.configure.skills.tip": "繰り返し行うタスクに必要な指示、ファイル、スクリプトを Skill にまとめます。Prompt で / を使って参照します。詳しく見る\n\nBuild モードでは、エージェントがこれらを設定できます。",
|
||||
"agentDetail.configure.skills.toggle": "スキルの表示を切り替え",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "並び替えオプション",
|
||||
"roster.sort.recentlyCreated": "最近作成",
|
||||
"roster.updateSuccess": "エージェントを更新しました。",
|
||||
"roster.usageStatus.draft": "ドラフト"
|
||||
"roster.usageStatus.draft": "ドラフト",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "サイドバーを折りたたむ",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "サイドバーを展開",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "一致するファイルはありません。",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "ファイルを検索",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "ホーム",
|
||||
"mainNav.integrations": "連携",
|
||||
"mainNav.marketplace": "マーケットプレイス",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Webアプリが見つかりません",
|
||||
"mainNav.webApps.openApp": "{{name}} のWebアプリを開く",
|
||||
"mainNav.webApps.searchPlaceholder": "Webアプリを検索",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "ダウンロード中...",
|
||||
"operation.duplicate": "複製",
|
||||
"operation.edit": "編集",
|
||||
"operation.export": "エクスポート",
|
||||
"operation.exporting": "エクスポート中",
|
||||
"operation.fill": "自動入力",
|
||||
"operation.format": "フォーマット",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "確認済み",
|
||||
"operation.toggleFullscreen": "全画面表示を切り替え",
|
||||
"operation.toggleMute": "ミュートを切り替え",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "表示",
|
||||
"operation.viewDetails": "詳細を見る",
|
||||
"operation.viewMore": "さらに表示",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview는 사용자가 보게 될 방식으로 완성된 에이전트를 실행하며, 깔끔한 답변과 채팅 기능을 보여줍니다.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "에이전트 미리보기",
|
||||
"agentDetail.configure.skills.add": "스킬 추가",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "스킬 상세 내용",
|
||||
"agentDetail.configure.skills.detail.fileCount": "파일 {{count}}개",
|
||||
"agentDetail.configure.skills.detail.files": "파일",
|
||||
"agentDetail.configure.skills.empty.description": "스킬은 에이전트가 작업하면서 호출할 수 있는 재사용 가능한 전문 능력을 제공합니다",
|
||||
"agentDetail.configure.skills.empty.title": "아직 스킬이 없습니다",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "스킬",
|
||||
"agentDetail.configure.skills.label": "스킬",
|
||||
"agentDetail.configure.skills.missing": "스킬을 찾을 수 없음",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "{{name}} 제거",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "반복 작업에 필요한 지침, 파일, 스크립트를 Skill로 패키징하세요. Prompt에서 /로 참조하세요. <docLink>자세히 알아보기</docLink>\n\nBuild mode에서는 에이전트가 이를 대신 설정할 수 있습니다.",
|
||||
"agentDetail.configure.skills.tip": "반복 작업에 필요한 지침, 파일, 스크립트를 Skill로 패키징하세요. Prompt에서 /로 참조하세요. 자세히 알아보기\n\nBuild mode에서는 에이전트가 이를 대신 설정할 수 있습니다.",
|
||||
"agentDetail.configure.skills.toggle": "스킬 전환",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "정렬 옵션",
|
||||
"roster.sort.recentlyCreated": "최근 생성",
|
||||
"roster.updateSuccess": "에이전트가 업데이트되었습니다.",
|
||||
"roster.usageStatus.draft": "초안"
|
||||
"roster.usageStatus.draft": "초안",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "사이드바 접기",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "사이드바 펼치기",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "일치하는 파일이 없습니다.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "파일 검색",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "홈",
|
||||
"mainNav.integrations": "연동",
|
||||
"mainNav.marketplace": "마켓플레이스",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "웹 앱을 찾을 수 없습니다",
|
||||
"mainNav.webApps.openApp": "{{name}} 웹 앱 열기",
|
||||
"mainNav.webApps.searchPlaceholder": "웹 앱 검색",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "다운로드 중...",
|
||||
"operation.duplicate": "중복",
|
||||
"operation.edit": "편집",
|
||||
"operation.export": "내보내기",
|
||||
"operation.exporting": "내보내는 중",
|
||||
"operation.fill": "자동 입력",
|
||||
"operation.format": "형식",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "확인",
|
||||
"operation.toggleFullscreen": "전체 화면 전환",
|
||||
"operation.toggleMute": "음소거 전환",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "보기",
|
||||
"operation.viewDetails": "세부 정보보기",
|
||||
"operation.viewMore": "더보기",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview voert de voltooide agent uit zoals je gebruikers die zien, met duidelijke antwoorden en chatfuncties.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Voorbeeld van je agent",
|
||||
"agentDetail.configure.skills.add": "Skill toevoegen",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Skill-detailinhoud",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} BESTANDEN",
|
||||
"agentDetail.configure.skills.detail.files": "Bestanden",
|
||||
"agentDetail.configure.skills.empty.description": "Skills geven de agent herbruikbare expertise die hij tijdens het werken kan inzetten",
|
||||
"agentDetail.configure.skills.empty.title": "Nog geen skills",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Skill",
|
||||
"agentDetail.configure.skills.label": "Skills",
|
||||
"agentDetail.configure.skills.missing": "Skill niet gevonden",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "{{name}} verwijderen",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Bundel de instructies, bestanden en scripts voor een terugkerende taak in een skill. Verwijs ernaar met / in de Prompt. <docLink>Meer informatie</docLink>\n\nIn de Build-modus kan de agent dit voor u instellen.",
|
||||
"agentDetail.configure.skills.tip": "Bundel de instructies, bestanden en scripts voor een terugkerende taak in een skill. Verwijs ernaar met / in de Prompt. Meer informatie\n\nIn de Build-modus kan de agent dit voor u instellen.",
|
||||
"agentDetail.configure.skills.toggle": "Skills in/uit",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Sorteeropties",
|
||||
"roster.sort.recentlyCreated": "Recent aangemaakt",
|
||||
"roster.updateSuccess": "Agent bijgewerkt.",
|
||||
"roster.usageStatus.draft": "Concept"
|
||||
"roster.usageStatus.draft": "Concept",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Zijbalk inklappen",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Zijbalk uitklappen",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Geen overeenkomende bestanden.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Bestanden zoeken",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
"mainNav.home": "Start",
|
||||
"mainNav.integrations": "Integraties",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Geen webapps gevonden",
|
||||
"mainNav.webApps.openApp": "Webapp {{name}} openen",
|
||||
"mainNav.webApps.searchPlaceholder": "Webapps zoeken",
|
||||
@@ -462,6 +463,7 @@
|
||||
"operation.downloading": "Downloaden...",
|
||||
"operation.duplicate": "Duplicate",
|
||||
"operation.edit": "Edit",
|
||||
"operation.export": "Exporteren",
|
||||
"operation.exporting": "Exporteren",
|
||||
"operation.fill": "Automatisch invullen",
|
||||
"operation.format": "Format",
|
||||
@@ -503,6 +505,7 @@
|
||||
"operation.sure": "I'm sure",
|
||||
"operation.toggleFullscreen": "Volledig scherm schakelen",
|
||||
"operation.toggleMute": "Dempen schakelen",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "View",
|
||||
"operation.viewDetails": "View Details",
|
||||
"operation.viewMore": "VIEW MORE",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview uruchamia gotowego agenta tak, jak zobaczą go użytkownicy, z przejrzystymi odpowiedziami i funkcjami czatu.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Podgląd agenta",
|
||||
"agentDetail.configure.skills.add": "Dodaj umiejętność",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Zawartość szczegółów umiejętności",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} PLIKÓW",
|
||||
"agentDetail.configure.skills.detail.files": "Pliki",
|
||||
"agentDetail.configure.skills.empty.description": "Umiejętności dają agentowi reużywalną wiedzę, którą może wywołać podczas pracy",
|
||||
"agentDetail.configure.skills.empty.title": "Brak umiejętności",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Umiejętność",
|
||||
"agentDetail.configure.skills.label": "Umiejętności",
|
||||
"agentDetail.configure.skills.missing": "Nie znaleziono umiejętności",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Usuń {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Spakuj instrukcje, pliki i skrypty dla powtarzalnego zadania w skill. Odwołuj się za pomocą / w Prompt. <docLink>Dowiedz się więcej</docLink>\n\nW trybie Build agent może skonfigurować je za Ciebie.",
|
||||
"agentDetail.configure.skills.tip": "Spakuj instrukcje, pliki i skrypty dla powtarzalnego zadania w skill. Odwołuj się za pomocą / w Prompt. Dowiedz się więcej\n\nW trybie Build agent może skonfigurować je za Ciebie.",
|
||||
"agentDetail.configure.skills.toggle": "Przełącz umiejętności",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Opcje sortowania",
|
||||
"roster.sort.recentlyCreated": "Ostatnio utworzone",
|
||||
"roster.updateSuccess": "Agent zaktualizowany.",
|
||||
"roster.usageStatus.draft": "Wersja robocza"
|
||||
"roster.usageStatus.draft": "Wersja robocza",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Zwiń pasek boczny",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Rozwiń pasek boczny",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Brak pasujących plików.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Szukaj plików",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Strona główna",
|
||||
"mainNav.integrations": "Integracje",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Nie znaleziono aplikacji webowych",
|
||||
"mainNav.webApps.openApp": "Otwórz aplikację webową {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Szukaj aplikacji webowych",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Pobieranie...",
|
||||
"operation.duplicate": "Duplikuj",
|
||||
"operation.edit": "Edytuj",
|
||||
"operation.export": "Eksportuj",
|
||||
"operation.exporting": "Eksportowanie",
|
||||
"operation.fill": "Autouzupełnianie",
|
||||
"operation.format": "Format",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "Jestem pewien",
|
||||
"operation.toggleFullscreen": "Przełącz pełny ekran",
|
||||
"operation.toggleMute": "Przełącz wyciszenie",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Widok",
|
||||
"operation.viewDetails": "Wyświetl szczegóły",
|
||||
"operation.viewMore": "ZOBACZ WIĘCEJ",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview executa o agente finalizado como seus usuários o verão, com respostas claras e recursos de chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Pré-visualize seu agente",
|
||||
"agentDetail.configure.skills.add": "Adicionar habilidade",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Conteúdo dos detalhes da habilidade",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ARQUIVOS",
|
||||
"agentDetail.configure.skills.detail.files": "Arquivos",
|
||||
"agentDetail.configure.skills.empty.description": "As habilidades dão ao agente expertise reutilizável que ele pode invocar enquanto trabalha",
|
||||
"agentDetail.configure.skills.empty.title": "Ainda não há habilidades",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Habilidade",
|
||||
"agentDetail.configure.skills.label": "Habilidades",
|
||||
"agentDetail.configure.skills.missing": "Habilidade não encontrada",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Remover {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Empacote as instruções, arquivos e scripts de uma tarefa recorrente em uma skill. Referencie com / no Prompt. <docLink>Saiba mais</docLink>\n\nNo modo Build, o agente pode configurar isso para você.",
|
||||
"agentDetail.configure.skills.tip": "Empacote as instruções, arquivos e scripts de uma tarefa recorrente em uma skill. Referencie com / no Prompt. Saiba mais\n\nNo modo Build, o agente pode configurar isso para você.",
|
||||
"agentDetail.configure.skills.toggle": "Alternar habilidades",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Opções de ordenação",
|
||||
"roster.sort.recentlyCreated": "Criados recentemente",
|
||||
"roster.updateSuccess": "Agente atualizado.",
|
||||
"roster.usageStatus.draft": "Rascunho"
|
||||
"roster.usageStatus.draft": "Rascunho",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Recolher barra lateral",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Expandir barra lateral",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Nenhum arquivo correspondente.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Buscar arquivos",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Início",
|
||||
"mainNav.integrations": "Integrações",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Nenhum app web encontrado",
|
||||
"mainNav.webApps.openApp": "Abrir o app web {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Pesquisar apps web",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Baixando...",
|
||||
"operation.duplicate": "Duplicada",
|
||||
"operation.edit": "Editar",
|
||||
"operation.export": "Exportar",
|
||||
"operation.exporting": "Exportando",
|
||||
"operation.fill": "Preenchimento automático",
|
||||
"operation.format": "Formato",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "Tenho certeza",
|
||||
"operation.toggleFullscreen": "Alternar tela cheia",
|
||||
"operation.toggleMute": "Alternar mudo",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Vista",
|
||||
"operation.viewDetails": "Ver detalhes",
|
||||
"operation.viewMore": "VER MAIS",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview rulează agentul finalizat așa cum îl vor vedea utilizatorii, cu răspunsuri clare și funcții de chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Previzualizează agentul",
|
||||
"agentDetail.configure.skills.add": "Adaugă abilitate",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Conținutul detaliilor abilității",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FIȘIERE",
|
||||
"agentDetail.configure.skills.detail.files": "Fișiere",
|
||||
"agentDetail.configure.skills.empty.description": "Abilitățile oferă agentului expertiză reutilizabilă pe care o poate apela în timpul lucrului",
|
||||
"agentDetail.configure.skills.empty.title": "Nicio abilitate încă",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Abilitate",
|
||||
"agentDetail.configure.skills.label": "Abilități",
|
||||
"agentDetail.configure.skills.missing": "Abilitatea nu a fost găsită",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Elimină {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Împachetați instrucțiunile, fișierele și scripturile pentru o sarcină recurentă într-un skill. Faceți referire cu / în Prompt. <docLink>Aflați mai multe</docLink>\n\nÎn modul Build, agentul le poate configura pentru dvs.",
|
||||
"agentDetail.configure.skills.tip": "Împachetați instrucțiunile, fișierele și scripturile pentru o sarcină recurentă într-un skill. Faceți referire cu / în Prompt. Aflați mai multe\n\nÎn modul Build, agentul le poate configura pentru dvs.",
|
||||
"agentDetail.configure.skills.toggle": "Comută abilitățile",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Opțiuni de sortare",
|
||||
"roster.sort.recentlyCreated": "Create recent",
|
||||
"roster.updateSuccess": "Agent actualizat.",
|
||||
"roster.usageStatus.draft": "Ciornă"
|
||||
"roster.usageStatus.draft": "Ciornă",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Restrânge bara laterală",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Extinde bara laterală",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Nu există fișiere potrivite.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Caută fișiere",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Acasă",
|
||||
"mainNav.integrations": "Integrări",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Nu s-au găsit aplicații web",
|
||||
"mainNav.webApps.openApp": "Deschide aplicația web {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Caută aplicații web",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Se descarcă...",
|
||||
"operation.duplicate": "Duplică",
|
||||
"operation.edit": "Editează",
|
||||
"operation.export": "Exportă",
|
||||
"operation.exporting": "Se exportă",
|
||||
"operation.fill": "Completare automată",
|
||||
"operation.format": "Format",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "Sunt sigur",
|
||||
"operation.toggleFullscreen": "Comută ecran complet",
|
||||
"operation.toggleMute": "Comută sunetul",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Vedere",
|
||||
"operation.viewDetails": "Vezi detalii",
|
||||
"operation.viewMore": "VEZI MAI MULT",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview запускает готового агента так, как его увидят пользователи, с чистыми ответами и функциями чата.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Предпросмотр агента",
|
||||
"agentDetail.configure.skills.add": "Добавить навык",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Содержимое деталей навыка",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ФАЙЛОВ",
|
||||
"agentDetail.configure.skills.detail.files": "Файлы",
|
||||
"agentDetail.configure.skills.empty.description": "Навыки дают агенту переиспользуемую экспертизу, которую он может вызывать в работе",
|
||||
"agentDetail.configure.skills.empty.title": "Пока нет навыков",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Навык",
|
||||
"agentDetail.configure.skills.label": "Навыки",
|
||||
"agentDetail.configure.skills.missing": "Навык не найден",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Удалить {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Упакуйте инструкции, файлы и скрипты для повторяющейся задачи в Skill. Ссылайтесь на него через / в Prompt. <docLink>Подробнее</docLink>\n\nВ режиме Build агент может настроить это за вас.",
|
||||
"agentDetail.configure.skills.tip": "Упакуйте инструкции, файлы и скрипты для повторяющейся задачи в Skill. Ссылайтесь на него через / в Prompt. Подробнее\n\nВ режиме Build агент может настроить это за вас.",
|
||||
"agentDetail.configure.skills.toggle": "Переключить навыки",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Параметры сортировки",
|
||||
"roster.sort.recentlyCreated": "Недавно созданные",
|
||||
"roster.updateSuccess": "Агент обновлён.",
|
||||
"roster.usageStatus.draft": "Черновик"
|
||||
"roster.usageStatus.draft": "Черновик",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Свернуть боковую панель",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Развернуть боковую панель",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Нет подходящих файлов.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Поиск файлов",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
"mainNav.home": "Главная",
|
||||
"mainNav.integrations": "Интеграции",
|
||||
"mainNav.marketplace": "Маркетплейс",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Веб-приложения не найдены",
|
||||
"mainNav.webApps.openApp": "Открыть веб-приложение {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Поиск веб-приложений",
|
||||
@@ -463,6 +464,7 @@
|
||||
"operation.downloading": "Скачивание...",
|
||||
"operation.duplicate": "Дублировать",
|
||||
"operation.edit": "Редактировать",
|
||||
"operation.export": "Экспорт",
|
||||
"operation.exporting": "Экспорт",
|
||||
"operation.fill": "Автозаполнение",
|
||||
"operation.format": "Формат",
|
||||
@@ -504,6 +506,7 @@
|
||||
"operation.sure": "Я уверен",
|
||||
"operation.toggleFullscreen": "Переключить полноэкранный режим",
|
||||
"operation.toggleMute": "Переключить звук",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Вид",
|
||||
"operation.viewDetails": "Подробнее",
|
||||
"operation.viewMore": "ПОДРОБНЕЕ",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview zažene dokončanega agenta tako, kot ga bodo videli uporabniki, z jasnimi odgovori in funkcijami klepeta.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Predogled agenta",
|
||||
"agentDetail.configure.skills.add": "Dodaj veščino",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Vsebina podrobnosti veščine",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} DATOTEK",
|
||||
"agentDetail.configure.skills.detail.files": "Datoteke",
|
||||
"agentDetail.configure.skills.empty.description": "Veščine agentu dajejo ponovno uporabno strokovnost, ki jo lahko kliče med delom",
|
||||
"agentDetail.configure.skills.empty.title": "Še ni veščin",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "Veščina",
|
||||
"agentDetail.configure.skills.label": "Veščine",
|
||||
"agentDetail.configure.skills.missing": "Veščina ni bila najdena",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "Odstrani {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "Navodila, datoteke in skripte za ponavljajočo se nalogo zapakirajte v skill. Sklicujte se z / v Promptu. <docLink>Več informacij</docLink>\n\nV načinu Build jih lahko agent nastavi namesto vas.",
|
||||
"agentDetail.configure.skills.tip": "Navodila, datoteke in skripte za ponavljajočo se nalogo zapakirajte v skill. Sklicujte se z / v Promptu. Več informacij\n\nV načinu Build jih lahko agent nastavi namesto vas.",
|
||||
"agentDetail.configure.skills.toggle": "Preklopi veščine",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "Možnosti razvrščanja",
|
||||
"roster.sort.recentlyCreated": "Nedavno ustvarjeno",
|
||||
"roster.updateSuccess": "Agent posodobljen.",
|
||||
"roster.usageStatus.draft": "Osnutek"
|
||||
"roster.usageStatus.draft": "Osnutek",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "Strni stransko vrstico",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "Razširi stransko vrstico",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "Ni ujemajočih se datotek.",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "Išči datoteke",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
"mainNav.home": "Domov",
|
||||
"mainNav.integrations": "Integracije",
|
||||
"mainNav.marketplace": "Marketplace",
|
||||
"mainNav.skills": "Skills",
|
||||
"mainNav.webApps.noResults": "Ni najdenih spletnih aplikacij",
|
||||
"mainNav.webApps.openApp": "Odpri spletno aplikacijo {{name}}",
|
||||
"mainNav.webApps.searchPlaceholder": "Išči spletne aplikacije",
|
||||
@@ -462,6 +463,7 @@
|
||||
"operation.downloading": "Prenašanje...",
|
||||
"operation.duplicate": "Podvoji",
|
||||
"operation.edit": "Uredi",
|
||||
"operation.export": "Izvozi",
|
||||
"operation.exporting": "Izvažanje",
|
||||
"operation.fill": "Samodejno izpolni",
|
||||
"operation.format": "Format",
|
||||
@@ -503,6 +505,7 @@
|
||||
"operation.sure": "Prepričan sem",
|
||||
"operation.toggleFullscreen": "Preklopi celozaslonski način",
|
||||
"operation.toggleMute": "Preklopi utišanje",
|
||||
"operation.upload": "Upload",
|
||||
"operation.view": "Pogled",
|
||||
"operation.viewDetails": "Poglej podrobnosti",
|
||||
"operation.viewMore": "POGLEJ VEČ",
|
||||
|
||||
@@ -203,15 +203,22 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview เรียกใช้เอเจนต์ที่เสร็จแล้วในแบบที่ผู้ใช้จะเห็น พร้อมคำตอบที่ชัดเจนและฟีเจอร์แชท",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "ดูตัวอย่างเอเจนต์",
|
||||
"agentDetail.configure.skills.add": "เพิ่มทักษะ",
|
||||
"agentDetail.configure.skills.addMenu.upload.badge": "EMBEDDED",
|
||||
"agentDetail.configure.skills.addMenu.upload.label": "Upload package",
|
||||
"agentDetail.configure.skills.addMenu.workspace.label": "From skill library",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "เนื้อหารายละเอียดทักษะ",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ไฟล์",
|
||||
"agentDetail.configure.skills.detail.files": "ไฟล์",
|
||||
"agentDetail.configure.skills.empty.description": "ทักษะให้ตัวแทนมีความเชี่ยวชาญที่นำกลับมาใช้ใหม่ได้ซึ่งสามารถเรียกใช้ได้ขณะทำงาน",
|
||||
"agentDetail.configure.skills.empty.title": "ยังไม่มีทักษะ",
|
||||
"agentDetail.configure.skills.fromSkillLibrary": "From skill library",
|
||||
"agentDetail.configure.skills.itemType": "ทักษะ",
|
||||
"agentDetail.configure.skills.label": "ทักษะ",
|
||||
"agentDetail.configure.skills.missing": "ไม่พบทักษะ",
|
||||
"agentDetail.configure.skills.moreActions": "More actions for {{name}}",
|
||||
"agentDetail.configure.skills.openInLibrary": "Open in Skill library",
|
||||
"agentDetail.configure.skills.remove": "ลบ {{name}}",
|
||||
"agentDetail.configure.skills.removeAction": "Remove",
|
||||
"agentDetail.configure.skills.richTip": "แพ็กเกจคำสั่ง ไฟล์ และสคริปต์สำหรับงานที่ทำซ้ำเป็น Skill อ้างอิงด้วย / ใน Prompt <docLink>เรียนรู้เพิ่มเติม</docLink>\n\nในโหมด Build agent สามารถตั้งค่าสิ่งเหล่านี้ให้คุณได้",
|
||||
"agentDetail.configure.skills.tip": "แพ็กเกจคำสั่ง ไฟล์ และสคริปต์สำหรับงานที่ทำซ้ำเป็น Skill อ้างอิงด้วย / ใน Prompt เรียนรู้เพิ่มเติม\n\nในโหมด Build agent สามารถตั้งค่าสิ่งเหล่านี้ให้คุณได้",
|
||||
"agentDetail.configure.skills.toggle": "สลับทักษะ",
|
||||
@@ -421,5 +428,117 @@
|
||||
"roster.sort.optionsLabel": "ตัวเลือกการเรียง",
|
||||
"roster.sort.recentlyCreated": "สร้างล่าสุด",
|
||||
"roster.updateSuccess": "อัปเดตตัวแทนแล้ว",
|
||||
"roster.usageStatus.draft": "ฉบับร่าง"
|
||||
"roster.usageStatus.draft": "ฉบับร่าง",
|
||||
"skillManagement.clearTags": "Clear tags",
|
||||
"skillManagement.create": "Create",
|
||||
"skillManagement.createFailed": "Failed to create skill.",
|
||||
"skillManagement.createSuccess": "Skill created.",
|
||||
"skillManagement.dateTimeFormat": "MMM D, YYYY HH:mm",
|
||||
"skillManagement.deleteDialog.description": "This skill will be removed from the workspace. Agents that reference it may lose access to this capability.",
|
||||
"skillManagement.deleteDialog.title": "Delete {{name}}?",
|
||||
"skillManagement.deleteFailed": "Failed to delete skill.",
|
||||
"skillManagement.deleteSuccess": "Skill deleted.",
|
||||
"skillManagement.detail.addMetadata": "Add metadata",
|
||||
"skillManagement.detail.addMetadataDescription": "Add a frontmatter field to this Markdown file.",
|
||||
"skillManagement.detail.addTag": "Add tag",
|
||||
"skillManagement.detail.addTagDescription": "Create or bind a tag to this Skill. New tags are saved with the Skill metadata.",
|
||||
"skillManagement.detail.addTagSuccess": "Tag added.",
|
||||
"skillManagement.detail.back": "Back to Skills",
|
||||
"skillManagement.detail.cancelAddMetadata": "Cancel adding metadata",
|
||||
"skillManagement.detail.closeFileTab": "Close {{name}}",
|
||||
"skillManagement.detail.collapseSidebar": "ยุบแถบด้านข้าง",
|
||||
"skillManagement.detail.createFile": "New file",
|
||||
"skillManagement.detail.createFileDescription": "Enter a file path relative to the skill root.",
|
||||
"skillManagement.detail.createFileMenu": "New file...",
|
||||
"skillManagement.detail.createFileSuccess": "File created.",
|
||||
"skillManagement.detail.createFolder": "New folder",
|
||||
"skillManagement.detail.createFolderDescription": "Enter a folder path relative to the skill root.",
|
||||
"skillManagement.detail.createFolderMenu": "New folder...",
|
||||
"skillManagement.detail.createFolderSuccess": "Folder created.",
|
||||
"skillManagement.detail.createdBy": "Created by {{name}}",
|
||||
"skillManagement.detail.currentDraft": "Current draft",
|
||||
"skillManagement.detail.deleteFileConfirm": "Delete this file?",
|
||||
"skillManagement.detail.deleteFileSuccess": "File deleted.",
|
||||
"skillManagement.detail.deleteVersionConfirm": "Delete this version?",
|
||||
"skillManagement.detail.deleteVersionFailed": "Failed to delete version.",
|
||||
"skillManagement.detail.deleteVersionSuccess": "Version deleted.",
|
||||
"skillManagement.detail.downloadFile": "Download file",
|
||||
"skillManagement.detail.draft": "Draft",
|
||||
"skillManagement.detail.expandSidebar": "ขยายแถบด้านข้าง",
|
||||
"skillManagement.detail.fileCount": "{{count}} FILES",
|
||||
"skillManagement.detail.fileMeta": "{{type}} · {{size}} bytes",
|
||||
"skillManagement.detail.fileMissing": "File not found.",
|
||||
"skillManagement.detail.fileOperationFailed": "File operation failed.",
|
||||
"skillManagement.detail.files": "Files",
|
||||
"skillManagement.detail.latest": "Latest",
|
||||
"skillManagement.detail.loadFailed": "Failed to load skill.",
|
||||
"skillManagement.detail.markdownLiveMode": "Live preview",
|
||||
"skillManagement.detail.markdownSourceMode": "Source editor",
|
||||
"skillManagement.detail.metadataKey": "name",
|
||||
"skillManagement.detail.metadataValue": "value",
|
||||
"skillManagement.detail.moveFileSuccess": "File moved.",
|
||||
"skillManagement.detail.moveFilesSuccess": "Files moved.",
|
||||
"skillManagement.detail.noFileSelected": "Select a file to preview.",
|
||||
"skillManagement.detail.noFiles": "No files.",
|
||||
"skillManagement.detail.noSearchResults": "ไม่พบไฟล์ที่ตรงกัน",
|
||||
"skillManagement.detail.noVersions": "No published versions yet.",
|
||||
"skillManagement.detail.previewUnsupported": "Preview is not supported for this file.",
|
||||
"skillManagement.detail.publish": "Publish",
|
||||
"skillManagement.detail.publishFailed": "Failed to publish skill.",
|
||||
"skillManagement.detail.publishSuccess": "Skill published.",
|
||||
"skillManagement.detail.published": "Published",
|
||||
"skillManagement.detail.readonly": "Read only",
|
||||
"skillManagement.detail.referenceFiles.confirm": "Enter Confirm",
|
||||
"skillManagement.detail.referenceFiles.empty": "No files available.",
|
||||
"skillManagement.detail.referenceFiles.livePlaceholder": "Write instructions for the agent, type / to reference files",
|
||||
"skillManagement.detail.referenceFiles.navigate": "↑↓ Navigate",
|
||||
"skillManagement.detail.referenceFiles.title": "Reference files",
|
||||
"skillManagement.detail.referencedBy": "Referenced by {{count}} Apps",
|
||||
"skillManagement.detail.removeMetadata": "Delete {{name}} metadata",
|
||||
"skillManagement.detail.removeTag": "Remove {{tag}}",
|
||||
"skillManagement.detail.removeTagSuccess": "Tag removed.",
|
||||
"skillManagement.detail.renameFile": "Rename file",
|
||||
"skillManagement.detail.renameFileDescription": "Enter the new path relative to the skill root.",
|
||||
"skillManagement.detail.renameFileSuccess": "File renamed.",
|
||||
"skillManagement.detail.renameVersion": "Rename",
|
||||
"skillManagement.detail.renameVersionFailed": "Failed to rename version.",
|
||||
"skillManagement.detail.renameVersionPrompt": "Version name",
|
||||
"skillManagement.detail.renameVersionSuccess": "Version renamed.",
|
||||
"skillManagement.detail.restoreVersion": "Restore",
|
||||
"skillManagement.detail.restoreVersionFailed": "Failed to restore version.",
|
||||
"skillManagement.detail.restoreVersionSuccess": "Version restored to draft.",
|
||||
"skillManagement.detail.save": "Save",
|
||||
"skillManagement.detail.saveFailed": "Failed to save file.",
|
||||
"skillManagement.detail.saveSuccess": "File saved.",
|
||||
"skillManagement.detail.saved": "Saved",
|
||||
"skillManagement.detail.savedAt": "Saved {{time}}",
|
||||
"skillManagement.detail.saving": "Saving...",
|
||||
"skillManagement.detail.searchFiles": "ค้นหาไฟล์",
|
||||
"skillManagement.detail.unsavedChanges": "Unsaved changes",
|
||||
"skillManagement.detail.updateTagsFailed": "Failed to update tags.",
|
||||
"skillManagement.detail.uploadFile": "Upload file",
|
||||
"skillManagement.detail.uploadFileFailed": "File upload failed.",
|
||||
"skillManagement.detail.uploadFileSuccess": "File uploaded.",
|
||||
"skillManagement.detail.uploadFilesMenu": "Upload files...",
|
||||
"skillManagement.detail.versionMeta": "{{hash}} · {{time}}",
|
||||
"skillManagement.detail.versions": "Versions",
|
||||
"skillManagement.draft": "Draft",
|
||||
"skillManagement.duplicateFailed": "Failed to duplicate skill.",
|
||||
"skillManagement.duplicateSuccess": "Skill duplicated.",
|
||||
"skillManagement.editedAt": "Edited {{time}}",
|
||||
"skillManagement.empty": "No skills yet",
|
||||
"skillManagement.emptySearch": "No skills found",
|
||||
"skillManagement.import": "Import",
|
||||
"skillManagement.importFailed": "Failed to import skill.",
|
||||
"skillManagement.importSuccess": "Skill imported.",
|
||||
"skillManagement.listLabel": "Workspace skills",
|
||||
"skillManagement.loadingError": "Failed to load skills",
|
||||
"skillManagement.moreActions": "More actions for {{name}}",
|
||||
"skillManagement.noTags": "No tags",
|
||||
"skillManagement.publishedAt": "Published {{time}}",
|
||||
"skillManagement.referenceCount": "{{count}} Refs",
|
||||
"skillManagement.searchLabel": "Search skills",
|
||||
"skillManagement.searchPlaceholder": "Search",
|
||||
"skillManagement.tags": "Tags",
|
||||
"skillManagement.title": "Skills"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user