diff --git a/apps/ui/js/agents.js b/apps/ui/js/agents.js index 3128d33..53fa723 100644 --- a/apps/ui/js/agents.js +++ b/apps/ui/js/agents.js @@ -5,7 +5,7 @@ function agentUiStatus(status) { } function mapAgent(agent) { - return { + var mapped = { id: agent.id, slug: agent.slug, display_name: agent.display_name, @@ -21,10 +21,12 @@ function mapAgent(agent) { latest_published_version: agent.latest_published_version, mcp_endpoint: agent.mcp_endpoint || '', }; + + return window.localizeDemoAgent ? window.localizeDemoAgent(mapped) : mapped; } function mapOperation(operation) { - return { + var mapped = { id: operation.id, name: operation.name, display_name: operation.display_name, @@ -33,6 +35,8 @@ function mapOperation(operation) { current_draft_version: operation.current_draft_version || 1, latest_published_version: operation.latest_published_version, }; + + return window.localizeDemoOperation ? window.localizeDemoOperation(mapped) : mapped; } function currentLocale() { diff --git a/apps/ui/js/catalog.js b/apps/ui/js/catalog.js index 19e4820..525b0f1 100644 --- a/apps/ui/js/catalog.js +++ b/apps/ui/js/catalog.js @@ -33,7 +33,7 @@ function currentLocale() { } function mapOperation(item) { - return { + var mapped = { id: item.id, name: item.name, display_name: item.display_name, @@ -53,6 +53,8 @@ function mapOperation(item) { }, agent_refs: item.agent_refs || [], }; + + return window.localizeDemoOperation ? window.localizeDemoOperation(mapped) : mapped; } function emptyStats() { diff --git a/apps/ui/js/i18n.js b/apps/ui/js/i18n.js index 552d527..a0bca7c 100644 --- a/apps/ui/js/i18n.js +++ b/apps/ui/js/i18n.js @@ -750,6 +750,20 @@ var TRANSLATIONS = { 'agents.toast.lifecycle_error_message': 'Failed to update agent lifecycle', 'agents.toast.endpoint_title': 'MCP endpoint copied', + // Demo content + 'demo.agent.revops-copilot.display_name': 'RevOps Copilot', + 'demo.agent.revops-copilot.description': 'Revenue operations assistant with CRM and billing tools.', + 'demo.agent.support-triage.display_name': 'Support Triage', + 'demo.agent.support-triage.description': 'Draft support agent focused on unary gRPC workflows.', + 'demo.operation.crm_create_lead.display_name': 'Create CRM Lead', + 'demo.operation.crm_create_lead.description': 'Create a lead record in the CRM system.', + 'demo.operation.billing_get_invoice.display_name': 'Get Invoice Status', + 'demo.operation.billing_get_invoice.description': 'Fetch invoice state and amount from billing.', + 'demo.operation.support_lookup_ticket.display_name': 'Lookup Support Ticket', + 'demo.operation.support_lookup_ticket.description': 'Draft unary gRPC support lookup using a descriptor set.', + 'demo.operation.marketing_archive_contact.display_name': 'Archive Marketing Contact', + 'demo.operation.marketing_archive_contact.description': 'Legacy archived flow kept for audit only.', + // Login 'login.title': 'Sign in', 'login.subtitle': 'Welcome back to your workspace', @@ -1515,6 +1529,20 @@ var TRANSLATIONS = { 'agents.toast.lifecycle_error_message': 'Не удалось обновить состояние агента', 'agents.toast.endpoint_title': 'MCP endpoint скопирован', + // Demo content + 'demo.agent.revops-copilot.display_name': 'Помощник RevOps', + 'demo.agent.revops-copilot.description': 'Ассистент Revenue Operations с CRM- и billing-инструментами.', + 'demo.agent.support-triage.display_name': 'Триаж поддержки', + 'demo.agent.support-triage.description': 'Черновой агент поддержки, сфокусированный на unary gRPC-сценариях.', + 'demo.operation.crm_create_lead.display_name': 'Создать CRM-лид', + 'demo.operation.crm_create_lead.description': 'Создает запись лида в CRM-системе.', + 'demo.operation.billing_get_invoice.display_name': 'Статус инвойса', + 'demo.operation.billing_get_invoice.description': 'Получает состояние и сумму инвойса из billing-системы.', + 'demo.operation.support_lookup_ticket.display_name': 'Найти тикет поддержки', + 'demo.operation.support_lookup_ticket.description': 'Черновой unary gRPC lookup поддержки через descriptor set.', + 'demo.operation.marketing_archive_contact.display_name': 'Архивировать маркетинговый контакт', + 'demo.operation.marketing_archive_contact.description': 'Устаревший архивный сценарий, оставленный только для аудита.', + // Login 'login.title': 'Войти', 'login.subtitle': 'Добро пожаловать обратно', @@ -1547,6 +1575,64 @@ function tf(key, vars) { }, t(key)); } +function hasTranslation(key) { + return t(key) !== key; +} + +function localizeDemoAgent(agent) { + if (!agent || !agent.slug) { + return agent; + } + + var displayNameKey = 'demo.agent.' + agent.slug + '.display_name'; + var descriptionKey = 'demo.agent.' + agent.slug + '.description'; + + if (!hasTranslation(displayNameKey) && !hasTranslation(descriptionKey)) { + return agent; + } + + return Object.assign({}, agent, { + display_name: hasTranslation(displayNameKey) ? t(displayNameKey) : agent.display_name, + description: hasTranslation(descriptionKey) ? t(descriptionKey) : agent.description, + }); +} + +function localizeDemoOperation(operation) { + if (!operation || !operation.name) { + return operation; + } + + var displayNameKey = 'demo.operation.' + operation.name + '.display_name'; + var descriptionKey = 'demo.operation.' + operation.name + '.description'; + + if (!hasTranslation(displayNameKey) && !hasTranslation(descriptionKey)) { + return operation; + } + + var next = Object.assign({}, operation); + + if (hasTranslation(displayNameKey)) { + next.display_name = t(displayNameKey); + if (Object.prototype.hasOwnProperty.call(operation, 'operation_display_name')) { + next.operation_display_name = t(displayNameKey); + } + } + + if (hasTranslation(descriptionKey)) { + if (Object.prototype.hasOwnProperty.call(operation, 'description')) { + next.description = t(descriptionKey); + } + if (Object.prototype.hasOwnProperty.call(operation, 'tool_description')) { + next.tool_description = t(descriptionKey); + } + } + + return next; +} + +window.localizeDemoAgent = localizeDemoAgent; +window.localizeDemoOperation = localizeDemoOperation; + function applyLang() { // text content document.querySelectorAll('[data-i18n]').forEach(function(el) { diff --git a/apps/ui/js/logs.js b/apps/ui/js/logs.js index 385e11c..fbfdc90 100644 --- a/apps/ui/js/logs.js +++ b/apps/ui/js/logs.js @@ -69,6 +69,20 @@ document.addEventListener('DOMContentLoaded', function () { function normalizeLog(record) { var log = record.log; + var agent = window.localizeDemoAgent + ? window.localizeDemoAgent({ + slug: record.agent_slug, + display_name: record.agent_display_name, + description: '', + }) + : null; + var operation = window.localizeDemoOperation + ? window.localizeDemoOperation({ + name: record.operation_name, + operation_display_name: record.operation_display_name, + }) + : null; + return { id: log.id, createdAt: log.created_at, @@ -80,9 +94,9 @@ document.addEventListener('DOMContentLoaded', function () { toolName: log.tool_name, message: log.message, operationName: record.operation_name, - operationDisplayName: record.operation_display_name, + operationDisplayName: operation ? operation.operation_display_name : record.operation_display_name, agentSlug: record.agent_slug, - agentDisplayName: record.agent_display_name, + agentDisplayName: agent ? agent.display_name : record.agent_display_name, requestPreview: log.request_preview, responsePreview: log.response_preview, errorKind: log.error_kind, @@ -143,7 +157,7 @@ document.addEventListener('DOMContentLoaded', function () { var opSpan = document.createElement('span'); opSpan.className = 'log-op-name'; - opSpan.textContent = item.operationName; + opSpan.textContent = item.operationDisplayName || item.operationName; msgDiv.appendChild(opSpan); msgDiv.appendChild(document.createTextNode('\u00a0\u00a0')); diff --git a/apps/ui/js/usage.js b/apps/ui/js/usage.js index 22c7a6a..99f16e0 100644 --- a/apps/ui/js/usage.js +++ b/apps/ui/js/usage.js @@ -71,6 +71,18 @@ document.addEventListener('DOMContentLoaded', function () { return 'REST'; } + function localizedUsageOperation(operation) { + if (!window.localizeDemoOperation) { + return operation; + } + + return window.localizeDemoOperation({ + name: operation.operation_name, + operation_display_name: operation.operation_display_name, + description: operation.operation_description || null, + }); + } + function chartBucketLabel(timestamp, period, index) { var date = new Date(timestamp); if (period === '7d') { @@ -276,6 +288,7 @@ document.addEventListener('DOMContentLoaded', function () { }, 0); operations.forEach(function (operation) { + var localizedOperation = localizedUsageOperation(operation); var node = rowTemplate.content.cloneNode(true); var errorRate = operation.calls_total === 0 ? 0 @@ -284,7 +297,7 @@ document.addEventListener('DOMContentLoaded', function () { ? 0 : ((operation.calls_total / totalCalls) * 100); - node.querySelector('.col-name').textContent = operation.operation_display_name; + node.querySelector('.col-name').textContent = localizedOperation.operation_display_name || operation.operation_display_name; node.querySelector('.col-op-slug').textContent = operation.operation_name; var proto = node.querySelector('.col-proto'); @@ -371,11 +384,12 @@ document.addEventListener('DOMContentLoaded', function () { var rows = [tKey('usage.csv.header')]; state.usage.operations.forEach(function (operation) { + var localizedOperation = localizedUsageOperation(operation); var errorRate = operation.calls_total === 0 ? 0 : ((operation.calls_error / operation.calls_total) * 100); rows.push([ - '"' + operation.operation_display_name + '"', + '"' + (localizedOperation.operation_display_name || operation.operation_display_name) + '"', '"' + protocolLabel(operation.protocol) + '"', operation.calls_total, operation.calls_error,