mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-10-24 01:15:24 +08:00
feat(i18n): localize backend messages by workspace language
This commit is contained in:
@@ -17,6 +17,8 @@ from ....workspace.errors import WorkspaceNotFoundError
|
||||
from ....cloud.entitlements import EntitlementUnavailableError
|
||||
from ....core.errors import TaskCapacityError
|
||||
from ....provider.modelmgr.codex_errors import CodexProviderError
|
||||
from ....i18n import DEFAULT_LANGUAGE, language, language_scope, t, error_text
|
||||
from ....i18n.workspace import get_workspace_language
|
||||
from ..authz import (
|
||||
AuthenticationDeniedError,
|
||||
AuthorizationError,
|
||||
@@ -137,7 +139,7 @@ class RouterGroup(abc.ABC):
|
||||
# receive RequestContext or enforce Workspace permissions.
|
||||
self._inject_handler_context(f, kwargs, user_email, None, account)
|
||||
except Exception as e:
|
||||
return self._auth_error_response(e)
|
||||
return await self._localized_auth_error_response(e)
|
||||
|
||||
elif auth_type == AuthType.USER_TOKEN:
|
||||
# get token from Authorization header
|
||||
@@ -160,7 +162,7 @@ class RouterGroup(abc.ABC):
|
||||
require_permission(request_context, permission)
|
||||
self._inject_handler_context(f, kwargs, user_email, request_context)
|
||||
except Exception as e:
|
||||
return self._auth_error_response(e)
|
||||
return await self._localized_auth_error_response(e)
|
||||
|
||||
elif auth_type == AuthType.API_KEY:
|
||||
# get API key from Authorization header or X-API-Key header
|
||||
@@ -179,7 +181,7 @@ class RouterGroup(abc.ABC):
|
||||
require_permission(request_context, permission)
|
||||
self._inject_handler_context(f, kwargs, None, request_context)
|
||||
except Exception as e:
|
||||
return self._auth_error_response(e)
|
||||
return await self._localized_auth_error_response(e)
|
||||
|
||||
elif auth_type == AuthType.USER_TOKEN_OR_API_KEY:
|
||||
token = quart.request.headers.get('Authorization', '').replace('Bearer ', '')
|
||||
@@ -193,7 +195,7 @@ class RouterGroup(abc.ABC):
|
||||
require_permission(request_context, permission)
|
||||
self._inject_handler_context(f, kwargs, None, request_context)
|
||||
except Exception as e:
|
||||
return self._auth_error_response(e)
|
||||
return await self._localized_auth_error_response(e)
|
||||
# Try API key first (check X-API-Key header)
|
||||
elif api_key := quart.request.headers.get('X-API-Key', ''):
|
||||
# API key authentication
|
||||
@@ -203,7 +205,7 @@ class RouterGroup(abc.ABC):
|
||||
require_permission(request_context, permission)
|
||||
self._inject_handler_context(f, kwargs, None, request_context)
|
||||
except Exception as e:
|
||||
return self._auth_error_response(e)
|
||||
return await self._localized_auth_error_response(e)
|
||||
else:
|
||||
# Try user token authentication (Authorization header)
|
||||
if not token:
|
||||
@@ -223,7 +225,7 @@ class RouterGroup(abc.ABC):
|
||||
# Authentication succeeded and authorization was
|
||||
# evaluated. Do not reinterpret a denied user token
|
||||
# as an API key, which would mask the stable 403/404.
|
||||
return self._auth_error_response(e)
|
||||
return await self._localized_auth_error_response(e)
|
||||
except Exception:
|
||||
# If user token fails, maybe it's an API key in Authorization header
|
||||
try:
|
||||
@@ -232,10 +234,12 @@ class RouterGroup(abc.ABC):
|
||||
require_permission(request_context, permission)
|
||||
self._inject_handler_context(f, kwargs, None, request_context)
|
||||
except Exception as e:
|
||||
return self._auth_error_response(e)
|
||||
return await self._localized_auth_error_response(e)
|
||||
|
||||
observed_at = time.monotonic()
|
||||
try:
|
||||
if request_context is not None:
|
||||
language.set(await get_workspace_language(self.ap, request_context.workspace_uuid))
|
||||
if request_context is not None:
|
||||
with bounded_executor.blocking_work_scope(request_context.workspace_uuid):
|
||||
persistence_mgr = getattr(
|
||||
@@ -319,7 +323,11 @@ class RouterGroup(abc.ABC):
|
||||
)
|
||||
return self.internal_error_response(request_id)
|
||||
|
||||
new_f = handler_error
|
||||
async def new_f(*args, **kwargs):
|
||||
# Reset even on errors; child tasks inherit this request's locale.
|
||||
with language_scope(quart.request.headers.get('X-LangBot-Language', DEFAULT_LANGUAGE)):
|
||||
return await handler_error(*args, **kwargs)
|
||||
|
||||
# Quart/Flask requires a unique endpoint name even when the same URL
|
||||
# intentionally has separate handlers for different HTTP methods.
|
||||
# Include the method set so CRUD routes can declare distinct
|
||||
@@ -687,6 +695,14 @@ class RouterGroup(abc.ABC):
|
||||
'Invalid authentication credentials',
|
||||
)
|
||||
|
||||
async def _localized_auth_error_response(self, error: Exception):
|
||||
# A resolved context is trusted even when its permissions deny this
|
||||
# operation. Never look up a language using the unverified selector.
|
||||
context = getattr(quart.g, 'request_context', None)
|
||||
if context is not None:
|
||||
language.set(await get_workspace_language(self.ap, context.workspace_uuid))
|
||||
return self._auth_error_response(error)
|
||||
|
||||
def request_id(self) -> str:
|
||||
"""Return one stable request ID for authentication, logs, and errors."""
|
||||
|
||||
@@ -707,7 +723,7 @@ class RouterGroup(abc.ABC):
|
||||
response = quart.jsonify(
|
||||
{
|
||||
'code': 'internal_error',
|
||||
'msg': 'Internal server error',
|
||||
'msg': t('Internal server error'),
|
||||
'request_id': resolved_request_id,
|
||||
}
|
||||
)
|
||||
@@ -729,7 +745,7 @@ class RouterGroup(abc.ABC):
|
||||
return quart.jsonify(
|
||||
{
|
||||
'code': code,
|
||||
'msg': msg,
|
||||
'msg': error_text(code, msg),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .....i18n import t
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import json
|
||||
@@ -45,20 +47,20 @@ def debug_stream_response(service, context, agent_uuid: str, payload: dict) -> q
|
||||
if isinstance(exc, RunnerExecutionError):
|
||||
code, message = exc.error_code or 'runner_execution_failed', exc.message
|
||||
elif isinstance(exc, RunnerNotFoundError):
|
||||
code, message = 'runner_not_found', 'The configured Agent runner is unavailable'
|
||||
code, message = 'runner_not_found', t('Agent runner temporarily unavailable. Please try again.')
|
||||
elif isinstance(exc, RunnerNotAuthorizedError):
|
||||
code, message = 'runner_not_authorized', 'The configured Agent runner is not authorized'
|
||||
code, message = 'runner_not_authorized', t('Agent runner not authorized for this pipeline.')
|
||||
elif isinstance(exc, RunnerProtocolError):
|
||||
code, message = 'runner_protocol_error', 'The Agent runner returned an invalid response'
|
||||
code, message = 'runner_protocol_error', t('The Agent runner returned an invalid response.')
|
||||
elif isinstance(exc, ValueError):
|
||||
code, message = 'invalid_request', str(exc)
|
||||
elif isinstance(exc, RunnerError):
|
||||
code, message = 'runner_error', 'The Agent runner could not complete this test'
|
||||
code, message = 'runner_error', t('Agent runner execution failed.')
|
||||
else:
|
||||
logger = getattr(application, 'logger', None)
|
||||
if logger is not None:
|
||||
logger.exception('Agent debug stream execution failed')
|
||||
code, message = 'runner_error', 'The Agent debug execution failed'
|
||||
code, message = 'runner_error', t('Agent runner execution failed.')
|
||||
await queue.put({'kind': 'error', 'code': code, 'msg': message})
|
||||
|
||||
task = asyncio.create_task(execute())
|
||||
|
||||
@@ -21,6 +21,8 @@ import httpx
|
||||
import quart
|
||||
|
||||
from ... import group
|
||||
from ......i18n import language, t
|
||||
from ......i18n.workspace import get_workspace_language
|
||||
from ......utils import httpclient, paths
|
||||
from ......platform.sources.websocket_manager import WebSocketScope, is_valid_session_id, ws_connection_manager
|
||||
from .websocket_chat import create_scoped_duplex_tasks, wait_for_duplex_tasks
|
||||
@@ -73,6 +75,7 @@ class EmbedRouterGroup(group.RouterGroup):
|
||||
bot = await self.ap.platform_mgr.resolve_public_bot(bot_uuid)
|
||||
pipeline_uuid = bot.get_pipeline_target_for_event_type('message.received') if bot is not None else None
|
||||
if bot is not None and bot.bot_entity.adapter == 'web_page_bot' and bot.bot_entity.enable and pipeline_uuid:
|
||||
language.set(await get_workspace_language(self.ap, bot.execution_context.workspace_uuid))
|
||||
return bot, pipeline_uuid
|
||||
return None, None
|
||||
|
||||
@@ -284,7 +287,7 @@ class EmbedRouterGroup(group.RouterGroup):
|
||||
return self.http_status(404, -1, 'WebSocket adapter not found')
|
||||
|
||||
websocket_adapter.reset_session(pipeline_uuid, session_type, session_id)
|
||||
return self.success(data={'message': 'Session reset successfully'})
|
||||
return self.success(data={'message': t('Session reset successfully')})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Failed to reset embed session: {e}', exc_info=True)
|
||||
@@ -338,7 +341,9 @@ class EmbedRouterGroup(group.RouterGroup):
|
||||
|
||||
runtime_bot, pipeline_uuid = await self._resolve_bot(bot_uuid)
|
||||
if runtime_bot is None:
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Bot not found or not available'}))
|
||||
await quart.websocket.send(
|
||||
json.dumps({'type': 'error', 'message': t('Bot not found or not available')})
|
||||
)
|
||||
return
|
||||
|
||||
session_type = quart.websocket.args.get('session_type', 'person')
|
||||
@@ -357,13 +362,15 @@ class EmbedRouterGroup(group.RouterGroup):
|
||||
await self._authenticate_websocket(runtime_bot)
|
||||
await self._assert_execution_active(runtime_bot)
|
||||
except Exception:
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Unauthorized'}))
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Unauthorized')}))
|
||||
return
|
||||
try:
|
||||
proxy_bot = await self.ap.platform_mgr.get_websocket_proxy_bot(runtime_bot.execution_context)
|
||||
websocket_adapter = proxy_bot.adapter
|
||||
if not websocket_adapter:
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': 'WebSocket adapter not found'}))
|
||||
await quart.websocket.send(
|
||||
json.dumps({'type': 'error', 'message': t('WebSocket adapter not found')})
|
||||
)
|
||||
return
|
||||
|
||||
connection = await ws_connection_manager.add_connection(
|
||||
@@ -428,7 +435,7 @@ class EmbedRouterGroup(group.RouterGroup):
|
||||
except Exception as e:
|
||||
logger.error(f'Embed WebSocket connection error: {e}', exc_info=True)
|
||||
try:
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Internal server error'}))
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Internal server error')}))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -451,7 +458,7 @@ class EmbedRouterGroup(group.RouterGroup):
|
||||
try:
|
||||
current_bot = await self._resolve_connected_bot(owner_bot, pipeline_uuid)
|
||||
except Exception:
|
||||
await connection.send_queue.put({'type': 'error', 'message': 'Bot is unavailable'})
|
||||
await connection.send_queue.put({'type': 'error', 'message': t('Bot is unavailable')})
|
||||
break
|
||||
await websocket_adapter.handle_websocket_message(connection, data, owner_bot=current_bot)
|
||||
elif message_type == 'disconnect':
|
||||
@@ -460,7 +467,7 @@ class EmbedRouterGroup(group.RouterGroup):
|
||||
pass
|
||||
|
||||
except json.JSONDecodeError:
|
||||
await connection.send_queue.put({'type': 'error', 'message': 'Invalid JSON format'})
|
||||
await connection.send_queue.put({'type': 'error', 'message': t('Invalid JSON format')})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Embed receive error: {e}', exc_info=True)
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ......i18n import language, t
|
||||
from ......i18n.workspace import get_workspace_language
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import json
|
||||
@@ -217,8 +220,9 @@ class WebSocketChatRouterGroup(group.RouterGroup):
|
||||
try:
|
||||
request_context, token = await self._authenticate_websocket()
|
||||
except Exception:
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Unauthorized'}))
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Unauthorized')}))
|
||||
return
|
||||
language.set(await get_workspace_language(self.ap, request_context.workspace_uuid))
|
||||
session_type = quart.websocket.args.get('session_type', 'person')
|
||||
if session_type not in ['person', 'group']:
|
||||
await quart.websocket.send(
|
||||
@@ -229,7 +233,7 @@ class WebSocketChatRouterGroup(group.RouterGroup):
|
||||
try:
|
||||
websocket_adapter = await self._get_scoped_adapter(request_context, pipeline_uuid)
|
||||
if websocket_adapter is None:
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Pipeline not found'}))
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Pipeline not found')}))
|
||||
return
|
||||
|
||||
connection = await ws_connection_manager.add_connection(
|
||||
@@ -294,7 +298,7 @@ class WebSocketChatRouterGroup(group.RouterGroup):
|
||||
except Exception:
|
||||
logger.error('Dashboard WebSocket connection error', exc_info=True)
|
||||
try:
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': 'Internal server error'}))
|
||||
await quart.websocket.send(json.dumps({'type': 'error', 'message': t('Internal server error')}))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -419,7 +423,7 @@ class WebSocketChatRouterGroup(group.RouterGroup):
|
||||
try:
|
||||
request_context = await self._revalidate_websocket_authorization(request_context, token)
|
||||
except Exception:
|
||||
await connection.send_queue.put({'type': 'error', 'message': 'Unauthorized'})
|
||||
await connection.send_queue.put({'type': 'error', 'message': t('Unauthorized')})
|
||||
break
|
||||
await websocket_adapter.handle_websocket_message(connection, data)
|
||||
elif message_type == 'disconnect':
|
||||
@@ -427,7 +431,7 @@ class WebSocketChatRouterGroup(group.RouterGroup):
|
||||
else:
|
||||
logger.warning(f'Unknown WebSocket message type: {message_type}')
|
||||
except json.JSONDecodeError:
|
||||
await connection.send_queue.put({'type': 'error', 'message': 'Invalid JSON format'})
|
||||
await connection.send_queue.put({'type': 'error', 'message': t('Invalid JSON format')})
|
||||
|
||||
except Exception:
|
||||
logger.error('Dashboard WebSocket receive error', exc_info=True)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from langbot.pkg.i18n import t, DEFAULT_LANGUAGE
|
||||
from langbot.pkg.i18n.workspace import get_workspace_language
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import mimetypes
|
||||
@@ -213,16 +215,20 @@ class AdaptersRouterGroup(group.RouterGroup):
|
||||
|
||||
@self.route('/slack/setup/callback', methods=['GET'], auth_type=group.AuthType.NONE)
|
||||
async def slack_callback():
|
||||
session = slack_setup.sessions.get(quart.request.args.get('state', ''))
|
||||
locale = DEFAULT_LANGUAGE
|
||||
if session is not None:
|
||||
locale = await get_workspace_language(self.ap, session['context'].workspace_uuid)
|
||||
try:
|
||||
await slack_setup.finish(
|
||||
quart.request.args.get('state', ''),
|
||||
quart.request.args.get('code', ''),
|
||||
quart.request.args.get('error'),
|
||||
)
|
||||
message = 'Slack authorization completed. Return to LangBot to finish setup. / Slack 授权完成,请返回 LangBot 完成配置。'
|
||||
message = t('Slack authorization completed. Return to LangBot to finish setup.', locale=locale)
|
||||
status = 200
|
||||
except SlackSetupError:
|
||||
message = 'Slack authorization failed or expired. Return to LangBot for details. / Slack 授权失败或已过期,请返回 LangBot 查看详情。'
|
||||
message = t('Slack authorization failed or expired. Return to LangBot for details.', locale=locale)
|
||||
status = 400
|
||||
return quart.Response(
|
||||
message,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .....i18n import t
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import collections.abc
|
||||
@@ -305,7 +307,11 @@ class PluginsRouterGroup(group.RouterGroup):
|
||||
mcp_servers = await self.ap.mcp_service.get_mcp_servers(request_context)
|
||||
total_extensions = len(plugins) + len(mcp_servers)
|
||||
if total_extensions >= max_extensions:
|
||||
return self.http_status(400, -1, f'Maximum number of extensions ({max_extensions}) reached')
|
||||
return self.http_status(
|
||||
400,
|
||||
-1,
|
||||
t('Maximum number of {resource} ({limit}) reached', resource=t('extensions'), limit=max_extensions),
|
||||
)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -14,6 +14,8 @@ from .....workspace.errors import WorkspaceNotFoundError
|
||||
from .....workspace.invitation_delivery import InvitationDeliveryService
|
||||
from .....operation_trace import service as settings_service
|
||||
from .. import group
|
||||
from .....i18n import SUPPORTED_LANGUAGES
|
||||
from .....i18n.workspace import get_workspace_language, set_workspace_language
|
||||
|
||||
|
||||
def _workspace_payload(workspace: Workspace) -> dict[str, typing.Any]:
|
||||
@@ -74,6 +76,34 @@ class WorkspacesRouterGroup(group.RouterGroup):
|
||||
return await operation()
|
||||
|
||||
async def initialize(self) -> None:
|
||||
@self.route(
|
||||
'/current/language',
|
||||
methods=['GET'],
|
||||
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
|
||||
permission=Permission.WORKSPACE_VIEW,
|
||||
)
|
||||
async def get_language(request_context: RequestContext):
|
||||
return self.success(
|
||||
data={
|
||||
'language': await get_workspace_language(self.ap, request_context.workspace_uuid, refresh=True),
|
||||
'supported_languages': list(SUPPORTED_LANGUAGES),
|
||||
}
|
||||
)
|
||||
|
||||
@self.route(
|
||||
'/current/language',
|
||||
methods=['PUT'],
|
||||
auth_type=group.AuthType.USER_TOKEN_OR_API_KEY,
|
||||
permission=Permission.WORKSPACE_UPDATE,
|
||||
)
|
||||
async def update_language(request_context: RequestContext):
|
||||
body = await quart.request.get_json()
|
||||
locale = body.get('language') if isinstance(body, dict) else None
|
||||
if locale not in SUPPORTED_LANGUAGES:
|
||||
return self.http_status(400, 'invalid_language', 'Unsupported Workspace language')
|
||||
locale = await set_workspace_language(self.ap, request_context.workspace_uuid, locale)
|
||||
return self.success(data={'language': locale})
|
||||
|
||||
@self.route('/bootstrap', methods=['GET'], auth_type=group.AuthType.ACCOUNT_TOKEN)
|
||||
async def _(account) -> typing.Any:
|
||||
"""List the active Workspaces available to an authenticated Account.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import t
|
||||
|
||||
from contextlib import aclosing
|
||||
|
||||
import datetime
|
||||
@@ -432,7 +434,7 @@ class AgentService:
|
||||
pipeline_uuid = await self.ap.pipeline_service.create_pipeline(
|
||||
context,
|
||||
{
|
||||
'name': agent_data.get('name') or 'New Pipeline',
|
||||
'name': agent_data.get('name') or t('New Pipeline'),
|
||||
'description': agent_data.get('description') or '',
|
||||
'emoji': agent_data.get('emoji') or '⚙️',
|
||||
'config': {},
|
||||
@@ -461,7 +463,7 @@ class AgentService:
|
||||
values = {
|
||||
'workspace_uuid': workspace_uuid,
|
||||
'uuid': new_uuid,
|
||||
'name': agent_data.get('name') or 'New Agent',
|
||||
'name': agent_data.get('name') or t('New Agent'),
|
||||
'description': agent_data.get('description') or '',
|
||||
'emoji': agent_data.get('emoji') or ('🧩' if kind == AGENT_KIND_EVENT_PROCESSOR else '🤖'),
|
||||
'kind': kind,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import t
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
from ....api.http.authz import WorkspaceRequiredError
|
||||
@@ -63,7 +65,13 @@ class KnowledgeService:
|
||||
if max_knowledge_bases >= 0:
|
||||
knowledge_bases = await self.ap.rag_mgr.get_all_knowledge_base_details(context)
|
||||
if len(knowledge_bases) >= max_knowledge_bases:
|
||||
raise ValueError(f'Maximum number of knowledge bases ({max_knowledge_bases}) reached')
|
||||
raise ValueError(
|
||||
t(
|
||||
'Maximum number of {resource} ({limit}) reached',
|
||||
resource=t('knowledge bases'),
|
||||
limit=max_knowledge_bases,
|
||||
)
|
||||
)
|
||||
|
||||
knowledge_engine_plugin_id = kb_data.get('knowledge_engine_plugin_id')
|
||||
if not knowledge_engine_plugin_id:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import t
|
||||
|
||||
import copy
|
||||
import re
|
||||
import uuid
|
||||
@@ -228,7 +230,9 @@ class MCPService:
|
||||
.where(persistence_plugin.PluginSetting.workspace_uuid == workspace_uuid)
|
||||
)
|
||||
if (mcp_count_result.scalar() or 0) + (plugin_count_result.scalar() or 0) >= max_extensions:
|
||||
raise ValueError(f'Maximum number of extensions ({max_extensions}) reached')
|
||||
raise ValueError(
|
||||
t('Maximum number of {resource} ({limit}) reached', resource=t('extensions'), limit=max_extensions)
|
||||
)
|
||||
|
||||
payload = dict(server_data)
|
||||
payload.pop('workspace_uuid', None)
|
||||
@@ -246,7 +250,7 @@ class MCPService:
|
||||
)
|
||||
)
|
||||
if existing_result.first() is not None:
|
||||
raise ValueError(f'MCP server already exists: {server_name}')
|
||||
raise ValueError(t('MCP server already exists: {name}', name=server_name))
|
||||
|
||||
await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_mcp.MCPServer).values(payload))
|
||||
created = await self._get_mcp_server_by_uuid_raw(execution_context, payload['uuid'])
|
||||
@@ -332,7 +336,7 @@ class MCPService:
|
||||
raise ValueError('MCP server name is required')
|
||||
duplicate = await self._get_mcp_server_by_name_raw(execution_context, payload['name'])
|
||||
if duplicate is not None and duplicate['uuid'] != server_uuid:
|
||||
raise ValueError(f'MCP server already exists: {payload["name"]}')
|
||||
raise ValueError(t('MCP server already exists: {name}', name=payload['name']))
|
||||
|
||||
effective_server = {**old_server, **payload}
|
||||
# Existing disabled rows remain readable/deletable. Switching away
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import t
|
||||
|
||||
import uuid
|
||||
|
||||
import sqlalchemy
|
||||
@@ -146,7 +148,13 @@ async def _validate_provider_supports(
|
||||
if not support_type:
|
||||
return
|
||||
if model_type not in support_type:
|
||||
raise ValueError(f'Provider requester "{requester_name}" does not support {model_type} models')
|
||||
raise ValueError(
|
||||
t(
|
||||
'Provider {provider} does not support {model_type} models',
|
||||
provider=requester_name,
|
||||
model_type=model_type,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def _require_workspace_provider(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import t
|
||||
|
||||
import sqlalchemy
|
||||
import argon2
|
||||
import jwt
|
||||
@@ -423,12 +425,12 @@ class UserService:
|
||||
|
||||
user_obj = await self.get_user_by_email(user_email)
|
||||
if user_obj is None:
|
||||
raise ValueError('用户不存在')
|
||||
raise ValueError(t('Resource not found'))
|
||||
self._require_active_account(user_obj)
|
||||
|
||||
# Check if this user has a local password set
|
||||
if not user_obj.password:
|
||||
raise ValueError('请使用 LangBot 账号登录')
|
||||
raise ValueError(t('Please sign in with your LangBot Account'))
|
||||
|
||||
await self._verify_password(user_obj.password, password)
|
||||
|
||||
|
||||
@@ -19,11 +19,14 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import typing
|
||||
import functools
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from ..http.authz import Permission, require_permission
|
||||
from .context import get_request_context
|
||||
from ...i18n import language_scope
|
||||
from ...i18n.workspace import get_workspace_language, set_workspace_language
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from ...core import app as app_module
|
||||
@@ -78,6 +81,20 @@ class LangBotMCPServer:
|
||||
ap = self.ap
|
||||
mcp = self
|
||||
|
||||
@mcp.tool(description='Get the Workspace language used for backend messages and default bot replies.')
|
||||
async def get_workspace_message_language() -> str:
|
||||
context = _authorized(Permission.WORKSPACE_VIEW)
|
||||
return _dump({'language': await get_workspace_language(ap, context.workspace_uuid, refresh=True)})
|
||||
|
||||
@mcp.tool(
|
||||
description='Set the Workspace message language. Requires workspace.update; does not change personal UI language.'
|
||||
)
|
||||
async def set_workspace_message_language(
|
||||
language: typing.Literal['en_US', 'zh_Hans', 'zh_Hant', 'ja_JP', 'es_ES', 'ru_RU', 'th_TH', 'vi_VN'],
|
||||
) -> str:
|
||||
context = _authorized(Permission.WORKSPACE_UPDATE)
|
||||
return _dump({'language': await set_workspace_language(ap, context.workspace_uuid, language)})
|
||||
|
||||
# ----- System (read-only) -------------------------------------- #
|
||||
@mcp.tool(description='Get basic LangBot system/runtime information (version, edition).')
|
||||
async def get_system_info() -> str:
|
||||
@@ -571,7 +588,12 @@ class LangBotMCPServer:
|
||||
"""Register a tool boundary before its authorization and service call."""
|
||||
|
||||
def register(fn):
|
||||
observed = fn
|
||||
@functools.wraps(fn)
|
||||
async def observed(*args, **kwargs):
|
||||
context = get_request_context()
|
||||
with language_scope(await get_workspace_language(self.ap, context.workspace_uuid)):
|
||||
return await fn(*args, **kwargs)
|
||||
|
||||
return self.mcp.tool(**options)(observed)
|
||||
|
||||
return register
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..i18n import t
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import typing
|
||||
@@ -202,7 +204,7 @@ class TaskWrapper:
|
||||
"""Return the stable task projection exposed to API-key callers."""
|
||||
if self.task.cancelled():
|
||||
status = 'cancelled'
|
||||
error = {'type': 'task_cancelled', 'message': 'Task was cancelled'}
|
||||
error = {'type': 'task_cancelled', 'message': t('Task was cancelled')}
|
||||
result = None
|
||||
elif not self.task.done():
|
||||
status = 'running'
|
||||
@@ -212,7 +214,7 @@ class TaskWrapper:
|
||||
exception = self.assume_exception()
|
||||
if exception is not None:
|
||||
status = 'failed'
|
||||
error = {'type': 'task_failed', 'message': 'Task execution failed'}
|
||||
error = {'type': 'task_failed', 'message': t('Task execution failed')}
|
||||
result = None
|
||||
else:
|
||||
status = 'succeeded'
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
"""Small, dependency-free catalog for LangBot-owned runtime messages.
|
||||
|
||||
English message templates are the stable catalog IDs. Diagnostic text from
|
||||
providers, plugins and validators is deliberately not translated.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import contextvars
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
DEFAULT_LANGUAGE = 'en_US'
|
||||
SUPPORTED_LANGUAGES = ('en_US', 'zh_Hans', 'zh_Hant', 'ja_JP', 'es_ES', 'ru_RU', 'th_TH', 'vi_VN')
|
||||
language = contextvars.ContextVar('langbot_message_language', default=DEFAULT_LANGUAGE)
|
||||
_catalogs = {
|
||||
locale: json.loads((Path(__file__).parent / 'locales' / f'{locale}.json').read_text(encoding='utf-8'))
|
||||
for locale in SUPPORTED_LANGUAGES
|
||||
}
|
||||
|
||||
# Normalize existing API wording at the response boundary. Only exact owned
|
||||
# messages are matched; validators and third-party diagnostics pass through.
|
||||
_ALIASES = {
|
||||
**{
|
||||
name: 'Resource not found'
|
||||
for name in (
|
||||
'bot not found',
|
||||
'model not found',
|
||||
'provider not found',
|
||||
'plugin not found',
|
||||
'Server not found',
|
||||
'Skill not found',
|
||||
'Pipeline not found',
|
||||
'pipeline not found',
|
||||
'knowledge base not found',
|
||||
'Knowledge base not found',
|
||||
'User not found',
|
||||
'Account not found',
|
||||
'agent not found',
|
||||
'API key not found',
|
||||
'Message not found',
|
||||
'Task not found',
|
||||
'Passkey not found',
|
||||
'Webhook not found',
|
||||
'Bot not found or not available',
|
||||
'Bot is unavailable',
|
||||
'WebSocket adapter not found',
|
||||
'adapter not found',
|
||||
'requester not found',
|
||||
'icon not found',
|
||||
'Session not found',
|
||||
)
|
||||
},
|
||||
'Unauthorized': 'Invalid authentication credentials',
|
||||
'No valid user token provided': 'Invalid authentication credentials',
|
||||
'No valid API key provided': 'Invalid authentication credentials',
|
||||
'Invalid username or password': 'Invalid authentication credentials',
|
||||
'Invalid TOTP code': 'Invalid TOTP or recovery code',
|
||||
'Too many attempts, try again later': 'Too many requests. Please try again later.',
|
||||
'The configured Agent runner is unavailable': 'Agent runner temporarily unavailable. Please try again.',
|
||||
'The configured Agent runner is not authorized': 'Agent runner not authorized for this pipeline.',
|
||||
'The Agent runner could not complete this test': 'Agent runner execution failed.',
|
||||
'Forbidden': 'Permission denied',
|
||||
'Member removal permission is required': 'Permission denied',
|
||||
'The Agent runner returned an invalid response': 'The Agent runner returned an invalid response.',
|
||||
'Invalid or expired state': 'Authorization failed. Please try signing in again.',
|
||||
'Space OAuth Workspace binding mismatch': 'Authorization failed. Please try signing in again.',
|
||||
'Space OAuth response is missing the Cloud Workspace binding': 'Authorization failed. Please try signing in again.',
|
||||
'Space launch requires Cloud mode': 'This feature is unavailable in this Workspace.',
|
||||
'An Account is required': 'Please sign in with your LangBot Account',
|
||||
'ChatGPT authorization requires an authorized workspace user': 'Permission denied',
|
||||
'ChatGPT authorization is busy. Please retry shortly.': 'Server is busy. Please try again later.',
|
||||
'ChatGPT authorization network error. Please retry.': 'Connection failed',
|
||||
'ChatGPT credential storage failed. Please retry.': 'Failed to save ChatGPT credentials. Please retry.',
|
||||
'ChatGPT Codex response was not completed': 'ChatGPT Codex response failed or was incomplete. Please retry.',
|
||||
'ChatGPT Codex stream ended before completion. Please retry.': 'ChatGPT Codex response failed or was incomplete. Please retry.',
|
||||
'Not permitted': 'Permission denied',
|
||||
'Unauthorized or session expired': 'Invalid authentication credentials',
|
||||
'TOTP is not enabled for that Account': 'TOTP is not enabled for this Account',
|
||||
'No pending TOTP enrolment for this Account': 'TOTP is not enabled for this Account',
|
||||
'Too many active credential exchanges': 'Server is busy. Please try again later.',
|
||||
'Timeout waiting for QR code': 'Request timed out',
|
||||
'Model availability is unavailable': 'This feature is unavailable in this Workspace.',
|
||||
'Survey not available': 'This feature is unavailable in this Workspace.',
|
||||
**{
|
||||
message: 'Submission failed. Please try again.'
|
||||
for message in (
|
||||
'Failed to submit response',
|
||||
'Failed to submit feedback',
|
||||
'Failed to dismiss',
|
||||
)
|
||||
},
|
||||
**{
|
||||
message: 'File size exceeds the limit.'
|
||||
for message in (
|
||||
'Image size exceeds 10MB limit.',
|
||||
'file size exceeds 10MB limit',
|
||||
'attachment too large',
|
||||
)
|
||||
},
|
||||
**{
|
||||
message: 'Authorization failed. Please try signing in again.'
|
||||
for message in (
|
||||
'Space launch failed',
|
||||
'Space OAuth failed',
|
||||
'LangBot Account binding failed',
|
||||
'Failed to get access token from Space',
|
||||
'ChatGPT returned an incomplete authorization. Please sign in again.',
|
||||
'ChatGPT authorization was cancelled or replaced. Please retry.',
|
||||
'ChatGPT returned an invalid authorization response',
|
||||
'ChatGPT returned an invalid device code',
|
||||
'ChatGPT returned an invalid device code expiry',
|
||||
'ChatGPT device authorization failed. Please start again.',
|
||||
'ChatGPT returned an incomplete device authorization',
|
||||
'ChatGPT token exchange failed. Please start sign-in again.',
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def normalize_language(value: str | None) -> str:
|
||||
value = (value or '').replace('-', '_')
|
||||
aliases = {
|
||||
'zh_CN': 'zh_Hans',
|
||||
'zh_TW': 'zh_Hant',
|
||||
'en': 'en_US',
|
||||
'ja': 'ja_JP',
|
||||
'es': 'es_ES',
|
||||
'ru': 'ru_RU',
|
||||
'th': 'th_TH',
|
||||
'vi': 'vi_VN',
|
||||
}
|
||||
value = aliases.get(value, value)
|
||||
return value if value in SUPPORTED_LANGUAGES else DEFAULT_LANGUAGE
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def language_scope(locale: str):
|
||||
token = language.set(normalize_language(locale))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
language.reset(token)
|
||||
|
||||
|
||||
def t(message: str, *, locale: str | None = None, **params) -> str:
|
||||
selected = normalize_language(locale or language.get())
|
||||
# Preserve established English diagnostic wording and resource names.
|
||||
# Aliases only share translations; they do not rewrite the source language.
|
||||
key = _ALIASES.get(message, message)
|
||||
text = message if selected == DEFAULT_LANGUAGE else _catalogs[selected].get(key, message)
|
||||
return text.format(**params) if params else text
|
||||
|
||||
|
||||
def error_text(code: int | str, message: str) -> str:
|
||||
"""Stable business codes take precedence over variable diagnostic wording."""
|
||||
if language.get() == DEFAULT_LANGUAGE:
|
||||
return message
|
||||
messages = {
|
||||
'permission_denied': 'Permission denied',
|
||||
'resource_not_found': 'Resource not found',
|
||||
'membership_not_found': 'Resource not found',
|
||||
'invalid_authentication': 'Invalid authentication credentials',
|
||||
'task_capacity_exceeded': 'Server is busy. Please try again later.',
|
||||
'blocking_work_capacity_exceeded': 'Server is busy. Please try again later.',
|
||||
'edition_limit': 'This feature is unavailable in this Workspace.',
|
||||
'entitlement_unavailable': 'This feature is unavailable in this Workspace.',
|
||||
'last_owner_required': 'The Workspace must retain an owner.',
|
||||
'already_a_member': 'This user is already a member.',
|
||||
'invitation_email_mismatch': 'The invitation email does not match your Account',
|
||||
'totp_invalid_code': 'Invalid TOTP or recovery code',
|
||||
'totp_challenge_invalid': 'Invalid TOTP or recovery code',
|
||||
'totp_already_enrolled': 'TOTP is already enabled for this Account',
|
||||
'totp_not_enrolled': 'TOTP is not enabled for this Account',
|
||||
}
|
||||
if code in ('invitation_invalid', 'invitation_expired', 'invitation_revoked', 'invitation_used'):
|
||||
return t('Invalid or expired invitation')
|
||||
return t(messages.get(code, message))
|
||||
|
||||
|
||||
def pipeline_notice(query, name: str, default: str, **params) -> str:
|
||||
"""Empty overrides inherit the Workspace language; user text stays literal.
|
||||
|
||||
Only documented placeholders are substituted, so arbitrary braces in custom
|
||||
messages cannot cause a formatting exception.
|
||||
"""
|
||||
config = query.pipeline_config.get('output', {}).get('misc', {})
|
||||
custom = config.get(name)
|
||||
# This was the shipped default before language inheritance existed.
|
||||
if name == 'failure-hint' and custom == 'Request failed.':
|
||||
custom = None
|
||||
if not isinstance(custom, str) or not custom.strip():
|
||||
return t(default, **params)
|
||||
for key, value in params.items():
|
||||
custom = custom.replace('{' + key + '}', str(value))
|
||||
return custom
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Language boundaries for adapter-owned replies outside the Pipeline."""
|
||||
|
||||
import functools
|
||||
|
||||
from . import DEFAULT_LANGUAGE, language_scope, t
|
||||
from .workspace import _cache, get_workspace_language
|
||||
|
||||
|
||||
def _owner(adapter):
|
||||
logger = getattr(adapter, 'logger', None)
|
||||
context = getattr(logger, 'execution_context', None)
|
||||
workspace_uuid = getattr(context, 'workspace_uuid', None)
|
||||
return getattr(logger, 'ap', None), workspace_uuid if isinstance(workspace_uuid, str) else None
|
||||
|
||||
|
||||
def adapter_t(adapter, message: str, **params) -> str:
|
||||
"""Synchronous SDK callbacks use the bot's own cached language, never a peer's."""
|
||||
ap, workspace_uuid = _owner(adapter)
|
||||
cached = _cache(ap).get(workspace_uuid) if ap is not None and workspace_uuid else None
|
||||
return t(message, locale=cached[0] if cached else DEFAULT_LANGUAGE, **params)
|
||||
|
||||
|
||||
def localized_adapter(method):
|
||||
@functools.wraps(method)
|
||||
async def wrapped(self, *args, **kwargs):
|
||||
ap, workspace_uuid = _owner(self)
|
||||
locale = await get_workspace_language(ap, workspace_uuid) if ap is not None else DEFAULT_LANGUAGE
|
||||
with language_scope(locale):
|
||||
return await method(self, *args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"Request failed.": "Request failed.",
|
||||
"Too many requests. Please try again later.": "Too many requests. Please try again later.",
|
||||
"Your message contains inappropriate content. Please revise it.": "Your message contains inappropriate content. Please revise it.",
|
||||
"Content checking failed. Please contact the administrator.": "Content checking failed. Please contact the administrator.",
|
||||
"Calling {tools}...": "Calling {tools}...",
|
||||
"Agent runner not found: {runner}": "Agent runner not found: {runner}",
|
||||
"Agent runner not authorized for this pipeline.": "Agent runner not authorized for this pipeline.",
|
||||
"Agent runner temporarily unavailable. Please try again.": "Agent runner temporarily unavailable. Please try again.",
|
||||
"Agent runner execution failed.": "Agent runner execution failed.",
|
||||
"The Agent runner returned an invalid response.": "The Agent runner returned an invalid response.",
|
||||
"Model request failed": "Model request failed",
|
||||
"Context length exceeded": "Context length exceeded",
|
||||
"Invalid request parameters": "Invalid request parameters",
|
||||
"Invalid API key": "Invalid API key",
|
||||
"Invalid model or endpoint": "Invalid model or endpoint",
|
||||
"Rate limit exceeded or insufficient balance": "Rate limit exceeded or insufficient balance",
|
||||
"Request timed out": "Request timed out",
|
||||
"Connection failed": "Connection failed",
|
||||
"API error": "API error",
|
||||
"Unknown error": "Unknown error",
|
||||
"Internal server error": "Internal server error",
|
||||
"Resource not found": "Resource not found",
|
||||
"Permission denied": "Permission denied",
|
||||
"Invalid authentication credentials": "Invalid authentication credentials",
|
||||
"MCP runtime failed": "MCP runtime failed",
|
||||
"Task execution failed": "Task execution failed",
|
||||
"Task was cancelled": "Task was cancelled",
|
||||
"Maximum number of {resource} ({limit}) reached": "Maximum number of {resource} ({limit}) reached",
|
||||
"extensions": "extensions",
|
||||
"knowledge bases": "knowledge bases",
|
||||
"MCP server already exists: {name}": "MCP server already exists: {name}",
|
||||
"LangBot Models is managed by Cloud and cannot be modified": "LangBot Models is managed by Cloud and cannot be modified",
|
||||
"Please sign in with your LangBot Account": "Please sign in with your LangBot Account",
|
||||
"Current password is incorrect": "Current password is incorrect",
|
||||
"Invalid TOTP or recovery code": "Invalid TOTP or recovery code",
|
||||
"No local password set, please set a password first": "No local password set, please set a password first",
|
||||
"This LangBot Account is already bound to another user": "This LangBot Account is already bound to another user",
|
||||
"Invalid recovery key": "Invalid recovery key",
|
||||
"Done": "Done",
|
||||
"Thank you for your feedback": "Thank you for your feedback",
|
||||
"Failed to process feedback": "Failed to process feedback",
|
||||
"Helpful": "Helpful",
|
||||
"Not helpful": "Not helpful",
|
||||
"Select an option": "Select an option",
|
||||
"Enter a value": "Enter a value",
|
||||
"Submit": "Submit",
|
||||
"Submitted": "Submitted",
|
||||
"Thinking": "Thinking",
|
||||
"AI-generated": "AI-generated",
|
||||
"AI-generated content. Refer to the cited sources for details.": "AI-generated content. Refer to the cited sources for details.",
|
||||
"No bridges configured.": "No bridges configured.",
|
||||
"Signing in to all bridges again...": "Signing in to all bridges again...",
|
||||
"Bridge status:": "Bridge status:",
|
||||
"Signed in ✓": "Signed in ✓",
|
||||
"Not signed in ✗": "Not signed in ✗",
|
||||
"None": "None",
|
||||
"Bridge {bridge}: skipped (login command or DM room missing).": "Bridge {bridge}: skipped (login command or DM room missing).",
|
||||
"Bridge {bridge}: sending {command}...": "Bridge {bridge}: sending {command}...",
|
||||
"Bridge {bridge}: sign-out failed.": "Bridge {bridge}: sign-out failed.",
|
||||
"Bridge {bridge}: sign-in started.": "Bridge {bridge}: sign-in started.",
|
||||
"Slack authorization completed. Return to LangBot to finish setup.": "Slack authorization completed. Return to LangBot to finish setup.",
|
||||
"Slack authorization failed or expired. Return to LangBot for details.": "Slack authorization failed or expired. Return to LangBot for details.",
|
||||
"TOTP is already enabled for this Account": "TOTP is already enabled for this Account",
|
||||
"TOTP is not enabled for this Account": "TOTP is not enabled for this Account",
|
||||
"Invalid or expired passkey challenge": "Invalid or expired passkey challenge",
|
||||
"Passkey credential not recognized": "Passkey credential not recognized",
|
||||
"Account is disabled": "Account is disabled",
|
||||
"Modifying login info is disabled": "Modifying login info is disabled",
|
||||
"Invalid GitHub repository URL": "Invalid GitHub repository URL",
|
||||
"Server is busy. Please try again later.": "Server is busy. Please try again later.",
|
||||
"This feature is unavailable in this Workspace.": "This feature is unavailable in this Workspace.",
|
||||
"The Workspace must retain an owner.": "The Workspace must retain an owner.",
|
||||
"Invalid or expired invitation": "Invalid or expired invitation",
|
||||
"This user is already a member.": "This user is already a member.",
|
||||
"Voice connection failed": "Voice connection failed",
|
||||
"User is not in the voice channel": "User is not in the voice channel",
|
||||
"Voice service is unavailable": "Voice service is unavailable",
|
||||
"Submission failed. Please try again.": "Submission failed. Please try again.",
|
||||
"File size exceeds the limit.": "File size exceeds the limit.",
|
||||
"File size exceeds 10MB limit. Please split large files into smaller parts.": "File size exceeds 10MB limit. Please split large files into smaller parts.",
|
||||
"Authorization failed. Please try signing in again.": "Authorization failed. Please try signing in again.",
|
||||
"Bind the LangBot Account with the same email as this local Account": "Bind the LangBot Account with the same email as this local Account",
|
||||
"Only local accounts can bind to Space": "Only local accounts can bind to Space",
|
||||
"System already initialized": "System already initialized",
|
||||
"System not initialized": "System not initialized",
|
||||
"Password login is disabled on LangBot Cloud": "Password login is disabled on LangBot Cloud",
|
||||
"This edition supports one Workspace per instance": "This edition supports one Workspace per instance",
|
||||
"Cloud Workspaces are created by the SaaS control plane": "Cloud Workspaces are created by the SaaS control plane",
|
||||
"Login with your LangBot Account to accept this invitation": "Login with your LangBot Account to accept this invitation",
|
||||
"RAG migration is not needed": "RAG migration is not needed",
|
||||
"New Agent": "New Agent",
|
||||
"New Pipeline": "New Pipeline",
|
||||
"Provider {provider} does not support {model_type} models": "Provider {provider} does not support {model_type} models",
|
||||
"Invalid JSON format": "Invalid JSON format",
|
||||
"ChatGPT sign-in required. Open this provider and sign in again.": "ChatGPT sign-in required. Open this provider and sign in again.",
|
||||
"ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.",
|
||||
"ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT denied this request. Check subscription and workspace permissions.",
|
||||
"ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT rejected the model or request. Check the selected model and request settings.",
|
||||
"ChatGPT Codex upstream request failed. Please retry later.": "ChatGPT Codex upstream request failed. Please retry later.",
|
||||
"ChatGPT subscription usage limit reached. Please retry later or check your plan.": "ChatGPT subscription usage limit reached. Please retry later or check your plan.",
|
||||
"ChatGPT Codex response failed or was incomplete. Please retry.": "ChatGPT Codex response failed or was incomplete. Please retry.",
|
||||
"ChatGPT token refresh temporarily failed. Please retry.": "ChatGPT token refresh temporarily failed. Please retry.",
|
||||
"A second factor is required": "A second factor is required",
|
||||
"Sign in or provide registration details to accept this invitation": "Sign in or provide registration details to accept this invitation",
|
||||
"Sign out before creating the invited local Account": "Sign out before creating the invited local Account",
|
||||
"Turnstile verification failed": "Turnstile verification failed",
|
||||
"The invitation email does not match your Account": "The invitation email does not match your Account",
|
||||
"Failed to save ChatGPT credentials. Please retry.": "Failed to save ChatGPT credentials. Please retry.",
|
||||
"Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.",
|
||||
"Option {number}": "Option {number}",
|
||||
"Confirmation needed": "Confirmation needed",
|
||||
"Session reset successfully": "Session reset successfully"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"Request failed.": "La solicitud ha fallado.",
|
||||
"Too many requests. Please try again later.": "Demasiadas solicitudes. Inténtalo más tarde.",
|
||||
"Your message contains inappropriate content. Please revise it.": "Tu mensaje contiene contenido inapropiado. Modifícalo.",
|
||||
"Content checking failed. Please contact the administrator.": "No se pudo verificar el contenido. Contacta con el administrador.",
|
||||
"Calling {tools}...": "Llamando a {tools}…",
|
||||
"Agent runner not found: {runner}": "No se encontró el ejecutor del agente: {runner}",
|
||||
"Agent runner not authorized for this pipeline.": "Este flujo no tiene autorización para usar el ejecutor del agente.",
|
||||
"Agent runner temporarily unavailable. Please try again.": "El ejecutor del agente no está disponible temporalmente. Reinténtalo.",
|
||||
"Agent runner execution failed.": "La ejecución del agente ha fallado.",
|
||||
"The Agent runner returned an invalid response.": "El ejecutor del agente devolvió una respuesta no válida.",
|
||||
"Model request failed": "Error en la solicitud al modelo",
|
||||
"Context length exceeded": "Se superó el límite de contexto",
|
||||
"Invalid request parameters": "Parámetros de solicitud no válidos",
|
||||
"Invalid API key": "Clave API no válida",
|
||||
"Invalid model or endpoint": "Modelo o endpoint no válido",
|
||||
"Rate limit exceeded or insufficient balance": "Límite de solicitudes excedido o saldo insuficiente",
|
||||
"Request timed out": "La solicitud agotó el tiempo de espera",
|
||||
"Connection failed": "Error de conexión",
|
||||
"API error": "Error de API",
|
||||
"Unknown error": "Error desconocido",
|
||||
"Internal server error": "Error interno del servidor",
|
||||
"Resource not found": "Recurso no encontrado",
|
||||
"Permission denied": "No tienes permiso para realizar esta acción",
|
||||
"Invalid authentication credentials": "Error de autenticación. Vuelve a iniciar sesión",
|
||||
"MCP runtime failed": "Error de ejecución de MCP",
|
||||
"Task execution failed": "La tarea ha fallado",
|
||||
"Task was cancelled": "La tarea se ha cancelado",
|
||||
"Maximum number of {resource} ({limit}) reached": "Se alcanzó el máximo de {resource} ({limit})",
|
||||
"extensions": "extensiones",
|
||||
"knowledge bases": "bases de conocimiento",
|
||||
"MCP server already exists: {name}": "El servidor MCP ya existe: {name}",
|
||||
"LangBot Models is managed by Cloud and cannot be modified": "LangBot Models está gestionado por Cloud y no se puede modificar",
|
||||
"Please sign in with your LangBot Account": "Inicia sesión con tu cuenta de LangBot",
|
||||
"Current password is incorrect": "La contraseña actual es incorrecta",
|
||||
"Invalid TOTP or recovery code": "Código TOTP o de recuperación no válido",
|
||||
"No local password set, please set a password first": "No hay contraseña local. Configura una primero",
|
||||
"This LangBot Account is already bound to another user": "Esta cuenta de LangBot ya está vinculada a otro usuario",
|
||||
"Invalid recovery key": "Clave de recuperación no válida",
|
||||
"Done": "Hecho",
|
||||
"Thank you for your feedback": "Gracias por tus comentarios",
|
||||
"Failed to process feedback": "No se pudieron procesar los comentarios",
|
||||
"Helpful": "Útil",
|
||||
"Not helpful": "No es útil",
|
||||
"Select an option": "Selecciona una opción",
|
||||
"Enter a value": "Introduce un valor",
|
||||
"Submit": "Enviar",
|
||||
"Submitted": "Enviado",
|
||||
"Thinking": "Razonamiento",
|
||||
"AI-generated": "Generado por IA",
|
||||
"AI-generated content. Refer to the cited sources for details.": "Contenido generado por IA. Consulta las fuentes citadas para más detalles.",
|
||||
"No bridges configured.": "No hay puentes configurados.",
|
||||
"Signing in to all bridges again...": "Iniciando sesión de nuevo en todos los puentes…",
|
||||
"Bridge status:": "Estado de los puentes:",
|
||||
"Signed in ✓": "Sesión iniciada ✓",
|
||||
"Not signed in ✗": "Sin sesión ✗",
|
||||
"None": "Ninguno",
|
||||
"Bridge {bridge}: skipped (login command or DM room missing).": "Puente {bridge}: omitido (falta comando de inicio de sesión o sala DM).",
|
||||
"Bridge {bridge}: sending {command}...": "Puente {bridge}: enviando {command}…",
|
||||
"Bridge {bridge}: sign-out failed.": "Puente {bridge}: no se pudo cerrar sesión.",
|
||||
"Bridge {bridge}: sign-in started.": "Puente {bridge}: inicio de sesión en curso.",
|
||||
"Slack authorization completed. Return to LangBot to finish setup.": "Autorización de Slack completada. Vuelve a LangBot para terminar la configuración.",
|
||||
"Slack authorization failed or expired. Return to LangBot for details.": "La autorización de Slack falló o caducó. Consulta los detalles en LangBot.",
|
||||
"TOTP is already enabled for this Account": "Esta cuenta ya tiene TOTP activado",
|
||||
"TOTP is not enabled for this Account": "Esta cuenta no tiene TOTP activado",
|
||||
"Invalid or expired passkey challenge": "El desafío de clave de acceso no es válido o ha caducado",
|
||||
"Passkey credential not recognized": "No se reconoce la clave de acceso",
|
||||
"Account is disabled": "La cuenta está desactivada",
|
||||
"Modifying login info is disabled": "No se permite modificar los datos de inicio de sesión",
|
||||
"Invalid GitHub repository URL": "URL del repositorio GitHub no válida",
|
||||
"Server is busy. Please try again later.": "El servidor está ocupado. Inténtalo más tarde.",
|
||||
"This feature is unavailable in this Workspace.": "Esta función no está disponible en este espacio de trabajo.",
|
||||
"The Workspace must retain an owner.": "El espacio de trabajo debe conservar un propietario.",
|
||||
"Invalid or expired invitation": "Invitación no válida o caducada",
|
||||
"This user is already a member.": "Este usuario ya es miembro.",
|
||||
"Voice connection failed": "Error de conexión de voz",
|
||||
"User is not in the voice channel": "El usuario no está en el canal de voz",
|
||||
"Voice service is unavailable": "El servicio de voz no está disponible",
|
||||
"Submission failed. Please try again.": "El envío ha fallado. Reinténtalo.",
|
||||
"File size exceeds the limit.": "El archivo supera el tamaño permitido.",
|
||||
"File size exceeds 10MB limit. Please split large files into smaller parts.": "El archivo supera los 10 MB. Divídelo en partes más pequeñas.",
|
||||
"Authorization failed. Please try signing in again.": "La autorización ha fallado. Vuelve a iniciar sesión.",
|
||||
"Bind the LangBot Account with the same email as this local Account": "Vincula una cuenta de LangBot con el mismo correo que esta cuenta local",
|
||||
"Only local accounts can bind to Space": "Solo las cuentas locales pueden vincularse a Space",
|
||||
"System already initialized": "El sistema ya está inicializado",
|
||||
"System not initialized": "El sistema no está inicializado",
|
||||
"Password login is disabled on LangBot Cloud": "El acceso con contraseña está desactivado en LangBot Cloud",
|
||||
"This edition supports one Workspace per instance": "Esta edición admite un espacio de trabajo por instancia",
|
||||
"Cloud Workspaces are created by the SaaS control plane": "Los espacios de trabajo Cloud se crean desde el panel SaaS",
|
||||
"Login with your LangBot Account to accept this invitation": "Inicia sesión con tu cuenta de LangBot para aceptar la invitación",
|
||||
"RAG migration is not needed": "No es necesaria la migración de RAG",
|
||||
"New Agent": "Nuevo agente",
|
||||
"New Pipeline": "Nuevo flujo",
|
||||
"Provider {provider} does not support {model_type} models": "El proveedor {provider} no admite modelos {model_type}",
|
||||
"Invalid JSON format": "Formato JSON no válido",
|
||||
"ChatGPT sign-in required. Open this provider and sign in again.": "Debes iniciar sesión en ChatGPT. Abre este proveedor y vuelve a iniciar sesión.",
|
||||
"ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT limitó la solicitud por frecuencia o uso. Inténtalo más tarde o revisa tu plan.",
|
||||
"ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT rechazó la solicitud. Revisa la suscripción y los permisos del espacio de trabajo.",
|
||||
"ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT rechazó el modelo o la solicitud. Revisa el modelo seleccionado y la configuración.",
|
||||
"ChatGPT Codex upstream request failed. Please retry later.": "Falló la solicitud a ChatGPT Codex. Inténtalo más tarde.",
|
||||
"ChatGPT subscription usage limit reached. Please retry later or check your plan.": "Se alcanzó el límite de uso de tu suscripción de ChatGPT. Inténtalo más tarde o revisa tu plan.",
|
||||
"ChatGPT Codex response failed or was incomplete. Please retry.": "La respuesta de ChatGPT Codex falló o está incompleta. Inténtalo de nuevo.",
|
||||
"ChatGPT token refresh temporarily failed. Please retry.": "No se pudo renovar el token de ChatGPT temporalmente. Inténtalo de nuevo.",
|
||||
"A second factor is required": "Se requiere un segundo factor de autenticación",
|
||||
"Sign in or provide registration details to accept this invitation": "Inicia sesión o proporciona los datos de registro para aceptar esta invitación",
|
||||
"Sign out before creating the invited local Account": "Cierra sesión antes de crear la cuenta local invitada",
|
||||
"Turnstile verification failed": "La verificación de Turnstile falló. Inténtalo de nuevo",
|
||||
"The invitation email does not match your Account": "El correo de la invitación no coincide con tu cuenta",
|
||||
"Failed to save ChatGPT credentials. Please retry.": "No se pudieron guardar las credenciales de ChatGPT. Inténtalo de nuevo.",
|
||||
"Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "No se pudo iniciar el acceso por dispositivo. Activa el inicio de sesión con código de dispositivo en la configuración de ChatGPT.",
|
||||
"Option {number}": "Opción {number}",
|
||||
"Confirmation needed": "Se requiere confirmación",
|
||||
"Session reset successfully": "Sesión restablecida"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"Request failed.": "リクエストに失敗しました。",
|
||||
"Too many requests. Please try again later.": "リクエストが多すぎます。しばらくしてから再試行してください。",
|
||||
"Your message contains inappropriate content. Please revise it.": "不適切な内容が含まれています。メッセージを修正してください。",
|
||||
"Content checking failed. Please contact the administrator.": "コンテンツの確認に失敗しました。管理者にお問い合わせください。",
|
||||
"Calling {tools}...": "{tools} を呼び出しています…",
|
||||
"Agent runner not found: {runner}": "Agent ランナーが見つかりません:{runner}",
|
||||
"Agent runner not authorized for this pipeline.": "このパイプラインには Agent ランナーの使用権限がありません。",
|
||||
"Agent runner temporarily unavailable. Please try again.": "Agent ランナーは一時的に利用できません。再試行してください。",
|
||||
"Agent runner execution failed.": "Agent の実行に失敗しました。",
|
||||
"The Agent runner returned an invalid response.": "Agent ランナーが無効な応答を返しました。",
|
||||
"Model request failed": "モデルへのリクエストに失敗しました",
|
||||
"Context length exceeded": "コンテキスト長の上限を超えました",
|
||||
"Invalid request parameters": "リクエストのパラメータが無効です",
|
||||
"Invalid API key": "API キーが無効です",
|
||||
"Invalid model or endpoint": "モデルまたはエンドポイントが無効です",
|
||||
"Rate limit exceeded or insufficient balance": "リクエスト制限を超えたか、残高が不足しています",
|
||||
"Request timed out": "リクエストがタイムアウトしました",
|
||||
"Connection failed": "接続に失敗しました",
|
||||
"API error": "API エラー",
|
||||
"Unknown error": "不明なエラー",
|
||||
"Internal server error": "サーバー内部エラー",
|
||||
"Resource not found": "リソースが見つかりません",
|
||||
"Permission denied": "この操作の権限がありません",
|
||||
"Invalid authentication credentials": "認証に失敗しました。再度ログインしてください",
|
||||
"MCP runtime failed": "MCP の実行に失敗しました",
|
||||
"Task execution failed": "タスクの実行に失敗しました",
|
||||
"Task was cancelled": "タスクがキャンセルされました",
|
||||
"Maximum number of {resource} ({limit}) reached": "{resource}の上限数({limit})に達しました",
|
||||
"extensions": "拡張機能",
|
||||
"knowledge bases": "ナレッジベース",
|
||||
"MCP server already exists: {name}": "MCP サーバーは既に存在します:{name}",
|
||||
"LangBot Models is managed by Cloud and cannot be modified": "LangBot Models はクラウドで管理されているため変更できません",
|
||||
"Please sign in with your LangBot Account": "LangBot アカウントでログインしてください",
|
||||
"Current password is incorrect": "現在のパスワードが正しくありません",
|
||||
"Invalid TOTP or recovery code": "認証コードまたはリカバリーコードが無効です",
|
||||
"No local password set, please set a password first": "ローカルパスワードが未設定です。先に設定してください",
|
||||
"This LangBot Account is already bound to another user": "この LangBot アカウントは別のユーザーに連携されています",
|
||||
"Invalid recovery key": "リカバリーキーが無効です",
|
||||
"Done": "完了しました",
|
||||
"Thank you for your feedback": "フィードバックありがとうございます",
|
||||
"Failed to process feedback": "フィードバックの処理に失敗しました",
|
||||
"Helpful": "役に立った",
|
||||
"Not helpful": "役に立たなかった",
|
||||
"Select an option": "選択してください",
|
||||
"Enter a value": "入力してください",
|
||||
"Submit": "送信",
|
||||
"Submitted": "送信済み",
|
||||
"Thinking": "思考過程",
|
||||
"AI-generated": "AI による生成コンテンツ",
|
||||
"AI-generated content. Refer to the cited sources for details.": "AI が生成した内容です。詳細は引用元を参照してください。",
|
||||
"No bridges configured.": "ブリッジが設定されていません。",
|
||||
"Signing in to all bridges again...": "すべてのブリッジに再ログインしています…",
|
||||
"Bridge status:": "ブリッジの状態:",
|
||||
"Signed in ✓": "ログイン済み ✓",
|
||||
"Not signed in ✗": "未ログイン ✗",
|
||||
"None": "なし",
|
||||
"Bridge {bridge}: skipped (login command or DM room missing).": "ブリッジ {bridge}:スキップしました(ログインコマンドまたは DM ルームがありません)。",
|
||||
"Bridge {bridge}: sending {command}...": "ブリッジ {bridge}:{command} を送信中…",
|
||||
"Bridge {bridge}: sign-out failed.": "ブリッジ {bridge}:ログアウトに失敗しました。",
|
||||
"Bridge {bridge}: sign-in started.": "ブリッジ {bridge}:ログインを開始しました。",
|
||||
"Slack authorization completed. Return to LangBot to finish setup.": "Slack の認証が完了しました。LangBot に戻って設定を完了してください。",
|
||||
"Slack authorization failed or expired. Return to LangBot for details.": "Slack の認証に失敗したか期限切れです。詳細は LangBot で確認してください。",
|
||||
"TOTP is already enabled for this Account": "このアカウントでは二段階認証が有効です",
|
||||
"TOTP is not enabled for this Account": "このアカウントでは二段階認証が無効です",
|
||||
"Invalid or expired passkey challenge": "パスキーの認証要求が無効または期限切れです",
|
||||
"Passkey credential not recognized": "このパスキーは認識できません",
|
||||
"Account is disabled": "アカウントは無効です",
|
||||
"Modifying login info is disabled": "ログイン情報の変更は無効です",
|
||||
"Invalid GitHub repository URL": "GitHub リポジトリの URL が無効です",
|
||||
"Server is busy. Please try again later.": "サーバーが混雑しています。後で再試行してください。",
|
||||
"This feature is unavailable in this Workspace.": "このワークスペースではこの機能を利用できません。",
|
||||
"The Workspace must retain an owner.": "ワークスペースにはオーナーが必要です。",
|
||||
"Invalid or expired invitation": "招待が無効または期限切れです",
|
||||
"This user is already a member.": "このユーザーは既にメンバーです。",
|
||||
"Voice connection failed": "音声接続に失敗しました",
|
||||
"User is not in the voice channel": "ユーザーは音声チャンネルに参加していません",
|
||||
"Voice service is unavailable": "音声サービスを利用できません",
|
||||
"Submission failed. Please try again.": "送信に失敗しました。再試行してください。",
|
||||
"File size exceeds the limit.": "ファイルサイズが上限を超えています。",
|
||||
"File size exceeds 10MB limit. Please split large files into smaller parts.": "ファイルが 10MB を超えています。分割してアップロードしてください。",
|
||||
"Authorization failed. Please try signing in again.": "認証に失敗しました。再度ログインしてください。",
|
||||
"Bind the LangBot Account with the same email as this local Account": "このローカルアカウントと同じメールアドレスの LangBot アカウントを連携してください",
|
||||
"Only local accounts can bind to Space": "Space と連携できるのはローカルアカウントのみです",
|
||||
"System already initialized": "システムは初期化済みです",
|
||||
"System not initialized": "システムが初期化されていません",
|
||||
"Password login is disabled on LangBot Cloud": "LangBot Cloud ではパスワードログインは無効です",
|
||||
"This edition supports one Workspace per instance": "このエディションではインスタンスごとにワークスペースは1つです",
|
||||
"Cloud Workspaces are created by the SaaS control plane": "クラウドのワークスペースは SaaS 管理画面で作成します",
|
||||
"Login with your LangBot Account to accept this invitation": "招待を承諾するには LangBot アカウントでログインしてください",
|
||||
"RAG migration is not needed": "RAG の移行は不要です",
|
||||
"New Agent": "新しい Agent",
|
||||
"New Pipeline": "新しいパイプライン",
|
||||
"Provider {provider} does not support {model_type} models": "プロバイダー {provider} は {model_type} モデルに対応していません",
|
||||
"Invalid JSON format": "JSON 形式が無効です",
|
||||
"ChatGPT sign-in required. Open this provider and sign in again.": "ChatGPT へのログインが必要です。このプロバイダーを開いて再ログインしてください。",
|
||||
"ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT のリクエストが制限されました(頻度または使用量)。後で再試行するか、プランを確認してください。",
|
||||
"ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT がリクエストを拒否しました。サブスクリプションとワークスペースの権限を確認してください。",
|
||||
"ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT がモデルまたはリクエストを拒否しました。選択したモデルとリクエスト設定を確認してください。",
|
||||
"ChatGPT Codex upstream request failed. Please retry later.": "ChatGPT Codex の上流リクエストに失敗しました。後で再試行してください。",
|
||||
"ChatGPT subscription usage limit reached. Please retry later or check your plan.": "ChatGPT のサブスクリプション使用量が上限に達しました。後で再試行するか、プランを確認してください。",
|
||||
"ChatGPT Codex response failed or was incomplete. Please retry.": "ChatGPT Codex の応答に失敗したか、応答が不完全です。再試行してください。",
|
||||
"ChatGPT token refresh temporarily failed. Please retry.": "ChatGPT トークンの更新に一時的に失敗しました。再試行してください。",
|
||||
"A second factor is required": "二要素認証が必要です",
|
||||
"Sign in or provide registration details to accept this invitation": "招待を承諾するにはログインするか、登録情報を入力してください",
|
||||
"Sign out before creating the invited local Account": "招待されたローカルアカウントを作成する前にログアウトしてください",
|
||||
"Turnstile verification failed": "人間確認に失敗しました。再試行してください",
|
||||
"The invitation email does not match your Account": "招待のメールアドレスがアカウントと一致しません",
|
||||
"Failed to save ChatGPT credentials. Please retry.": "ChatGPT 認証情報の保存に失敗しました。再試行してください。",
|
||||
"Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "ChatGPT のデバイスログインを開始できません。ChatGPT の設定でデバイスコードログインを有効にしてください。",
|
||||
"Option {number}": "選択肢 {number}",
|
||||
"Confirmation needed": "確認が必要です",
|
||||
"Session reset successfully": "セッションをリセットしました"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"Request failed.": "Запрос не выполнен.",
|
||||
"Too many requests. Please try again later.": "Слишком много запросов. Повторите позже.",
|
||||
"Your message contains inappropriate content. Please revise it.": "Сообщение содержит недопустимый контент. Измените его.",
|
||||
"Content checking failed. Please contact the administrator.": "Ошибка проверки содержимого. Обратитесь к администратору.",
|
||||
"Calling {tools}...": "Вызов {tools}…",
|
||||
"Agent runner not found: {runner}": "Исполнитель агента не найден: {runner}",
|
||||
"Agent runner not authorized for this pipeline.": "У конвейера нет разрешения на использование исполнителя агента.",
|
||||
"Agent runner temporarily unavailable. Please try again.": "Исполнитель агента временно недоступен. Повторите попытку.",
|
||||
"Agent runner execution failed.": "Ошибка выполнения агента.",
|
||||
"The Agent runner returned an invalid response.": "Исполнитель агента вернул некорректный ответ.",
|
||||
"Model request failed": "Ошибка запроса к модели",
|
||||
"Context length exceeded": "Превышена длина контекста",
|
||||
"Invalid request parameters": "Некорректные параметры запроса",
|
||||
"Invalid API key": "Недействительный API-ключ",
|
||||
"Invalid model or endpoint": "Некорректная модель или адрес API",
|
||||
"Rate limit exceeded or insufficient balance": "Превышен лимит запросов или недостаточно средств",
|
||||
"Request timed out": "Время ожидания запроса истекло",
|
||||
"Connection failed": "Ошибка подключения",
|
||||
"API error": "Ошибка API",
|
||||
"Unknown error": "Неизвестная ошибка",
|
||||
"Internal server error": "Внутренняя ошибка сервера",
|
||||
"Resource not found": "Ресурс не найден",
|
||||
"Permission denied": "Нет разрешения на это действие",
|
||||
"Invalid authentication credentials": "Ошибка аутентификации. Войдите снова",
|
||||
"MCP runtime failed": "Ошибка выполнения MCP",
|
||||
"Task execution failed": "Ошибка выполнения задачи",
|
||||
"Task was cancelled": "Задача отменена",
|
||||
"Maximum number of {resource} ({limit}) reached": "Достигнуто максимальное число {resource} ({limit})",
|
||||
"extensions": "расширений",
|
||||
"knowledge bases": "баз знаний",
|
||||
"MCP server already exists: {name}": "Сервер MCP уже существует: {name}",
|
||||
"LangBot Models is managed by Cloud and cannot be modified": "LangBot Models управляется облаком и не может быть изменён",
|
||||
"Please sign in with your LangBot Account": "Войдите с учётной записью LangBot",
|
||||
"Current password is incorrect": "Текущий пароль неверен",
|
||||
"Invalid TOTP or recovery code": "Неверный код TOTP или восстановления",
|
||||
"No local password set, please set a password first": "Локальный пароль не задан. Сначала задайте пароль",
|
||||
"This LangBot Account is already bound to another user": "Эта учётная запись LangBot уже связана с другим пользователем",
|
||||
"Invalid recovery key": "Недействительный ключ восстановления",
|
||||
"Done": "Готово",
|
||||
"Thank you for your feedback": "Спасибо за отзыв",
|
||||
"Failed to process feedback": "Не удалось обработать отзыв",
|
||||
"Helpful": "Полезно",
|
||||
"Not helpful": "Не полезно",
|
||||
"Select an option": "Выберите вариант",
|
||||
"Enter a value": "Введите значение",
|
||||
"Submit": "Отправить",
|
||||
"Submitted": "Отправлено",
|
||||
"Thinking": "Ход рассуждений",
|
||||
"AI-generated": "Создано ИИ",
|
||||
"AI-generated content. Refer to the cited sources for details.": "Контент создан ИИ. Подробности смотрите в указанных источниках.",
|
||||
"No bridges configured.": "Мосты не настроены.",
|
||||
"Signing in to all bridges again...": "Повторный вход во все мосты…",
|
||||
"Bridge status:": "Состояние мостов:",
|
||||
"Signed in ✓": "Вход выполнен ✓",
|
||||
"Not signed in ✗": "Вход не выполнен ✗",
|
||||
"None": "Нет",
|
||||
"Bridge {bridge}: skipped (login command or DM room missing).": "Мост {bridge}: пропущен (нет команды входа или комнаты DM).",
|
||||
"Bridge {bridge}: sending {command}...": "Мост {bridge}: отправка {command}…",
|
||||
"Bridge {bridge}: sign-out failed.": "Мост {bridge}: ошибка выхода.",
|
||||
"Bridge {bridge}: sign-in started.": "Мост {bridge}: вход начат.",
|
||||
"Slack authorization completed. Return to LangBot to finish setup.": "Авторизация Slack завершена. Вернитесь в LangBot для завершения настройки.",
|
||||
"Slack authorization failed or expired. Return to LangBot for details.": "Авторизация Slack не удалась или истекла. Подробности в LangBot.",
|
||||
"TOTP is already enabled for this Account": "Для этой учётной записи TOTP уже включён",
|
||||
"TOTP is not enabled for this Account": "Для этой учётной записи TOTP не включён",
|
||||
"Invalid or expired passkey challenge": "Запрос проверки ключа доступа недействителен или истёк",
|
||||
"Passkey credential not recognized": "Ключ доступа не распознан",
|
||||
"Account is disabled": "Учётная запись отключена",
|
||||
"Modifying login info is disabled": "Изменение данных входа отключено",
|
||||
"Invalid GitHub repository URL": "Некорректный URL репозитория GitHub",
|
||||
"Server is busy. Please try again later.": "Сервер занят. Повторите позже.",
|
||||
"This feature is unavailable in this Workspace.": "Эта функция недоступна в данном рабочем пространстве.",
|
||||
"The Workspace must retain an owner.": "У рабочего пространства должен остаться владелец.",
|
||||
"Invalid or expired invitation": "Приглашение недействительно или истекло",
|
||||
"This user is already a member.": "Этот пользователь уже является участником.",
|
||||
"Voice connection failed": "Ошибка голосового соединения",
|
||||
"User is not in the voice channel": "Пользователь не в голосовом канале",
|
||||
"Voice service is unavailable": "Голосовая служба недоступна",
|
||||
"Submission failed. Please try again.": "Не удалось отправить. Повторите попытку.",
|
||||
"File size exceeds the limit.": "Размер файла превышает ограничение.",
|
||||
"File size exceeds 10MB limit. Please split large files into smaller parts.": "Размер файла превышает 10 МБ. Разделите его на части.",
|
||||
"Authorization failed. Please try signing in again.": "Авторизация не удалась. Войдите снова.",
|
||||
"Bind the LangBot Account with the same email as this local Account": "Свяжите учётную запись LangBot с тем же адресом почты, что и локальная",
|
||||
"Only local accounts can bind to Space": "К Space можно привязать только локальные учётные записи",
|
||||
"System already initialized": "Система уже инициализирована",
|
||||
"System not initialized": "Система не инициализирована",
|
||||
"Password login is disabled on LangBot Cloud": "В LangBot Cloud вход по паролю отключён",
|
||||
"This edition supports one Workspace per instance": "Эта редакция поддерживает одно рабочее пространство на экземпляр",
|
||||
"Cloud Workspaces are created by the SaaS control plane": "Облачные рабочие пространства создаются в панели SaaS",
|
||||
"Login with your LangBot Account to accept this invitation": "Войдите в LangBot, чтобы принять приглашение",
|
||||
"RAG migration is not needed": "Миграция RAG не требуется",
|
||||
"New Agent": "Новый агент",
|
||||
"New Pipeline": "Новый конвейер",
|
||||
"Provider {provider} does not support {model_type} models": "Провайдер {provider} не поддерживает модели {model_type}",
|
||||
"Invalid JSON format": "Некорректный формат JSON",
|
||||
"ChatGPT sign-in required. Open this provider and sign in again.": "Требуется вход в ChatGPT. Откройте этого провайдера и войдите снова.",
|
||||
"ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "Запрос ChatGPT ограничен по частоте или использованию. Повторите позже или проверьте тариф.",
|
||||
"ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT отклонил запрос. Проверьте подписку и права рабочего пространства.",
|
||||
"ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT отклонил модель или запрос. Проверьте выбранную модель и настройки запроса.",
|
||||
"ChatGPT Codex upstream request failed. Please retry later.": "Ошибка запроса к ChatGPT Codex. Повторите позже.",
|
||||
"ChatGPT subscription usage limit reached. Please retry later or check your plan.": "Достигнут лимит использования подписки ChatGPT. Повторите позже или проверьте тариф.",
|
||||
"ChatGPT Codex response failed or was incomplete. Please retry.": "Ошибка или неполный ответ ChatGPT Codex. Повторите запрос.",
|
||||
"ChatGPT token refresh temporarily failed. Please retry.": "Временная ошибка обновления токена ChatGPT. Повторите попытку.",
|
||||
"A second factor is required": "Требуется второй фактор аутентификации",
|
||||
"Sign in or provide registration details to accept this invitation": "Войдите или укажите данные регистрации, чтобы принять приглашение",
|
||||
"Sign out before creating the invited local Account": "Выйдите перед созданием приглашённой локальной учётной записи",
|
||||
"Turnstile verification failed": "Проверка Turnstile не пройдена. Повторите попытку",
|
||||
"The invitation email does not match your Account": "Адрес приглашения не совпадает с вашей учётной записью",
|
||||
"Failed to save ChatGPT credentials. Please retry.": "Не удалось сохранить учётные данные ChatGPT. Повторите попытку.",
|
||||
"Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "Не удалось начать вход с устройства. Включите вход по коду устройства в настройках ChatGPT.",
|
||||
"Option {number}": "Вариант {number}",
|
||||
"Confirmation needed": "Требуется подтверждение",
|
||||
"Session reset successfully": "Сессия сброшена"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"Request failed.": "คำขอล้มเหลว",
|
||||
"Too many requests. Please try again later.": "มีคำขอมากเกินไป โปรดลองอีกครั้งภายหลัง",
|
||||
"Your message contains inappropriate content. Please revise it.": "ข้อความมีเนื้อหาไม่เหมาะสม โปรดแก้ไข",
|
||||
"Content checking failed. Please contact the administrator.": "ตรวจสอบเนื้อหาไม่สำเร็จ โปรดติดต่อผู้ดูแลระบบ",
|
||||
"Calling {tools}...": "กำลังเรียกใช้ {tools}…",
|
||||
"Agent runner not found: {runner}": "ไม่พบตัวรัน Agent: {runner}",
|
||||
"Agent runner not authorized for this pipeline.": "ไปป์ไลน์นี้ไม่ได้รับอนุญาตให้ใช้ตัวรัน Agent",
|
||||
"Agent runner temporarily unavailable. Please try again.": "ตัวรัน Agent ไม่พร้อมใช้งานชั่วคราว โปรดลองอีกครั้ง",
|
||||
"Agent runner execution failed.": "การทำงานของ Agent ล้มเหลว",
|
||||
"The Agent runner returned an invalid response.": "ตัวรัน Agent ส่งการตอบกลับที่ไม่ถูกต้อง",
|
||||
"Model request failed": "คำขอไปยังโมเดลล้มเหลว",
|
||||
"Context length exceeded": "ความยาวบริบทเกินขีดจำกัด",
|
||||
"Invalid request parameters": "พารามิเตอร์คำขอไม่ถูกต้อง",
|
||||
"Invalid API key": "คีย์ API ไม่ถูกต้อง",
|
||||
"Invalid model or endpoint": "โมเดลหรือจุดเชื่อมต่อไม่ถูกต้อง",
|
||||
"Rate limit exceeded or insufficient balance": "เกินขีดจำกัดคำขอหรือยอดเงินไม่เพียงพอ",
|
||||
"Request timed out": "คำขอหมดเวลา",
|
||||
"Connection failed": "การเชื่อมต่อล้มเหลว",
|
||||
"API error": "ข้อผิดพลาด API",
|
||||
"Unknown error": "ข้อผิดพลาดที่ไม่ทราบสาเหตุ",
|
||||
"Internal server error": "ข้อผิดพลาดภายในเซิร์ฟเวอร์",
|
||||
"Resource not found": "ไม่พบทรัพยากร",
|
||||
"Permission denied": "ไม่มีสิทธิ์ดำเนินการนี้",
|
||||
"Invalid authentication credentials": "ยืนยันตัวตนไม่สำเร็จ โปรดเข้าสู่ระบบอีกครั้ง",
|
||||
"MCP runtime failed": "การทำงานของ MCP ล้มเหลว",
|
||||
"Task execution failed": "การทำงานของงานล้มเหลว",
|
||||
"Task was cancelled": "งานถูกยกเลิกแล้ว",
|
||||
"Maximum number of {resource} ({limit}) reached": "ถึงจำนวนสูงสุดของ{resource} ({limit}) แล้ว",
|
||||
"extensions": "ส่วนขยาย",
|
||||
"knowledge bases": "ฐานความรู้",
|
||||
"MCP server already exists: {name}": "มีเซิร์ฟเวอร์ MCP อยู่แล้ว: {name}",
|
||||
"LangBot Models is managed by Cloud and cannot be modified": "LangBot Models จัดการโดย Cloud และไม่สามารถแก้ไขได้",
|
||||
"Please sign in with your LangBot Account": "โปรดเข้าสู่ระบบด้วยบัญชี LangBot",
|
||||
"Current password is incorrect": "รหัสผ่านปัจจุบันไม่ถูกต้อง",
|
||||
"Invalid TOTP or recovery code": "รหัส TOTP หรือรหัสกู้คืนไม่ถูกต้อง",
|
||||
"No local password set, please set a password first": "ยังไม่ได้ตั้งรหัสผ่านในระบบ โปรดตั้งรหัสผ่านก่อน",
|
||||
"This LangBot Account is already bound to another user": "บัญชี LangBot นี้เชื่อมโยงกับผู้ใช้อื่นแล้ว",
|
||||
"Invalid recovery key": "คีย์กู้คืนไม่ถูกต้อง",
|
||||
"Done": "เสร็จแล้ว",
|
||||
"Thank you for your feedback": "ขอบคุณสำหรับความคิดเห็น",
|
||||
"Failed to process feedback": "ประมวลผลความคิดเห็นไม่สำเร็จ",
|
||||
"Helpful": "มีประโยชน์",
|
||||
"Not helpful": "ไม่มีประโยชน์",
|
||||
"Select an option": "โปรดเลือก",
|
||||
"Enter a value": "โปรดป้อนค่า",
|
||||
"Submit": "ส่ง",
|
||||
"Submitted": "ส่งแล้ว",
|
||||
"Thinking": "กระบวนการคิด",
|
||||
"AI-generated": "สร้างโดย AI",
|
||||
"AI-generated content. Refer to the cited sources for details.": "เนื้อหาสร้างโดย AI ดูรายละเอียดจากแหล่งอ้างอิง",
|
||||
"No bridges configured.": "ยังไม่ได้ตั้งค่าบริดจ์",
|
||||
"Signing in to all bridges again...": "กำลังเข้าสู่ระบบบริดจ์ทั้งหมดอีกครั้ง…",
|
||||
"Bridge status:": "สถานะบริดจ์:",
|
||||
"Signed in ✓": "เข้าสู่ระบบแล้ว ✓",
|
||||
"Not signed in ✗": "ยังไม่ได้เข้าสู่ระบบ ✗",
|
||||
"None": "ไม่มี",
|
||||
"Bridge {bridge}: skipped (login command or DM room missing).": "บริดจ์ {bridge}: ข้ามแล้ว (ไม่มีคำสั่งเข้าสู่ระบบหรือห้อง DM)",
|
||||
"Bridge {bridge}: sending {command}...": "บริดจ์ {bridge}: กำลังส่ง {command}…",
|
||||
"Bridge {bridge}: sign-out failed.": "บริดจ์ {bridge}: ออกจากระบบไม่สำเร็จ",
|
||||
"Bridge {bridge}: sign-in started.": "บริดจ์ {bridge}: เริ่มเข้าสู่ระบบแล้ว",
|
||||
"Slack authorization completed. Return to LangBot to finish setup.": "อนุญาต Slack สำเร็จแล้ว กลับไปที่ LangBot เพื่อดำเนินการตั้งค่าให้เสร็จ",
|
||||
"Slack authorization failed or expired. Return to LangBot for details.": "อนุญาต Slack ไม่สำเร็จหรือหมดอายุ ดูรายละเอียดใน LangBot",
|
||||
"TOTP is already enabled for this Account": "บัญชีนี้เปิดใช้ TOTP แล้ว",
|
||||
"TOTP is not enabled for this Account": "บัญชีนี้ยังไม่ได้เปิดใช้ TOTP",
|
||||
"Invalid or expired passkey challenge": "คำขอยืนยัน Passkey ไม่ถูกต้องหรือหมดอายุ",
|
||||
"Passkey credential not recognized": "ไม่รู้จัก Passkey นี้",
|
||||
"Account is disabled": "บัญชีถูกปิดใช้งาน",
|
||||
"Modifying login info is disabled": "ไม่อนุญาตให้แก้ไขข้อมูลเข้าสู่ระบบ",
|
||||
"Invalid GitHub repository URL": "URL ที่เก็บ GitHub ไม่ถูกต้อง",
|
||||
"Server is busy. Please try again later.": "เซิร์ฟเวอร์ไม่ว่าง โปรดลองอีกครั้งภายหลัง",
|
||||
"This feature is unavailable in this Workspace.": "พื้นที่ทำงานนี้ไม่สามารถใช้ฟีเจอร์นี้ได้",
|
||||
"The Workspace must retain an owner.": "พื้นที่ทำงานต้องมีเจ้าของอย่างน้อยหนึ่งคน",
|
||||
"Invalid or expired invitation": "คำเชิญไม่ถูกต้องหรือหมดอายุ",
|
||||
"This user is already a member.": "ผู้ใช้นี้เป็นสมาชิกอยู่แล้ว",
|
||||
"Voice connection failed": "การเชื่อมต่อเสียงล้มเหลว",
|
||||
"User is not in the voice channel": "ผู้ใช้ไม่ได้อยู่ในช่องเสียง",
|
||||
"Voice service is unavailable": "บริการเสียงไม่พร้อมใช้งาน",
|
||||
"Submission failed. Please try again.": "ส่งไม่สำเร็จ โปรดลองอีกครั้ง",
|
||||
"File size exceeds the limit.": "ขนาดไฟล์เกินขีดจำกัด",
|
||||
"File size exceeds 10MB limit. Please split large files into smaller parts.": "ไฟล์ใหญ่กว่า 10MB โปรดแบ่งเป็นไฟล์ย่อย",
|
||||
"Authorization failed. Please try signing in again.": "การอนุญาตล้มเหลว โปรดเข้าสู่ระบบอีกครั้ง",
|
||||
"Bind the LangBot Account with the same email as this local Account": "เชื่อมโยงบัญชี LangBot ที่ใช้อีเมลเดียวกับบัญชีในระบบนี้",
|
||||
"Only local accounts can bind to Space": "เฉพาะบัญชีในระบบเท่านั้นที่เชื่อมโยงกับ Space ได้",
|
||||
"System already initialized": "ระบบเริ่มต้นแล้ว",
|
||||
"System not initialized": "ระบบยังไม่ได้เริ่มต้น",
|
||||
"Password login is disabled on LangBot Cloud": "LangBot Cloud ปิดการเข้าสู่ระบบด้วยรหัสผ่าน",
|
||||
"This edition supports one Workspace per instance": "รุ่นนี้รองรับพื้นที่ทำงานหนึ่งแห่งต่ออินสแตนซ์",
|
||||
"Cloud Workspaces are created by the SaaS control plane": "พื้นที่ทำงาน Cloud สร้างจากแผงควบคุม SaaS",
|
||||
"Login with your LangBot Account to accept this invitation": "เข้าสู่ระบบด้วยบัญชี LangBot เพื่อรับคำเชิญ",
|
||||
"RAG migration is not needed": "ไม่จำเป็นต้องย้าย RAG",
|
||||
"New Agent": "Agent ใหม่",
|
||||
"New Pipeline": "ไปป์ไลน์ใหม่",
|
||||
"Provider {provider} does not support {model_type} models": "ผู้ให้บริการ {provider} ไม่รองรับโมเดล {model_type}",
|
||||
"Invalid JSON format": "รูปแบบ JSON ไม่ถูกต้อง",
|
||||
"ChatGPT sign-in required. Open this provider and sign in again.": "ต้องเข้าสู่ระบบ ChatGPT โปรดเปิดผู้ให้บริการนี้แล้วเข้าสู่ระบบอีกครั้ง",
|
||||
"ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "คำขอ ChatGPT ถูกจำกัดตามความถี่หรือปริมาณการใช้งาน โปรดลองภายหลังหรือตรวจสอบแผนของคุณ",
|
||||
"ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT ปฏิเสธคำขอ โปรดตรวจสอบการสมัครสมาชิกและสิทธิ์ของพื้นที่ทำงาน",
|
||||
"ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT ปฏิเสธโมเดลหรือคำขอ โปรดตรวจสอบโมเดลและการตั้งค่าคำขอ",
|
||||
"ChatGPT Codex upstream request failed. Please retry later.": "คำขอไปยัง ChatGPT Codex ล้มเหลว โปรดลองอีกครั้งภายหลัง",
|
||||
"ChatGPT subscription usage limit reached. Please retry later or check your plan.": "การใช้งานสมาชิก ChatGPT ถึงขีดจำกัดแล้ว โปรดลองภายหลังหรือตรวจสอบแผนของคุณ",
|
||||
"ChatGPT Codex response failed or was incomplete. Please retry.": "การตอบกลับของ ChatGPT Codex ล้มเหลวหรือไม่ครบถ้วน โปรดลองอีกครั้ง",
|
||||
"ChatGPT token refresh temporarily failed. Please retry.": "ไม่สามารถรีเฟรชโทเค็น ChatGPT ได้ชั่วคราว โปรดลองอีกครั้ง",
|
||||
"A second factor is required": "ต้องยืนยันตัวตนสองขั้นตอน",
|
||||
"Sign in or provide registration details to accept this invitation": "เข้าสู่ระบบหรือกรอกข้อมูลลงทะเบียนเพื่อยอมรับคำเชิญนี้",
|
||||
"Sign out before creating the invited local Account": "ออกจากระบบก่อนสร้างบัญชีภายในที่ได้รับเชิญ",
|
||||
"Turnstile verification failed": "การยืนยัน Turnstile ล้มเหลว โปรดลองอีกครั้ง",
|
||||
"The invitation email does not match your Account": "อีเมลคำเชิญไม่ตรงกับบัญชีของคุณ",
|
||||
"Failed to save ChatGPT credentials. Please retry.": "บันทึกข้อมูลรับรอง ChatGPT ไม่สำเร็จ โปรดลองอีกครั้ง",
|
||||
"Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "ไม่สามารถเริ่มเข้าสู่ระบบด้วยอุปกรณ์ได้ โปรดเปิดการเข้าสู่ระบบด้วยรหัสอุปกรณ์ในการตั้งค่า ChatGPT",
|
||||
"Option {number}": "ตัวเลือก {number}",
|
||||
"Confirmation needed": "ต้องยืนยัน",
|
||||
"Session reset successfully": "รีเซ็ตเซสชันแล้ว"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"Request failed.": "Yêu cầu thất bại.",
|
||||
"Too many requests. Please try again later.": "Quá nhiều yêu cầu. Vui lòng thử lại sau.",
|
||||
"Your message contains inappropriate content. Please revise it.": "Tin nhắn chứa nội dung không phù hợp. Vui lòng chỉnh sửa.",
|
||||
"Content checking failed. Please contact the administrator.": "Kiểm tra nội dung thất bại. Vui lòng liên hệ quản trị viên.",
|
||||
"Calling {tools}...": "Đang gọi {tools}…",
|
||||
"Agent runner not found: {runner}": "Không tìm thấy trình chạy Agent: {runner}",
|
||||
"Agent runner not authorized for this pipeline.": "Pipeline này không được phép sử dụng trình chạy Agent.",
|
||||
"Agent runner temporarily unavailable. Please try again.": "Trình chạy Agent tạm thời không khả dụng. Vui lòng thử lại.",
|
||||
"Agent runner execution failed.": "Thực thi Agent thất bại.",
|
||||
"The Agent runner returned an invalid response.": "Trình chạy Agent trả về phản hồi không hợp lệ.",
|
||||
"Model request failed": "Yêu cầu đến mô hình thất bại",
|
||||
"Context length exceeded": "Đã vượt quá độ dài ngữ cảnh",
|
||||
"Invalid request parameters": "Tham số yêu cầu không hợp lệ",
|
||||
"Invalid API key": "Khóa API không hợp lệ",
|
||||
"Invalid model or endpoint": "Mô hình hoặc địa chỉ API không hợp lệ",
|
||||
"Rate limit exceeded or insufficient balance": "Vượt giới hạn yêu cầu hoặc số dư không đủ",
|
||||
"Request timed out": "Yêu cầu đã hết thời gian chờ",
|
||||
"Connection failed": "Kết nối thất bại",
|
||||
"API error": "Lỗi API",
|
||||
"Unknown error": "Lỗi không xác định",
|
||||
"Internal server error": "Lỗi máy chủ nội bộ",
|
||||
"Resource not found": "Không tìm thấy tài nguyên",
|
||||
"Permission denied": "Bạn không có quyền thực hiện thao tác này",
|
||||
"Invalid authentication credentials": "Xác thực thất bại. Vui lòng đăng nhập lại",
|
||||
"MCP runtime failed": "Thực thi MCP thất bại",
|
||||
"Task execution failed": "Thực thi tác vụ thất bại",
|
||||
"Task was cancelled": "Tác vụ đã bị hủy",
|
||||
"Maximum number of {resource} ({limit}) reached": "Đã đạt số lượng tối đa của {resource} ({limit})",
|
||||
"extensions": "tiện ích mở rộng",
|
||||
"knowledge bases": "cơ sở tri thức",
|
||||
"MCP server already exists: {name}": "Máy chủ MCP đã tồn tại: {name}",
|
||||
"LangBot Models is managed by Cloud and cannot be modified": "LangBot Models được quản lý bởi Cloud và không thể chỉnh sửa",
|
||||
"Please sign in with your LangBot Account": "Vui lòng đăng nhập bằng tài khoản LangBot",
|
||||
"Current password is incorrect": "Mật khẩu hiện tại không đúng",
|
||||
"Invalid TOTP or recovery code": "Mã TOTP hoặc mã khôi phục không hợp lệ",
|
||||
"No local password set, please set a password first": "Chưa có mật khẩu cục bộ. Vui lòng đặt mật khẩu trước",
|
||||
"This LangBot Account is already bound to another user": "Tài khoản LangBot này đã được liên kết với người dùng khác",
|
||||
"Invalid recovery key": "Khóa khôi phục không hợp lệ",
|
||||
"Done": "Hoàn tất",
|
||||
"Thank you for your feedback": "Cảm ơn bạn đã phản hồi",
|
||||
"Failed to process feedback": "Không thể xử lý phản hồi",
|
||||
"Helpful": "Hữu ích",
|
||||
"Not helpful": "Không hữu ích",
|
||||
"Select an option": "Chọn một mục",
|
||||
"Enter a value": "Nhập giá trị",
|
||||
"Submit": "Gửi",
|
||||
"Submitted": "Đã gửi",
|
||||
"Thinking": "Quá trình suy nghĩ",
|
||||
"AI-generated": "Được tạo bởi AI",
|
||||
"AI-generated content. Refer to the cited sources for details.": "Nội dung do AI tạo. Xem nguồn trích dẫn để biết chi tiết.",
|
||||
"No bridges configured.": "Chưa cấu hình cầu nối nào.",
|
||||
"Signing in to all bridges again...": "Đang đăng nhập lại vào tất cả cầu nối…",
|
||||
"Bridge status:": "Trạng thái cầu nối:",
|
||||
"Signed in ✓": "Đã đăng nhập ✓",
|
||||
"Not signed in ✗": "Chưa đăng nhập ✗",
|
||||
"None": "Không có",
|
||||
"Bridge {bridge}: skipped (login command or DM room missing).": "Cầu nối {bridge}: đã bỏ qua (thiếu lệnh đăng nhập hoặc phòng DM).",
|
||||
"Bridge {bridge}: sending {command}...": "Cầu nối {bridge}: đang gửi {command}…",
|
||||
"Bridge {bridge}: sign-out failed.": "Cầu nối {bridge}: đăng xuất thất bại.",
|
||||
"Bridge {bridge}: sign-in started.": "Cầu nối {bridge}: đã bắt đầu đăng nhập.",
|
||||
"Slack authorization completed. Return to LangBot to finish setup.": "Đã cấp quyền Slack. Quay lại LangBot để hoàn tất thiết lập.",
|
||||
"Slack authorization failed or expired. Return to LangBot for details.": "Cấp quyền Slack thất bại hoặc đã hết hạn. Xem chi tiết trong LangBot.",
|
||||
"TOTP is already enabled for this Account": "Tài khoản này đã bật TOTP",
|
||||
"TOTP is not enabled for this Account": "Tài khoản này chưa bật TOTP",
|
||||
"Invalid or expired passkey challenge": "Yêu cầu xác minh passkey không hợp lệ hoặc đã hết hạn",
|
||||
"Passkey credential not recognized": "Không nhận diện được passkey",
|
||||
"Account is disabled": "Tài khoản đã bị vô hiệu hóa",
|
||||
"Modifying login info is disabled": "Không được phép sửa thông tin đăng nhập",
|
||||
"Invalid GitHub repository URL": "URL kho GitHub không hợp lệ",
|
||||
"Server is busy. Please try again later.": "Máy chủ đang bận. Vui lòng thử lại sau.",
|
||||
"This feature is unavailable in this Workspace.": "Tính năng này không khả dụng trong không gian làm việc này.",
|
||||
"The Workspace must retain an owner.": "Không gian làm việc phải có ít nhất một chủ sở hữu.",
|
||||
"Invalid or expired invitation": "Lời mời không hợp lệ hoặc đã hết hạn",
|
||||
"This user is already a member.": "Người dùng này đã là thành viên.",
|
||||
"Voice connection failed": "Kết nối thoại thất bại",
|
||||
"User is not in the voice channel": "Người dùng không ở trong kênh thoại",
|
||||
"Voice service is unavailable": "Dịch vụ thoại không khả dụng",
|
||||
"Submission failed. Please try again.": "Gửi thất bại. Vui lòng thử lại.",
|
||||
"File size exceeds the limit.": "Kích thước tệp vượt quá giới hạn.",
|
||||
"File size exceeds 10MB limit. Please split large files into smaller parts.": "Tệp vượt quá 10MB. Vui lòng chia thành các phần nhỏ hơn.",
|
||||
"Authorization failed. Please try signing in again.": "Cấp quyền thất bại. Vui lòng đăng nhập lại.",
|
||||
"Bind the LangBot Account with the same email as this local Account": "Liên kết tài khoản LangBot có cùng email với tài khoản cục bộ này",
|
||||
"Only local accounts can bind to Space": "Chỉ tài khoản cục bộ mới có thể liên kết với Space",
|
||||
"System already initialized": "Hệ thống đã được khởi tạo",
|
||||
"System not initialized": "Hệ thống chưa được khởi tạo",
|
||||
"Password login is disabled on LangBot Cloud": "LangBot Cloud không cho phép đăng nhập bằng mật khẩu",
|
||||
"This edition supports one Workspace per instance": "Phiên bản này hỗ trợ một không gian làm việc cho mỗi phiên bản triển khai",
|
||||
"Cloud Workspaces are created by the SaaS control plane": "Không gian làm việc Cloud được tạo từ bảng điều khiển SaaS",
|
||||
"Login with your LangBot Account to accept this invitation": "Đăng nhập bằng tài khoản LangBot để chấp nhận lời mời",
|
||||
"RAG migration is not needed": "Không cần di chuyển RAG",
|
||||
"New Agent": "Agent mới",
|
||||
"New Pipeline": "Pipeline mới",
|
||||
"Provider {provider} does not support {model_type} models": "Nhà cung cấp {provider} không hỗ trợ mô hình {model_type}",
|
||||
"Invalid JSON format": "Định dạng JSON không hợp lệ",
|
||||
"ChatGPT sign-in required. Open this provider and sign in again.": "Cần đăng nhập ChatGPT. Mở nhà cung cấp này và đăng nhập lại.",
|
||||
"ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "Yêu cầu ChatGPT bị giới hạn tần suất hoặc mức sử dụng. Hãy thử lại sau hoặc kiểm tra gói của bạn.",
|
||||
"ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT đã từ chối yêu cầu. Kiểm tra gói đăng ký và quyền của không gian làm việc.",
|
||||
"ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT đã từ chối mô hình hoặc yêu cầu. Kiểm tra mô hình đã chọn và cài đặt yêu cầu.",
|
||||
"ChatGPT Codex upstream request failed. Please retry later.": "Yêu cầu tới ChatGPT Codex thất bại. Vui lòng thử lại sau.",
|
||||
"ChatGPT subscription usage limit reached. Please retry later or check your plan.": "Đã đạt giới hạn sử dụng gói ChatGPT. Hãy thử lại sau hoặc kiểm tra gói của bạn.",
|
||||
"ChatGPT Codex response failed or was incomplete. Please retry.": "Phản hồi ChatGPT Codex thất bại hoặc chưa hoàn tất. Vui lòng thử lại.",
|
||||
"ChatGPT token refresh temporarily failed. Please retry.": "Tạm thời không thể làm mới token ChatGPT. Vui lòng thử lại.",
|
||||
"A second factor is required": "Cần xác thực hai yếu tố",
|
||||
"Sign in or provide registration details to accept this invitation": "Đăng nhập hoặc cung cấp thông tin đăng ký để chấp nhận lời mời",
|
||||
"Sign out before creating the invited local Account": "Đăng xuất trước khi tạo tài khoản cục bộ được mời",
|
||||
"Turnstile verification failed": "Xác minh Turnstile thất bại. Vui lòng thử lại.",
|
||||
"The invitation email does not match your Account": "Email trong lời mời không khớp với tài khoản của bạn",
|
||||
"Failed to save ChatGPT credentials. Please retry.": "Không thể lưu thông tin xác thực ChatGPT. Vui lòng thử lại.",
|
||||
"Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "Không thể bắt đầu đăng nhập thiết bị ChatGPT. Hãy bật đăng nhập bằng mã thiết bị trong cài đặt ChatGPT.",
|
||||
"Option {number}": "Lựa chọn {number}",
|
||||
"Confirmation needed": "Cần xác nhận",
|
||||
"Session reset successfully": "Đã đặt lại phiên"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"Request failed.": "请求失败。",
|
||||
"Too many requests. Please try again later.": "请求过于频繁,请稍后重试。",
|
||||
"Your message contains inappropriate content. Please revise it.": "消息中存在不合适的内容,请修改。",
|
||||
"Content checking failed. Please contact the administrator.": "内容检查失败,请联系管理员。",
|
||||
"Calling {tools}...": "正在调用 {tools}…",
|
||||
"Agent runner not found: {runner}": "找不到 Agent 运行器:{runner}",
|
||||
"Agent runner not authorized for this pipeline.": "此流水线未获授权使用该 Agent 运行器。",
|
||||
"Agent runner temporarily unavailable. Please try again.": "Agent 运行器暂时不可用,请重试。",
|
||||
"Agent runner execution failed.": "Agent 执行失败。",
|
||||
"The Agent runner returned an invalid response.": "Agent 运行器返回了无效响应。",
|
||||
"Model request failed": "模型请求失败",
|
||||
"Context length exceeded": "上下文长度超限",
|
||||
"Invalid request parameters": "请求参数错误",
|
||||
"Invalid API key": "API 密钥无效",
|
||||
"Invalid model or endpoint": "模型或接口地址无效",
|
||||
"Rate limit exceeded or insufficient balance": "请求过于频繁或余额不足",
|
||||
"Request timed out": "请求超时",
|
||||
"Connection failed": "连接失败",
|
||||
"API error": "API 错误",
|
||||
"Unknown error": "未知错误",
|
||||
"Internal server error": "服务器内部错误",
|
||||
"Resource not found": "资源不存在",
|
||||
"Permission denied": "没有执行此操作的权限",
|
||||
"Invalid authentication credentials": "身份验证失败,请重新登录",
|
||||
"MCP runtime failed": "MCP 运行失败",
|
||||
"Task execution failed": "任务执行失败",
|
||||
"Task was cancelled": "任务已取消",
|
||||
"Maximum number of {resource} ({limit}) reached": "{resource}数量已达到上限({limit})",
|
||||
"extensions": "扩展",
|
||||
"knowledge bases": "知识库",
|
||||
"MCP server already exists: {name}": "MCP 服务器已存在:{name}",
|
||||
"LangBot Models is managed by Cloud and cannot be modified": "LangBot Models 由云端管理,无法修改",
|
||||
"Please sign in with your LangBot Account": "请使用 LangBot 账号登录",
|
||||
"Current password is incorrect": "当前密码不正确",
|
||||
"Invalid TOTP or recovery code": "验证码或恢复码无效",
|
||||
"No local password set, please set a password first": "尚未设置本地密码,请先设置密码",
|
||||
"This LangBot Account is already bound to another user": "此 LangBot 账号已绑定其他用户",
|
||||
"Invalid recovery key": "恢复密钥无效",
|
||||
"Done": "操作成功",
|
||||
"Thank you for your feedback": "感谢您的反馈",
|
||||
"Failed to process feedback": "反馈处理失败",
|
||||
"Helpful": "有帮助",
|
||||
"Not helpful": "无帮助",
|
||||
"Select an option": "请选择",
|
||||
"Enter a value": "请输入",
|
||||
"Submit": "提交",
|
||||
"Submitted": "已提交",
|
||||
"Thinking": "思考过程",
|
||||
"AI-generated": "内容由 AI 生成",
|
||||
"AI-generated content. Refer to the cited sources for details.": "内容由 AI 生成,详情请参考引用来源。",
|
||||
"No bridges configured.": "没有配置任何桥。",
|
||||
"Signing in to all bridges again...": "正在重新登录所有桥…",
|
||||
"Bridge status:": "桥状态:",
|
||||
"Signed in ✓": "已登录 ✓",
|
||||
"Not signed in ✗": "未登录 ✗",
|
||||
"None": "无",
|
||||
"Bridge {bridge}: skipped (login command or DM room missing).": "桥 {bridge}:已跳过(缺少登录命令或私聊房间)。",
|
||||
"Bridge {bridge}: sending {command}...": "桥 {bridge}:正在发送 {command}…",
|
||||
"Bridge {bridge}: sign-out failed.": "桥 {bridge}:退出登录失败。",
|
||||
"Bridge {bridge}: sign-in started.": "桥 {bridge}:已开始登录。",
|
||||
"Slack authorization completed. Return to LangBot to finish setup.": "Slack 授权完成,请返回 LangBot 完成配置。",
|
||||
"Slack authorization failed or expired. Return to LangBot for details.": "Slack 授权失败或已过期,请返回 LangBot 查看详情。",
|
||||
"TOTP is already enabled for this Account": "此账号已启用两步验证",
|
||||
"TOTP is not enabled for this Account": "此账号尚未启用两步验证",
|
||||
"Invalid or expired passkey challenge": "通行密钥验证请求无效或已过期",
|
||||
"Passkey credential not recognized": "无法识别此通行密钥",
|
||||
"Account is disabled": "账号已停用",
|
||||
"Modifying login info is disabled": "不允许修改登录信息",
|
||||
"Invalid GitHub repository URL": "GitHub 仓库地址无效",
|
||||
"Server is busy. Please try again later.": "服务器繁忙,请稍后重试。",
|
||||
"This feature is unavailable in this Workspace.": "此工作区无法使用该功能。",
|
||||
"The Workspace must retain an owner.": "工作区必须保留一位所有者。",
|
||||
"Invalid or expired invitation": "邀请无效或已过期",
|
||||
"This user is already a member.": "此用户已是工作区成员。",
|
||||
"Voice connection failed": "语音连接失败",
|
||||
"User is not in the voice channel": "用户不在语音频道中",
|
||||
"Voice service is unavailable": "语音服务不可用",
|
||||
"Submission failed. Please try again.": "提交失败,请重试。",
|
||||
"File size exceeds the limit.": "文件大小超过限制。",
|
||||
"File size exceeds 10MB limit. Please split large files into smaller parts.": "文件大小超过 10MB,请拆分后上传。",
|
||||
"Authorization failed. Please try signing in again.": "授权失败,请重新登录。",
|
||||
"Bind the LangBot Account with the same email as this local Account": "请绑定与此本地账号邮箱相同的 LangBot 账号",
|
||||
"Only local accounts can bind to Space": "只有本地账号可以绑定 Space",
|
||||
"System already initialized": "系统已初始化",
|
||||
"System not initialized": "系统尚未初始化",
|
||||
"Password login is disabled on LangBot Cloud": "LangBot Cloud 不支持密码登录",
|
||||
"This edition supports one Workspace per instance": "此版本每个实例仅支持一个工作区",
|
||||
"Cloud Workspaces are created by the SaaS control plane": "云工作区由 SaaS 控制台创建",
|
||||
"Login with your LangBot Account to accept this invitation": "请登录 LangBot 账号以接受邀请",
|
||||
"RAG migration is not needed": "无需迁移知识库",
|
||||
"New Agent": "新 Agent",
|
||||
"New Pipeline": "新流水线",
|
||||
"Provider {provider} does not support {model_type} models": "提供商 {provider} 不支持 {model_type} 模型",
|
||||
"Invalid JSON format": "JSON 格式无效",
|
||||
"ChatGPT sign-in required. Open this provider and sign in again.": "需要登录 ChatGPT。请打开此提供商并重新登录。",
|
||||
"ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT 请求受限(请求频率或用量限制)。请稍后重试或检查套餐。",
|
||||
"ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT 拒绝了此请求。请检查订阅和工作区权限。",
|
||||
"ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT 拒绝了模型或请求。请检查所选模型和请求设置。",
|
||||
"ChatGPT Codex upstream request failed. Please retry later.": "ChatGPT Codex 上游请求失败,请稍后重试。",
|
||||
"ChatGPT subscription usage limit reached. Please retry later or check your plan.": "ChatGPT 订阅用量已达上限。请稍后重试或检查套餐。",
|
||||
"ChatGPT Codex response failed or was incomplete. Please retry.": "ChatGPT Codex 响应失败或不完整,请重试。",
|
||||
"ChatGPT token refresh temporarily failed. Please retry.": "ChatGPT 令牌暂时刷新失败,请重试。",
|
||||
"A second factor is required": "需要二次验证",
|
||||
"Sign in or provide registration details to accept this invitation": "请登录或填写注册信息以接受此邀请",
|
||||
"Sign out before creating the invited local Account": "请先退出登录,再创建受邀本地账号",
|
||||
"Turnstile verification failed": "人机验证失败,请重试",
|
||||
"The invitation email does not match your Account": "邀请邮箱与您的账号不匹配",
|
||||
"Failed to save ChatGPT credentials. Please retry.": "保存 ChatGPT 凭据失败,请重试。",
|
||||
"Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "无法启动 ChatGPT 设备登录。请在 ChatGPT 设置中启用设备代码登录。",
|
||||
"Option {number}": "选项 {number}",
|
||||
"Confirmation needed": "需要确认",
|
||||
"Session reset successfully": "会话已重置"
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"Request failed.": "請求失敗。",
|
||||
"Too many requests. Please try again later.": "請求過於頻繁,請稍後重試。",
|
||||
"Your message contains inappropriate content. Please revise it.": "訊息中存在不適當的內容,請修改。",
|
||||
"Content checking failed. Please contact the administrator.": "內容檢查失敗,請聯絡管理員。",
|
||||
"Calling {tools}...": "正在呼叫 {tools}…",
|
||||
"Agent runner not found: {runner}": "找不到 Agent 執行器:{runner}",
|
||||
"Agent runner not authorized for this pipeline.": "此流水線未獲授權使用該 Agent 執行器。",
|
||||
"Agent runner temporarily unavailable. Please try again.": "Agent 執行器暫時無法使用,請重試。",
|
||||
"Agent runner execution failed.": "Agent 執行失敗。",
|
||||
"The Agent runner returned an invalid response.": "Agent 執行器傳回了無效回應。",
|
||||
"Model request failed": "模型請求失敗",
|
||||
"Context length exceeded": "上下文長度超出限制",
|
||||
"Invalid request parameters": "請求參數錯誤",
|
||||
"Invalid API key": "API 金鑰無效",
|
||||
"Invalid model or endpoint": "模型或介面位址無效",
|
||||
"Rate limit exceeded or insufficient balance": "請求過於頻繁或餘額不足",
|
||||
"Request timed out": "請求逾時",
|
||||
"Connection failed": "連線失敗",
|
||||
"API error": "API 錯誤",
|
||||
"Unknown error": "未知錯誤",
|
||||
"Internal server error": "伺服器內部錯誤",
|
||||
"Resource not found": "資源不存在",
|
||||
"Permission denied": "沒有執行此操作的權限",
|
||||
"Invalid authentication credentials": "身分驗證失敗,請重新登入",
|
||||
"MCP runtime failed": "MCP 執行失敗",
|
||||
"Task execution failed": "任務執行失敗",
|
||||
"Task was cancelled": "任務已取消",
|
||||
"Maximum number of {resource} ({limit}) reached": "{resource}數量已達上限({limit})",
|
||||
"extensions": "擴充功能",
|
||||
"knowledge bases": "知識庫",
|
||||
"MCP server already exists: {name}": "MCP 伺服器已存在:{name}",
|
||||
"LangBot Models is managed by Cloud and cannot be modified": "LangBot Models 由雲端管理,無法修改",
|
||||
"Please sign in with your LangBot Account": "請使用 LangBot 帳號登入",
|
||||
"Current password is incorrect": "目前密碼不正確",
|
||||
"Invalid TOTP or recovery code": "驗證碼或復原碼無效",
|
||||
"No local password set, please set a password first": "尚未設定本機密碼,請先設定密碼",
|
||||
"This LangBot Account is already bound to another user": "此 LangBot 帳號已綁定其他使用者",
|
||||
"Invalid recovery key": "復原金鑰無效",
|
||||
"Done": "操作成功",
|
||||
"Thank you for your feedback": "感謝您的回饋",
|
||||
"Failed to process feedback": "回饋處理失敗",
|
||||
"Helpful": "有幫助",
|
||||
"Not helpful": "沒有幫助",
|
||||
"Select an option": "請選擇",
|
||||
"Enter a value": "請輸入",
|
||||
"Submit": "提交",
|
||||
"Submitted": "已提交",
|
||||
"Thinking": "思考過程",
|
||||
"AI-generated": "內容由 AI 產生",
|
||||
"AI-generated content. Refer to the cited sources for details.": "內容由 AI 產生,詳情請參考引用來源。",
|
||||
"No bridges configured.": "尚未設定任何橋接。",
|
||||
"Signing in to all bridges again...": "正在重新登入所有橋接…",
|
||||
"Bridge status:": "橋接狀態:",
|
||||
"Signed in ✓": "已登入 ✓",
|
||||
"Not signed in ✗": "未登入 ✗",
|
||||
"None": "無",
|
||||
"Bridge {bridge}: skipped (login command or DM room missing).": "橋接 {bridge}:已略過(缺少登入命令或私訊房間)。",
|
||||
"Bridge {bridge}: sending {command}...": "橋接 {bridge}:正在傳送 {command}…",
|
||||
"Bridge {bridge}: sign-out failed.": "橋接 {bridge}:登出失敗。",
|
||||
"Bridge {bridge}: sign-in started.": "橋接 {bridge}:已開始登入。",
|
||||
"Slack authorization completed. Return to LangBot to finish setup.": "Slack 授權完成,請返回 LangBot 完成設定。",
|
||||
"Slack authorization failed or expired. Return to LangBot for details.": "Slack 授權失敗或已過期,請返回 LangBot 查看詳情。",
|
||||
"TOTP is already enabled for this Account": "此帳號已啟用兩步驗證",
|
||||
"TOTP is not enabled for this Account": "此帳號尚未啟用兩步驗證",
|
||||
"Invalid or expired passkey challenge": "通行金鑰驗證請求無效或已過期",
|
||||
"Passkey credential not recognized": "無法識別此通行金鑰",
|
||||
"Account is disabled": "帳號已停用",
|
||||
"Modifying login info is disabled": "不允許修改登入資訊",
|
||||
"Invalid GitHub repository URL": "GitHub 儲存庫網址無效",
|
||||
"Server is busy. Please try again later.": "伺服器忙碌中,請稍後重試。",
|
||||
"This feature is unavailable in this Workspace.": "此工作區無法使用該功能。",
|
||||
"The Workspace must retain an owner.": "工作區必須保留一位擁有者。",
|
||||
"Invalid or expired invitation": "邀請無效或已過期",
|
||||
"This user is already a member.": "此使用者已是工作區成員。",
|
||||
"Voice connection failed": "語音連線失敗",
|
||||
"User is not in the voice channel": "使用者不在語音頻道中",
|
||||
"Voice service is unavailable": "語音服務無法使用",
|
||||
"Submission failed. Please try again.": "提交失敗,請重試。",
|
||||
"File size exceeds the limit.": "檔案大小超過限制。",
|
||||
"File size exceeds 10MB limit. Please split large files into smaller parts.": "檔案大小超過 10MB,請分割後上傳。",
|
||||
"Authorization failed. Please try signing in again.": "授權失敗,請重新登入。",
|
||||
"Bind the LangBot Account with the same email as this local Account": "請綁定與此本機帳號電子郵件相同的 LangBot 帳號",
|
||||
"Only local accounts can bind to Space": "只有本機帳號可以綁定 Space",
|
||||
"System already initialized": "系統已初始化",
|
||||
"System not initialized": "系統尚未初始化",
|
||||
"Password login is disabled on LangBot Cloud": "LangBot Cloud 不支援密碼登入",
|
||||
"This edition supports one Workspace per instance": "此版本每個執行個體僅支援一個工作區",
|
||||
"Cloud Workspaces are created by the SaaS control plane": "雲端工作區由 SaaS 控制台建立",
|
||||
"Login with your LangBot Account to accept this invitation": "請登入 LangBot 帳號以接受邀請",
|
||||
"RAG migration is not needed": "無需遷移知識庫",
|
||||
"New Agent": "新 Agent",
|
||||
"New Pipeline": "新流水線",
|
||||
"Provider {provider} does not support {model_type} models": "提供商 {provider} 不支援 {model_type} 模型",
|
||||
"Invalid JSON format": "JSON 格式無效",
|
||||
"ChatGPT sign-in required. Open this provider and sign in again.": "需要登入 ChatGPT。請開啟此供應商並重新登入。",
|
||||
"ChatGPT request was limited (rate limit or usage restriction). Please retry later or check your plan.": "ChatGPT 請求受限(請求頻率或用量限制)。請稍後重試或檢查方案。",
|
||||
"ChatGPT denied this request. Check subscription and workspace permissions.": "ChatGPT 拒絕了此請求。請檢查訂閱和工作區權限。",
|
||||
"ChatGPT rejected the model or request. Check the selected model and request settings.": "ChatGPT 拒絕了模型或請求。請檢查所選模型和請求設定。",
|
||||
"ChatGPT Codex upstream request failed. Please retry later.": "ChatGPT Codex 上游請求失敗,請稍後重試。",
|
||||
"ChatGPT subscription usage limit reached. Please retry later or check your plan.": "ChatGPT 訂閱用量已達上限。請稍後重試或檢查方案。",
|
||||
"ChatGPT Codex response failed or was incomplete. Please retry.": "ChatGPT Codex 回應失敗或不完整,請重試。",
|
||||
"ChatGPT token refresh temporarily failed. Please retry.": "ChatGPT 權杖暫時更新失敗,請重試。",
|
||||
"A second factor is required": "需要雙重驗證",
|
||||
"Sign in or provide registration details to accept this invitation": "請登入或填寫註冊資料以接受此邀請",
|
||||
"Sign out before creating the invited local Account": "請先登出,再建立受邀本機帳號",
|
||||
"Turnstile verification failed": "人機驗證失敗,請重試",
|
||||
"The invitation email does not match your Account": "邀請信箱與您的帳號不符",
|
||||
"Failed to save ChatGPT credentials. Please retry.": "儲存 ChatGPT 憑證失敗,請重試。",
|
||||
"Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.": "無法啟動 ChatGPT 裝置登入。請在 ChatGPT 設定中啟用裝置代碼登入。",
|
||||
"Option {number}": "選項 {number}",
|
||||
"Confirmation needed": "需要確認",
|
||||
"Session reset successfully": "對話已重設"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Workspace-owned message language, stored in the existing tenant metadata."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from collections import OrderedDict
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import insert as pg_insert
|
||||
from sqlalchemy.dialects.sqlite import insert as sqlite_insert
|
||||
|
||||
from ..entity.persistence.metadata import WorkspaceMetadata
|
||||
from . import DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, normalize_language
|
||||
|
||||
LANGUAGE_KEY = 'message_language'
|
||||
_CACHE_TTL = 30
|
||||
_CACHE_SIZE = 256
|
||||
|
||||
|
||||
def _cache(ap) -> OrderedDict:
|
||||
cache = getattr(ap, '_message_language_cache', None)
|
||||
if cache is None:
|
||||
cache = OrderedDict()
|
||||
ap._message_language_cache = cache
|
||||
return cache
|
||||
|
||||
|
||||
async def get_workspace_language(ap, workspace_uuid: str, *, refresh: bool = False) -> str:
|
||||
if not workspace_uuid:
|
||||
return DEFAULT_LANGUAGE
|
||||
cache = _cache(ap)
|
||||
cached = cache.get(workspace_uuid)
|
||||
if not refresh and cached and cached[1] > time.monotonic():
|
||||
return cached[0]
|
||||
manager = getattr(ap, 'persistence_mgr', None)
|
||||
if manager is None or not callable(getattr(manager, 'tenant_uow', None)):
|
||||
return DEFAULT_LANGUAGE
|
||||
async with manager.tenant_uow(workspace_uuid) as uow:
|
||||
result = await uow.execute(
|
||||
sa.select(WorkspaceMetadata.value).where(
|
||||
WorkspaceMetadata.workspace_uuid == workspace_uuid,
|
||||
WorkspaceMetadata.key == LANGUAGE_KEY,
|
||||
)
|
||||
)
|
||||
locale = normalize_language(result.scalar_one_or_none())
|
||||
cache[workspace_uuid] = (locale, time.monotonic() + _CACHE_TTL)
|
||||
cache.move_to_end(workspace_uuid)
|
||||
while len(cache) > _CACHE_SIZE:
|
||||
cache.popitem(last=False)
|
||||
return locale
|
||||
|
||||
|
||||
async def set_workspace_language(ap, workspace_uuid: str, locale: str) -> str:
|
||||
if locale not in SUPPORTED_LANGUAGES:
|
||||
raise ValueError('Unsupported Workspace language')
|
||||
async with ap.persistence_mgr.tenant_uow(workspace_uuid) as uow:
|
||||
dialect = ap.persistence_mgr.get_db_engine().dialect.name
|
||||
insert = pg_insert if dialect == 'postgresql' else sqlite_insert
|
||||
statement = insert(WorkspaceMetadata).values(workspace_uuid=workspace_uuid, key=LANGUAGE_KEY, value=locale)
|
||||
await uow.execute(
|
||||
statement.on_conflict_do_update(
|
||||
index_elements=['workspace_uuid', 'key'],
|
||||
set_={'value': locale},
|
||||
)
|
||||
)
|
||||
# Synchronous platform callbacks may run in SDK threads. Publish the new
|
||||
# value only after commit, so they see changes without restarting a bot.
|
||||
_cache(ap)[workspace_uuid] = (locale, time.monotonic() + _CACHE_TTL)
|
||||
while len(_cache(ap)) > _CACHE_SIZE:
|
||||
_cache(ap).popitem(last=False)
|
||||
return locale
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import pipeline_notice
|
||||
|
||||
from .. import entities
|
||||
from .. import filter as filter_model
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
@@ -58,6 +60,10 @@ class BaiduCloudExamine(filter_model.ContentFilter):
|
||||
return entities.FilterResult(
|
||||
level=entities.ResultLevel.BLOCK,
|
||||
replacement=message,
|
||||
user_notice='消息中存在不合适的内容, 请修改',
|
||||
user_notice=pipeline_notice(
|
||||
query,
|
||||
'content-filter-hint',
|
||||
'Your message contains inappropriate content. Please revise it.',
|
||||
),
|
||||
console_notice=f'百度云判定结果:{conclusion}',
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import pipeline_notice
|
||||
|
||||
from .. import filter as filter_model
|
||||
from .. import entities
|
||||
import langbot_plugin.api.entities.builtin.pipeline.query as pipeline_query
|
||||
@@ -35,13 +37,19 @@ class BanWordFilter(filter_model.ContentFilter):
|
||||
return entities.FilterResult(
|
||||
level=entities.ResultLevel.BLOCK,
|
||||
replacement='',
|
||||
user_notice='内容检查规则执行失败,请联系管理员',
|
||||
user_notice=pipeline_notice(
|
||||
query, 'content-filter-error-hint', 'Content checking failed. Please contact the administrator.'
|
||||
),
|
||||
console_notice=f'Sensitive-word regex rejected: {exc}',
|
||||
)
|
||||
|
||||
return entities.FilterResult(
|
||||
level=entities.ResultLevel.MASKED if found else entities.ResultLevel.PASS,
|
||||
replacement=current,
|
||||
user_notice='消息中存在不合适的内容, 请修改' if found else '',
|
||||
user_notice=pipeline_notice(
|
||||
query, 'content-filter-hint', 'Your message contains inappropriate content. Please revise it.'
|
||||
)
|
||||
if found
|
||||
else '',
|
||||
console_notice='',
|
||||
)
|
||||
|
||||
@@ -20,6 +20,8 @@ from ..api.http.context import ExecutionContext, PrincipalContext, PrincipalType
|
||||
from ..workspace.errors import WorkspaceError, WorkspaceInvariantError
|
||||
from .config_coercion import coerce_pipeline_config
|
||||
from .pool import get_query_execution_context
|
||||
from ..i18n import language_scope
|
||||
from ..i18n.workspace import get_workspace_language
|
||||
from ..agent.runner.config_resolver import RunnerConfigResolver
|
||||
from .extension_preferences import normalize_extension_preferences
|
||||
|
||||
@@ -414,6 +416,11 @@ class RuntimePipeline:
|
||||
return event_type[:160] or 'message.received'
|
||||
|
||||
async def process_query(self, query: pipeline_query.Query):
|
||||
workspace_uuid = getattr(query, 'workspace_uuid', None) or getattr(self, 'workspace_uuid', '')
|
||||
with language_scope(await get_workspace_language(self.ap, workspace_uuid)):
|
||||
return await self._process_query_with_language(query)
|
||||
|
||||
async def _process_query_with_language(self, query: pipeline_query.Query):
|
||||
from ..telemetry import trace as trace_mod
|
||||
from ..telemetry.execution import ingress
|
||||
from ..telemetry.execution import record as record_execution
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import pipeline_notice
|
||||
|
||||
import uuid
|
||||
import typing
|
||||
import traceback
|
||||
@@ -238,14 +240,20 @@ class ChatMessageHandler(handler.MessageHandler):
|
||||
|
||||
# Handle specific runner errors with appropriate messages
|
||||
if isinstance(e, RunnerNotFoundError):
|
||||
user_notice = f'Agent runner not found: {e.runner_id}'
|
||||
user_notice = pipeline_notice(
|
||||
query, 'runner-not-found-hint', 'Agent runner not found: {runner}', runner=e.runner_id
|
||||
)
|
||||
elif isinstance(e, RunnerNotAuthorizedError):
|
||||
user_notice = 'Agent runner not authorized for this pipeline'
|
||||
user_notice = pipeline_notice(
|
||||
query, 'runner-not-authorized-hint', 'Agent runner not authorized for this pipeline.'
|
||||
)
|
||||
elif isinstance(e, RunnerExecutionError):
|
||||
if e.retryable:
|
||||
user_notice = 'Agent runner temporarily unavailable. Please try again.'
|
||||
user_notice = pipeline_notice(
|
||||
query, 'runner-unavailable-hint', 'Agent runner temporarily unavailable. Please try again.'
|
||||
)
|
||||
else:
|
||||
user_notice = 'Agent runner execution failed.'
|
||||
user_notice = pipeline_notice(query, 'failure-hint', 'Agent runner execution failed.')
|
||||
else:
|
||||
# Use existing exception handling
|
||||
exception_handling = query.pipeline_config['output']['misc'].get('exception-handling', 'show-hint')
|
||||
@@ -253,7 +261,7 @@ class ChatMessageHandler(handler.MessageHandler):
|
||||
if exception_handling == 'show-error':
|
||||
user_notice = f'{e}'
|
||||
elif exception_handling == 'show-hint':
|
||||
user_notice = query.pipeline_config['output']['misc'].get('failure-hint', 'Request failed.')
|
||||
user_notice = pipeline_notice(query, 'failure-hint', 'Request failed.')
|
||||
else: # hide
|
||||
user_notice = None
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ...i18n import pipeline_notice
|
||||
|
||||
import typing
|
||||
|
||||
from .. import entities, stage
|
||||
@@ -62,7 +64,7 @@ class RateLimit(stage.PipelineStage):
|
||||
result_type=entities.ResultType.INTERRUPT,
|
||||
new_query=query,
|
||||
console_notice=f'根据限速规则忽略 {query.launcher_type.value}:{query.launcher_id} 消息',
|
||||
user_notice='请求数超过限速器设定值,已丢弃本消息。',
|
||||
user_notice=pipeline_notice(query, 'rate-limit-hint', 'Too many requests. Please try again later.'),
|
||||
)
|
||||
elif stage_inst_name == 'ReleaseRateLimitOccupancy':
|
||||
await self.algo.release_access(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ...i18n import pipeline_notice
|
||||
|
||||
import typing
|
||||
|
||||
from .. import entities
|
||||
@@ -175,7 +177,9 @@ class ResponseWrapper(stage.PipelineStage):
|
||||
if result.tool_calls is not None and len(result.tool_calls) > 0: # 有函数调用
|
||||
function_names = [tc.function.name for tc in result.tool_calls]
|
||||
|
||||
reply_text = f'Call {".".join(function_names)}...'
|
||||
reply_text = pipeline_notice(
|
||||
query, 'tool-call-hint', 'Calling {tools}...', tools=', '.join(function_names)
|
||||
)
|
||||
|
||||
# Only surface the tool-call notice when the pipeline option
|
||||
# is enabled. Emitting it unconditionally would become the
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from langbot.pkg.i18n.adapters import adapter_t, localized_adapter
|
||||
|
||||
from langbot.pkg.platform.sources.lark import (
|
||||
LarkAdapter as LegacyLarkAdapter,
|
||||
NonBlockingLarkWSClient,
|
||||
@@ -347,6 +349,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
self.request_tenant_access_token(tenant_key)
|
||||
return self.tenant_access_tokens.get(tenant_key, {}).get('token')
|
||||
|
||||
@localized_adapter
|
||||
async def send_message(
|
||||
self,
|
||||
target_type: str,
|
||||
@@ -380,6 +383,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
message_id=message_ids[-1] if message_ids else '', raw={'message_ids': message_ids}
|
||||
)
|
||||
|
||||
@localized_adapter
|
||||
async def reply_message(
|
||||
self,
|
||||
message_source: platform_events.MessageEvent,
|
||||
@@ -538,6 +542,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
raise RuntimeError(f'Lark card update failed: {response.code} {response.msg}')
|
||||
self.closed_streaming_cards.add(card_id)
|
||||
|
||||
@localized_adapter
|
||||
async def reply_message_chunk(
|
||||
self,
|
||||
message_source: platform_events.MessageEvent,
|
||||
@@ -592,6 +597,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
self.card_last_update_dict.pop(card_id, None)
|
||||
self.closed_streaming_cards.discard(card_id)
|
||||
|
||||
@localized_adapter
|
||||
async def call_platform_api(self, action: str, params: dict = {}) -> dict:
|
||||
if action == 'interaction.request':
|
||||
return await send_interaction(self, params)
|
||||
@@ -632,6 +638,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
return f'{event.group.id}_{thread_id}'
|
||||
return None
|
||||
|
||||
@localized_adapter
|
||||
async def handle_unified_webhook(self, bot_uuid: str, path: str, request):
|
||||
try:
|
||||
data = await request.json
|
||||
@@ -668,7 +675,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
feedback_event = self._feedback_event_from_webhook(data)
|
||||
if feedback_event and platform_events.FeedbackEvent in self.listeners:
|
||||
await self.listeners[platform_events.FeedbackEvent](feedback_event, self)
|
||||
return {'toast': {'type': 'success', 'content': '感谢您的反馈'}}
|
||||
return {'toast': {'type': 'success', 'content': adapter_t(self, 'Thank you for your feedback')}}
|
||||
await self._dispatch_eba_event(LarkEventConverter.platform_specific(data, event_type, data))
|
||||
return {'code': 200, 'message': 'ok'}
|
||||
except Exception:
|
||||
@@ -689,6 +696,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
def _webhook_header(self, data: dict):
|
||||
return type('LarkWebhookHeader', (), data.get('header', {}))()
|
||||
|
||||
@localized_adapter
|
||||
async def run_async(self):
|
||||
self.event_loop = asyncio.get_running_loop()
|
||||
if self.config.get('app_type', 'self') != 'isv':
|
||||
@@ -892,12 +900,13 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
self._submit_coro(self.listeners[platform_events.FeedbackEvent](feedback_event, self))
|
||||
from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse
|
||||
|
||||
return P2CardActionTriggerResponse({'toast': {'type': 'success', 'content': '感谢您的反馈'}})
|
||||
return P2CardActionTriggerResponse(
|
||||
{'toast': {'type': 'success', 'content': adapter_t(self, 'Thank you for your feedback')}}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _interaction_action_response(event: platform_events.PlatformSpecificEvent) -> dict[str, typing.Any]:
|
||||
def _interaction_action_response(self, event: platform_events.PlatformSpecificEvent) -> dict[str, typing.Any]:
|
||||
response: dict[str, typing.Any] = {
|
||||
'toast': {'type': 'success', 'content': 'Submitted / 已提交'},
|
||||
'toast': {'type': 'success', 'content': adapter_t(self, 'Submitted')},
|
||||
}
|
||||
if not event.data.get('cardkit'):
|
||||
response['card'] = {
|
||||
@@ -907,7 +916,7 @@ class LarkAdapter(LarkAPIMixin, abstract_platform_adapter.AbstractPlatformAdapte
|
||||
'elements': [
|
||||
{
|
||||
'tag': 'div',
|
||||
'text': {'tag': 'lark_md', 'content': '**Submitted / 已提交**'},
|
||||
'text': {'tag': 'lark_md', 'content': '**' + adapter_t(self, 'Submitted') + '**'},
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from langbot.pkg.i18n import t
|
||||
|
||||
import json
|
||||
import time
|
||||
import typing
|
||||
@@ -61,8 +63,7 @@ def _field_form_elements(
|
||||
component_name = 'lbi_field_0'
|
||||
label = str(field.get('label') or field_id)
|
||||
placeholder = str(
|
||||
field.get('placeholder')
|
||||
or ('Select an option / 请选择' if field_type == 'select' else 'Enter a value / 请输入')
|
||||
field.get('placeholder') or (t('Select an option') if field_type == 'select' else t('Enter a value'))
|
||||
)
|
||||
required = bool(field.get('required'))
|
||||
if field_type == 'select':
|
||||
@@ -122,7 +123,7 @@ def _field_form_elements(
|
||||
submit_button = {
|
||||
'tag': 'button',
|
||||
'name': 'lbi_submit',
|
||||
'text': {'tag': 'plain_text', 'content': 'Submit / 提交'},
|
||||
'text': {'tag': 'plain_text', 'content': t('Submit')},
|
||||
'type': 'primary',
|
||||
'width': 'fill',
|
||||
'form_action_type': 'submit',
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import re
|
||||
|
||||
from langbot.pkg.i18n import t
|
||||
|
||||
|
||||
def split_content(content: str) -> tuple[str, str]:
|
||||
reasoning, answer = [], []
|
||||
@@ -28,7 +30,7 @@ def streaming_text(content: str) -> str:
|
||||
if len(reasoning) > 360:
|
||||
preview = '…' + preview
|
||||
quote = '> ' + preview.replace('\n', '\n> ')
|
||||
return '**思考过程 / Thinking**\n\n' + quote + ('\n\n---\n\n' + answer if answer else '')
|
||||
return '**' + t('Thinking') + '**\n\n' + quote + ('\n\n---\n\n' + answer if answer else '')
|
||||
|
||||
|
||||
def build_card(content: str = '', *, streaming: bool = False, finished: bool = True) -> dict:
|
||||
@@ -42,7 +44,7 @@ def build_card(content: str = '', *, streaming: bool = False, finished: bool = T
|
||||
{
|
||||
'tag': 'collapsible_panel',
|
||||
'expanded': not finished,
|
||||
'header': {'title': {'tag': 'plain_text', 'content': '思考过程 / Thinking'}},
|
||||
'header': {'title': {'tag': 'plain_text', 'content': t('Thinking')}},
|
||||
'elements': [{'tag': 'markdown', 'content': reasoning}],
|
||||
}
|
||||
)
|
||||
@@ -58,9 +60,7 @@ def build_card(content: str = '', *, streaming: bool = False, finished: bool = T
|
||||
'width': 'weighted',
|
||||
'weight': 1,
|
||||
'vertical_align': 'center',
|
||||
'elements': [
|
||||
{'tag': 'markdown', 'content': '内容由 AI 生成 / AI-generated', 'text_size': 'notation'}
|
||||
],
|
||||
'elements': [{'tag': 'markdown', 'content': t('AI-generated'), 'text_size': 'notation'}],
|
||||
},
|
||||
{
|
||||
'tag': 'column',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ..i18n import language_scope
|
||||
from ..i18n.workspace import get_workspace_language
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import dataclasses
|
||||
@@ -1713,21 +1716,22 @@ class RuntimeBot:
|
||||
def tenant_scoped_listener(listener):
|
||||
@functools.wraps(listener)
|
||||
async def wrapped(*args, **kwargs):
|
||||
tenant_scope = getattr(self.ap.persistence_mgr, 'tenant_scope', None)
|
||||
cloud_runtime = (
|
||||
getattr(
|
||||
getattr(self.ap.persistence_mgr, 'mode', None),
|
||||
'value',
|
||||
None,
|
||||
with language_scope(await get_workspace_language(self.ap, self.workspace_uuid)):
|
||||
tenant_scope = getattr(self.ap.persistence_mgr, 'tenant_scope', None)
|
||||
cloud_runtime = (
|
||||
getattr(
|
||||
getattr(self.ap.persistence_mgr, 'mode', None),
|
||||
'value',
|
||||
None,
|
||||
)
|
||||
== 'cloud_runtime'
|
||||
)
|
||||
== 'cloud_runtime'
|
||||
)
|
||||
if cloud_runtime:
|
||||
if not callable(tenant_scope):
|
||||
raise RuntimeError('Cloud platform callbacks require a tenant scope')
|
||||
async with tenant_scope(self.workspace_uuid):
|
||||
return await listener(*args, **kwargs)
|
||||
return await listener(*args, **kwargs)
|
||||
if cloud_runtime:
|
||||
if not callable(tenant_scope):
|
||||
raise RuntimeError('Cloud platform callbacks require a tenant scope')
|
||||
async with tenant_scope(self.workspace_uuid):
|
||||
return await listener(*args, **kwargs)
|
||||
return await listener(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from langbot.pkg.i18n import t
|
||||
from langbot.pkg.i18n.adapters import adapter_t, localized_adapter
|
||||
|
||||
import discord
|
||||
from discord import ui as discord_ui
|
||||
|
||||
@@ -219,11 +222,13 @@ class VoiceConnectionManager:
|
||||
# 获取服务器和频道对象
|
||||
guild = self.bot.get_guild(guild_id)
|
||||
if not guild:
|
||||
raise VoiceConnectionError(f'无法找到服务器 {guild_id}', 'GUILD_NOT_FOUND', guild_id)
|
||||
raise VoiceConnectionError(t('Resource not found') + f': {guild_id}', 'GUILD_NOT_FOUND', guild_id)
|
||||
|
||||
channel = guild.get_channel(channel_id)
|
||||
if not channel or not isinstance(channel, discord.VoiceChannel):
|
||||
raise VoiceConnectionError(f'无法找到语音频道 {channel_id}', 'CHANNEL_NOT_FOUND', guild_id)
|
||||
raise VoiceConnectionError(
|
||||
t('Resource not found') + f': {channel_id}', 'CHANNEL_NOT_FOUND', guild_id
|
||||
)
|
||||
|
||||
# 验证用户是否在语音频道中(如果提供了用户ID)
|
||||
if user_id:
|
||||
@@ -258,12 +263,14 @@ class VoiceConnectionManager:
|
||||
return voice_client
|
||||
|
||||
except discord.ClientException as e:
|
||||
raise VoiceNetworkError(f'Discord 客户端错误: {str(e)}')
|
||||
raise VoiceNetworkError(t('Voice connection failed') + f': {e}')
|
||||
except discord.opus.OpusNotLoaded as e:
|
||||
raise VoiceConnectionError(f'Opus 编码器未加载: {str(e)}', 'OPUS_NOT_LOADED', guild_id)
|
||||
raise VoiceConnectionError(
|
||||
t('Voice service is unavailable') + f': Opus: {e}', 'OPUS_NOT_LOADED', guild_id
|
||||
)
|
||||
except Exception as e:
|
||||
await self.logger.error(f'连接语音频道时发生未知错误: {str(e)}')
|
||||
raise VoiceConnectionError(f'连接失败: {str(e)}', 'UNKNOWN_ERROR', guild_id)
|
||||
raise VoiceConnectionError(t('Voice connection failed') + f': {e}', 'UNKNOWN_ERROR', guild_id)
|
||||
|
||||
async def leave_voice_channel(self, guild_id: int) -> bool:
|
||||
"""
|
||||
@@ -494,11 +501,13 @@ class VoiceConnectionManager:
|
||||
"""
|
||||
member = guild.get_member(user_id)
|
||||
if not member:
|
||||
raise VoicePermissionError(f'无法找到用户 {user_id}', ['member_not_found'], user_id, channel.id)
|
||||
raise VoicePermissionError(
|
||||
t('Resource not found') + f': {user_id}', ['member_not_found'], user_id, channel.id
|
||||
)
|
||||
|
||||
if not member.voice or member.voice.channel != channel:
|
||||
raise VoicePermissionError(
|
||||
f'用户 {member.display_name} 不在语音频道 {channel.name} 中',
|
||||
t('User is not in the voice channel') + f': {member.display_name}, {channel.name}',
|
||||
['user_not_in_channel'],
|
||||
user_id,
|
||||
channel.id,
|
||||
@@ -528,7 +537,7 @@ class VoiceConnectionManager:
|
||||
|
||||
if missing_permissions:
|
||||
raise VoicePermissionError(
|
||||
f'机器人在频道 {channel.name} 中缺少权限: {", ".join(missing_permissions)}',
|
||||
t('Permission denied') + f': {channel.name}: {", ".join(missing_permissions)}',
|
||||
missing_permissions,
|
||||
channel_id=channel.id,
|
||||
)
|
||||
@@ -843,7 +852,7 @@ class DiscordFormView(discord_ui.View):
|
||||
# silently — most Dify forms have ≤10 actions in practice.
|
||||
for idx, action in enumerate(actions[:25]):
|
||||
action_id = str(action.get('id') or '')
|
||||
label = str(action.get('title') or action_id or f'Option {idx + 1}')
|
||||
label = str(action.get('title') or action_id or adapter_t(adapter, 'Option {number}', number=idx + 1))
|
||||
style = self._STYLE_MAP.get(
|
||||
str(action.get('button_style') or '').lower(),
|
||||
discord.ButtonStyle.secondary,
|
||||
@@ -966,6 +975,7 @@ class DiscordAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
self._pending_forms.pop(next(iter(self._pending_forms)), None)
|
||||
|
||||
# Voice functionality methods
|
||||
@localized_adapter
|
||||
async def join_voice_channel(self, guild_id: int, channel_id: int, user_id: int = None) -> discord.VoiceClient:
|
||||
"""
|
||||
加入语音频道
|
||||
@@ -990,7 +1000,7 @@ class DiscordAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
VoiceConnectionError: 其他连接错误
|
||||
"""
|
||||
if not self.voice_manager:
|
||||
raise VoiceConnectionError('语音管理器未初始化', 'MANAGER_NOT_READY')
|
||||
raise VoiceConnectionError(t('Voice service is unavailable'), 'MANAGER_NOT_READY')
|
||||
|
||||
return await self.voice_manager.join_voice_channel(guild_id, channel_id, user_id)
|
||||
|
||||
@@ -1358,7 +1368,7 @@ class DiscordAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
)
|
||||
return
|
||||
|
||||
node_title = str(form_data.get('node_title') or 'Confirmation needed')
|
||||
node_title = str(form_data.get('node_title') or adapter_t(self, 'Confirmation needed'))
|
||||
form_content = str(form_data.get('form_content') or '').strip()
|
||||
|
||||
# Two paths:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from langbot.pkg.i18n.adapters import adapter_t, localized_adapter
|
||||
|
||||
import lark_oapi
|
||||
from lark_oapi.api.im.v1 import CreateImageRequest, CreateImageRequestBody, CreateFileRequest, CreateFileRequestBody
|
||||
import traceback
|
||||
@@ -1240,7 +1242,9 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
|
||||
from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse
|
||||
|
||||
return P2CardActionTriggerResponse({'toast': {'type': 'success', 'content': '操作成功'}})
|
||||
return P2CardActionTriggerResponse(
|
||||
{'toast': {'type': 'success', 'content': adapter_t(self, 'Done')}}
|
||||
)
|
||||
|
||||
if action_value == '有帮助':
|
||||
feedback_type = 1
|
||||
@@ -1249,7 +1253,9 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
else:
|
||||
from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse
|
||||
|
||||
return P2CardActionTriggerResponse({'toast': {'type': 'success', 'content': '操作成功'}})
|
||||
return P2CardActionTriggerResponse(
|
||||
{'toast': {'type': 'success', 'content': adapter_t(self, 'Done')}}
|
||||
)
|
||||
|
||||
operator = getattr(event.event, 'operator', None)
|
||||
context = getattr(event.event, 'context', None)
|
||||
@@ -1286,13 +1292,17 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
|
||||
from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse
|
||||
|
||||
return P2CardActionTriggerResponse({'toast': {'type': 'success', 'content': '感谢您的反馈'}})
|
||||
return P2CardActionTriggerResponse(
|
||||
{'toast': {'type': 'success', 'content': adapter_t(self, 'Thank you for your feedback')}}
|
||||
)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
schedule_on_app_loop(self.logger.error(f'Error in lark card action callback: {traceback.format_exc()}'))
|
||||
from lark_oapi.event.callback.model.p2_card_action_trigger import P2CardActionTriggerResponse
|
||||
|
||||
return P2CardActionTriggerResponse({'toast': {'type': 'error', 'content': '反馈处理失败'}})
|
||||
return P2CardActionTriggerResponse(
|
||||
{'toast': {'type': 'error', 'content': adapter_t(self, 'Failed to process feedback')}}
|
||||
)
|
||||
|
||||
event_handler = (
|
||||
lark_oapi.EventDispatcherHandler.builder('', '')
|
||||
@@ -1545,6 +1555,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
return True
|
||||
return False
|
||||
|
||||
@localized_adapter
|
||||
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||
text_elements, media_items = await self.message_converter.yiri2target(message, self.api_client)
|
||||
|
||||
@@ -1809,7 +1820,11 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
'elements': [
|
||||
{
|
||||
'tag': 'markdown',
|
||||
'content': '<font color="grey-600">以上内容由 AI 生成,仅供参考。更多详细、准确信息可点击引用链接查看</font>',
|
||||
'content': '<font color="grey-600">'
|
||||
+ adapter_t(
|
||||
self, 'AI-generated content. Refer to the cited sources for details.'
|
||||
)
|
||||
+ '</font>',
|
||||
'text_align': 'left',
|
||||
'text_size': 'notation',
|
||||
'margin': '4px 0px 0px 0px',
|
||||
@@ -1840,7 +1855,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
'width': 'fill',
|
||||
'size': 'medium',
|
||||
'icon': {'tag': 'standard_icon', 'token': 'thumbsup_outlined'},
|
||||
'hover_tips': {'tag': 'plain_text', 'content': '有帮助'},
|
||||
'hover_tips': {'tag': 'plain_text', 'content': adapter_t(self, 'Helpful')},
|
||||
'behaviors': [{'type': 'callback', 'value': {'feedback': '有帮助'}}],
|
||||
'margin': '0px 0px 0px 0px',
|
||||
}
|
||||
@@ -1864,7 +1879,10 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
'width': 'default',
|
||||
'size': 'medium',
|
||||
'icon': {'tag': 'standard_icon', 'token': 'thumbdown_outlined'},
|
||||
'hover_tips': {'tag': 'plain_text', 'content': '无帮助'},
|
||||
'hover_tips': {
|
||||
'tag': 'plain_text',
|
||||
'content': adapter_t(self, 'Not helpful'),
|
||||
},
|
||||
'behaviors': [{'type': 'callback', 'value': {'feedback': '无帮助'}}],
|
||||
'margin': '0px 0px 0px 0px',
|
||||
}
|
||||
@@ -2064,6 +2082,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
)
|
||||
return new_card_id
|
||||
|
||||
@localized_adapter
|
||||
async def reply_message(
|
||||
self,
|
||||
message_source: platform_events.MessageEvent,
|
||||
@@ -2174,6 +2193,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
f'client.im.v1.message.reply ({media["msg_type"]}) failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}'
|
||||
)
|
||||
|
||||
@localized_adapter
|
||||
async def reply_message_chunk(
|
||||
self,
|
||||
message_source: platform_events.MessageEvent,
|
||||
@@ -2491,7 +2511,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
'tag': 'select_static',
|
||||
'name': component_name,
|
||||
'label': {'tag': 'plain_text', 'content': field_name},
|
||||
'placeholder': {'tag': 'plain_text', 'content': '请选择'},
|
||||
'placeholder': {'tag': 'plain_text', 'content': adapter_t(self, 'Select an option')},
|
||||
'options': [
|
||||
{
|
||||
'text': {'tag': 'plain_text', 'content': option},
|
||||
@@ -2525,7 +2545,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
'tag': 'input',
|
||||
'name': component_name,
|
||||
'label': {'tag': 'plain_text', 'content': field_name},
|
||||
'placeholder': {'tag': 'plain_text', 'content': '请输入'},
|
||||
'placeholder': {'tag': 'plain_text', 'content': adapter_t(self, 'Enter a value')},
|
||||
'default_value': _dify_default_value(field),
|
||||
'width': 'fill',
|
||||
'required': False,
|
||||
@@ -2820,7 +2840,11 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
'elements': [
|
||||
{
|
||||
'tag': 'markdown',
|
||||
'content': '<font color="grey-600">以上内容由 AI 生成,仅供参考。更多详细、准确信息可点击引用链接查看</font>',
|
||||
'content': '<font color="grey-600">'
|
||||
+ adapter_t(
|
||||
self, 'AI-generated content. Refer to the cited sources for details.'
|
||||
)
|
||||
+ '</font>',
|
||||
'text_align': 'left',
|
||||
'text_size': 'notation',
|
||||
'margin': '4px 0px 0px 0px',
|
||||
@@ -2855,7 +2879,10 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
'width': 'fill',
|
||||
'size': 'medium',
|
||||
'icon': {'tag': 'standard_icon', 'token': 'thumbsup_outlined'},
|
||||
'hover_tips': {'tag': 'plain_text', 'content': '有帮助'},
|
||||
'hover_tips': {
|
||||
'tag': 'plain_text',
|
||||
'content': adapter_t(self, 'Helpful'),
|
||||
},
|
||||
'behaviors': [{'type': 'callback', 'value': {'feedback': '有帮助'}}],
|
||||
'margin': '0px 0px 0px 0px',
|
||||
}
|
||||
@@ -2879,7 +2906,10 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
'width': 'default',
|
||||
'size': 'medium',
|
||||
'icon': {'tag': 'standard_icon', 'token': 'thumbdown_outlined'},
|
||||
'hover_tips': {'tag': 'plain_text', 'content': '无帮助'},
|
||||
'hover_tips': {
|
||||
'tag': 'plain_text',
|
||||
'content': adapter_t(self, 'Not helpful'),
|
||||
},
|
||||
'behaviors': [{'type': 'callback', 'value': {'feedback': '无帮助'}}],
|
||||
'margin': '0px 0px 0px 0px',
|
||||
}
|
||||
@@ -2973,6 +3003,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
else:
|
||||
return data['type']
|
||||
|
||||
@localized_adapter
|
||||
async def handle_unified_webhook(self, bot_uuid: str, path: str, request):
|
||||
"""处理统一 webhook 请求。
|
||||
Args:
|
||||
@@ -3132,14 +3163,14 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
},
|
||||
)
|
||||
|
||||
return {'toast': {'type': 'success', 'content': '操作成功'}}
|
||||
return {'toast': {'type': 'success', 'content': adapter_t(self, 'Done')}}
|
||||
|
||||
if action_value == '有帮助':
|
||||
feedback_type = 1
|
||||
elif action_value == '无帮助':
|
||||
feedback_type = 2
|
||||
else:
|
||||
return {'toast': {'type': 'success', 'content': '操作成功'}}
|
||||
return {'toast': {'type': 'success', 'content': adapter_t(self, 'Done')}}
|
||||
|
||||
user_id = operator.get('open_id') or operator.get('user_id')
|
||||
open_chat_id = context_data.get('open_chat_id')
|
||||
@@ -3171,10 +3202,10 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
if platform_events.FeedbackEvent in self.listeners:
|
||||
await self.listeners[platform_events.FeedbackEvent](feedback_event, self)
|
||||
|
||||
return {'toast': {'type': 'success', 'content': '感谢您的反馈'}}
|
||||
return {'toast': {'type': 'success', 'content': adapter_t(self, 'Thank you for your feedback')}}
|
||||
except Exception:
|
||||
await self.logger.error(f'Error in lark card action callback: {traceback.format_exc()}')
|
||||
return {'toast': {'type': 'error', 'content': '反馈处理失败'}}
|
||||
return {'toast': {'type': 'error', 'content': adapter_t(self, 'Failed to process feedback')}}
|
||||
|
||||
elif 'im.chat.member.bot.added_v1' == type:
|
||||
try:
|
||||
@@ -3227,6 +3258,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
await self.logger.error(f'Error in lark callback: {traceback.format_exc()}')
|
||||
return {'code': 500, 'message': 'error'}
|
||||
|
||||
@localized_adapter
|
||||
async def run_async(self):
|
||||
enable_webhook = self.config['enable-webhook']
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from langbot.pkg.i18n import t
|
||||
from langbot.pkg.i18n.adapters import localized_adapter
|
||||
|
||||
import typing
|
||||
import asyncio
|
||||
import traceback
|
||||
@@ -666,25 +669,26 @@ class MatrixAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
except Exception:
|
||||
await self.logger.error(f'[{bridge.user_id}] Bridge status check fatal error: {traceback.format_exc()}')
|
||||
|
||||
@localized_adapter
|
||||
async def _handle_relogin_command(self, room_id: str):
|
||||
"""Handle !relogin command: logout then re-login all bridges."""
|
||||
if not self._bridges:
|
||||
await self.client.room_send(
|
||||
room_id=room_id,
|
||||
message_type='m.room.message',
|
||||
content={'msgtype': 'm.text', 'body': '没有配置任何桥。'},
|
||||
content={'msgtype': 'm.text', 'body': t('No bridges configured.')},
|
||||
)
|
||||
return
|
||||
|
||||
lines = ['开始重新登录所有桥...']
|
||||
lines = [t('Signing in to all bridges again...')]
|
||||
for bridge in self._bridges:
|
||||
if not bridge.login_command or not bridge.dm_room_id:
|
||||
lines.append(f'[{bridge.user_id}] 跳过(未配置登录命令或无DM房间)')
|
||||
lines.append(t('Bridge {bridge}: skipped (login command or DM room missing).', bridge=bridge.user_id))
|
||||
continue
|
||||
|
||||
# Use configured logout command, fallback to deriving from login command
|
||||
logout_cmd = bridge.logout_command or bridge.login_command.replace('login', 'logout')
|
||||
lines.append(f'[{bridge.user_id}] 发送 "{logout_cmd}"...')
|
||||
lines.append(t('Bridge {bridge}: sending {command}...', bridge=bridge.user_id, command=logout_cmd))
|
||||
|
||||
# Cancel existing tasks
|
||||
if bridge.login_task and not bridge.login_task.done():
|
||||
@@ -700,14 +704,14 @@ class MatrixAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
content={'msgtype': 'm.text', 'body': logout_cmd},
|
||||
)
|
||||
except Exception as e:
|
||||
lines.append(f'[{bridge.user_id}] logout 发送失败: {e}')
|
||||
lines.append(t('Bridge {bridge}: sign-out failed.', bridge=bridge.user_id) + f' {e}')
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
# Reset state and restart login
|
||||
bridge.logged_in = False
|
||||
self._restart_bridge_login(bridge)
|
||||
lines.append(f'[{bridge.user_id}] 已触发重新登录')
|
||||
lines.append(t('Bridge {bridge}: sign-in started.', bridge=bridge.user_id))
|
||||
|
||||
await self.client.room_send(
|
||||
room_id=room_id,
|
||||
@@ -715,20 +719,21 @@ class MatrixAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
content={'msgtype': 'm.text', 'body': '\n'.join(lines)},
|
||||
)
|
||||
|
||||
@localized_adapter
|
||||
async def _handle_status_command(self, room_id: str):
|
||||
"""Handle !status command: show bridge states."""
|
||||
if not self._bridges:
|
||||
await self.client.room_send(
|
||||
room_id=room_id,
|
||||
message_type='m.room.message',
|
||||
content={'msgtype': 'm.text', 'body': '没有配置任何桥。'},
|
||||
content={'msgtype': 'm.text', 'body': t('No bridges configured.')},
|
||||
)
|
||||
return
|
||||
|
||||
lines = ['桥状态:']
|
||||
lines = [t('Bridge status:')]
|
||||
for bridge in self._bridges:
|
||||
status = '已登录 ✓' if bridge.logged_in else '未登录 ✗'
|
||||
dm = bridge.dm_room_id or '无'
|
||||
status = t('Signed in ✓') if bridge.logged_in else t('Not signed in ✗')
|
||||
dm = bridge.dm_room_id or t('None')
|
||||
lines.append(f'• {bridge.user_id}: {status} (DM: {dm})')
|
||||
await self.client.room_send(
|
||||
room_id=room_id,
|
||||
|
||||
@@ -23,6 +23,7 @@ from ...api.http.context import PrincipalType, RequestContext
|
||||
from ...api.http.authz import Permission, has_permission
|
||||
from ...api.http.service.tenant import require_workspace_uuid
|
||||
from ...workspace.errors import WorkspaceNotFoundError
|
||||
from ...i18n import t
|
||||
|
||||
REQUESTER = 'openai-codex'
|
||||
BASE_URL = 'https://chatgpt.com/backend-api/codex'
|
||||
@@ -77,7 +78,7 @@ def _tokens(data: dict, previous: dict | None = None) -> dict:
|
||||
or not math.isfinite(expires_at)
|
||||
or expires_at <= time.time()
|
||||
):
|
||||
raise ValueError('ChatGPT returned an incomplete authorization. Please sign in again.')
|
||||
raise ValueError(t('ChatGPT returned an incomplete authorization. Please sign in again.'))
|
||||
return {
|
||||
'access_token': access,
|
||||
'refresh_token': refresh,
|
||||
@@ -99,7 +100,7 @@ class CodexAuth:
|
||||
try:
|
||||
return await self.ap.persistence_mgr.execute_async(statement)
|
||||
except Exception:
|
||||
raise ValueError('ChatGPT credential storage failed. Please retry.') from None
|
||||
raise ValueError(t('ChatGPT credential storage failed. Please retry.')) from None
|
||||
|
||||
async def _read(self, workspace: str, provider: str) -> dict | None:
|
||||
result = await self._execute(sa.select(CodexCredential).where(*self._where(workspace, provider)))
|
||||
@@ -107,7 +108,7 @@ class CodexAuth:
|
||||
row = result.first()
|
||||
return dict(row._mapping) if row is not None else None
|
||||
except Exception:
|
||||
raise ValueError('ChatGPT credential storage failed. Please retry.') from None
|
||||
raise ValueError(t('ChatGPT credential storage failed. Please retry.')) from None
|
||||
|
||||
async def _provider(self, context, provider: str, *, user: bool = False) -> str:
|
||||
workspace = require_workspace_uuid(context)
|
||||
@@ -117,7 +118,7 @@ class CodexAuth:
|
||||
or not context.account_uuid
|
||||
or not has_permission(context, Permission.PROVIDER_SECRET_MANAGE)
|
||||
):
|
||||
raise ValueError('ChatGPT authorization requires an authorized workspace user')
|
||||
raise ValueError(t('ChatGPT authorization requires an authorized workspace user'))
|
||||
result = await self._execute(
|
||||
sa.select(ModelProvider.requester).where(
|
||||
ModelProvider.workspace_uuid == workspace, ModelProvider.uuid == provider
|
||||
@@ -147,9 +148,9 @@ class CodexAuth:
|
||||
if result.rowcount == 1:
|
||||
break
|
||||
if await self._read(workspace, provider) is None:
|
||||
raise ValueError(LOGIN_REQUIRED)
|
||||
raise ValueError(t(LOGIN_REQUIRED))
|
||||
if time.monotonic() >= deadline:
|
||||
raise ValueError('ChatGPT authorization is busy. Please retry shortly.')
|
||||
raise ValueError(t('ChatGPT authorization is busy. Please retry shortly.'))
|
||||
await asyncio.sleep(0.1)
|
||||
try:
|
||||
yield owner
|
||||
@@ -171,7 +172,7 @@ class CodexAuth:
|
||||
.values(payload=payload, version=CodexCredential.version + 1)
|
||||
)
|
||||
if result.rowcount != 1:
|
||||
raise ValueError('ChatGPT authorization was cancelled or replaced. Please retry.')
|
||||
raise ValueError(t('ChatGPT authorization was cancelled or replaced. Please retry.'))
|
||||
|
||||
async def _post(self, path: str, *, data=None, json_body=None) -> httpx.Response:
|
||||
try:
|
||||
@@ -186,7 +187,7 @@ class CodexAuth:
|
||||
25,
|
||||
)
|
||||
except (httpx.HTTPError, TimeoutError):
|
||||
raise ValueError('ChatGPT authorization network error. Please retry.') from None
|
||||
raise ValueError(t('ChatGPT authorization network error. Please retry.')) from None
|
||||
|
||||
@staticmethod
|
||||
def _json(response: httpx.Response) -> dict:
|
||||
@@ -196,7 +197,7 @@ class CodexAuth:
|
||||
raise ValueError
|
||||
return value
|
||||
except ValueError:
|
||||
raise ValueError('ChatGPT returned an invalid authorization response') from None
|
||||
raise ValueError(t('ChatGPT returned an invalid authorization response')) from None
|
||||
|
||||
async def status(self, context, provider: str) -> dict:
|
||||
workspace = await self._provider(context, provider, user=True)
|
||||
@@ -215,7 +216,9 @@ class CodexAuth:
|
||||
async with self._lease(workspace, provider) as owner:
|
||||
response = await self._post('/api/accounts/deviceauth/usercode', json_body={'client_id': CLIENT_ID})
|
||||
if response.status_code != 200:
|
||||
raise ValueError('Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.')
|
||||
raise ValueError(
|
||||
t('Unable to start ChatGPT device login. Enable device code login in ChatGPT settings.')
|
||||
)
|
||||
data = self._json(response)
|
||||
try:
|
||||
code = data.get('user_code') or data['usercode']
|
||||
@@ -224,7 +227,7 @@ class CodexAuth:
|
||||
if not isinstance(code, str) or not isinstance(device, str) or not code or not device:
|
||||
raise ValueError
|
||||
except (KeyError, ValueError, TypeError):
|
||||
raise ValueError('ChatGPT returned an invalid device code') from None
|
||||
raise ValueError(t('ChatGPT returned an invalid device code')) from None
|
||||
now = time.time()
|
||||
try:
|
||||
expiry = data.get('expires_at')
|
||||
@@ -241,7 +244,7 @@ class CodexAuth:
|
||||
raise ValueError
|
||||
expires_at = min(now + 900, expires_at)
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError('ChatGPT returned an invalid device code expiry') from None
|
||||
raise ValueError(t('ChatGPT returned an invalid device code expiry')) from None
|
||||
pending = {
|
||||
'authorization_id': secrets.token_urlsafe(32),
|
||||
'user_code': code,
|
||||
@@ -306,12 +309,12 @@ class CodexAuth:
|
||||
if response.status_code != 200:
|
||||
payload.pop('pending', None)
|
||||
await self._save(workspace, provider, owner, payload)
|
||||
raise ValueError('ChatGPT device authorization failed. Please start again.')
|
||||
raise ValueError(t('ChatGPT device authorization failed. Please start again.'))
|
||||
data = self._json(response)
|
||||
if not data.get('authorization_code') or not data.get('code_verifier'):
|
||||
payload.pop('pending', None)
|
||||
await self._save(workspace, provider, owner, payload)
|
||||
raise ValueError('ChatGPT returned an incomplete device authorization')
|
||||
raise ValueError(t('ChatGPT returned an incomplete device authorization'))
|
||||
# Keep an attempt tombstone so cancel can preempt exchange, but never replay a code.
|
||||
pending['consumed'] = True
|
||||
await self._save(workspace, provider, owner, payload)
|
||||
@@ -326,7 +329,7 @@ class CodexAuth:
|
||||
},
|
||||
)
|
||||
if response.status_code != 200:
|
||||
raise ValueError('ChatGPT token exchange failed. Please start sign-in again.')
|
||||
raise ValueError(t('ChatGPT token exchange failed. Please start sign-in again.'))
|
||||
tokens = _tokens(self._json(response))
|
||||
await self._save(
|
||||
workspace,
|
||||
@@ -384,7 +387,7 @@ class CodexAuth:
|
||||
payload = row['payload'] if row else {}
|
||||
tokens = payload.get('tokens')
|
||||
if not tokens or payload.get('invalid'):
|
||||
raise ValueError(LOGIN_REQUIRED)
|
||||
raise ValueError(t(LOGIN_REQUIRED))
|
||||
if tokens['expires_at'] > time.time() + 120 and tokens['access_token'] != rejected_token:
|
||||
return tokens
|
||||
async with self._lease(workspace, provider, refresh=True) as owner:
|
||||
@@ -392,7 +395,7 @@ class CodexAuth:
|
||||
payload = dict(row['payload'])
|
||||
tokens = payload.get('tokens')
|
||||
if not tokens or payload.get('invalid'):
|
||||
raise ValueError(LOGIN_REQUIRED)
|
||||
raise ValueError(t(LOGIN_REQUIRED))
|
||||
if tokens['expires_at'] > time.time() + 120 and tokens['access_token'] != rejected_token:
|
||||
return tokens
|
||||
response = await self._post(
|
||||
@@ -411,9 +414,9 @@ class CodexAuth:
|
||||
payload.pop('tokens', None)
|
||||
payload.pop('completed', None)
|
||||
await self._save(workspace, provider, owner, payload)
|
||||
raise ValueError(LOGIN_REQUIRED)
|
||||
raise ValueError(t(LOGIN_REQUIRED))
|
||||
if response.status_code != 200:
|
||||
raise ValueError('ChatGPT token refresh temporarily failed. Please retry.')
|
||||
raise ValueError(t('ChatGPT token refresh temporarily failed. Please retry.'))
|
||||
refreshed = _tokens(self._json(response), tokens)
|
||||
payload['tokens'] = refreshed
|
||||
await self._save(workspace, provider, owner, payload)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
"""Explicitly safe Codex failures; never construct messages from upstream bodies."""
|
||||
|
||||
from ...i18n import t
|
||||
|
||||
|
||||
class CodexProviderError(ValueError):
|
||||
"""A known provider failure safe to expose at the HTTP boundary."""
|
||||
|
||||
def __init__(self, message: str, status_code: int = 502, error_code: str = 'codex_upstream_failure'):
|
||||
super().__init__(message)
|
||||
super().__init__(t(message))
|
||||
self.status_code = status_code
|
||||
self.error_code = error_code
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from ...i18n import t
|
||||
|
||||
|
||||
class RequesterError(Exception):
|
||||
"""Base class for all Requester errors."""
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__('模型请求失败: ' + message)
|
||||
super().__init__(t('Model request failed') + ': ' + message)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import t
|
||||
|
||||
import typing
|
||||
import asyncio
|
||||
|
||||
@@ -1037,22 +1039,22 @@ class LiteLLMRequester(requester.ProviderAPIRequester):
|
||||
"""Convert LiteLLM exceptions to RequesterError. Never returns, always raises."""
|
||||
# Check more specific exceptions first (they inherit from base exceptions)
|
||||
if isinstance(e, litellm.ContextWindowExceededError):
|
||||
raise errors.RequesterError(f'上下文长度超限: {str(e)}')
|
||||
raise errors.RequesterError(t('Context length exceeded') + f': {e}')
|
||||
if isinstance(e, litellm.BadRequestError):
|
||||
raise errors.RequesterError(f'请求参数错误: {str(e)}')
|
||||
raise errors.RequesterError(t('Invalid request parameters') + f': {e}')
|
||||
if isinstance(e, litellm.AuthenticationError):
|
||||
raise errors.RequesterError(f'API key 无效: {str(e)}')
|
||||
raise errors.RequesterError(t('Invalid API key') + f': {e}')
|
||||
if isinstance(e, litellm.NotFoundError):
|
||||
raise errors.RequesterError(f'模型或路径无效: {str(e)}')
|
||||
raise errors.RequesterError(t('Invalid model or endpoint') + f': {e}')
|
||||
if isinstance(e, litellm.RateLimitError):
|
||||
raise errors.RequesterError(f'请求过于频繁或余额不足: {str(e)}')
|
||||
raise errors.RequesterError(t('Rate limit exceeded or insufficient balance') + f': {e}')
|
||||
if isinstance(e, litellm.Timeout):
|
||||
raise errors.RequesterError(f'请求超时: {str(e)}')
|
||||
raise errors.RequesterError(t('Request timed out') + f': {e}')
|
||||
if isinstance(e, litellm.APIConnectionError):
|
||||
raise errors.RequesterError(f'连接错误: {str(e)}')
|
||||
raise errors.RequesterError(t('Connection failed') + f': {e}')
|
||||
if isinstance(e, litellm.APIError):
|
||||
raise errors.RequesterError(f'API 错误: {str(e)}')
|
||||
raise errors.RequesterError(f'未知错误: {str(e)}')
|
||||
raise errors.RequesterError(t('API error') + f': {e}')
|
||||
raise errors.RequesterError(t('Unknown error') + f': {e}')
|
||||
|
||||
async def _build_completion_args(
|
||||
self,
|
||||
@@ -1523,9 +1525,9 @@ class LiteLLMRequester(requester.ProviderAPIRequester):
|
||||
body = await httpclient.response_text(e.response)
|
||||
except Exception:
|
||||
pass
|
||||
raise errors.RequesterError(f'rerank 请求失败 (HTTP {e.response.status_code}): {body or str(e)}')
|
||||
raise errors.RequesterError(t('API error') + f' (HTTP {e.response.status_code}): {body or str(e)}')
|
||||
except httpx.HTTPError as e:
|
||||
raise errors.RequesterError(f'rerank 连接错误: {str(e)}')
|
||||
raise errors.RequesterError(t('Connection failed') + f': {e}')
|
||||
|
||||
raw_results = data.get('results', []) if isinstance(data, dict) else []
|
||||
results = []
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ....i18n import t
|
||||
|
||||
import dataclasses
|
||||
import enum
|
||||
import json
|
||||
@@ -1490,7 +1492,7 @@ class RuntimeMCPSession:
|
||||
# Raw transport exceptions may echo command arguments, headers, or
|
||||
# environment values. Detailed diagnostics belong in AUDIT_VIEW
|
||||
# logs; resource-list responses expose only a stable status.
|
||||
'error_message': 'MCP runtime failed' if self.error_message else None,
|
||||
'error_message': t('MCP runtime failed') if self.error_message else None,
|
||||
'error_code': self._public_error_code if self.error_message else None,
|
||||
'error_phase': self.error_phase.value if self.error_phase else None,
|
||||
'retry_count': self.retry_count,
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
},
|
||||
"misc": {
|
||||
"exception-handling": "show-hint",
|
||||
"failure-hint": "Request failed.",
|
||||
"failure-hint": "",
|
||||
"at-sender": true,
|
||||
"quote-origin": true,
|
||||
"track-function-calls": false,
|
||||
|
||||
@@ -106,11 +106,81 @@ stages:
|
||||
en_US: Failure Hint Text
|
||||
zh_Hans: 失败提示文本
|
||||
description:
|
||||
en_US: The text to display when a request fails. Only effective when Exception Handling Strategy is set to "Show Failure Hint"
|
||||
zh_Hans: 请求失败时显示的提示文本,仅在异常处理策略设置为"仅文字提示"时生效
|
||||
en_US: Leave empty to use the Workspace language. Used for request and runner execution failures; custom text is not translated.
|
||||
zh_Hans: 留空时使用工作区语言的默认提示。用于请求和运行器执行失败;自定义文本不会自动翻译。
|
||||
type: string
|
||||
required: false
|
||||
default: 'Request failed.'
|
||||
default: ''
|
||||
- name: rate-limit-hint
|
||||
label:
|
||||
en_US: Rate Limit Hint
|
||||
zh_Hans: 限速提示
|
||||
description:
|
||||
en_US: "Leave empty to use the Workspace language."
|
||||
zh_Hans: "留空时使用工作区语言的默认提示。"
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
- name: content-filter-hint
|
||||
label:
|
||||
en_US: Content Filter Hint
|
||||
zh_Hans: 内容过滤提示
|
||||
description:
|
||||
en_US: "Leave empty to use the Workspace language."
|
||||
zh_Hans: "留空时使用工作区语言的默认提示。"
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
- name: content-filter-error-hint
|
||||
label:
|
||||
en_US: Content Check Failure Hint
|
||||
zh_Hans: 内容检查失败提示
|
||||
description:
|
||||
en_US: "Leave empty to use the Workspace language."
|
||||
zh_Hans: "留空时使用工作区语言的默认提示。"
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
- name: tool-call-hint
|
||||
label:
|
||||
en_US: Tool Call Hint
|
||||
zh_Hans: 工具调用提示
|
||||
description:
|
||||
en_US: "Leave empty to use the Workspace language. Use {tools} for tool names. Requires Track Function Calls."
|
||||
zh_Hans: "留空时使用工作区语言的默认提示。 可用 {tools} 插入工具名称,需开启跟踪函数调用。"
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
- name: runner-not-found-hint
|
||||
label:
|
||||
en_US: Runner Missing Hint
|
||||
zh_Hans: 运行器不存在提示
|
||||
description:
|
||||
en_US: "Leave empty to use the Workspace language. Use {runner} for the runner ID."
|
||||
zh_Hans: "留空时使用工作区语言的默认提示。 可用 {runner} 插入运行器 ID。"
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
- name: runner-not-authorized-hint
|
||||
label:
|
||||
en_US: Runner Authorization Hint
|
||||
zh_Hans: 运行器未授权提示
|
||||
description:
|
||||
en_US: "Leave empty to use the Workspace language."
|
||||
zh_Hans: "留空时使用工作区语言的默认提示。"
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
- name: runner-unavailable-hint
|
||||
label:
|
||||
en_US: Runner Unavailable Hint
|
||||
zh_Hans: 运行器不可用提示
|
||||
description:
|
||||
en_US: "Leave empty to use the Workspace language."
|
||||
zh_Hans: "留空时使用工作区语言的默认提示。"
|
||||
type: string
|
||||
required: false
|
||||
default: ''
|
||||
- name: at-sender
|
||||
label:
|
||||
en_US: At Sender
|
||||
|
||||
Reference in New Issue
Block a user