From 287a666e1b2bb155e86aa88ebace201d1e8a51f6 Mon Sep 17 00:00:00 2001
From: zhou zhou <3272660260@qq.com>
Date: 星期四, 19 三月 2026 13:26:02 +0800
Subject: [PATCH] #AI.国际化
---
rsf-admin/src/page/system/aiShared/AiRuntimeSummary.jsx | 25
rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx | 55 +-
rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx | 54 +-
rsf-admin/src/page/system/aiParam/AiParamList.jsx | 41
rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx | 48 +
rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx | 104 ++--
rsf-admin/src/i18n/zh.js | 304 +++++++++++++
rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx | 43 +
rsf-admin/src/i18n/en.js | 304 +++++++++++++
rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx | 73 +-
rsf-admin/src/page/system/aiParam/AiParamForm.jsx | 56 +-
rsf-admin/src/layout/AiChatDrawer.jsx | 174 ++++---
rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx | 14
13 files changed, 986 insertions(+), 309 deletions(-)
diff --git a/rsf-admin/src/i18n/en.js b/rsf-admin/src/i18n/en.js
index f0b39e1..ae41b15 100644
--- a/rsf-admin/src/i18n/en.js
+++ b/rsf-admin/src/i18n/en.js
@@ -232,6 +232,310 @@
taskPathTemplateMerge: 'TaskPathTemplateMerge',
missionFlowStepInstance: 'Mission Flow Steps',
},
+ ai: {
+ common: {
+ cancel: "Cancel",
+ close: "Close",
+ save: "Save",
+ new: "New",
+ detail: "Details",
+ delete: "Delete",
+ enabled: "Enabled",
+ disabled: "Disabled",
+ saveSuccess: "Saved successfully",
+ updateSuccess: "Updated successfully",
+ deleteSuccess: "Deleted successfully",
+ deleteFailed: "Delete failed",
+ operationFailed: "Operation failed",
+ confirmDelete: "Delete \"%{name}\"?",
+ none: "None",
+ notValidated: "Not validated",
+ notTested: "Not tested",
+ lastUpdatedBy: "Last updated by",
+ lastUpdatedAt: "Last updated at",
+ target: "Target",
+ lastTest: "Last test",
+ prompt: "Prompt",
+ model: "Model",
+ error: "Error",
+ testing: "Testing...",
+ },
+ runtimeSummary: {
+ fetchFailed: "Failed to load runtime summary",
+ title: "Current Runtime",
+ description: "Shows the active model, prompt, and MCP mounts.",
+ currentModel: "Current Model",
+ validateStatus: "Validate %{status}",
+ currentPrompt: "Current Prompt",
+ lastUpdated: "Last updated: %{time} / %{user}",
+ enabledMcp: "Enabled MCP",
+ enabledMcpCount: "%{count} enabled",
+ },
+ param: {
+ fields: {
+ providerType: "Provider Type",
+ model: "Model",
+ baseUrl: "Base URL",
+ apiKey: "API Key",
+ temperature: "Temperature",
+ topP: "Top P",
+ maxTokens: "Max Tokens",
+ timeoutMs: "Timeout(ms)",
+ streamingEnabled: "Enable Streaming",
+ validateStatus: "Latest Validate Status",
+ lastValidateElapsedMs: "Latest Validate Elapsed(ms)",
+ lastValidateTime: "Latest Validate Time",
+ lastValidateMessage: "Latest Validate Result",
+ },
+ list: {
+ emptyTitle: "No AI parameter configs",
+ emptyDescription: "Create an OpenAI-compatible model card first.",
+ streaming: "Streaming",
+ nonStreaming: "Non-streaming",
+ },
+ dialog: {
+ create: "New AI Parameter",
+ edit: "Edit AI Parameter",
+ show: "View AI Parameter Details",
+ },
+ validate: {
+ success: "AI parameter validation succeeded",
+ failed: "AI parameter validation failed",
+ loading: "Validating...",
+ beforeSave: "Validate Before Save",
+ description: "Validates the current Base URL, API Key, and model directly.",
+ },
+ form: {
+ sections: {
+ main: "Main Configuration",
+ runtime: "Runtime and Audit",
+ },
+ },
+ },
+ prompt: {
+ fields: {
+ code: "Code",
+ scene: "Scene",
+ systemPrompt: "System Prompt",
+ userPromptTemplate: "User Prompt Template",
+ },
+ list: {
+ emptyTitle: "No prompt configs",
+ emptyDescription: "Once you create a prompt card, AI chat will load it dynamically.",
+ sceneValue: "Scene: %{value}",
+ },
+ dialog: {
+ create: "New Prompt",
+ edit: "Edit Prompt",
+ show: "View Prompt Details",
+ },
+ preview: {
+ defaultInput: "Please summarize what this page can do",
+ success: "Prompt preview generated",
+ failed: "Prompt preview failed",
+ title: "Prompt Preview",
+ input: "Sample Input",
+ metadata: "Sample Metadata JSON",
+ loading: "Rendering...",
+ render: "Render Preview",
+ description: "Render the System Prompt and User Prompt with the current form values.",
+ resolvedVariables: "Resolved variables: %{value}",
+ renderedSystemPrompt: "Rendered System Prompt",
+ renderedUserPrompt: "Rendered User Prompt",
+ },
+ form: {
+ sections: {
+ main: "Prompt Configuration",
+ runtime: "Runtime and Audit",
+ },
+ },
+ },
+ mcp: {
+ fields: {
+ transportType: "Transport Type",
+ builtinCode: "Built-in MCP",
+ serverUrl: "Server URL",
+ command: "Command",
+ sort: "Sort",
+ healthStatus: "Health Status",
+ lastInitElapsedMs: "Last Init Elapsed(ms)",
+ lastTestTime: "Last Test Time",
+ lastTestMessage: "Last Test Result",
+ },
+ groups: {
+ builtin: {
+ title: "Built-in MCP",
+ description: "Built-in tool mounts for exposing platform capabilities directly.",
+ },
+ sse: {
+ title: "Remote SSE MCP",
+ description: "Mount external tools through a remote MCP server.",
+ },
+ stdio: {
+ title: "Local STDIO MCP",
+ description: "Mount external MCP tools through a local command process.",
+ },
+ },
+ health: {
+ healthy: "Healthy",
+ unhealthy: "Failed",
+ },
+ list: {
+ emptyTitle: "No MCP mounts",
+ emptyDescription: "Create a built-in MCP, remote SSE mount, or local STDIO mount.",
+ sortValue: "Sort %{value}",
+ noConnectivityTest: "No connectivity test has been run yet",
+ connectivityTest: "Connectivity Test",
+ },
+ dialog: {
+ create: "New MCP Mount",
+ edit: "Edit MCP Mount",
+ show: "View MCP Mount Details",
+ },
+ connectivity: {
+ success: "Connectivity test completed",
+ failed: "Connectivity test failed",
+ },
+ form: {
+ testBeforeSave: "Test Before Save",
+ testDescription: "Validate connectivity directly with the current draft without saving.",
+ sections: {
+ main: "MCP Mount Configuration",
+ runtime: "Runtime Info",
+ },
+ },
+ tools: {
+ schemaParseFailed: "Failed to parse input schema: %{message}",
+ loadFailed: "Failed to load tools",
+ inputRequired: "Please enter tool test JSON",
+ testSuccess: "Tool %{name} test completed",
+ testFailed: "Tool test failed",
+ saveBeforePreview: "Save the mount before previewing tools and running tests.",
+ title: "Tool Preview and Test",
+ description: "Supports connectivity checks, structured schema preview, and generated test forms from input parameters.",
+ refresh: "Refresh Tools",
+ noTools: "No tools were resolved for this mount.",
+ purpose: "Purpose: %{value}",
+ fieldCount: "%{count} fields",
+ queryBoundary: "Query boundary: %{value}",
+ exampleQuestions: "Example Questions",
+ formattedSchema: "Formatted Input Schema",
+ testInput: "Test Input JSON",
+ testInputPlaceholder: "Example: {\"code\":\"A01\"}",
+ executeTest: "Run Test",
+ testResult: "Test Result",
+ },
+ },
+ observe: {
+ fields: {
+ requestId: "Request ID",
+ promptCode: "Prompt Code",
+ userId: "User ID",
+ sessionId: "Session ID",
+ mountedMcp: "MCP Mounts",
+ },
+ summary: {
+ fetchFailed: "Failed to load AI observe stats",
+ title: "Observe Overview",
+ description: "Statistics for AI chats and MCP tool calls in the current tenant.",
+ callCount: "AI Calls",
+ successFailure: "Success %{success} / Failed %{failure}",
+ avgElapsed: "Average Elapsed",
+ firstToken: "First token %{value} ms",
+ tokenUsage: "Token Usage",
+ avgToken: "Average %{value}",
+ toolSuccessRate: "Tool Success Rate",
+ toolCallFailure: "Calls %{call} / Failed %{failure}",
+ },
+ status: {
+ completed: "Success",
+ failed: "Failed",
+ aborted: "Aborted",
+ },
+ detail: {
+ mcpLogsFailed: "Failed to load MCP call logs",
+ title: "AI Call Details",
+ mcpLogs: "MCP Tool Call Logs",
+ noMcpLogs: "No MCP tool logs were generated for this call.",
+ inputSummary: "Input Summary",
+ outputSummary: "Output Summary / Error",
+ },
+ list: {
+ emptyTitle: "No AI call logs",
+ emptyDescription: "After starting AI chats, statistics and audit records will appear here.",
+ userValue: "User %{value}",
+ elapsedValue: "Elapsed %{value} ms",
+ tokenValue: "Token %{value}",
+ mcpToolCalls: "MCP / Tool Calls",
+ mcpToolSummary: "%{mcp} mounts, %{success} tool successes, %{failure} failures",
+ },
+ },
+ drawer: {
+ title: "AI Chat",
+ runtimeFailed: "Failed to load AI runtime",
+ sessionListFailed: "Failed to load AI sessions",
+ sessionDeleted: "Session deleted",
+ deleteSessionFailed: "Failed to delete AI session",
+ pinned: "Session pinned",
+ unpinned: "Session unpinned",
+ pinFailed: "Failed to update session pin state",
+ renamed: "Session renamed",
+ renameFailed: "Failed to rename session",
+ memoryCleared: "Session memory cleared",
+ clearMemoryFailed: "Failed to clear session memory",
+ retainLatestRoundSuccess: "Only the latest round was kept",
+ retainLatestRoundFailed: "Failed to keep only the latest round",
+ stopSuccess: "Current output stopped",
+ chatFailed: "AI chat failed",
+ newSession: "New Session",
+ sessionList: "Sessions",
+ searchPlaceholder: "Search session titles",
+ noSessions: "No history sessions",
+ sessionTitle: "Session %{id}",
+ pinAction: "Pin session",
+ unpinAction: "Unpin session",
+ renameAction: "Rename session",
+ deleteAction: "Delete session",
+ toolTrace: "Tool Trace",
+ noToolTrace: "No tool call was triggered in this round",
+ unknownTool: "Unknown tool",
+ toolStatusFailed: "Failed",
+ toolStatusCompleted: "Completed",
+ toolStatusRunning: "Running",
+ collapseDetail: "Hide Details",
+ viewDetail: "View Details",
+ toolInput: "Input: %{value}",
+ toolOutput: "Output summary: %{value}",
+ toolError: "Error: %{value}",
+ hasSummary: "Summary",
+ noSummary: "No Summary",
+ hasFacts: "Facts",
+ noFacts: "No Facts",
+ retainLatestRound: "Keep Latest Round",
+ clearMemory: "Clear Memory",
+ loadingRuntime: "Loading AI runtime info...",
+ emptyHint: "AI responses stream back through SSE here. You can also maintain parameters, prompts, and MCP mounts from the quick links above.",
+ userRole: "You",
+ assistantRole: "AI",
+ thinking: "Thinking...",
+ inputPlaceholder: "Type your question. Press Enter to send, Shift + Enter for a new line",
+ clearInput: "Clear Input",
+ stop: "Stop",
+ send: "Send",
+ renameDialogTitle: "Rename Session",
+ sessionTitleField: "Session Title",
+ requestMetric: "Req: %{value}",
+ sessionMetric: "Session: %{id}",
+ promptMetric: "Prompt: %{value}",
+ modelMetric: "Model: %{value}",
+ mcpMetric: "MCP: %{value}",
+ historyMetric: "History: %{value}",
+ recentMetric: "Recent: %{value}",
+ elapsedMetric: "Elapsed: %{value} ms",
+ firstTokenMetric: "First token: %{value} ms",
+ tokenMetric: "Tokens: prompt %{prompt} / completion %{completion} / total %{total}",
+ },
+ },
table: {
field: {
basStationArea: {
diff --git a/rsf-admin/src/i18n/zh.js b/rsf-admin/src/i18n/zh.js
index 7998128..ba8a698 100644
--- a/rsf-admin/src/i18n/zh.js
+++ b/rsf-admin/src/i18n/zh.js
@@ -248,6 +248,310 @@
taskPathTemplateMerge: '浠诲姟璺緞妯℃澘鍚堝苟',
missionFlowStepInstance: '浠诲姟娴佺▼姝ラ',
},
+ ai: {
+ common: {
+ cancel: "鍙栨秷",
+ close: "鍏抽棴",
+ save: "淇濆瓨",
+ new: "鏂板缓",
+ detail: "璇︽儏",
+ delete: "鍒犻櫎",
+ enabled: "鍚敤",
+ disabled: "鍋滅敤",
+ saveSuccess: "淇濆瓨鎴愬姛",
+ updateSuccess: "鏇存柊鎴愬姛",
+ deleteSuccess: "鍒犻櫎鎴愬姛",
+ deleteFailed: "鍒犻櫎澶辫触",
+ operationFailed: "鎿嶄綔澶辫触",
+ confirmDelete: "纭鍒犻櫎鈥�%{name}鈥濆悧锛�",
+ none: "鏃�",
+ notValidated: "鏈牎楠�",
+ notTested: "鏈祴璇�",
+ lastUpdatedBy: "鏈�杩戞洿鏂颁汉",
+ lastUpdatedAt: "鏈�杩戞洿鏂版椂闂�",
+ target: "鐩爣",
+ lastTest: "鏈�杩戞祴璇�",
+ prompt: "Prompt",
+ model: "妯″瀷",
+ error: "閿欒",
+ testing: "娴嬭瘯涓�...",
+ },
+ runtimeSummary: {
+ fetchFailed: "鑾峰彇杩愯鎬佹憳瑕佸け璐�",
+ title: "褰撳墠杩愯鎬�",
+ description: "灞曠ず褰撳墠鐢熸晥鐨勬ā鍨嬨�丳rompt 涓� MCP 鎸傝浇淇℃伅銆�",
+ currentModel: "褰撳墠妯″瀷",
+ validateStatus: "鏍¢獙 %{status}",
+ currentPrompt: "褰撳墠 Prompt",
+ lastUpdated: "鏈�杩戞洿鏂帮細%{time} / %{user}",
+ enabledMcp: "宸插惎鐢� MCP",
+ enabledMcpCount: "%{count} 涓�",
+ },
+ param: {
+ fields: {
+ providerType: "鎻愪緵鏂圭被鍨�",
+ model: "妯″瀷",
+ baseUrl: "Base URL",
+ apiKey: "API Key",
+ temperature: "Temperature",
+ topP: "Top P",
+ maxTokens: "Max Tokens",
+ timeoutMs: "Timeout(ms)",
+ streamingEnabled: "鍚敤娴佸紡鍝嶅簲",
+ validateStatus: "鏈�杩戞牎楠岀姸鎬�",
+ lastValidateElapsedMs: "鏈�杩戞牎楠岃�楁椂(ms)",
+ lastValidateTime: "鏈�杩戞牎楠屾椂闂�",
+ lastValidateMessage: "鏈�杩戞牎楠岀粨鏋�",
+ },
+ list: {
+ emptyTitle: "鏆傛棤 AI 鍙傛暟閰嶇疆",
+ emptyDescription: "鍙互鍏堟柊寤轰竴涓� OpenAI 鍏煎妯″瀷鍙傛暟鍗$墖銆�",
+ streaming: "娴佸紡鍝嶅簲",
+ nonStreaming: "闈炴祦寮�",
+ },
+ dialog: {
+ create: "鏂板缓 AI 鍙傛暟",
+ edit: "缂栬緫 AI 鍙傛暟",
+ show: "鏌ョ湅 AI 鍙傛暟璇︽儏",
+ },
+ validate: {
+ success: "AI 鍙傛暟楠岃瘉鎴愬姛",
+ failed: "AI 鍙傛暟楠岃瘉澶辫触",
+ loading: "楠岃瘉涓�...",
+ beforeSave: "淇濆瓨鍓嶉獙璇�",
+ description: "浼氱洿鎺ユ牎楠屽綋鍓� Base URL銆丄PI Key 涓庢ā鍨嬫槸鍚﹀彲璋冪敤銆�",
+ },
+ form: {
+ sections: {
+ main: "涓昏閰嶇疆",
+ runtime: "杩愯涓庡璁′俊鎭�",
+ },
+ },
+ },
+ prompt: {
+ fields: {
+ code: "缂栫爜",
+ scene: "鍦烘櫙",
+ systemPrompt: "System Prompt",
+ userPromptTemplate: "User Prompt Template",
+ },
+ list: {
+ emptyTitle: "鏆傛棤 Prompt 閰嶇疆",
+ emptyDescription: "鏂板缓涓�寮� Prompt 鍗$墖鍚庯紝AI 瀵硅瘽浼氬姩鎬佸姞杞借繖閲岀殑鍐呭銆�",
+ sceneValue: "鍦烘櫙: %{value}",
+ },
+ dialog: {
+ create: "鏂板缓 Prompt",
+ edit: "缂栬緫 Prompt",
+ show: "鏌ョ湅 Prompt 璇︽儏",
+ },
+ preview: {
+ defaultInput: "璇峰府鎴戞�荤粨褰撳墠椤甸潰鑳藉仛浠�涔�",
+ success: "Prompt 棰勮瀹屾垚",
+ failed: "Prompt 棰勮澶辫触",
+ title: "Prompt 棰勮",
+ input: "绀轰緥杈撳叆",
+ metadata: "绀轰緥鍏冩暟鎹� JSON",
+ loading: "棰勮涓�...",
+ render: "棰勮娓叉煋",
+ description: "鐢ㄥ綋鍓嶈〃鍗曞唴瀹规覆鏌� System Prompt 鍜� User Prompt銆�",
+ resolvedVariables: "宸茶В鏋愬彉閲忥細%{value}",
+ renderedSystemPrompt: "娓叉煋鍚庣殑 System Prompt",
+ renderedUserPrompt: "娓叉煋鍚庣殑 User Prompt",
+ },
+ form: {
+ sections: {
+ main: "Prompt 閰嶇疆",
+ runtime: "杩愯涓庡璁′俊鎭�",
+ },
+ },
+ },
+ mcp: {
+ fields: {
+ transportType: "浼犺緭绫诲瀷",
+ builtinCode: "鍐呯疆 MCP",
+ serverUrl: "鏈嶅姟鍦板潃",
+ command: "鍛戒护",
+ sort: "鎺掑簭",
+ healthStatus: "鍋ュ悍鐘舵��",
+ lastInitElapsedMs: "鏈�杩戝垵濮嬪寲鑰楁椂(ms)",
+ lastTestTime: "鏈�杩戞祴璇曟椂闂�",
+ lastTestMessage: "鏈�杩戞祴璇曠粨鏋�",
+ },
+ groups: {
+ builtin: {
+ title: "鍐呯疆 MCP",
+ description: "绯荤粺鍐呯疆宸ュ叿鎸傝浇锛岄�傚悎鐩存帴鏆撮湶骞冲彴鑳藉姏銆�",
+ },
+ sse: {
+ title: "杩滅▼ SSE MCP",
+ description: "閫氳繃杩滅▼ MCP Server 鎸傝浇澶栭儴宸ュ叿銆�",
+ },
+ stdio: {
+ title: "鏈湴 STDIO MCP",
+ description: "閫氳繃鏈湴鍛戒护杩涚▼鎸傝浇澶栭儴 MCP銆�",
+ },
+ },
+ health: {
+ healthy: "姝e父",
+ unhealthy: "澶辫触",
+ },
+ list: {
+ emptyTitle: "鏆傛棤 MCP 鎸傝浇",
+ emptyDescription: "鍙互鏂板缓鍐呯疆 MCP銆佽繙绋� SSE 鎸傝浇鎴栨湰鍦� STDIO 鎸傝浇銆�",
+ sortValue: "鎺掑簭 %{value}",
+ noConnectivityTest: "灏氭湭鎵ц杩為�氭�ф祴璇�",
+ connectivityTest: "杩為�氭祴璇�",
+ },
+ dialog: {
+ create: "鏂板缓 MCP 鎸傝浇",
+ edit: "缂栬緫 MCP 鎸傝浇",
+ show: "鏌ョ湅 MCP 鎸傝浇璇︽儏",
+ },
+ connectivity: {
+ success: "杩為�氭�ф祴璇曞畬鎴�",
+ failed: "杩為�氭�ф祴璇曞け璐�",
+ },
+ form: {
+ testBeforeSave: "淇濆瓨鍓嶆祴璇�",
+ testDescription: "鐢ㄥ綋鍓嶈崏绋块厤缃洿鎺ユ牎楠岃繛閫氭�э紝涓嶄細钀藉簱銆�",
+ sections: {
+ main: "MCP 鎸傝浇閰嶇疆",
+ runtime: "杩愯鎬佷俊鎭�",
+ },
+ },
+ tools: {
+ schemaParseFailed: "Input Schema 瑙f瀽澶辫触: %{message}",
+ loadFailed: "鑾峰彇宸ュ叿鍒楄〃澶辫触",
+ inputRequired: "璇疯緭鍏ュ伐鍏锋祴璇� JSON",
+ testSuccess: "宸ュ叿 %{name} 娴嬭瘯瀹屾垚",
+ testFailed: "宸ュ叿娴嬭瘯澶辫触",
+ saveBeforePreview: "淇濆瓨鎸傝浇鍚庡嵆鍙瑙堝伐鍏峰苟鎵ц娴嬭瘯銆�",
+ title: "宸ュ叿棰勮涓庢祴璇�",
+ description: "鏀寔杩為�氭�ф祴璇曘�佺粨鏋勫寲 Schema 棰勮鍜屾寜杈撳叆鍙傛暟鑷姩鐢熸垚娴嬭瘯琛ㄥ崟銆�",
+ refresh: "鍒锋柊宸ュ叿",
+ noTools: "褰撳墠鎸傝浇鏈В鏋愬嚭浠讳綍宸ュ叿銆�",
+ purpose: "鐢ㄩ��: %{value}",
+ fieldCount: "%{count} 涓弬鏁�",
+ queryBoundary: "鏌ヨ杈圭晫: %{value}",
+ exampleQuestions: "绀轰緥鎻愰棶",
+ formattedSchema: "鏍煎紡鍖� Input Schema",
+ testInput: "娴嬭瘯杈撳叆 JSON",
+ testInputPlaceholder: "渚嬪锛歿\"code\":\"A01\"}",
+ executeTest: "鎵ц娴嬭瘯",
+ testResult: "娴嬭瘯缁撴灉",
+ },
+ },
+ observe: {
+ fields: {
+ requestId: "璇锋眰ID",
+ promptCode: "Prompt 缂栫爜",
+ userId: "鐢ㄦ埛ID",
+ sessionId: "浼氳瘽ID",
+ mountedMcp: "MCP 鎸傝浇",
+ },
+ summary: {
+ fetchFailed: "鑾峰彇 AI 瑙傛祴缁熻澶辫触",
+ title: "瑙傛祴鎬昏",
+ description: "褰撳墠绉熸埛涓嬬殑 AI 瀵硅瘽璋冪敤涓� MCP 宸ュ叿璋冪敤缁熻銆�",
+ callCount: "AI 璋冪敤閲�",
+ successFailure: "鎴愬姛 %{success} / 澶辫触 %{failure}",
+ avgElapsed: "骞冲潎鑰楁椂",
+ firstToken: "棣栧寘 %{value} ms",
+ tokenUsage: "Token 浣跨敤",
+ avgToken: "骞冲潎 %{value}",
+ toolSuccessRate: "宸ュ叿鎴愬姛鐜�",
+ toolCallFailure: "璋冪敤 %{call} / 澶辫触 %{failure}",
+ },
+ status: {
+ completed: "鎴愬姛",
+ failed: "澶辫触",
+ aborted: "涓柇",
+ },
+ detail: {
+ mcpLogsFailed: "鑾峰彇 MCP 璋冪敤鏃ュ織澶辫触",
+ title: "AI 璋冪敤璇︽儏",
+ mcpLogs: "MCP 宸ュ叿璋冪敤鏃ュ織",
+ noMcpLogs: "褰撳墠璋冪敤娌℃湁浜х敓 MCP 宸ュ叿鏃ュ織銆�",
+ inputSummary: "杈撳叆鎽樿",
+ outputSummary: "杈撳嚭鎽樿 / 閿欒",
+ },
+ list: {
+ emptyTitle: "鏆傛棤 AI 璋冪敤鏃ュ織",
+ emptyDescription: "鍙戣捣 AI 瀵硅瘽鍚庯紝杩欓噷浼氬睍绀鸿皟鐢ㄧ粺璁″拰瀹¤璁板綍銆�",
+ userValue: "鐢ㄦ埛 %{value}",
+ elapsedValue: "鑰楁椂 %{value} ms",
+ tokenValue: "Token %{value}",
+ mcpToolCalls: "MCP / 宸ュ叿璋冪敤",
+ mcpToolSummary: "鎸傝浇 %{mcp} 涓紝宸ュ叿鎴愬姛 %{success}锛屽け璐� %{failure}",
+ },
+ },
+ drawer: {
+ title: "AI 瀵硅瘽",
+ runtimeFailed: "鑾峰彇 AI 杩愯鏃跺け璐�",
+ sessionListFailed: "鑾峰彇 AI 浼氳瘽鍒楄〃澶辫触",
+ sessionDeleted: "浼氳瘽宸插垹闄�",
+ deleteSessionFailed: "鍒犻櫎 AI 浼氳瘽澶辫触",
+ pinned: "浼氳瘽宸茬疆椤�",
+ unpinned: "浼氳瘽宸插彇娑堢疆椤�",
+ pinFailed: "鏇存柊浼氳瘽缃《鐘舵�佸け璐�",
+ renamed: "浼氳瘽宸查噸鍛藉悕",
+ renameFailed: "閲嶅懡鍚嶄細璇濆け璐�",
+ memoryCleared: "浼氳瘽璁板繂宸叉竻绌�",
+ clearMemoryFailed: "娓呯┖浼氳瘽璁板繂澶辫触",
+ retainLatestRoundSuccess: "宸蹭粎淇濈暀褰撳墠杞蹇�",
+ retainLatestRoundFailed: "淇濈暀褰撳墠杞蹇嗗け璐�",
+ stopSuccess: "宸插仠姝㈠綋鍓嶅璇濊緭鍑�",
+ chatFailed: "AI 瀵硅瘽澶辫触",
+ newSession: "鏂板缓浼氳瘽",
+ sessionList: "浼氳瘽鍒楄〃",
+ searchPlaceholder: "鎼滅储浼氳瘽鏍囬",
+ noSessions: "鏆傛棤鍘嗗彶浼氳瘽",
+ sessionTitle: "浼氳瘽 %{id}",
+ pinAction: "缃《浼氳瘽",
+ unpinAction: "鍙栨秷缃《",
+ renameAction: "閲嶅懡鍚嶄細璇�",
+ deleteAction: "鍒犻櫎浼氳瘽",
+ toolTrace: "宸ュ叿璋冪敤杞ㄨ抗",
+ noToolTrace: "褰撳墠杞湭瑙﹀彂宸ュ叿璋冪敤",
+ unknownTool: "鏈煡宸ュ叿",
+ toolStatusFailed: "澶辫触",
+ toolStatusCompleted: "瀹屾垚",
+ toolStatusRunning: "鎵ц涓�",
+ collapseDetail: "鏀惰捣璇︽儏",
+ viewDetail: "鏌ョ湅璇︽儏",
+ toolInput: "鍏ュ弬: %{value}",
+ toolOutput: "缁撴灉鎽樿: %{value}",
+ toolError: "閿欒: %{value}",
+ hasSummary: "鏈夋憳瑕�",
+ noSummary: "鏃犳憳瑕�",
+ hasFacts: "鏈変簨瀹�",
+ noFacts: "鏃犱簨瀹�",
+ retainLatestRound: "浠呬繚鐣欏綋鍓嶈疆",
+ clearMemory: "娓呯┖璁板繂",
+ loadingRuntime: "姝e湪鍔犺浇 AI 杩愯鏃朵俊鎭�...",
+ emptyHint: "杩欓噷浼氶�氳繃 SSE 娴佸紡杩斿洖 AI 鍥炲銆備綘涔熷彲浠ュ厛鍘讳笂闈㈢殑蹇嵎鍏ュ彛缁存姢鍙傛暟銆丳rompt 鍜� MCP 鎸傝浇銆�",
+ userRole: "浣�",
+ assistantRole: "AI",
+ thinking: "鎬濊�冧腑...",
+ inputPlaceholder: "杈撳叆浣犵殑闂锛屾寜 Enter 鍙戦�侊紝Shift + Enter 鎹㈣",
+ clearInput: "娓呯┖杈撳叆",
+ stop: "鍋滄",
+ send: "鍙戦��",
+ renameDialogTitle: "閲嶅懡鍚嶄細璇�",
+ sessionTitleField: "浼氳瘽鏍囬",
+ requestMetric: "Req: %{value}",
+ sessionMetric: "Session: %{id}",
+ promptMetric: "Prompt: %{value}",
+ modelMetric: "Model: %{value}",
+ mcpMetric: "MCP: %{value}",
+ historyMetric: "History: %{value}",
+ recentMetric: "Recent: %{value}",
+ elapsedMetric: "鑰楁椂: %{value} ms",
+ firstTokenMetric: "棣栧寘: %{value} ms",
+ tokenMetric: "Tokens: prompt %{prompt} / completion %{completion} / total %{total}",
+ },
+ },
table: {
field: {
flowStepInstance: {
diff --git a/rsf-admin/src/layout/AiChatDrawer.jsx b/rsf-admin/src/layout/AiChatDrawer.jsx
index e649aad..e557728 100644
--- a/rsf-admin/src/layout/AiChatDrawer.jsx
+++ b/rsf-admin/src/layout/AiChatDrawer.jsx
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
-import { useNotify } from "react-admin";
+import { useNotify, useTranslate } from "react-admin";
import {
Alert,
Box,
@@ -43,17 +43,14 @@
const DEFAULT_PROMPT_CODE = "home.default";
-const quickLinks = [
- { label: "AI 鍙傛暟", path: "/aiParam", icon: <SettingsSuggestOutlinedIcon fontSize="small" /> },
- { label: "Prompt", path: "/aiPrompt", icon: <PsychologyAltOutlinedIcon fontSize="small" /> },
- { label: "MCP", path: "/aiMcpMount", icon: <CableOutlinedIcon fontSize="small" /> },
-];
-
const AiChatDrawer = ({ open, onClose }) => {
const navigate = useNavigate();
const location = useLocation();
const notify = useNotify();
+ const translate = useTranslate();
const abortRef = useRef(null);
+ const messagesContainerRef = useRef(null);
+ const messagesBottomRef = useRef(null);
const [runtime, setRuntime] = useState(null);
const [sessionId, setSessionId] = useState(null);
const [sessions, setSessions] = useState([]);
@@ -68,6 +65,12 @@
const [drawerError, setDrawerError] = useState("");
const [sessionKeyword, setSessionKeyword] = useState("");
const [renameDialog, setRenameDialog] = useState({ open: false, sessionId: null, title: "" });
+
+ const quickLinks = useMemo(() => ([
+ { label: translate("menu.aiParam"), path: "/aiParam", icon: <SettingsSuggestOutlinedIcon fontSize="small" /> },
+ { label: translate("menu.aiPrompt"), path: "/aiPrompt", icon: <PsychologyAltOutlinedIcon fontSize="small" /> },
+ { label: translate("menu.aiMcpMount"), path: "/aiMcpMount", icon: <CableOutlinedIcon fontSize="small" /> },
+ ]), [translate]);
const promptCode = runtime?.promptCode || DEFAULT_PROMPT_CODE;
@@ -95,6 +98,16 @@
stopStream(false);
}, []);
+ useEffect(() => {
+ if (!open) {
+ return;
+ }
+ const timer = window.requestAnimationFrame(() => {
+ scrollMessagesToBottom();
+ });
+ return () => window.cancelAnimationFrame(timer);
+ }, [open, messages, streaming]);
+
const initializeDrawer = async (targetSessionId = null) => {
setToolEvents([]);
setExpandedToolIds([]);
@@ -115,7 +128,7 @@
setPersistedMessages(historyMessages);
setMessages(historyMessages);
} catch (error) {
- const message = error.message || "鑾峰彇 AI 杩愯鏃跺け璐�";
+ const message = error.message || translate("ai.drawer.runtimeFailed");
setDrawerError(message);
} finally {
setLoadingRuntime(false);
@@ -127,7 +140,7 @@
const data = await getAiSessions(DEFAULT_PROMPT_CODE, keyword);
setSessions(data);
} catch (error) {
- const message = error.message || "鑾峰彇 AI 浼氳瘽鍒楄〃澶辫触";
+ const message = error.message || translate("ai.drawer.sessionListFailed");
setDrawerError(message);
}
};
@@ -171,14 +184,14 @@
}
try {
await removeAiSession(targetSessionId);
- notify("浼氳瘽宸插垹闄�");
+ notify(translate("ai.drawer.sessionDeleted"));
if (targetSessionId === sessionId) {
startNewSession();
await loadRuntime(null);
}
await loadSessions(sessionKeyword);
} catch (error) {
- const message = error.message || "鍒犻櫎 AI 浼氳瘽澶辫触";
+ const message = error.message || translate("ai.drawer.deleteSessionFailed");
setDrawerError(message);
notify(message, { type: "error" });
}
@@ -190,10 +203,10 @@
}
try {
await pinAiSession(targetSessionId, pinned);
- notify(pinned ? "浼氳瘽宸茬疆椤�" : "浼氳瘽宸插彇娑堢疆椤�");
+ notify(translate(pinned ? "ai.drawer.pinned" : "ai.drawer.unpinned"));
await loadSessions(sessionKeyword);
} catch (error) {
- const message = error.message || "鏇存柊浼氳瘽缃《鐘舵�佸け璐�";
+ const message = error.message || translate("ai.drawer.pinFailed");
setDrawerError(message);
notify(message, { type: "error" });
}
@@ -217,11 +230,11 @@
}
try {
await renameAiSession(renameDialog.sessionId, renameDialog.title);
- notify("浼氳瘽宸查噸鍛藉悕");
+ notify(translate("ai.drawer.renamed"));
closeRenameDialog();
await loadSessions(sessionKeyword);
} catch (error) {
- const message = error.message || "閲嶅懡鍚嶄細璇濆け璐�";
+ const message = error.message || translate("ai.drawer.renameFailed");
setDrawerError(message);
notify(message, { type: "error" });
}
@@ -233,13 +246,13 @@
}
try {
await clearAiSessionMemory(sessionId);
- notify("浼氳瘽璁板繂宸叉竻绌�");
+ notify(translate("ai.drawer.memoryCleared"));
await Promise.all([
loadRuntime(sessionId),
loadSessions(sessionKeyword),
]);
} catch (error) {
- const message = error.message || "娓呯┖浼氳瘽璁板繂澶辫触";
+ const message = error.message || translate("ai.drawer.clearMemoryFailed");
setDrawerError(message);
notify(message, { type: "error" });
}
@@ -251,13 +264,13 @@
}
try {
await retainAiSessionLatestRound(sessionId);
- notify("宸蹭粎淇濈暀褰撳墠杞蹇�");
+ notify(translate("ai.drawer.retainLatestRoundSuccess"));
await Promise.all([
loadRuntime(sessionId),
loadSessions(sessionKeyword),
]);
} catch (error) {
- const message = error.message || "淇濈暀褰撳墠杞蹇嗗け璐�";
+ const message = error.message || translate("ai.drawer.retainLatestRoundFailed");
setDrawerError(message);
notify(message, { type: "error" });
}
@@ -269,8 +282,18 @@
abortRef.current = null;
setStreaming(false);
if (showTip) {
- notify("宸插仠姝㈠綋鍓嶅璇濊緭鍑�");
+ notify(translate("ai.drawer.stopSuccess"));
}
+ }
+ };
+
+ const scrollMessagesToBottom = () => {
+ if (messagesBottomRef.current) {
+ messagesBottomRef.current.scrollIntoView({ block: "end" });
+ return;
+ }
+ if (messagesContainerRef.current) {
+ messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
}
};
@@ -380,7 +403,7 @@
}
}
if (eventName === "error") {
- const message = payload?.message || "AI 瀵硅瘽澶辫触";
+ const message = payload?.message || translate("ai.drawer.chatFailed");
const displayMessage = payload?.requestId ? `${message} [${payload.requestId}]` : message;
setDrawerError(displayMessage);
notify(displayMessage, { type: "error" });
@@ -390,7 +413,7 @@
);
} catch (error) {
if (error?.name !== "AbortError") {
- const message = error.message || "AI 瀵硅瘽澶辫触";
+ const message = error.message || translate("ai.drawer.chatFailed");
setDrawerError(message);
notify(message, { type: "error" });
}
@@ -432,12 +455,12 @@
<Stack direction="row" alignItems="center" spacing={1} px={2} py={1.5}>
<SmartToyOutlinedIcon color="primary" />
<Typography variant="h6" flex={1}>
- AI 瀵硅瘽
+ {translate("ai.drawer.title")}
</Typography>
- <IconButton size="small" onClick={startNewSession} title="鏂板缓浼氳瘽" disabled={streaming}>
+ <IconButton size="small" onClick={startNewSession} title={translate("ai.drawer.newSession")} disabled={streaming}>
<AddCommentOutlinedIcon fontSize="small" />
</IconButton>
- <IconButton size="small" onClick={onClose} title="鍏抽棴">
+ <IconButton size="small" onClick={onClose} title={translate("ai.common.close")}>
<CloseIcon fontSize="small" />
</IconButton>
</Stack>
@@ -454,9 +477,9 @@
>
<Box px={2} py={1.5}>
<Stack direction="row" alignItems="center" justifyContent="space-between" mb={1}>
- <Typography variant="subtitle2">浼氳瘽鍒楄〃</Typography>
+ <Typography variant="subtitle2">{translate("ai.drawer.sessionList")}</Typography>
<Button size="small" onClick={startNewSession} disabled={streaming}>
- 鏂板缓浼氳瘽
+ {translate("ai.drawer.newSession")}
</Button>
</Stack>
<TextField
@@ -464,7 +487,7 @@
onChange={(event) => setSessionKeyword(event.target.value)}
fullWidth
size="small"
- placeholder="鎼滅储浼氳瘽鏍囬"
+ placeholder={translate("ai.drawer.searchPlaceholder")}
InputProps={{
startAdornment: <SearchOutlinedIcon fontSize="small" sx={{ mr: 1, color: "text.secondary" }} />,
}}
@@ -474,7 +497,7 @@
{!sessions.length ? (
<Box px={1.5} py={1.25}>
<Typography variant="body2" color="text.secondary">
- 鏆傛棤鍘嗗彶浼氳瘽
+ {translate("ai.drawer.noSessions")}
</Typography>
</Box>
) : (
@@ -488,8 +511,8 @@
alignItems="flex-start"
>
<ListItemText
- primary={item.title || `浼氳瘽 ${item.sessionId}`}
- secondary={item.lastMessagePreview || item.lastMessageTime || `Session ${item.sessionId}`}
+ primary={item.title || translate("ai.drawer.sessionTitle", { id: item.sessionId })}
+ secondary={item.lastMessagePreview || item.lastMessageTime || translate("ai.drawer.sessionMetric", { id: item.sessionId })}
primaryTypographyProps={{
noWrap: true,
fontSize: 14,
@@ -508,7 +531,7 @@
event.stopPropagation();
handlePinSession(item.sessionId, !item.pinned);
}}
- title={item.pinned ? "鍙栨秷缃《" : "缃《浼氳瘽"}
+ title={translate(item.pinned ? "ai.drawer.unpinAction" : "ai.drawer.pinAction")}
>
{item.pinned ? <PushPinIcon fontSize="small" /> : <PushPinOutlinedIcon fontSize="small" />}
</IconButton>
@@ -520,7 +543,7 @@
event.stopPropagation();
openRenameDialog(item);
}}
- title="閲嶅懡鍚嶄細璇�"
+ title={translate("ai.drawer.renameAction")}
>
<EditOutlinedIcon fontSize="small" />
</IconButton>
@@ -532,7 +555,7 @@
event.stopPropagation();
handleDeleteSession(item.sessionId);
}}
- title="鍒犻櫎浼氳瘽"
+ title={translate("ai.drawer.deleteAction")}
>
<DeleteOutlineOutlinedIcon fontSize="small" />
</IconButton>
@@ -554,13 +577,13 @@
>
<Box px={2} py={1.5} display="flex" flexDirection="column" minHeight={0}>
<Typography variant="subtitle2" mb={1}>
- 宸ュ叿璋冪敤杞ㄨ抗
+ {translate("ai.drawer.toolTrace")}
</Typography>
<Paper variant="outlined" sx={{ flex: 1, minHeight: { xs: 140, md: 0 }, overflow: "hidden", bgcolor: "grey.50" }}>
{!toolEvents.length ? (
<Box px={1.5} py={1.25}>
<Typography variant="body2" color="text.secondary">
- 褰撳墠杞湭瑙﹀彂宸ュ叿璋冪敤
+ {translate("ai.drawer.noToolTrace")}
</Typography>
</Box>
) : (
@@ -577,12 +600,12 @@
>
<Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
<Typography variant="body2" fontWeight={700}>
- {item.toolName || "鏈煡宸ュ叿"}
+ {item.toolName || translate("ai.drawer.unknownTool")}
</Typography>
<Chip
size="small"
color={item.status === "FAILED" ? "error" : item.status === "COMPLETED" ? "success" : "info"}
- label={item.status === "FAILED" ? "澶辫触" : item.status === "COMPLETED" ? "瀹屾垚" : "鎵ц涓�"}
+ label={translate(item.status === "FAILED" ? "ai.drawer.toolStatusFailed" : item.status === "COMPLETED" ? "ai.drawer.toolStatusCompleted" : "ai.drawer.toolStatusRunning")}
/>
{item.durationMs != null && (
<Typography variant="caption" color="text.secondary">
@@ -598,24 +621,24 @@
: <ExpandMoreOutlinedIcon fontSize="small" />}
sx={{ ml: "auto", minWidth: "auto", px: 0.5 }}
>
- {expandedToolIds.includes(item.toolCallId) ? "鏀惰捣璇︽儏" : "鏌ョ湅璇︽儏"}
+ {expandedToolIds.includes(item.toolCallId) ? translate("ai.drawer.collapseDetail") : translate("ai.drawer.viewDetail")}
</Button>
)}
</Stack>
<Collapse in={expandedToolIds.includes(item.toolCallId)} timeout="auto" unmountOnExit>
{!!item.inputSummary && (
<Typography variant="caption" display="block" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
- 鍏ュ弬: {item.inputSummary}
+ {translate("ai.drawer.toolInput", { value: item.inputSummary })}
</Typography>
)}
{!!item.outputSummary && (
<Typography variant="caption" display="block" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
- 缁撴灉鎽樿: {item.outputSummary}
+ {translate("ai.drawer.toolOutput", { value: item.outputSummary })}
</Typography>
)}
{!!item.errorMessage && (
<Typography variant="caption" color="error.main" display="block" sx={{ mt: 0.75, whiteSpace: "pre-wrap" }}>
- 閿欒: {item.errorMessage}
+ {translate("ai.drawer.toolError", { value: item.errorMessage })}
</Typography>
)}
</Collapse>
@@ -630,15 +653,15 @@
<Box flex={1} display="flex" flexDirection="column" minHeight={0}>
<Box px={2} py={1.5}>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
- <Chip size="small" label={`Req: ${runtimeSummary.requestId}`} />
- <Chip size="small" label={`Session: ${sessionId || "--"}`} />
- <Chip size="small" label={`Prompt: ${runtimeSummary.promptName}`} />
- <Chip size="small" label={`Model: ${runtimeSummary.model}`} />
- <Chip size="small" label={`MCP: ${runtimeSummary.mountedMcpCount}`} />
- <Chip size="small" label={`History: ${persistedMessages.length}`} />
- <Chip size="small" label={`Recent: ${runtimeSummary.recentMessageCount}`} />
- <Chip size="small" color={runtimeSummary.hasSummary ? "success" : "default"} label={runtimeSummary.hasSummary ? "鏈夋憳瑕�" : "鏃犳憳瑕�"} />
- <Chip size="small" color={runtimeSummary.hasFacts ? "info" : "default"} label={runtimeSummary.hasFacts ? "鏈変簨瀹�" : "鏃犱簨瀹�"} />
+ <Chip size="small" label={translate("ai.drawer.requestMetric", { value: runtimeSummary.requestId })} />
+ <Chip size="small" label={translate("ai.drawer.sessionMetric", { id: sessionId || "--" })} />
+ <Chip size="small" label={translate("ai.drawer.promptMetric", { value: runtimeSummary.promptName })} />
+ <Chip size="small" label={translate("ai.drawer.modelMetric", { value: runtimeSummary.model })} />
+ <Chip size="small" label={translate("ai.drawer.mcpMetric", { value: runtimeSummary.mountedMcpCount })} />
+ <Chip size="small" label={translate("ai.drawer.historyMetric", { value: persistedMessages.length })} />
+ <Chip size="small" label={translate("ai.drawer.recentMetric", { value: runtimeSummary.recentMessageCount })} />
+ <Chip size="small" color={runtimeSummary.hasSummary ? "success" : "default"} label={translate(runtimeSummary.hasSummary ? "ai.drawer.hasSummary" : "ai.drawer.noSummary")} />
+ <Chip size="small" color={runtimeSummary.hasFacts ? "info" : "default"} label={translate(runtimeSummary.hasFacts ? "ai.drawer.hasFacts" : "ai.drawer.noFacts")} />
</Stack>
<Stack direction="row" spacing={1} mt={1.5} flexWrap="wrap" useFlexGap>
{quickLinks.map((item) => (
@@ -659,7 +682,7 @@
onClick={handleRetainLatestRound}
disabled={!sessionId || streaming}
>
- 浠呬繚鐣欏綋鍓嶈疆
+ {translate("ai.drawer.retainLatestRound")}
</Button>
<Button
size="small"
@@ -669,7 +692,7 @@
onClick={handleClearMemory}
disabled={!sessionId || streaming}
>
- 娓呯┖璁板繂
+ {translate("ai.drawer.clearMemory")}
</Button>
</Stack>
{!!runtime?.memorySummary && (
@@ -688,7 +711,7 @@
)}
{loadingRuntime && (
<Typography variant="body2" color="text.secondary" mt={1}>
- 姝e湪鍔犺浇 AI 杩愯鏃朵俊鎭�...
+ {translate("ai.drawer.loadingRuntime")}
</Typography>
)}
{!!drawerError && (
@@ -700,11 +723,20 @@
<Divider />
- <Box flex={1} overflow="auto" px={2} py={2} display="flex" flexDirection="column" gap={1.5}>
+ <Box
+ ref={messagesContainerRef}
+ flex={1}
+ overflow="auto"
+ px={2}
+ py={2}
+ display="flex"
+ flexDirection="column"
+ gap={1.5}
+ >
{!messages.length && (
<Paper variant="outlined" sx={{ p: 2, bgcolor: "grey.50" }}>
<Typography variant="body2" color="text.secondary">
- 杩欓噷浼氶�氳繃 SSE 娴佸紡杩斿洖 AI 鍥炲銆備綘涔熷彲浠ュ厛鍘讳笂闈㈢殑蹇嵎鍏ュ彛缁存姢鍙傛暟銆丳rompt 鍜� MCP 鎸傝浇銆�
+ {translate("ai.drawer.emptyHint")}
</Typography>
</Paper>
)}
@@ -728,14 +760,15 @@
}}
>
<Typography variant="caption" display="block" sx={{ opacity: 0.72, mb: 0.5 }}>
- {message.role === "user" ? "浣�" : "AI"}
+ {message.role === "user" ? translate("ai.drawer.userRole") : translate("ai.drawer.assistantRole")}
</Typography>
<Typography variant="body2">
- {message.content || (streaming && index === messages.length - 1 ? "鎬濊�冧腑..." : "")}
+ {message.content || (streaming && index === messages.length - 1 ? translate("ai.drawer.thinking") : "")}
</Typography>
</Paper>
</Box>
))}
+ <Box ref={messagesBottomRef} sx={{ height: 1 }} />
</Box>
<Divider />
@@ -743,12 +776,17 @@
<Box px={2} py={1.5}>
{usage?.elapsedMs != null && (
<Typography variant="caption" color="text.secondary" display="block" mb={0.5}>
- Elapsed: {usage.elapsedMs} ms{usage?.firstTokenLatencyMs != null ? ` / First token: ${usage.firstTokenLatencyMs} ms` : ""}
+ {translate("ai.drawer.elapsedMetric", { value: usage.elapsedMs })}
+ {usage?.firstTokenLatencyMs != null ? ` / ${translate("ai.drawer.firstTokenMetric", { value: usage.firstTokenLatencyMs })}` : ""}
</Typography>
)}
{usage?.totalTokens != null && (
<Typography variant="caption" color="text.secondary" display="block" mb={1}>
- Tokens: prompt {usage?.promptTokens ?? 0} / completion {usage?.completionTokens ?? 0} / total {usage?.totalTokens ?? 0}
+ {translate("ai.drawer.tokenMetric", {
+ prompt: usage?.promptTokens ?? 0,
+ completion: usage?.completionTokens ?? 0,
+ total: usage?.totalTokens ?? 0,
+ })}
</Typography>
)}
<TextField
@@ -759,17 +797,17 @@
multiline
minRows={3}
maxRows={6}
- placeholder="杈撳叆浣犵殑闂锛屾寜 Enter 鍙戦�侊紝Shift + Enter 鎹㈣"
+ placeholder={translate("ai.drawer.inputPlaceholder")}
/>
<Stack direction="row" spacing={1} justifyContent="flex-end" mt={1.25}>
- <Button onClick={() => setInput("")}>娓呯┖杈撳叆</Button>
+ <Button onClick={() => setInput("")}>{translate("ai.drawer.clearInput")}</Button>
{streaming ? (
<Button variant="outlined" color="warning" startIcon={<StopCircleOutlinedIcon />} onClick={() => stopStream(true)}>
- 鍋滄
+ {translate("ai.drawer.stop")}
</Button>
) : (
<Button variant="contained" startIcon={<SendRoundedIcon />} onClick={handleSend}>
- 鍙戦��
+ {translate("ai.drawer.send")}
</Button>
)}
</Stack>
@@ -778,21 +816,21 @@
</Box>
</Box>
<Dialog open={renameDialog.open} onClose={closeRenameDialog} fullWidth maxWidth="xs">
- <DialogTitle>閲嶅懡鍚嶄細璇�</DialogTitle>
+ <DialogTitle>{translate("ai.drawer.renameDialogTitle")}</DialogTitle>
<DialogContent>
<TextField
value={renameDialog.title}
onChange={(event) => setRenameDialog((prev) => ({ ...prev, title: event.target.value }))}
autoFocus
margin="dense"
- label="浼氳瘽鏍囬"
+ label={translate("ai.drawer.sessionTitleField")}
fullWidth
/>
</DialogContent>
<DialogActions>
- <Button onClick={closeRenameDialog}>鍙栨秷</Button>
+ <Button onClick={closeRenameDialog}>{translate("ai.common.cancel")}</Button>
<Button onClick={handleRenameSubmit} variant="contained" disabled={streaming || !renameDialog.title.trim()}>
- 淇濆瓨
+ {translate("ai.common.save")}
</Button>
</DialogActions>
</Dialog>
diff --git a/rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx b/rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx
index abb70b1..d3f73e5 100644
--- a/rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx
+++ b/rsf-admin/src/page/system/aiCallLog/AiCallLogList.jsx
@@ -8,6 +8,7 @@
TopToolbar,
useListContext,
useNotify,
+ useTranslate,
} from "react-admin";
import {
Alert,
@@ -32,12 +33,12 @@
const filters = [
<SearchInput source="condition" alwaysOn />,
- <TextInput source="requestId" label="璇锋眰ID" />,
- <TextInput source="promptCode" label="Prompt 缂栫爜" />,
- <TextInput source="userId" label="鐢ㄦ埛ID" />,
+ <TextInput source="requestId" label="ai.observe.fields.requestId" />,
+ <TextInput source="promptCode" label="ai.observe.fields.promptCode" />,
+ <TextInput source="userId" label="ai.observe.fields.userId" />,
<SelectInput
source="status"
- label="鐘舵��"
+ label="common.field.status"
choices={[
{ id: "RUNNING", name: "RUNNING" },
{ id: "COMPLETED", name: "COMPLETED" },
@@ -48,6 +49,7 @@
];
const ObserveSummary = () => {
+ const translate = useTranslate();
const [stats, setStats] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
@@ -64,7 +66,7 @@
})
.catch((err) => {
if (active) {
- setError(err?.message || "鑾峰彇 AI 瑙傛祴缁熻澶辫触");
+ setError(err?.message || translate("ai.observe.summary.fetchFailed"));
}
})
.finally(() => {
@@ -83,9 +85,9 @@
<CardContent>
<Stack direction="row" justifyContent="space-between" alignItems="center" mb={2}>
<Box>
- <Typography variant="h6">瑙傛祴鎬昏</Typography>
+ <Typography variant="h6">{translate("ai.observe.summary.title")}</Typography>
<Typography variant="body2" color="text.secondary">
- 褰撳墠绉熸埛涓嬬殑 AI 瀵硅瘽璋冪敤涓� MCP 宸ュ叿璋冪敤缁熻銆�
+ {translate("ai.observe.summary.description")}
</Typography>
</Box>
{loading && <CircularProgress size={24} />}
@@ -94,31 +96,31 @@
{!loading && !error && stats && (
<Grid container spacing={2}>
<Grid item xs={12} md={3}>
- <Typography variant="caption" color="text.secondary">AI 璋冪敤閲�</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.summary.callCount")}</Typography>
<Typography variant="h5">{stats.callCount ?? 0}</Typography>
<Typography variant="body2" color="text.secondary">
- 鎴愬姛 {stats.successCount ?? 0} / 澶辫触 {stats.failureCount ?? 0}
+ {translate("ai.observe.summary.successFailure", { success: stats.successCount ?? 0, failure: stats.failureCount ?? 0 })}
</Typography>
</Grid>
<Grid item xs={12} md={3}>
- <Typography variant="caption" color="text.secondary">骞冲潎鑰楁椂</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.summary.avgElapsed")}</Typography>
<Typography variant="h5">{stats.avgElapsedMs ?? 0} ms</Typography>
<Typography variant="body2" color="text.secondary">
- 棣栧寘 {stats.avgFirstTokenLatencyMs ?? 0} ms
+ {translate("ai.observe.summary.firstToken", { value: stats.avgFirstTokenLatencyMs ?? 0 })}
</Typography>
</Grid>
<Grid item xs={12} md={3}>
- <Typography variant="caption" color="text.secondary">Token 浣跨敤</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.summary.tokenUsage")}</Typography>
<Typography variant="h5">{stats.totalTokens ?? 0}</Typography>
<Typography variant="body2" color="text.secondary">
- 骞冲潎 {stats.avgTotalTokens ?? 0}
+ {translate("ai.observe.summary.avgToken", { value: stats.avgTotalTokens ?? 0 })}
</Typography>
</Grid>
<Grid item xs={12} md={3}>
- <Typography variant="caption" color="text.secondary">宸ュ叿鎴愬姛鐜�</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.summary.toolSuccessRate")}</Typography>
<Typography variant="h5">{Number(stats.toolSuccessRate || 0).toFixed(2)}%</Typography>
<Typography variant="body2" color="text.secondary">
- 璋冪敤 {stats.toolCallCount ?? 0} / 澶辫触 {stats.toolFailureCount ?? 0}
+ {translate("ai.observe.summary.toolCallFailure", { call: stats.toolCallCount ?? 0, failure: stats.toolFailureCount ?? 0 })}
</Typography>
</Grid>
</Grid>
@@ -129,21 +131,22 @@
);
};
-const resolveStatusChip = (status) => {
+const resolveStatusChip = (status, translate) => {
if (status === "COMPLETED") {
- return { color: "success", label: "鎴愬姛" };
+ return { color: "success", label: translate("ai.observe.status.completed") };
}
if (status === "FAILED") {
- return { color: "error", label: "澶辫触" };
+ return { color: "error", label: translate("ai.observe.status.failed") };
}
if (status === "ABORTED") {
- return { color: "warning", label: "涓柇" };
+ return { color: "warning", label: translate("ai.observe.status.aborted") };
}
return { color: "default", label: status || "--" };
};
const AiCallLogDetailDialog = ({ record, open, onClose }) => {
const notify = useNotify();
+ const translate = useTranslate();
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(false);
@@ -161,7 +164,7 @@
})
.catch((error) => {
if (active) {
- notify(error?.message || "鑾峰彇 MCP 璋冪敤鏃ュ織澶辫触", { type: "error" });
+ notify(error?.message || translate("ai.observe.detail.mcpLogsFailed"), { type: "error" });
}
})
.finally(() => {
@@ -180,35 +183,35 @@
return (
<Dialog open={open} onClose={onClose} fullWidth maxWidth="lg">
- <DialogTitle>AI 璋冪敤璇︽儏</DialogTitle>
+ <DialogTitle>{translate("ai.observe.detail.title")}</DialogTitle>
<DialogContent dividers>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
- <Typography variant="caption" color="text.secondary">璇锋眰ID</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.requestId")}</Typography>
<Typography variant="body2">{record.requestId || "--"}</Typography>
</Grid>
<Grid item xs={12} md={3}>
- <Typography variant="caption" color="text.secondary">鐢ㄦ埛ID</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.userId")}</Typography>
<Typography variant="body2">{record.userId || "--"}</Typography>
</Grid>
<Grid item xs={12} md={3}>
- <Typography variant="caption" color="text.secondary">浼氳瘽ID</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.sessionId")}</Typography>
<Typography variant="body2">{record.sessionId || "--"}</Typography>
</Grid>
<Grid item xs={12} md={4}>
- <Typography variant="caption" color="text.secondary">Prompt</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.common.prompt")}</Typography>
<Typography variant="body2">{record.promptName || "--"} / {record.promptCode || "--"}</Typography>
</Grid>
<Grid item xs={12} md={4}>
- <Typography variant="caption" color="text.secondary">妯″瀷</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.common.model")}</Typography>
<Typography variant="body2">{record.model || "--"}</Typography>
</Grid>
<Grid item xs={12} md={4}>
- <Typography variant="caption" color="text.secondary">鐘舵��</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("common.field.status")}</Typography>
<Typography variant="body2">{record.status || "--"}</Typography>
</Grid>
<Grid item xs={12}>
- <Typography variant="caption" color="text.secondary">MCP 鎸傝浇</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.mountedMcp")}</Typography>
<Typography variant="body2">{record.mountedMcpNames || "--"}</Typography>
</Grid>
{record.errorMessage && (
@@ -219,12 +222,12 @@
<Grid item xs={12}>
<Divider sx={{ my: 1 }} />
<Stack direction="row" justifyContent="space-between" alignItems="center" mb={1}>
- <Typography variant="h6">MCP 宸ュ叿璋冪敤鏃ュ織</Typography>
+ <Typography variant="h6">{translate("ai.observe.detail.mcpLogs")}</Typography>
{loading && <CircularProgress size={20} />}
</Stack>
{!loading && !logs.length && (
<Typography variant="body2" color="text.secondary">
- 褰撳墠璋冪敤娌℃湁浜х敓 MCP 宸ュ叿鏃ュ織銆�
+ {translate("ai.observe.detail.noMcpLogs")}
</Typography>
)}
<Stack spacing={1.5}>
@@ -244,13 +247,11 @@
label={item.status || "--"}
/>
</Stack>
- <Typography variant="caption" color="text.secondary">杈撳叆鎽樿</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.detail.inputSummary")}</Typography>
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
{item.inputSummary || "--"}
</Typography>
- <Typography variant="caption" color="text.secondary" display="block" mt={1}>
- 杈撳嚭鎽樿 / 閿欒
- </Typography>
+ <Typography variant="caption" color="text.secondary" display="block" mt={1}>{translate("ai.observe.detail.outputSummary")}</Typography>
<Typography variant="body2" sx={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
{item.outputSummary || item.errorMessage || "--"}
</Typography>
@@ -262,13 +263,14 @@
</Grid>
</DialogContent>
<DialogActions>
- <Button onClick={onClose}>鍏抽棴</Button>
+ <Button onClick={onClose}>{translate("ai.common.close")}</Button>
</DialogActions>
</Dialog>
);
};
const AiCallLogCards = ({ onView }) => {
+ const translate = useTranslate();
const { data, isLoading } = useListContext();
const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
@@ -284,9 +286,9 @@
return (
<Box px={2} py={6}>
<Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
- <Typography variant="subtitle1">鏆傛棤 AI 璋冪敤鏃ュ織</Typography>
+ <Typography variant="subtitle1">{translate("ai.observe.list.emptyTitle")}</Typography>
<Typography variant="body2" color="text.secondary" mt={1}>
- 鍙戣捣 AI 瀵硅瘽鍚庯紝杩欓噷浼氬睍绀鸿皟鐢ㄧ粺璁″拰瀹¤璁板綍銆�
+ {translate("ai.observe.list.emptyDescription")}
</Typography>
</Card>
</Box>
@@ -297,14 +299,14 @@
<Box px={2} py={2}>
<Grid container spacing={2}>
{records.map((record) => {
- const statusMeta = resolveStatusChip(record.status);
+ const statusMeta = resolveStatusChip(record.status, translate);
return (
<Grid item xs={12} md={6} xl={4} key={record.id}>
<Card variant="outlined" sx={{ height: "100%", borderRadius: 3, boxShadow: "0 8px 24px rgba(15, 23, 42, 0.06)" }}>
<CardContent sx={{ pb: 1.5 }}>
<Stack direction="row" justifyContent="space-between" alignItems="flex-start" spacing={1}>
<Box>
- <Typography variant="h6">{record.promptName || "AI 瀵硅瘽"}</Typography>
+ <Typography variant="h6">{record.promptName || translate("ai.drawer.title")}</Typography>
<Typography variant="body2" color="text.secondary">
{record.promptCode || "--"} / {record.model || "--"}
</Typography>
@@ -312,31 +314,31 @@
<Chip size="small" color={statusMeta.color} label={statusMeta.label} />
</Stack>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
- <Chip size="small" variant="outlined" label={`鐢ㄦ埛 ${record.userId || "--"}`} />
- <Chip size="small" variant="outlined" label={`鑰楁椂 ${record.elapsedMs ?? 0} ms`} />
- <Chip size="small" variant="outlined" label={`Token ${record.totalTokens ?? 0}`} />
+ <Chip size="small" variant="outlined" label={translate("ai.observe.list.userValue", { value: record.userId || "--" })} />
+ <Chip size="small" variant="outlined" label={translate("ai.observe.list.elapsedValue", { value: record.elapsedMs ?? 0 })} />
+ <Chip size="small" variant="outlined" label={translate("ai.observe.list.tokenValue", { value: record.totalTokens ?? 0 })} />
</Stack>
<Divider sx={{ my: 1.5 }} />
- <Typography variant="caption" color="text.secondary">璇锋眰ID</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.observe.fields.requestId")}</Typography>
<Typography variant="body2" sx={{ wordBreak: "break-all" }}>{record.requestId || "--"}</Typography>
- <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
- MCP / 宸ュ叿璋冪敤
- </Typography>
+ <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("ai.observe.list.mcpToolCalls")}</Typography>
<Typography variant="body2">
- 鎸傝浇 {record.mountedMcpCount ?? 0} 涓紝宸ュ叿鎴愬姛 {record.toolSuccessCount ?? 0}锛屽け璐� {record.toolFailureCount ?? 0}
+ {translate("ai.observe.list.mcpToolSummary", {
+ mcp: record.mountedMcpCount ?? 0,
+ success: record.toolSuccessCount ?? 0,
+ failure: record.toolFailureCount ?? 0,
+ })}
</Typography>
{record.errorMessage && (
<>
- <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
- 閿欒
- </Typography>
+ <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("ai.common.error")}</Typography>
<Typography variant="body2">{record.errorMessage}</Typography>
</>
)}
</CardContent>
<CardActions sx={{ px: 2, pb: 2, pt: 0 }}>
<Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record)}>
- 璇︽儏
+ {translate("ai.common.detail")}
</Button>
</CardActions>
</Card>
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx
index 7e1deee..b11f4d1 100644
--- a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountForm.jsx
@@ -5,6 +5,7 @@
SelectInput,
TextInput,
useNotify,
+ useTranslate,
} from "react-admin";
import { Alert, Button, Grid, Stack, Typography } from "@mui/material";
import StatusSelectInput from "@/page/components/StatusSelectInput";
@@ -18,6 +19,7 @@
const AiMcpDraftTestSection = ({ formData, readOnly }) => {
const notify = useNotify();
+ const translate = useTranslate();
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
@@ -26,11 +28,11 @@
try {
const data = await validateDraftMcpConnectivity(formData);
setResult(data);
- notify(data?.message || "鑽夌杩為�氭�ф祴璇曞畬鎴�");
+ notify(data?.message || translate("ai.mcp.connectivity.success"));
} catch (error) {
const nextResult = {
healthStatus: "UNHEALTHY",
- message: error?.message || "鑽夌杩為�氭�ф祴璇曞け璐�",
+ message: error?.message || translate("ai.mcp.connectivity.failed"),
};
setResult(nextResult);
notify(nextResult.message, { type: "error" });
@@ -48,10 +50,10 @@
<Grid item xs={12}>
<Stack direction="row" spacing={1} alignItems="center">
<Button variant="outlined" onClick={handleValidate} disabled={loading}>
- {loading ? "娴嬭瘯涓�..." : "淇濆瓨鍓嶆祴璇�"}
+ {loading ? translate("ai.common.testing") : translate("ai.mcp.form.testBeforeSave")}
</Button>
<Typography variant="body2" color="text.secondary">
- 鐢ㄥ綋鍓嶈崏绋块厤缃洿鎺ユ牎楠岃繛閫氭�э紝涓嶄細钀藉簱銆�
+ {translate("ai.mcp.form.testDescription")}
</Typography>
</Stack>
</Grid>
@@ -68,16 +70,19 @@
);
};
-const AiMcpMountForm = ({ readOnly = false }) => (
+const AiMcpMountForm = ({ readOnly = false }) => {
+ const translate = useTranslate();
+
+ return (
<Grid container spacing={2} width={{ xs: "100%", xl: "80%" }}>
<Grid item xs={12}>
- <Typography variant="h6">MCP 鎸傝浇閰嶇疆</Typography>
+ <Typography variant="h6">{translate("ai.mcp.form.sections.main")}</Typography>
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="name" label="鍚嶇О" fullWidth disabled={readOnly} />
+ <TextInput source="name" label="common.field.name" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={6}>
- <SelectInput source="transportType" label="浼犺緭绫诲瀷" choices={transportChoices} fullWidth disabled={readOnly} />
+ <SelectInput source="transportType" label="ai.mcp.fields.transportType" choices={transportChoices} fullWidth disabled={readOnly} />
</Grid>
<FormDataConsumer>
{({ formData }) => (
@@ -87,7 +92,7 @@
<Grid item xs={12}>
<SelectInput
source="builtinCode"
- label="鍐呯疆 MCP"
+ label="ai.mcp.fields.builtinCode"
choices={[
{ id: "RSF_WMS", name: "RSF_WMS" },
{ id: "RSF_WMS_STOCK", name: "RSF_WMS_STOCK" },
@@ -103,7 +108,7 @@
{formData.transportType === "SSE_HTTP" && (
<>
<Grid item xs={12}>
- <TextInput source="serverUrl" label="鏈嶅姟鍦板潃" fullWidth disabled={readOnly} />
+ <TextInput source="serverUrl" label="ai.mcp.fields.serverUrl" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12}>
<TextInput source="endpoint" label="SSE Endpoint" fullWidth disabled={readOnly} />
@@ -116,7 +121,7 @@
{formData.transportType === "STDIO" && (
<>
<Grid item xs={12}>
- <TextInput source="command" label="鍛戒护" fullWidth disabled={readOnly} />
+ <TextInput source="command" label="ai.mcp.fields.command" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12}>
<TextInput source="argsJson" label="Args JSON" fullWidth multiline minRows={4} disabled={readOnly} />
@@ -133,39 +138,40 @@
<NumberInput source="requestTimeoutMs" label="Timeout(ms)" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={4}>
- <NumberInput source="sort" label="鎺掑簭" fullWidth disabled={readOnly} />
+ <NumberInput source="sort" label="ai.mcp.fields.sort" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={4}>
<StatusSelectInput disabled={readOnly} />
</Grid>
<Grid item xs={12}>
- <TextInput source="memo" label="澶囨敞" fullWidth multiline minRows={3} disabled={readOnly} />
+ <TextInput source="memo" label="common.field.memo" fullWidth multiline minRows={3} disabled={readOnly} />
</Grid>
<FormDataConsumer>
{({ formData }) => <AiMcpDraftTestSection formData={formData} readOnly={readOnly} />}
</FormDataConsumer>
<Grid item xs={12}>
- <Typography variant="h6">杩愯鎬佷俊鎭�</Typography>
+ <Typography variant="h6">{translate("ai.mcp.form.sections.runtime")}</Typography>
</Grid>
<Grid item xs={12} md={4}>
- <TextInput source="healthStatus" label="鍋ュ悍鐘舵��" fullWidth disabled />
+ <TextInput source="healthStatus" label="ai.mcp.fields.healthStatus" fullWidth disabled />
</Grid>
<Grid item xs={12} md={4}>
- <TextInput source="lastInitElapsedMs" label="鏈�杩戝垵濮嬪寲鑰楁椂(ms)" fullWidth disabled />
+ <TextInput source="lastInitElapsedMs" label="ai.mcp.fields.lastInitElapsedMs" fullWidth disabled />
</Grid>
<Grid item xs={12} md={4}>
- <TextInput source="lastTestTime$" label="鏈�杩戞祴璇曟椂闂�" fullWidth disabled />
+ <TextInput source="lastTestTime$" label="ai.mcp.fields.lastTestTime" fullWidth disabled />
</Grid>
<Grid item xs={12}>
- <TextInput source="lastTestMessage" label="鏈�杩戞祴璇曠粨鏋�" fullWidth multiline minRows={3} disabled />
+ <TextInput source="lastTestMessage" label="ai.mcp.fields.lastTestMessage" fullWidth multiline minRows={3} disabled />
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="updateBy" label="鏈�杩戞洿鏂颁汉" fullWidth disabled />
+ <TextInput source="updateBy" label="ai.common.lastUpdatedBy" fullWidth disabled />
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="updateTime$" label="鏈�杩戞洿鏂版椂闂�" fullWidth disabled />
+ <TextInput source="updateTime$" label="ai.common.lastUpdatedAt" fullWidth disabled />
</Grid>
</Grid>
-);
+ );
+};
export default AiMcpMountForm;
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx
index 16c5e85..634aa30 100644
--- a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountList.jsx
@@ -9,6 +9,7 @@
useListContext,
useNotify,
useRefresh,
+ useTranslate,
} from "react-admin";
import {
Box,
@@ -39,7 +40,7 @@
<SearchInput source="condition" alwaysOn />,
<SelectInput
source="transportType"
- label="浼犺緭绫诲瀷"
+ label="ai.mcp.fields.transportType"
choices={[
{ id: "SSE_HTTP", name: "SSE_HTTP" },
{ id: "STDIO", name: "STDIO" },
@@ -48,7 +49,7 @@
/>,
<SelectInput
source="status"
- label="鐘舵��"
+ label="common.field.status"
choices={[
{ id: "1", name: "common.enums.statusTrue" },
{ id: "0", name: "common.enums.statusFalse" },
@@ -83,22 +84,23 @@
};
const transportGroups = [
- { key: "BUILTIN", title: "鍐呯疆 MCP", description: "绯荤粺鍐呯疆宸ュ叿鎸傝浇锛岄�傚悎鐩存帴鏆撮湶骞冲彴鑳藉姏銆�" },
- { key: "SSE_HTTP", title: "杩滅▼ SSE MCP", description: "閫氳繃杩滅▼ MCP Server 鎸傝浇澶栭儴宸ュ叿銆�" },
- { key: "STDIO", title: "鏈湴 STDIO MCP", description: "閫氳繃鏈湴鍛戒护杩涚▼鎸傝浇澶栭儴 MCP銆�" },
+ { key: "BUILTIN", titleKey: "ai.mcp.groups.builtin.title", descriptionKey: "ai.mcp.groups.builtin.description" },
+ { key: "SSE_HTTP", titleKey: "ai.mcp.groups.sse.title", descriptionKey: "ai.mcp.groups.sse.description" },
+ { key: "STDIO", titleKey: "ai.mcp.groups.stdio.title", descriptionKey: "ai.mcp.groups.stdio.description" },
];
-const resolveHealthMeta = (record) => {
+const resolveHealthMeta = (record, translate) => {
if (record.healthStatus === "HEALTHY") {
- return { color: "success", label: "姝e父" };
+ return { color: "success", label: translate("ai.mcp.health.healthy") };
}
if (record.healthStatus === "UNHEALTHY") {
- return { color: "error", label: "澶辫触" };
+ return { color: "error", label: translate("ai.mcp.health.unhealthy") };
}
- return { color: "default", label: "鏈祴璇�" };
+ return { color: "default", label: translate("ai.common.notTested") };
};
const AiMcpMountCards = ({ onView, onEdit, onDelete, onConnectivityTest, deleting, testingConnectivityId }) => {
+ const translate = useTranslate();
const { data, isLoading } = useListContext();
const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
const groupedRecords = useMemo(() => {
@@ -120,9 +122,9 @@
return (
<Box px={2} py={6}>
<Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
- <Typography variant="subtitle1">鏆傛棤 MCP 鎸傝浇</Typography>
+ <Typography variant="subtitle1">{translate("ai.mcp.list.emptyTitle")}</Typography>
<Typography variant="body2" color="text.secondary" mt={1}>
- 鍙互鏂板缓鍐呯疆 MCP銆佽繙绋� SSE 鎸傝浇鎴栨湰鍦� STDIO 鎸傝浇銆�
+ {translate("ai.mcp.list.emptyDescription")}
</Typography>
</Card>
</Box>
@@ -135,14 +137,14 @@
{groupedRecords.map((group) => (
<Box key={group.key}>
<Box mb={1.5}>
- <Typography variant="h6">{group.title}</Typography>
+ <Typography variant="h6">{translate(group.titleKey)}</Typography>
<Typography variant="body2" color="text.secondary">
- {group.description}
+ {translate(group.descriptionKey)}
</Typography>
</Box>
<Grid container spacing={2}>
{group.records.map((record) => {
- const healthMeta = resolveHealthMeta(record);
+ const healthMeta = resolveHealthMeta(record, translate);
return (
<Grid item xs={12} md={6} xl={4} key={record.id}>
<Card
@@ -167,39 +169,35 @@
<Chip
size="small"
color={record.statusBool ? "success" : "default"}
- label={record.statusBool ? "鍚敤" : "鍋滅敤"}
+ label={translate(record.statusBool ? "ai.common.enabled" : "ai.common.disabled")}
/>
<Chip size="small" color={healthMeta.color} label={healthMeta.label} />
</Stack>
</Stack>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
- <Chip size="small" variant="outlined" label={`鎺掑簭 ${record.sort ?? 0}`} />
+ <Chip size="small" variant="outlined" label={translate("ai.mcp.list.sortValue", { value: record.sort ?? 0 })} />
<Chip size="small" variant="outlined" label={`${record.requestTimeoutMs ?? "--"} ms`} />
<Chip size="small" variant="outlined" label={`Init ${record.lastInitElapsedMs ?? "--"} ms`} />
</Stack>
<Divider sx={{ my: 1.5 }} />
- <Typography variant="caption" color="text.secondary">鐩爣</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.common.target")}</Typography>
<Typography variant="body2" sx={{ mt: 0.5, wordBreak: "break-all" }}>
{truncateText(resolveTargetLabel(record), 120)}
</Typography>
- <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
- 鏈�杩戞祴璇�
- </Typography>
+ <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("ai.common.lastTest")}</Typography>
<Typography variant="body2">
- {record.lastTestTime$ ? `${record.lastTestTime$} 路 ${truncateText(record.lastTestMessage, 72)}` : "灏氭湭鎵ц杩為�氭�ф祴璇�"}
+ {record.lastTestTime$ ? `${record.lastTestTime$} 路 ${truncateText(record.lastTestMessage, 72)}` : translate("ai.mcp.list.noConnectivityTest")}
</Typography>
- <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
- 澶囨敞
- </Typography>
+ <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("common.field.memo")}</Typography>
<Typography variant="body2">{truncateText(record.memo)}</Typography>
</CardContent>
<CardActions sx={{ px: 2, pb: 2, pt: 0, justifyContent: "space-between", alignItems: "flex-start" }}>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
<Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record.id)}>
- 璇︽儏
+ {translate("ai.common.detail")}
</Button>
<Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}>
- 缂栬緫
+ {translate("common.button.edit")}
</Button>
<Button
size="small"
@@ -207,7 +205,7 @@
onClick={() => onConnectivityTest(record)}
disabled={testingConnectivityId === record.id}
>
- {testingConnectivityId === record.id ? "娴嬭瘯涓�..." : "杩為�氭祴璇�"}
+ {testingConnectivityId === record.id ? translate("ai.common.testing") : translate("ai.mcp.list.connectivityTest")}
</Button>
</Stack>
<Button
@@ -217,7 +215,7 @@
onClick={() => onDelete(record)}
disabled={deleting}
>
- 鍒犻櫎
+ {translate("ai.common.delete")}
</Button>
</CardActions>
</Card>
@@ -233,6 +231,7 @@
};
const AiMcpMountList = () => {
+ const translate = useTranslate();
const notify = useNotify();
const refresh = useRefresh();
const [deleteOne, { isPending: deleting }] = useDelete();
@@ -243,7 +242,7 @@
const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null });
const handleDelete = (record) => {
- if (!record?.id || !window.confirm(`纭鍒犻櫎鈥�${record.name}鈥濆悧锛焋)) {
+ if (!record?.id || !window.confirm(translate("ai.common.confirmDelete", { name: record.name }))) {
return;
}
deleteOne(
@@ -251,11 +250,11 @@
{ id: record.id },
{
onSuccess: () => {
- notify("鍒犻櫎鎴愬姛");
+ notify(translate("ai.common.deleteSuccess"));
refresh();
},
onError: (error) => {
- notify(error?.message || "鍒犻櫎澶辫触", { type: "error" });
+ notify(error?.message || translate("ai.common.deleteFailed"), { type: "error" });
},
}
);
@@ -268,19 +267,19 @@
setTestingConnectivityId(record.id);
try {
const result = await testMcpConnectivity(record.id);
- notify(result?.message || "杩為�氭�ф祴璇曞畬鎴�");
+ notify(result?.message || translate("ai.mcp.connectivity.success"));
refresh();
} catch (error) {
- notify(error?.message || "杩為�氭�ф祴璇曞け璐�", { type: "error" });
+ notify(error?.message || translate("ai.mcp.connectivity.failed"), { type: "error" });
} finally {
setTestingConnectivityId(null);
}
};
const dialogTitle = {
- create: "鏂板缓 MCP 鎸傝浇",
- edit: "缂栬緫 MCP 鎸傝浇",
- show: "鏌ョ湅 MCP 鎸傝浇璇︽儏",
+ create: translate("ai.mcp.dialog.create"),
+ edit: translate("ai.mcp.dialog.edit"),
+ show: translate("ai.mcp.dialog.show"),
}[dialogState.mode];
return (
@@ -293,7 +292,7 @@
<TopToolbar>
<FilterButton />
<Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}>
- 鏂板缓
+ {translate("ai.common.new")}
</Button>
<MyExportButton />
</TopToolbar>
diff --git a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
index 96edb0a..1b6158b 100644
--- a/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
+++ b/rsf-admin/src/page/system/aiMcpMount/AiMcpMountToolsPanel.jsx
@@ -1,4 +1,5 @@
import React, { useEffect, useMemo, useState } from "react";
+import { useTranslate, useNotify } from "react-admin";
import {
Accordion,
AccordionDetails,
@@ -18,10 +19,9 @@
import PlayCircleOutlineOutlinedIcon from "@mui/icons-material/PlayCircleOutlineOutlined";
import PreviewOutlinedIcon from "@mui/icons-material/PreviewOutlined";
import ExpandMoreOutlinedIcon from "@mui/icons-material/ExpandMoreOutlined";
-import { useNotify } from "react-admin";
import { previewMcpTools, testMcpConnectivity, testMcpTool } from "@/api/ai/mcpMount";
-const parseInputSchema = (inputSchema) => {
+const parseInputSchema = (inputSchema, translate) => {
if (!inputSchema) {
return { pretty: "", fields: [], required: [], error: "" };
}
@@ -46,7 +46,7 @@
pretty: inputSchema,
fields: [],
required: [],
- error: `Input Schema 瑙f瀽澶辫触: ${error.message}`,
+ error: translate("ai.mcp.tools.schemaParseFailed", { message: error.message }),
};
}
};
@@ -118,6 +118,7 @@
const AiMcpMountToolsPanel = ({ mountId }) => {
const notify = useNotify();
+ const translate = useTranslate();
const [loading, setLoading] = useState(false);
const [tools, setTools] = useState([]);
const [error, setError] = useState("");
@@ -130,10 +131,10 @@
const schemaInfoMap = useMemo(() => {
return tools.reduce((result, tool) => {
- result[tool.name] = parseInputSchema(tool.inputSchema);
+ result[tool.name] = parseInputSchema(tool.inputSchema, translate);
return result;
}, {});
- }, [tools]);
+ }, [tools, translate]);
useEffect(() => {
if (!mountId) {
@@ -158,7 +159,7 @@
setInputs({});
setStructuredInputs({});
} catch (requestError) {
- setError(requestError.message || "鑾峰彇宸ュ叿鍒楄〃澶辫触");
+ setError(requestError.message || translate("ai.mcp.tools.loadFailed"));
} finally {
setLoading(false);
}
@@ -169,9 +170,9 @@
try {
const result = await testMcpConnectivity(mountId);
setConnectivity(result);
- notify(result?.message || "杩為�氭�ф祴璇曞畬鎴�");
+ notify(result?.message || translate("ai.mcp.connectivity.success"));
} catch (requestError) {
- const message = requestError.message || "杩為�氭�ф祴璇曞け璐�";
+ const message = requestError.message || translate("ai.mcp.connectivity.failed");
notify(message, { type: "error" });
} finally {
setTestingConnectivity(false);
@@ -210,7 +211,7 @@
const handleTest = async (toolName) => {
const inputJson = inputs[toolName];
if (!inputJson || !inputJson.trim()) {
- notify("璇疯緭鍏ュ伐鍏锋祴璇� JSON", { type: "warning" });
+ notify(translate("ai.mcp.tools.inputRequired"), { type: "warning" });
return;
}
setTestingToolName(toolName);
@@ -223,9 +224,9 @@
...prev,
[toolName]: result?.output || "",
}));
- notify(`宸ュ叿 ${toolName} 娴嬭瘯瀹屾垚`);
+ notify(translate("ai.mcp.tools.testSuccess", { name: toolName }));
} catch (requestError) {
- const message = requestError.message || "宸ュ叿娴嬭瘯澶辫触";
+ const message = requestError.message || translate("ai.mcp.tools.testFailed");
setOutputs((prev) => ({
...prev,
[toolName]: message,
@@ -239,7 +240,7 @@
if (!mountId) {
return (
<Alert severity="info" sx={{ mt: 2 }}>
- 淇濆瓨鎸傝浇鍚庡嵆鍙瑙堝伐鍏峰苟鎵ц娴嬭瘯銆�
+ {translate("ai.mcp.tools.saveBeforePreview")}
</Alert>
);
}
@@ -249,16 +250,16 @@
<Accordion defaultExpanded={false} sx={{ borderRadius: 3, overflow: "hidden" }}>
<AccordionSummary expandIcon={<ExpandMoreOutlinedIcon />}>
<Box flex={1}>
- <Typography variant="h6">宸ュ叿棰勮涓庢祴璇�</Typography>
+ <Typography variant="h6">{translate("ai.mcp.tools.title")}</Typography>
<Typography variant="body2" color="text.secondary">
- 鏀寔杩為�氭�ф祴璇曘�佺粨鏋勫寲 Schema 棰勮鍜屾寜杈撳叆鍙傛暟鑷姩鐢熸垚娴嬭瘯琛ㄥ崟銆�
+ {translate("ai.mcp.tools.description")}
</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Stack direction="row" justifyContent="space-between" alignItems="center" mb={1.5} flexWrap="wrap" useFlexGap>
<Button size="small" startIcon={<PreviewOutlinedIcon />} onClick={loadTools} disabled={loading}>
- 鍒锋柊宸ュ叿
+ {translate("ai.mcp.tools.refresh")}
</Button>
<Button
size="small"
@@ -267,7 +268,7 @@
onClick={handleConnectivityTest}
disabled={testingConnectivity}
>
- {testingConnectivity ? "娴嬭瘯涓�..." : "杩為�氭�ф祴璇�"}
+ {testingConnectivity ? translate("ai.common.testing") : translate("ai.mcp.list.connectivityTest")}
</Button>
</Stack>
{!!connectivity && (
@@ -289,7 +290,7 @@
</Alert>
)}
{!loading && !error && !tools.length && (
- <Alert severity="info">褰撳墠鎸傝浇鏈В鏋愬嚭浠讳綍宸ュ叿銆�</Alert>
+ <Alert severity="info">{translate("ai.mcp.tools.noTools")}</Alert>
)}
<Grid container spacing={2}>
{tools.map((tool) => {
@@ -303,11 +304,11 @@
<Box>
<Typography variant="subtitle1">{tool.name}</Typography>
<Typography variant="body2" color="text.secondary">
- {tool.description || "鏆傛棤鎻忚堪"}
+ {tool.description || translate("ai.common.none")}
</Typography>
{!!tool.toolPurpose && (
<Typography variant="caption" color="text.secondary" display="block" mt={0.5}>
- 鐢ㄩ��: {tool.toolPurpose}
+ {translate("ai.mcp.tools.purpose", { value: tool.toolPurpose })}
</Typography>
)}
</Box>
@@ -318,7 +319,7 @@
</Typography>
)}
<Typography variant="caption" color="text.secondary">
- {schemaInfo.fields.length} 涓弬鏁�
+ {translate("ai.mcp.tools.fieldCount", { count: schemaInfo.fields.length })}
</Typography>
<Typography variant="caption" color="text.secondary">
{tool.returnDirect ? "returnDirect" : "normal"}
@@ -331,13 +332,13 @@
<CardContent>
{!!tool.queryBoundary && (
<Alert severity="info" sx={{ mb: 2 }}>
- 鏌ヨ杈圭晫: {tool.queryBoundary}
+ {translate("ai.mcp.tools.queryBoundary", { value: tool.queryBoundary })}
</Alert>
)}
{!!tool.exampleQuestions?.length && (
<Alert severity="success" sx={{ mb: 2 }}>
<Typography variant="body2" fontWeight={700} mb={0.5}>
- 绀轰緥鎻愰棶
+ {translate("ai.mcp.tools.exampleQuestions")}
</Typography>
{tool.exampleQuestions.map((question) => (
<Typography key={question} variant="body2">
@@ -352,7 +353,7 @@
</Alert>
)}
<TextField
- label="鏍煎紡鍖� Input Schema"
+ label={translate("ai.mcp.tools.formattedSchema")}
value={schemaInfo.pretty || tool.inputSchema || ""}
fullWidth
multiline
@@ -391,7 +392,7 @@
</Grid>
)}
<TextField
- label="娴嬭瘯杈撳叆 JSON"
+ label={translate("ai.mcp.tools.testInput")}
value={inputs[tool.name] || ""}
onChange={(event) => handleInputChange(tool.name, event.target.value)}
fullWidth
@@ -399,7 +400,7 @@
minRows={5}
maxRows={12}
sx={{ mt: 2 }}
- placeholder='渚嬪锛歿"code":"A01"}'
+ placeholder={translate("ai.mcp.tools.testInputPlaceholder")}
/>
<Stack direction="row" justifyContent="flex-end" mt={1.5}>
<Button
@@ -408,11 +409,11 @@
onClick={() => handleTest(tool.name)}
disabled={testingToolName === tool.name}
>
- {testingToolName === tool.name ? "娴嬭瘯涓�..." : "鎵ц娴嬭瘯"}
+ {testingToolName === tool.name ? translate("ai.common.testing") : translate("ai.mcp.tools.executeTest")}
</Button>
</Stack>
<TextField
- label="娴嬭瘯缁撴灉"
+ label={translate("ai.mcp.tools.testResult")}
value={outputs[tool.name] || ""}
fullWidth
multiline
diff --git a/rsf-admin/src/page/system/aiParam/AiParamForm.jsx b/rsf-admin/src/page/system/aiParam/AiParamForm.jsx
index 09eface..eabd8dd 100644
--- a/rsf-admin/src/page/system/aiParam/AiParamForm.jsx
+++ b/rsf-admin/src/page/system/aiParam/AiParamForm.jsx
@@ -6,6 +6,7 @@
SelectInput,
TextInput,
useNotify,
+ useTranslate,
} from "react-admin";
import { Alert, Button, Grid, Stack, Typography } from "@mui/material";
import StatusSelectInput from "@/page/components/StatusSelectInput";
@@ -17,6 +18,7 @@
const AiParamValidateSection = ({ formData, readOnly }) => {
const notify = useNotify();
+ const translate = useTranslate();
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
@@ -25,11 +27,11 @@
try {
const data = await validateAiParamDraft(formData);
setResult(data);
- notify(data?.message || "AI 鍙傛暟楠岃瘉鎴愬姛");
+ notify(data?.message || translate("ai.param.validate.success"));
} catch (error) {
const nextResult = {
status: "INVALID",
- message: error?.message || "AI 鍙傛暟楠岃瘉澶辫触",
+ message: error?.message || translate("ai.param.validate.failed"),
};
setResult(nextResult);
notify(nextResult.message, { type: "error" });
@@ -44,10 +46,10 @@
<Grid item xs={12}>
<Stack direction="row" spacing={1} alignItems="center">
<Button variant="outlined" onClick={handleValidate} disabled={loading}>
- {loading ? "楠岃瘉涓�..." : "淇濆瓨鍓嶉獙璇�"}
+ {loading ? translate("ai.param.validate.loading") : translate("ai.param.validate.beforeSave")}
</Button>
<Typography variant="body2" color="text.secondary">
- 浼氱洿鎺ユ牎楠屽綋鍓� Base URL銆丄PI Key 涓庢ā鍨嬫槸鍚﹀彲璋冪敤銆�
+ {translate("ai.param.validate.description")}
</Typography>
</Stack>
</Grid>
@@ -65,72 +67,76 @@
);
};
-const AiParamForm = ({ readOnly = false }) => (
+const AiParamForm = ({ readOnly = false }) => {
+ const translate = useTranslate();
+
+ return (
<Grid container spacing={2} width={{ xs: "100%", xl: "80%" }}>
<Grid item xs={12}>
- <Typography variant="h6">涓昏閰嶇疆</Typography>
+ <Typography variant="h6">{translate("ai.param.form.sections.main")}</Typography>
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="name" label="鍚嶇О" fullWidth disabled={readOnly} />
+ <TextInput source="name" label="common.field.name" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={6}>
- <SelectInput source="providerType" label="鎻愪緵鏂圭被鍨�" choices={providerChoices} fullWidth disabled={readOnly} />
+ <SelectInput source="providerType" label="ai.param.fields.providerType" choices={providerChoices} fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12}>
- <TextInput source="baseUrl" label="Base URL" fullWidth disabled={readOnly} />
+ <TextInput source="baseUrl" label="ai.param.fields.baseUrl" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="apiKey" label="API Key" fullWidth disabled={readOnly} />
+ <TextInput source="apiKey" label="ai.param.fields.apiKey" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="model" label="妯″瀷" fullWidth disabled={readOnly} />
+ <TextInput source="model" label="ai.param.fields.model" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={3}>
- <NumberInput source="temperature" label="Temperature" fullWidth disabled={readOnly} />
+ <NumberInput source="temperature" label="ai.param.fields.temperature" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={3}>
- <NumberInput source="topP" label="Top P" fullWidth disabled={readOnly} />
+ <NumberInput source="topP" label="ai.param.fields.topP" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={3}>
- <NumberInput source="maxTokens" label="Max Tokens" fullWidth disabled={readOnly} />
+ <NumberInput source="maxTokens" label="ai.param.fields.maxTokens" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={3}>
- <NumberInput source="timeoutMs" label="Timeout(ms)" fullWidth disabled={readOnly} />
+ <NumberInput source="timeoutMs" label="ai.param.fields.timeoutMs" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={6}>
- <BooleanInput source="streamingEnabled" label="鍚敤娴佸紡鍝嶅簲" disabled={readOnly} />
+ <BooleanInput source="streamingEnabled" label="ai.param.fields.streamingEnabled" disabled={readOnly} />
</Grid>
<Grid item xs={12} md={6}>
<StatusSelectInput disabled={readOnly} />
</Grid>
<Grid item xs={12}>
- <TextInput source="memo" label="澶囨敞" fullWidth multiline minRows={3} disabled={readOnly} />
+ <TextInput source="memo" label="common.field.memo" fullWidth multiline minRows={3} disabled={readOnly} />
</Grid>
<FormDataConsumer>
{({ formData }) => <AiParamValidateSection formData={formData} readOnly={readOnly} />}
</FormDataConsumer>
<Grid item xs={12}>
- <Typography variant="h6">杩愯涓庡璁′俊鎭�</Typography>
+ <Typography variant="h6">{translate("ai.param.form.sections.runtime")}</Typography>
</Grid>
<Grid item xs={12} md={3}>
- <TextInput source="validateStatus" label="鏈�杩戞牎楠岀姸鎬�" fullWidth disabled />
+ <TextInput source="validateStatus" label="ai.param.fields.validateStatus" fullWidth disabled />
</Grid>
<Grid item xs={12} md={3}>
- <TextInput source="lastValidateElapsedMs" label="鏈�杩戞牎楠岃�楁椂(ms)" fullWidth disabled />
+ <TextInput source="lastValidateElapsedMs" label="ai.param.fields.lastValidateElapsedMs" fullWidth disabled />
</Grid>
<Grid item xs={12} md={3}>
- <TextInput source="lastValidateTime$" label="鏈�杩戞牎楠屾椂闂�" fullWidth disabled />
+ <TextInput source="lastValidateTime$" label="ai.param.fields.lastValidateTime" fullWidth disabled />
</Grid>
<Grid item xs={12} md={3}>
- <TextInput source="updateBy" label="鏈�杩戞洿鏂颁汉" fullWidth disabled />
+ <TextInput source="updateBy" label="ai.common.lastUpdatedBy" fullWidth disabled />
</Grid>
<Grid item xs={12}>
- <TextInput source="lastValidateMessage" label="鏈�杩戞牎楠岀粨鏋�" fullWidth multiline minRows={3} disabled />
+ <TextInput source="lastValidateMessage" label="ai.param.fields.lastValidateMessage" fullWidth multiline minRows={3} disabled />
</Grid>
<Grid item xs={12}>
- <TextInput source="updateTime$" label="鏈�杩戞洿鏂版椂闂�" fullWidth disabled />
+ <TextInput source="updateTime$" label="ai.common.lastUpdatedAt" fullWidth disabled />
</Grid>
</Grid>
-);
+ );
+};
export default AiParamForm;
diff --git a/rsf-admin/src/page/system/aiParam/AiParamList.jsx b/rsf-admin/src/page/system/aiParam/AiParamList.jsx
index 225fbd9..ebee4e6 100644
--- a/rsf-admin/src/page/system/aiParam/AiParamList.jsx
+++ b/rsf-admin/src/page/system/aiParam/AiParamList.jsx
@@ -10,6 +10,7 @@
useListContext,
useNotify,
useRefresh,
+ useTranslate,
} from "react-admin";
import {
Box,
@@ -35,11 +36,11 @@
const filters = [
<SearchInput source="condition" alwaysOn />,
- <TextInput source="providerType" label="鎻愪緵鏂圭被鍨�" />,
- <TextInput source="model" label="妯″瀷" />,
+ <TextInput source="providerType" label="ai.param.fields.providerType" />,
+ <TextInput source="model" label="ai.param.fields.model" />,
<SelectInput
source="status"
- label="鐘舵��"
+ label="common.field.status"
choices={[
{ id: "1", name: "common.enums.statusTrue" },
{ id: "0", name: "common.enums.statusFalse" },
@@ -64,6 +65,7 @@
};
const AiParamCards = ({ onView, onEdit, onDelete, deleting }) => {
+ const translate = useTranslate();
const { data, isLoading } = useListContext();
const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
@@ -79,9 +81,9 @@
return (
<Box px={2} py={6}>
<Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
- <Typography variant="subtitle1">鏆傛棤 AI 鍙傛暟閰嶇疆</Typography>
+ <Typography variant="subtitle1">{translate("ai.param.list.emptyTitle")}</Typography>
<Typography variant="body2" color="text.secondary" mt={1}>
- 鍙互鍏堟柊寤轰竴涓� OpenAI 鍏煎妯″瀷鍙傛暟鍗$墖銆�
+ {translate("ai.param.list.emptyDescription")}
</Typography>
</Card>
</Box>
@@ -114,7 +116,7 @@
<Chip
size="small"
color={record.statusBool ? "success" : "default"}
- label={record.statusBool ? "鍚敤" : "鍋滅敤"}
+ label={translate(record.statusBool ? "ai.common.enabled" : "ai.common.disabled")}
/>
</Stack>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
@@ -123,7 +125,7 @@
size="small"
variant="outlined"
color={record.streamingEnabled ? "info" : "default"}
- label={record.streamingEnabled ? "娴佸紡鍝嶅簲" : "闈炴祦寮�"}
+ label={translate(record.streamingEnabled ? "ai.param.list.streaming" : "ai.param.list.nonStreaming")}
/>
</Stack>
<Divider sx={{ my: 1.5 }} />
@@ -151,18 +153,16 @@
<Typography variant="body2">{record.timeoutMs ?? "--"} ms</Typography>
</Grid>
</Grid>
- <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
- 澶囨敞
- </Typography>
+ <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("common.field.memo")}</Typography>
<Typography variant="body2">{truncateText(record.memo)}</Typography>
</CardContent>
<CardActions sx={{ px: 2, pb: 2, pt: 0, justifyContent: "space-between" }}>
<Stack direction="row" spacing={1}>
<Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record.id)}>
- 璇︽儏
+ {translate("ai.common.detail")}
</Button>
<Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}>
- 缂栬緫
+ {translate("common.button.edit")}
</Button>
</Stack>
<Button
@@ -172,7 +172,7 @@
onClick={() => onDelete(record)}
disabled={deleting}
>
- 鍒犻櫎
+ {translate("ai.common.delete")}
</Button>
</CardActions>
</Card>
@@ -184,6 +184,7 @@
};
const AiParamList = () => {
+ const translate = useTranslate();
const notify = useNotify();
const refresh = useRefresh();
const [deleteOne, { isPending: deleting }] = useDelete();
@@ -193,7 +194,7 @@
const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null });
const handleDelete = (record) => {
- if (!record?.id || !window.confirm(`纭鍒犻櫎鈥�${record.name}鈥濆悧锛焋)) {
+ if (!record?.id || !window.confirm(translate("ai.common.confirmDelete", { name: record.name }))) {
return;
}
deleteOne(
@@ -201,20 +202,20 @@
{ id: record.id },
{
onSuccess: () => {
- notify("鍒犻櫎鎴愬姛");
+ notify(translate("ai.common.deleteSuccess"));
refresh();
},
onError: (error) => {
- notify(error?.message || "鍒犻櫎澶辫触", { type: "error" });
+ notify(error?.message || translate("ai.common.deleteFailed"), { type: "error" });
},
}
);
};
const dialogTitle = {
- create: "鏂板缓 AI 鍙傛暟",
- edit: "缂栬緫 AI 鍙傛暟",
- show: "鏌ョ湅 AI 鍙傛暟璇︽儏",
+ create: translate("ai.param.dialog.create"),
+ edit: translate("ai.param.dialog.edit"),
+ show: translate("ai.param.dialog.show"),
}[dialogState.mode];
return (
@@ -227,7 +228,7 @@
<TopToolbar>
<FilterButton />
<Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}>
- 鏂板缓
+ {translate("ai.common.new")}
</Button>
<MyExportButton />
</TopToolbar>
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx
index 5c6ad35..36051d7 100644
--- a/rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptForm.jsx
@@ -3,6 +3,7 @@
FormDataConsumer,
TextInput,
useNotify,
+ useTranslate,
} from "react-admin";
import { Alert, Button, Grid, Stack, TextField, Typography } from "@mui/material";
import StatusSelectInput from "@/page/components/StatusSelectInput";
@@ -10,7 +11,8 @@
const AiPromptPreviewSection = ({ formData }) => {
const notify = useNotify();
- const [input, setInput] = useState("璇峰府鎴戞�荤粨褰撳墠椤甸潰鑳藉仛浠�涔�");
+ const translate = useTranslate();
+ const [input, setInput] = useState(() => translate("ai.prompt.preview.defaultInput"));
const [metadataText, setMetadataText] = useState("{\"path\":\"/system/aiPrompt\"}");
const [preview, setPreview] = useState(null);
const [loading, setLoading] = useState(false);
@@ -25,10 +27,10 @@
metadata,
});
setPreview(data);
- notify("Prompt 棰勮瀹屾垚");
+ notify(translate("ai.prompt.preview.success"));
} catch (error) {
setPreview(null);
- notify(error?.message || "Prompt 棰勮澶辫触", { type: "error" });
+ notify(error?.message || translate("ai.prompt.preview.failed"), { type: "error" });
} finally {
setLoading(false);
}
@@ -37,11 +39,11 @@
return (
<>
<Grid item xs={12}>
- <Typography variant="h6">Prompt 棰勮</Typography>
+ <Typography variant="h6">{translate("ai.prompt.preview.title")}</Typography>
</Grid>
<Grid item xs={12}>
<TextField
- label="绀轰緥杈撳叆"
+ label={translate("ai.prompt.preview.input")}
value={input}
onChange={(event) => setInput(event.target.value)}
fullWidth
@@ -51,7 +53,7 @@
</Grid>
<Grid item xs={12}>
<TextField
- label="绀轰緥鍏冩暟鎹� JSON"
+ label={translate("ai.prompt.preview.metadata")}
value={metadataText}
onChange={(event) => setMetadataText(event.target.value)}
fullWidth
@@ -62,10 +64,10 @@
<Grid item xs={12}>
<Stack direction="row" spacing={1} alignItems="center">
<Button variant="outlined" onClick={handlePreview} disabled={loading}>
- {loading ? "棰勮涓�..." : "棰勮娓叉煋"}
+ {loading ? translate("ai.prompt.preview.loading") : translate("ai.prompt.preview.render")}
</Button>
<Typography variant="body2" color="text.secondary">
- 鐢ㄥ綋鍓嶈〃鍗曞唴瀹规覆鏌� System Prompt 鍜� User Prompt銆�
+ {translate("ai.prompt.preview.description")}
</Typography>
</Stack>
</Grid>
@@ -73,12 +75,14 @@
<>
<Grid item xs={12}>
<Alert severity="success">
- 宸茶В鏋愬彉閲忥細{(preview.resolvedVariables || []).join(", ") || "鏃�"}
+ {translate("ai.prompt.preview.resolvedVariables", {
+ value: (preview.resolvedVariables || []).join(", ") || translate("ai.common.none"),
+ })}
</Alert>
</Grid>
<Grid item xs={12}>
<TextField
- label="娓叉煋鍚庣殑 System Prompt"
+ label={translate("ai.prompt.preview.renderedSystemPrompt")}
value={preview.renderedSystemPrompt || ""}
fullWidth
multiline
@@ -88,7 +92,7 @@
</Grid>
<Grid item xs={12}>
<TextField
- label="娓叉煋鍚庣殑 User Prompt"
+ label={translate("ai.prompt.preview.renderedUserPrompt")}
value={preview.renderedUserPrompt || ""}
fullWidth
multiline
@@ -102,45 +106,49 @@
);
};
-const AiPromptForm = ({ readOnly = false }) => (
+const AiPromptForm = ({ readOnly = false }) => {
+ const translate = useTranslate();
+
+ return (
<Grid container spacing={2} width={{ xs: "100%", xl: "80%" }}>
<Grid item xs={12}>
- <Typography variant="h6">Prompt 閰嶇疆</Typography>
+ <Typography variant="h6">{translate("ai.prompt.form.sections.main")}</Typography>
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="name" label="鍚嶇О" fullWidth disabled={readOnly} />
+ <TextInput source="name" label="common.field.name" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="code" label="缂栫爜" fullWidth disabled={readOnly} />
+ <TextInput source="code" label="ai.prompt.fields.code" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12}>
- <TextInput source="scene" label="鍦烘櫙" fullWidth disabled={readOnly} />
+ <TextInput source="scene" label="ai.prompt.fields.scene" fullWidth disabled={readOnly} />
</Grid>
<Grid item xs={12}>
- <TextInput source="systemPrompt" label="System Prompt" fullWidth multiline minRows={6} disabled={readOnly} />
+ <TextInput source="systemPrompt" label="ai.prompt.fields.systemPrompt" fullWidth multiline minRows={6} disabled={readOnly} />
</Grid>
<Grid item xs={12}>
- <TextInput source="userPromptTemplate" label="User Prompt Template" fullWidth multiline minRows={5} disabled={readOnly} />
+ <TextInput source="userPromptTemplate" label="ai.prompt.fields.userPromptTemplate" fullWidth multiline minRows={5} disabled={readOnly} />
</Grid>
<Grid item xs={12} md={6}>
<StatusSelectInput disabled={readOnly} />
</Grid>
<Grid item xs={12}>
- <TextInput source="memo" label="澶囨敞" fullWidth multiline minRows={3} disabled={readOnly} />
+ <TextInput source="memo" label="common.field.memo" fullWidth multiline minRows={3} disabled={readOnly} />
</Grid>
<FormDataConsumer>
{({ formData }) => <AiPromptPreviewSection formData={formData} />}
</FormDataConsumer>
<Grid item xs={12}>
- <Typography variant="h6">杩愯涓庡璁′俊鎭�</Typography>
+ <Typography variant="h6">{translate("ai.prompt.form.sections.runtime")}</Typography>
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="updateBy" label="鏈�杩戞洿鏂颁汉" fullWidth disabled />
+ <TextInput source="updateBy" label="ai.common.lastUpdatedBy" fullWidth disabled />
</Grid>
<Grid item xs={12} md={6}>
- <TextInput source="updateTime$" label="鏈�杩戞洿鏂版椂闂�" fullWidth disabled />
+ <TextInput source="updateTime$" label="ai.common.lastUpdatedAt" fullWidth disabled />
</Grid>
</Grid>
-);
+ );
+};
export default AiPromptForm;
diff --git a/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx b/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx
index 236fa02..089af71 100644
--- a/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx
+++ b/rsf-admin/src/page/system/aiPrompt/AiPromptList.jsx
@@ -10,6 +10,7 @@
useListContext,
useNotify,
useRefresh,
+ useTranslate,
} from "react-admin";
import {
Box,
@@ -35,11 +36,11 @@
const filters = [
<SearchInput source="condition" alwaysOn />,
- <TextInput source="code" label="缂栫爜" />,
- <TextInput source="scene" label="鍦烘櫙" />,
+ <TextInput source="code" label="ai.prompt.fields.code" />,
+ <TextInput source="scene" label="ai.prompt.fields.scene" />,
<SelectInput
source="status"
- label="鐘舵��"
+ label="common.field.status"
choices={[
{ id: "1", name: "common.enums.statusTrue" },
{ id: "0", name: "common.enums.statusFalse" },
@@ -61,6 +62,7 @@
};
const AiPromptCards = ({ onView, onEdit, onDelete, deleting }) => {
+ const translate = useTranslate();
const { data, isLoading } = useListContext();
const records = useMemo(() => (Array.isArray(data) ? data : []), [data]);
@@ -76,9 +78,9 @@
return (
<Box px={2} py={6}>
<Card variant="outlined" sx={{ p: 3, textAlign: "center", borderStyle: "dashed" }}>
- <Typography variant="subtitle1">鏆傛棤 Prompt 閰嶇疆</Typography>
+ <Typography variant="subtitle1">{translate("ai.prompt.list.emptyTitle")}</Typography>
<Typography variant="body2" color="text.secondary" mt={1}>
- 鏂板缓涓�寮� Prompt 鍗$墖鍚庯紝AI 瀵硅瘽浼氬姩鎬佸姞杞借繖閲岀殑鍐呭銆�
+ {translate("ai.prompt.list.emptyDescription")}
</Typography>
</Card>
</Box>
@@ -111,29 +113,27 @@
<Chip
size="small"
color={record.statusBool ? "success" : "default"}
- label={record.statusBool ? "鍚敤" : "鍋滅敤"}
+ label={translate(record.statusBool ? "ai.common.enabled" : "ai.common.disabled")}
/>
</Stack>
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}>
- <Chip size="small" variant="outlined" label={`Scene: ${record.scene || "--"}`} />
+ <Chip size="small" variant="outlined" label={translate("ai.prompt.list.sceneValue", { value: record.scene || "--" })} />
</Stack>
<Divider sx={{ my: 1.5 }} />
- <Typography variant="caption" color="text.secondary">System Prompt</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.prompt.fields.systemPrompt")}</Typography>
<Typography variant="body2" sx={{ mt: 0.5 }}>
{truncateText(record.systemPrompt)}
</Typography>
- <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>
- User Prompt Template
- </Typography>
+ <Typography variant="caption" color="text.secondary" display="block" mt={1.5}>{translate("ai.prompt.fields.userPromptTemplate")}</Typography>
<Typography variant="body2">{truncateText(record.userPromptTemplate, 100)}</Typography>
</CardContent>
<CardActions sx={{ px: 2, pb: 2, pt: 0, justifyContent: "space-between" }}>
<Stack direction="row" spacing={1}>
<Button size="small" startIcon={<VisibilityOutlinedIcon />} onClick={() => onView(record.id)}>
- 璇︽儏
+ {translate("ai.common.detail")}
</Button>
<Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}>
- 缂栬緫
+ {translate("common.button.edit")}
</Button>
</Stack>
<Button
@@ -143,7 +143,7 @@
onClick={() => onDelete(record)}
disabled={deleting}
>
- 鍒犻櫎
+ {translate("ai.common.delete")}
</Button>
</CardActions>
</Card>
@@ -155,6 +155,7 @@
};
const AiPromptList = () => {
+ const translate = useTranslate();
const notify = useNotify();
const refresh = useRefresh();
const [deleteOne, { isPending: deleting }] = useDelete();
@@ -164,7 +165,7 @@
const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null });
const handleDelete = (record) => {
- if (!record?.id || !window.confirm(`纭鍒犻櫎鈥�${record.name}鈥濆悧锛焋)) {
+ if (!record?.id || !window.confirm(translate("ai.common.confirmDelete", { name: record.name }))) {
return;
}
deleteOne(
@@ -172,20 +173,20 @@
{ id: record.id },
{
onSuccess: () => {
- notify("鍒犻櫎鎴愬姛");
+ notify(translate("ai.common.deleteSuccess"));
refresh();
},
onError: (error) => {
- notify(error?.message || "鍒犻櫎澶辫触", { type: "error" });
+ notify(error?.message || translate("ai.common.deleteFailed"), { type: "error" });
},
}
);
};
const dialogTitle = {
- create: "鏂板缓 Prompt",
- edit: "缂栬緫 Prompt",
- show: "鏌ョ湅 Prompt 璇︽儏",
+ create: translate("ai.prompt.dialog.create"),
+ edit: translate("ai.prompt.dialog.edit"),
+ show: translate("ai.prompt.dialog.show"),
}[dialogState.mode];
return (
@@ -198,7 +199,7 @@
<TopToolbar>
<FilterButton />
<Button variant="contained" startIcon={<AddRoundedIcon />} onClick={() => openDialog("create")}>
- 鏂板缓
+ {translate("ai.common.new")}
</Button>
<MyExportButton />
</TopToolbar>
diff --git a/rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx b/rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx
index ea5de3e..5b61e97 100644
--- a/rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx
+++ b/rsf-admin/src/page/system/aiShared/AiConfigDialog.jsx
@@ -7,6 +7,7 @@
Toolbar,
useNotify,
useRefresh,
+ useTranslate,
} from "react-admin";
import {
Button,
@@ -16,9 +17,9 @@
DialogTitle,
} from "@mui/material";
-const DialogFormToolbar = ({ onClose }) => (
+const DialogFormToolbar = ({ onClose, translate }) => (
<Toolbar sx={{ justifyContent: "space-between", px: 0 }}>
- <Button onClick={onClose}>鍙栨秷</Button>
+ <Button onClick={onClose}>{translate("ai.common.cancel")}</Button>
<SaveButton />
</Toolbar>
);
@@ -36,25 +37,26 @@
}) => {
const notify = useNotify();
const refresh = useRefresh();
+ const translate = useTranslate();
if (!open) {
return null;
}
const handleSuccess = () => {
- notify(mode === "create" ? "淇濆瓨鎴愬姛" : "鏇存柊鎴愬姛");
+ notify(translate(mode === "create" ? "ai.common.saveSuccess" : "ai.common.updateSuccess"));
refresh();
onClose();
};
const handleError = (error) => {
- notify(error?.message || "鎿嶄綔澶辫触", { type: "error" });
+ notify(error?.message || translate("ai.common.operationFailed"), { type: "error" });
};
const formContent = (
<SimpleForm
defaultValues={mode === "create" ? defaultValues : undefined}
- toolbar={mode === "show" ? false : <DialogFormToolbar onClose={onClose} />}
+ toolbar={mode === "show" ? false : <DialogFormToolbar onClose={onClose} translate={translate} />}
sx={{
"& .RaSimpleForm-form": {
maxWidth: "100%",
@@ -89,7 +91,7 @@
</DialogContent>
{mode === "show" && (
<DialogActions>
- <Button onClick={onClose}>鍏抽棴</Button>
+ <Button onClick={onClose}>{translate("ai.common.close")}</Button>
</DialogActions>
)}
</Dialog>
diff --git a/rsf-admin/src/page/system/aiShared/AiRuntimeSummary.jsx b/rsf-admin/src/page/system/aiShared/AiRuntimeSummary.jsx
index bf0a840..723ff59 100644
--- a/rsf-admin/src/page/system/aiShared/AiRuntimeSummary.jsx
+++ b/rsf-admin/src/page/system/aiShared/AiRuntimeSummary.jsx
@@ -1,8 +1,10 @@
import React, { useEffect, useState } from "react";
+import { useTranslate } from "react-admin";
import { Alert, Box, Card, CardContent, Chip, CircularProgress, Grid, Stack, Typography } from "@mui/material";
import { getAiConfigSummary } from "@/api/ai/configCenter";
const AiRuntimeSummary = ({ promptCode = "home.default" }) => {
+ const translate = useTranslate();
const [summary, setSummary] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
@@ -22,7 +24,7 @@
if (!active) {
return;
}
- setError(err?.message || "鑾峰彇杩愯鎬佹憳瑕佸け璐�");
+ setError(err?.message || translate("ai.runtimeSummary.fetchFailed"));
})
.finally(() => {
if (active) {
@@ -46,9 +48,9 @@
<CardContent>
<Stack direction="row" justifyContent="space-between" alignItems="center" mb={2}>
<Box>
- <Typography variant="h6">褰撳墠杩愯鎬�</Typography>
+ <Typography variant="h6">{translate("ai.runtimeSummary.title")}</Typography>
<Typography variant="body2" color="text.secondary">
- 灞曠ず褰撳墠鐢熸晥鐨勬ā鍨嬨�丳rompt 涓� MCP 鎸傝浇淇℃伅銆�
+ {translate("ai.runtimeSummary.description")}
</Typography>
</Box>
{loading && <CircularProgress size={24} />}
@@ -57,29 +59,32 @@
{!loading && !error && summary && (
<Grid container spacing={2}>
<Grid item xs={12} md={4}>
- <Typography variant="caption" color="text.secondary">褰撳墠妯″瀷</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.runtimeSummary.currentModel")}</Typography>
<Typography variant="body1">{summary.activeModel || "--"}</Typography>
<Typography variant="body2" color="text.secondary">
{summary.activeParamName || "--"}
</Typography>
<Stack direction="row" spacing={1} mt={1} flexWrap="wrap" useFlexGap>
- <Chip size="small" label={`鏍¢獙 ${summary.activeParamValidateStatus || "--"}`} />
- <Chip size="small" variant="outlined" label={summary.activeParamValidatedAt || "鏈牎楠�"} />
+ <Chip size="small" label={translate("ai.runtimeSummary.validateStatus", { status: summary.activeParamValidateStatus || "--" })} />
+ <Chip size="small" variant="outlined" label={summary.activeParamValidatedAt || translate("ai.common.notValidated")} />
</Stack>
</Grid>
<Grid item xs={12} md={4}>
- <Typography variant="caption" color="text.secondary">褰撳墠 Prompt</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.runtimeSummary.currentPrompt")}</Typography>
<Typography variant="body1">{summary.promptName || "--"}</Typography>
<Typography variant="body2" color="text.secondary">
{summary.promptCode || "--"} / {summary.promptScene || "--"}
</Typography>
<Typography variant="body2" color="text.secondary" mt={1}>
- 鏈�杩戞洿鏂帮細{summary.activePromptUpdatedAt || "--"} / {summary.activePromptUpdatedBy || "--"}
+ {translate("ai.runtimeSummary.lastUpdated", {
+ time: summary.activePromptUpdatedAt || "--",
+ user: summary.activePromptUpdatedBy || "--",
+ })}
</Typography>
</Grid>
<Grid item xs={12} md={4}>
- <Typography variant="caption" color="text.secondary">宸插惎鐢� MCP</Typography>
- <Typography variant="body1">{summary.enabledMcpCount ?? 0} 涓�</Typography>
+ <Typography variant="caption" color="text.secondary">{translate("ai.runtimeSummary.enabledMcp")}</Typography>
+ <Typography variant="body1">{translate("ai.runtimeSummary.enabledMcpCount", { count: summary.enabledMcpCount ?? 0 })}</Typography>
<Stack direction="row" spacing={1} mt={1} flexWrap="wrap" useFlexGap>
{(summary.enabledMcpNames || []).map((name) => (
<Chip key={name} size="small" variant="outlined" label={name} />
--
Gitblit v1.9.1