mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-08 03:48:40 +02:00
Add files via upload
This commit is contained in:
@@ -4411,6 +4411,7 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enrichSpecWithI18nKeys(spec)
|
||||||
c.JSON(http.StatusOK, spec)
|
c.JSON(http.StatusOK, spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
// apiDocI18n 为 OpenAPI 文档提供 x-i18n-* 扩展键,供前端 apiDocs 国际化使用。
|
||||||
|
// 前端通过 apiDocs.tags.* / apiDocs.summary.* / apiDocs.response.* 翻译。
|
||||||
|
|
||||||
|
var apiDocI18nTagToKey = map[string]string{
|
||||||
|
"认证": "auth", "对话管理": "conversationManagement", "对话交互": "conversationInteraction",
|
||||||
|
"批量任务": "batchTasks", "对话分组": "conversationGroups", "漏洞管理": "vulnerabilityManagement",
|
||||||
|
"角色管理": "roleManagement", "Skills管理": "skillsManagement", "监控": "monitoring",
|
||||||
|
"配置管理": "configManagement", "外部MCP管理": "externalMCPManagement", "攻击链": "attackChain",
|
||||||
|
"知识库": "knowledgeBase", "MCP": "mcp",
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiDocI18nSummaryToKey = map[string]string{
|
||||||
|
"用户登录": "login", "用户登出": "logout", "修改密码": "changePassword", "验证Token": "validateToken",
|
||||||
|
"创建对话": "createConversation", "列出对话": "listConversations", "查看对话详情": "getConversationDetail",
|
||||||
|
"更新对话": "updateConversation", "删除对话": "deleteConversation", "获取对话结果": "getConversationResult",
|
||||||
|
"发送消息并获取AI回复(非流式)": "sendMessageNonStream", "发送消息并获取AI回复(流式)": "sendMessageStream",
|
||||||
|
"取消任务": "cancelTask", "列出运行中的任务": "listRunningTasks", "列出已完成的任务": "listCompletedTasks",
|
||||||
|
"创建批量任务队列": "createBatchQueue", "列出批量任务队列": "listBatchQueues", "获取批量任务队列": "getBatchQueue",
|
||||||
|
"删除批量任务队列": "deleteBatchQueue", "启动批量任务队列": "startBatchQueue", "暂停批量任务队列": "pauseBatchQueue",
|
||||||
|
"添加任务到队列": "addTaskToQueue", "SQL注入扫描": "sqlInjectionScan", "端口扫描": "portScan",
|
||||||
|
"更新批量任务": "updateBatchTask", "删除批量任务": "deleteBatchTask",
|
||||||
|
"创建分组": "createGroup", "列出分组": "listGroups", "获取分组": "getGroup", "更新分组": "updateGroup",
|
||||||
|
"删除分组": "deleteGroup", "获取分组中的对话": "getGroupConversations", "添加对话到分组": "addConversationToGroup",
|
||||||
|
"从分组移除对话": "removeConversationFromGroup",
|
||||||
|
"列出漏洞": "listVulnerabilities", "创建漏洞": "createVulnerability", "获取漏洞统计": "getVulnerabilityStats",
|
||||||
|
"获取漏洞": "getVulnerability", "更新漏洞": "updateVulnerability", "删除漏洞": "deleteVulnerability",
|
||||||
|
"列出角色": "listRoles", "创建角色": "createRole", "获取角色": "getRole", "更新角色": "updateRole", "删除角色": "deleteRole",
|
||||||
|
"获取可用Skills列表": "getAvailableSkills", "列出Skills": "listSkills", "创建Skill": "createSkill",
|
||||||
|
"获取Skill统计": "getSkillStats", "清空Skill统计": "clearSkillStats", "获取Skill": "getSkill",
|
||||||
|
"更新Skill": "updateSkill", "删除Skill": "deleteSkill", "获取绑定角色": "getBoundRoles",
|
||||||
|
"获取监控信息": "getMonitorInfo", "获取执行记录": "getExecutionRecords", "删除执行记录": "deleteExecutionRecord",
|
||||||
|
"批量删除执行记录": "batchDeleteExecutionRecords", "获取统计信息": "getStats",
|
||||||
|
"获取配置": "getConfig", "更新配置": "updateConfig", "获取工具配置": "getToolConfig", "应用配置": "applyConfig",
|
||||||
|
"列出外部MCP": "listExternalMCP", "获取外部MCP统计": "getExternalMCPStats", "获取外部MCP": "getExternalMCP",
|
||||||
|
"添加或更新外部MCP": "addOrUpdateExternalMCP", "stdio模式配置": "stdioModeConfig", "SSE模式配置": "sseModeConfig",
|
||||||
|
"删除外部MCP": "deleteExternalMCP", "启动外部MCP": "startExternalMCP", "停止外部MCP": "stopExternalMCP",
|
||||||
|
"获取攻击链": "getAttackChain", "重新生成攻击链": "regenerateAttackChain",
|
||||||
|
"设置对话置顶": "pinConversation", "设置分组置顶": "pinGroup", "设置分组中对话的置顶": "pinGroupConversation",
|
||||||
|
"获取分类": "getCategories", "列出知识项": "listKnowledgeItems", "创建知识项": "createKnowledgeItem",
|
||||||
|
"获取知识项": "getKnowledgeItem", "更新知识项": "updateKnowledgeItem", "删除知识项": "deleteKnowledgeItem",
|
||||||
|
"获取索引状态": "getIndexStatus", "重建索引": "rebuildIndex", "扫描知识库": "scanKnowledgeBase",
|
||||||
|
"搜索知识库": "searchKnowledgeBase", "基础搜索": "basicSearch", "按风险类型搜索": "searchByRiskType",
|
||||||
|
"获取检索日志": "getRetrievalLogs", "删除检索日志": "deleteRetrievalLog",
|
||||||
|
"MCP端点": "mcpEndpoint", "列出所有工具": "listAllTools", "调用工具": "invokeTool", "初始化连接": "initConnection",
|
||||||
|
"成功响应": "successResponse", "错误响应": "errorResponse",
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiDocI18nResponseDescToKey = map[string]string{
|
||||||
|
"获取成功": "getSuccess", "未授权": "unauthorized", "未授权,需要有效的Token": "unauthorizedToken",
|
||||||
|
"创建成功": "createSuccess", "请求参数错误": "badRequest", "对话不存在": "conversationNotFound",
|
||||||
|
"对话不存在或结果不存在": "conversationOrResultNotFound", "请求参数错误(如task为空)": "badRequestTaskEmpty",
|
||||||
|
"请求参数错误或分组名称已存在": "badRequestGroupNameExists", "分组不存在": "groupNotFound",
|
||||||
|
"请求参数错误(如配置格式不正确、缺少必需字段等)": "badRequestConfig",
|
||||||
|
"请求参数错误(如query为空)": "badRequestQueryEmpty", "方法不允许(仅支持POST请求)": "methodNotAllowed",
|
||||||
|
"登录成功": "loginSuccess", "密码错误": "invalidPassword", "登出成功": "logoutSuccess",
|
||||||
|
"密码修改成功": "passwordChanged", "Token有效": "tokenValid", "Token无效或已过期": "tokenInvalid",
|
||||||
|
"对话创建成功": "conversationCreated", "服务器内部错误": "internalError", "更新成功": "updateSuccess",
|
||||||
|
"删除成功": "deleteSuccess", "队列不存在": "queueNotFound", "启动成功": "startSuccess",
|
||||||
|
"暂停成功": "pauseSuccess", "添加成功": "addSuccess",
|
||||||
|
"任务不存在": "taskNotFound", "对话或分组不存在": "conversationOrGroupNotFound",
|
||||||
|
"取消请求已提交": "cancelSubmitted", "未找到正在执行的任务": "noRunningTask",
|
||||||
|
"消息发送成功,返回AI回复": "messageSent", "流式响应(Server-Sent Events)": "streamResponse",
|
||||||
|
}
|
||||||
|
|
||||||
|
// enrichSpecWithI18nKeys 在 spec 的每个 operation 上写入 x-i18n-tags、x-i18n-summary,
|
||||||
|
// 在每个 response 上写入 x-i18n-description,供前端按 key 做国际化。
|
||||||
|
func enrichSpecWithI18nKeys(spec map[string]interface{}) {
|
||||||
|
paths, _ := spec["paths"].(map[string]interface{})
|
||||||
|
if paths == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, pathItem := range paths {
|
||||||
|
pm, _ := pathItem.(map[string]interface{})
|
||||||
|
if pm == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, method := range []string{"get", "post", "put", "delete", "patch"} {
|
||||||
|
opVal, ok := pm[method]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
op, _ := opVal.(map[string]interface{})
|
||||||
|
if op == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// x-i18n-tags: 与 tags 一一对应的 i18n 键数组(spec 中 tags 为 []string)
|
||||||
|
switch tags := op["tags"].(type) {
|
||||||
|
case []string:
|
||||||
|
if len(tags) > 0 {
|
||||||
|
keys := make([]string, 0, len(tags))
|
||||||
|
for _, s := range tags {
|
||||||
|
if k := apiDocI18nTagToKey[s]; k != "" {
|
||||||
|
keys = append(keys, k)
|
||||||
|
} else {
|
||||||
|
keys = append(keys, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
op["x-i18n-tags"] = keys
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
if len(tags) > 0 {
|
||||||
|
keys := make([]interface{}, 0, len(tags))
|
||||||
|
for _, t := range tags {
|
||||||
|
if s, ok := t.(string); ok {
|
||||||
|
if k := apiDocI18nTagToKey[s]; k != "" {
|
||||||
|
keys = append(keys, k)
|
||||||
|
} else {
|
||||||
|
keys = append(keys, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(keys) > 0 {
|
||||||
|
op["x-i18n-tags"] = keys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// x-i18n-summary
|
||||||
|
if summary, _ := op["summary"].(string); summary != "" {
|
||||||
|
if k := apiDocI18nSummaryToKey[summary]; k != "" {
|
||||||
|
op["x-i18n-summary"] = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// responses -> 每个 status -> x-i18n-description
|
||||||
|
if respMap, _ := op["responses"].(map[string]interface{}); respMap != nil {
|
||||||
|
for _, rv := range respMap {
|
||||||
|
if r, _ := rv.(map[string]interface{}); r != nil {
|
||||||
|
if desc, _ := r["description"].(string); desc != "" {
|
||||||
|
if k := apiDocI18nResponseDescToKey[desc]; k != "" {
|
||||||
|
r["x-i18n-description"] = k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+261
-2
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"lang": {
|
||||||
|
"zhCN": "中文",
|
||||||
|
"enUS": "English"
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
@@ -132,10 +136,51 @@
|
|||||||
"executeFailed": "Execution failed",
|
"executeFailed": "Execution failed",
|
||||||
"callOpenAIFailed": "Call OpenAI failed",
|
"callOpenAIFailed": "Call OpenAI failed",
|
||||||
"systemReadyMessage": "System is ready. Please enter your test requirements, and the system will automatically perform the corresponding security tests.",
|
"systemReadyMessage": "System is ready. Please enter your test requirements, and the system will automatically perform the corresponding security tests.",
|
||||||
"addNewGroup": "+ New group"
|
"addNewGroup": "+ New group",
|
||||||
|
"callNumber": "Call #{{n}}",
|
||||||
|
"iterationRound": "Iteration {{n}}",
|
||||||
|
"aiThinking": "AI thinking",
|
||||||
|
"toolCallsDetected": "Detected {{count}} tool call(s)",
|
||||||
|
"callTool": "Call tool: {{name}} ({{index}}/{{total}})",
|
||||||
|
"toolExecComplete": "Tool {{name}} completed",
|
||||||
|
"toolExecFailed": "Tool {{name}} failed",
|
||||||
|
"knowledgeRetrieval": "Knowledge retrieval",
|
||||||
|
"knowledgeRetrievalTag": "Knowledge retrieval",
|
||||||
|
"error": "Error",
|
||||||
|
"taskCancelled": "Task cancelled",
|
||||||
|
"unknownTool": "Unknown tool",
|
||||||
|
"noDescription": "No description",
|
||||||
|
"noResponseData": "No response data",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"loadFailed": "Load failed: {{message}}",
|
||||||
|
"noAttackChainData": "No attack chain data",
|
||||||
|
"copyFailedManual": "Copy failed, please select and copy manually",
|
||||||
|
"searching": "Searching...",
|
||||||
|
"loadFailedRetry": "Load failed, please retry",
|
||||||
|
"dataFormatError": "Data format error",
|
||||||
|
"progressInProgress": "Penetration test in progress..."
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"callingAI": "Calling AI model...",
|
||||||
|
"callingTool": "Calling tool: {{name}}",
|
||||||
|
"lastIterSummary": "Last iteration: generating summary and next steps...",
|
||||||
|
"summaryDone": "Summary complete",
|
||||||
|
"generatingFinalReply": "Generating final reply...",
|
||||||
|
"maxIterSummary": "Max iterations reached, generating summary..."
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"params": "Parameters:",
|
||||||
|
"executionResult": "Execution result:",
|
||||||
|
"executionId": "Execution ID:",
|
||||||
|
"noResult": "No result",
|
||||||
|
"running": "Running...",
|
||||||
|
"completed": "Completed",
|
||||||
|
"execFailed": "Execution failed"
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"title": "Task management",
|
"title": "Task management",
|
||||||
|
"stopTask": "Stop task",
|
||||||
|
"collapseDetail": "Collapse details",
|
||||||
"newTask": "New task",
|
"newTask": "New task",
|
||||||
"autoRefresh": "Auto refresh",
|
"autoRefresh": "Auto refresh",
|
||||||
"historyHint": "Tip: Completed task history available. Check \"Show history\" to view.",
|
"historyHint": "Tip: Completed task history available. Check \"Show history\" to view.",
|
||||||
@@ -500,7 +545,214 @@
|
|||||||
"loadCallStatsError": "Failed to load call statistics"
|
"loadCallStatsError": "Failed to load call statistics"
|
||||||
},
|
},
|
||||||
"apiDocs": {
|
"apiDocs": {
|
||||||
"curlCopied": "curl command copied to clipboard!"
|
"pageTitle": "API Docs - CyberStrikeAI",
|
||||||
|
"title": "API Docs",
|
||||||
|
"subtitle": "CyberStrikeAI platform API documentation with online testing",
|
||||||
|
"authTitle": "API Authentication",
|
||||||
|
"authAllNeedToken": "All API endpoints require Token authentication.",
|
||||||
|
"authGetToken": "1. Get Token:",
|
||||||
|
"authGetTokenDesc": "After logging in on the frontend, the Token is saved automatically. You can also get it via:",
|
||||||
|
"authUseToken": "2. Use Token:",
|
||||||
|
"authUseTokenDesc": "Add the Authorization header:",
|
||||||
|
"authTip": "💡 This page will use your logged-in Token automatically; no need to fill it manually.",
|
||||||
|
"tokenDetected": "✓ Token detected - You can test API endpoints directly",
|
||||||
|
"tokenNotDetected": "⚠ No Token detected - Please log in on the frontend first, then refresh this page. When testing, add Authorization: Bearer token in the request header",
|
||||||
|
"sidebarGroupTitle": "API Groups",
|
||||||
|
"allApis": "All APIs",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"loadingDesc": "Loading API documentation",
|
||||||
|
"errorLoginRequired": "Login required to view API docs. Please log in on the frontend first, then refresh this page.",
|
||||||
|
"errorLoadSpec": "Failed to load API spec: ",
|
||||||
|
"errorLoadFailed": "Failed to load API docs: ",
|
||||||
|
"errorSpecInvalid": "Invalid API spec format",
|
||||||
|
"loadFailed": "Load failed",
|
||||||
|
"backToLogin": "Back to login",
|
||||||
|
"noApis": "No APIs",
|
||||||
|
"noEndpointsInGroup": "No API endpoints in this group",
|
||||||
|
"sectionDescription": "Description",
|
||||||
|
"viewDetailDesc": "View details",
|
||||||
|
"hideDetailDesc": "Hide details",
|
||||||
|
"noDescription": "No description",
|
||||||
|
"sectionParams": "Parameters",
|
||||||
|
"paramName": "Parameter",
|
||||||
|
"type": "Type",
|
||||||
|
"description": "Description",
|
||||||
|
"required": "Required",
|
||||||
|
"optional": "Optional",
|
||||||
|
"sectionRequestBody": "Request body",
|
||||||
|
"example": "Example",
|
||||||
|
"exampleJson": "Example JSON:",
|
||||||
|
"sectionResponse": "Response",
|
||||||
|
"testSection": "Test",
|
||||||
|
"requestBodyJson": "Request body (JSON)",
|
||||||
|
"queryParams": "Query parameters:",
|
||||||
|
"sendRequest": "Send request",
|
||||||
|
"copyCurl": "Copy cURL",
|
||||||
|
"clearResult": "Clear result",
|
||||||
|
"copyCurlTitle": "Copy cURL command",
|
||||||
|
"clearResultTitle": "Clear test result",
|
||||||
|
"sendingRequest": "Sending request...",
|
||||||
|
"errorPathParamRequired": "Path parameter {{name}} is required",
|
||||||
|
"errorQueryParamRequired": "Query parameter {{name}} is required",
|
||||||
|
"errorTokenRequired": "No Token detected. Please log in on the frontend and refresh, or add Authorization: Bearer your_token in the request header",
|
||||||
|
"errorJsonInvalid": "Invalid request body JSON: ",
|
||||||
|
"requestFailed": "Request failed: ",
|
||||||
|
"copied": "Copied",
|
||||||
|
"curlCopied": "curl command copied to clipboard!",
|
||||||
|
"copyFailedManual": "Copy failed, please copy manually:\n\n",
|
||||||
|
"curlGenFailed": "Failed to generate cURL command: ",
|
||||||
|
"requestBodyPlaceholder": "Enter request body in JSON format",
|
||||||
|
"tags": {
|
||||||
|
"auth": "Authentication",
|
||||||
|
"conversationManagement": "Conversation Management",
|
||||||
|
"conversationInteraction": "Conversation Interaction",
|
||||||
|
"batchTasks": "Batch Tasks",
|
||||||
|
"conversationGroups": "Conversation Groups",
|
||||||
|
"vulnerabilityManagement": "Vulnerability Management",
|
||||||
|
"roleManagement": "Role Management",
|
||||||
|
"skillsManagement": "Skills Management",
|
||||||
|
"monitoring": "Monitoring",
|
||||||
|
"configManagement": "Configuration Management",
|
||||||
|
"externalMCPManagement": "External MCP Management",
|
||||||
|
"attackChain": "Attack Chain",
|
||||||
|
"knowledgeBase": "Knowledge Base",
|
||||||
|
"mcp": "MCP"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"login": "User login",
|
||||||
|
"logout": "User logout",
|
||||||
|
"changePassword": "Change password",
|
||||||
|
"validateToken": "Validate Token",
|
||||||
|
"createConversation": "Create conversation",
|
||||||
|
"listConversations": "List conversations",
|
||||||
|
"getConversationDetail": "Get conversation detail",
|
||||||
|
"updateConversation": "Update conversation",
|
||||||
|
"deleteConversation": "Delete conversation",
|
||||||
|
"getConversationResult": "Get conversation result",
|
||||||
|
"sendMessageNonStream": "Send message and get AI reply (non-stream)",
|
||||||
|
"sendMessageStream": "Send message and get AI reply (stream)",
|
||||||
|
"cancelTask": "Cancel task",
|
||||||
|
"listRunningTasks": "List running tasks",
|
||||||
|
"listCompletedTasks": "List completed tasks",
|
||||||
|
"createBatchQueue": "Create batch task queue",
|
||||||
|
"listBatchQueues": "List batch task queues",
|
||||||
|
"getBatchQueue": "Get batch task queue",
|
||||||
|
"deleteBatchQueue": "Delete batch task queue",
|
||||||
|
"startBatchQueue": "Start batch task queue",
|
||||||
|
"pauseBatchQueue": "Pause batch task queue",
|
||||||
|
"addTaskToQueue": "Add task to queue",
|
||||||
|
"sqlInjectionScan": "SQL injection scan",
|
||||||
|
"portScan": "Port scan",
|
||||||
|
"updateBatchTask": "Update batch task",
|
||||||
|
"deleteBatchTask": "Delete batch task",
|
||||||
|
"createGroup": "Create group",
|
||||||
|
"listGroups": "List groups",
|
||||||
|
"getGroup": "Get group",
|
||||||
|
"updateGroup": "Update group",
|
||||||
|
"deleteGroup": "Delete group",
|
||||||
|
"getGroupConversations": "Get conversations in group",
|
||||||
|
"addConversationToGroup": "Add conversation to group",
|
||||||
|
"removeConversationFromGroup": "Remove conversation from group",
|
||||||
|
"listVulnerabilities": "List vulnerabilities",
|
||||||
|
"createVulnerability": "Create vulnerability",
|
||||||
|
"getVulnerabilityStats": "Get vulnerability statistics",
|
||||||
|
"getVulnerability": "Get vulnerability",
|
||||||
|
"updateVulnerability": "Update vulnerability",
|
||||||
|
"deleteVulnerability": "Delete vulnerability",
|
||||||
|
"listRoles": "List roles",
|
||||||
|
"createRole": "Create role",
|
||||||
|
"getRole": "Get role",
|
||||||
|
"updateRole": "Update role",
|
||||||
|
"deleteRole": "Delete role",
|
||||||
|
"getAvailableSkills": "Get available Skills list",
|
||||||
|
"listSkills": "List Skills",
|
||||||
|
"createSkill": "Create Skill",
|
||||||
|
"getSkillStats": "Get Skill statistics",
|
||||||
|
"clearSkillStats": "Clear Skill statistics",
|
||||||
|
"getSkill": "Get Skill",
|
||||||
|
"updateSkill": "Update Skill",
|
||||||
|
"deleteSkill": "Delete Skill",
|
||||||
|
"getBoundRoles": "Get bound roles",
|
||||||
|
"clearSkillStatsAlt": "Clear Skill statistics",
|
||||||
|
"getMonitorInfo": "Get monitoring info",
|
||||||
|
"getExecutionRecords": "Get execution records",
|
||||||
|
"deleteExecutionRecord": "Delete execution record",
|
||||||
|
"batchDeleteExecutionRecords": "Batch delete execution records",
|
||||||
|
"getStats": "Get statistics",
|
||||||
|
"getConfig": "Get configuration",
|
||||||
|
"updateConfig": "Update configuration",
|
||||||
|
"getToolConfig": "Get tool configuration",
|
||||||
|
"applyConfig": "Apply configuration",
|
||||||
|
"listExternalMCP": "List external MCP",
|
||||||
|
"getExternalMCPStats": "Get external MCP statistics",
|
||||||
|
"getExternalMCP": "Get external MCP",
|
||||||
|
"addOrUpdateExternalMCP": "Add or update external MCP",
|
||||||
|
"stdioModeConfig": "stdio mode config",
|
||||||
|
"sseModeConfig": "SSE mode config",
|
||||||
|
"deleteExternalMCP": "Delete external MCP",
|
||||||
|
"startExternalMCP": "Start external MCP",
|
||||||
|
"stopExternalMCP": "Stop external MCP",
|
||||||
|
"getAttackChain": "Get attack chain",
|
||||||
|
"regenerateAttackChain": "Regenerate attack chain",
|
||||||
|
"pinConversation": "Pin conversation",
|
||||||
|
"pinGroup": "Pin group",
|
||||||
|
"pinGroupConversation": "Pin conversation in group",
|
||||||
|
"getCategories": "Get categories",
|
||||||
|
"listKnowledgeItems": "List knowledge items",
|
||||||
|
"createKnowledgeItem": "Create knowledge item",
|
||||||
|
"getKnowledgeItem": "Get knowledge item",
|
||||||
|
"updateKnowledgeItem": "Update knowledge item",
|
||||||
|
"deleteKnowledgeItem": "Delete knowledge item",
|
||||||
|
"getIndexStatus": "Get index status",
|
||||||
|
"rebuildIndex": "Rebuild index",
|
||||||
|
"scanKnowledgeBase": "Scan knowledge base",
|
||||||
|
"searchKnowledgeBase": "Search knowledge base",
|
||||||
|
"basicSearch": "Basic search",
|
||||||
|
"searchByRiskType": "Search by risk type",
|
||||||
|
"getRetrievalLogs": "Get retrieval logs",
|
||||||
|
"deleteRetrievalLog": "Delete retrieval log",
|
||||||
|
"mcpEndpoint": "MCP endpoint",
|
||||||
|
"listAllTools": "List all tools",
|
||||||
|
"invokeTool": "Invoke tool",
|
||||||
|
"initConnection": "Initialize connection",
|
||||||
|
"successResponse": "Success response",
|
||||||
|
"errorResponse": "Error response"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"getSuccess": "Success",
|
||||||
|
"unauthorized": "Unauthorized",
|
||||||
|
"unauthorizedToken": "Unauthorized, valid Token required",
|
||||||
|
"createSuccess": "Created successfully",
|
||||||
|
"badRequest": "Bad request",
|
||||||
|
"conversationNotFound": "Conversation not found",
|
||||||
|
"conversationOrResultNotFound": "Conversation or result not found",
|
||||||
|
"badRequestTaskEmpty": "Bad request (e.g. task is empty)",
|
||||||
|
"badRequestGroupNameExists": "Bad request or group name already exists",
|
||||||
|
"groupNotFound": "Group not found",
|
||||||
|
"badRequestConfig": "Bad request (e.g. invalid config or missing required fields)",
|
||||||
|
"badRequestQueryEmpty": "Bad request (e.g. query is empty)",
|
||||||
|
"methodNotAllowed": "Method not allowed (POST only)",
|
||||||
|
"loginSuccess": "Login successful",
|
||||||
|
"invalidPassword": "Invalid password",
|
||||||
|
"logoutSuccess": "Logout successful",
|
||||||
|
"passwordChanged": "Password changed successfully",
|
||||||
|
"tokenValid": "Token valid",
|
||||||
|
"tokenInvalid": "Token invalid or expired",
|
||||||
|
"conversationCreated": "Conversation created",
|
||||||
|
"internalError": "Internal server error",
|
||||||
|
"updateSuccess": "Updated successfully",
|
||||||
|
"deleteSuccess": "Deleted successfully",
|
||||||
|
"queueNotFound": "Queue not found",
|
||||||
|
"startSuccess": "Started successfully",
|
||||||
|
"pauseSuccess": "Paused successfully",
|
||||||
|
"addSuccess": "Added successfully",
|
||||||
|
"taskNotFound": "Task not found",
|
||||||
|
"conversationOrGroupNotFound": "Conversation or group not found",
|
||||||
|
"cancelSubmitted": "Cancel request submitted",
|
||||||
|
"noRunningTask": "No running task found",
|
||||||
|
"messageSent": "Message sent, AI reply returned",
|
||||||
|
"streamResponse": "Stream response (Server-Sent Events)"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"chatGroup": {
|
"chatGroup": {
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
@@ -799,6 +1051,13 @@
|
|||||||
"execInfo": "Execution info",
|
"execInfo": "Execution info",
|
||||||
"tool": "Tool",
|
"tool": "Tool",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
|
"statusPending": "Pending",
|
||||||
|
"statusRunning": "Running",
|
||||||
|
"statusCompleted": "Completed",
|
||||||
|
"statusFailed": "Failed",
|
||||||
|
"unknown": "Unknown",
|
||||||
|
"getDetailFailed": "Failed to get details",
|
||||||
|
"execSuccessNoContent": "Execution succeeded with no displayable content.",
|
||||||
"time": "Time",
|
"time": "Time",
|
||||||
"executionId": "Execution ID",
|
"executionId": "Execution ID",
|
||||||
"requestParams": "Request params",
|
"requestParams": "Request params",
|
||||||
|
|||||||
+261
-2
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"lang": {
|
||||||
|
"zhCN": "中文",
|
||||||
|
"enUS": "English"
|
||||||
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"ok": "确定",
|
"ok": "确定",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
@@ -132,10 +136,51 @@
|
|||||||
"executeFailed": "执行失败",
|
"executeFailed": "执行失败",
|
||||||
"callOpenAIFailed": "调用OpenAI失败",
|
"callOpenAIFailed": "调用OpenAI失败",
|
||||||
"systemReadyMessage": "系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。",
|
"systemReadyMessage": "系统已就绪。请输入您的测试需求,系统将自动执行相应的安全测试。",
|
||||||
"addNewGroup": "+ 新增分组"
|
"addNewGroup": "+ 新增分组",
|
||||||
|
"callNumber": "调用 #{{n}}",
|
||||||
|
"iterationRound": "第 {{n}} 轮迭代",
|
||||||
|
"aiThinking": "AI思考",
|
||||||
|
"toolCallsDetected": "检测到 {{count}} 个工具调用",
|
||||||
|
"callTool": "调用工具: {{name}} ({{index}}/{{total}})",
|
||||||
|
"toolExecComplete": "工具 {{name}} 执行完成",
|
||||||
|
"toolExecFailed": "工具 {{name}} 执行失败",
|
||||||
|
"knowledgeRetrieval": "知识检索",
|
||||||
|
"knowledgeRetrievalTag": "知识检索",
|
||||||
|
"error": "错误",
|
||||||
|
"taskCancelled": "任务已取消",
|
||||||
|
"unknownTool": "未知工具",
|
||||||
|
"noDescription": "暂无描述",
|
||||||
|
"noResponseData": "暂无响应数据",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"loadFailed": "加载失败: {{message}}",
|
||||||
|
"noAttackChainData": "暂无攻击链数据",
|
||||||
|
"copyFailedManual": "复制失败,请手动选择内容复制",
|
||||||
|
"searching": "搜索中...",
|
||||||
|
"loadFailedRetry": "加载失败,请重试",
|
||||||
|
"dataFormatError": "数据格式错误",
|
||||||
|
"progressInProgress": "渗透测试进行中..."
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"callingAI": "正在调用AI模型...",
|
||||||
|
"callingTool": "正在调用工具: {{name}}",
|
||||||
|
"lastIterSummary": "最后一次迭代:正在生成总结和下一步计划...",
|
||||||
|
"summaryDone": "总结生成完成",
|
||||||
|
"generatingFinalReply": "正在生成最终回复...",
|
||||||
|
"maxIterSummary": "达到最大迭代次数,正在生成总结..."
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"params": "参数:",
|
||||||
|
"executionResult": "执行结果:",
|
||||||
|
"executionId": "执行ID:",
|
||||||
|
"noResult": "无结果",
|
||||||
|
"running": "执行中...",
|
||||||
|
"completed": "已完成",
|
||||||
|
"execFailed": "执行失败"
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"title": "任务管理",
|
"title": "任务管理",
|
||||||
|
"stopTask": "停止任务",
|
||||||
|
"collapseDetail": "收起详情",
|
||||||
"newTask": "新建任务",
|
"newTask": "新建任务",
|
||||||
"autoRefresh": "自动刷新",
|
"autoRefresh": "自动刷新",
|
||||||
"historyHint": "提示:有已完成的任务历史,请勾选\"显示历史记录\"查看",
|
"historyHint": "提示:有已完成的任务历史,请勾选\"显示历史记录\"查看",
|
||||||
@@ -500,7 +545,214 @@
|
|||||||
"loadCallStatsError": "无法加载调用统计"
|
"loadCallStatsError": "无法加载调用统计"
|
||||||
},
|
},
|
||||||
"apiDocs": {
|
"apiDocs": {
|
||||||
"curlCopied": "curl命令已复制到剪贴板!"
|
"pageTitle": "API 文档 - CyberStrikeAI",
|
||||||
|
"title": "API 文档",
|
||||||
|
"subtitle": "CyberStrikeAI 平台 API 接口文档,支持在线测试",
|
||||||
|
"authTitle": "API 认证说明",
|
||||||
|
"authAllNeedToken": "所有 API 接口都需要 Token 认证。",
|
||||||
|
"authGetToken": "1. 获取 Token:",
|
||||||
|
"authGetTokenDesc": "在前端页面登录后,Token 会自动保存。您也可以通过以下方式获取:",
|
||||||
|
"authUseToken": "2. 使用 Token:",
|
||||||
|
"authUseTokenDesc": "在请求头中添加 Authorization 字段:",
|
||||||
|
"authTip": "💡 提示:本页面会自动使用您已登录的 Token,无需手动填写。",
|
||||||
|
"tokenDetected": "✓ 已检测到 Token - 您可以直接测试 API 接口",
|
||||||
|
"tokenNotDetected": "⚠ 未检测到 Token - 请先在前端页面登录,然后刷新此页面。测试接口时需要在请求头中添加 Authorization: Bearer token",
|
||||||
|
"sidebarGroupTitle": "API 分组",
|
||||||
|
"allApis": "全部接口",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"loadingDesc": "正在加载 API 文档",
|
||||||
|
"errorLoginRequired": "需要登录才能查看API文档。请先在前端页面登录,然后刷新此页面。",
|
||||||
|
"errorLoadSpec": "加载API规范失败: ",
|
||||||
|
"errorLoadFailed": "加载API文档失败: ",
|
||||||
|
"errorSpecInvalid": "API规范格式错误",
|
||||||
|
"loadFailed": "加载失败",
|
||||||
|
"backToLogin": "返回首页登录",
|
||||||
|
"noApis": "暂无API",
|
||||||
|
"noEndpointsInGroup": "该分组下没有API端点",
|
||||||
|
"sectionDescription": "描述",
|
||||||
|
"viewDetailDesc": "查看详细说明",
|
||||||
|
"hideDetailDesc": "隐藏详细说明",
|
||||||
|
"noDescription": "无描述",
|
||||||
|
"sectionParams": "参数",
|
||||||
|
"paramName": "参数名",
|
||||||
|
"type": "类型",
|
||||||
|
"description": "描述",
|
||||||
|
"required": "必需",
|
||||||
|
"optional": "可选",
|
||||||
|
"sectionRequestBody": "请求体",
|
||||||
|
"example": "示例",
|
||||||
|
"exampleJson": "示例JSON:",
|
||||||
|
"sectionResponse": "响应",
|
||||||
|
"testSection": "测试接口",
|
||||||
|
"requestBodyJson": "请求体 (JSON)",
|
||||||
|
"queryParams": "查询参数:",
|
||||||
|
"sendRequest": "发送请求",
|
||||||
|
"copyCurl": "复制curl",
|
||||||
|
"clearResult": "清除结果",
|
||||||
|
"copyCurlTitle": "复制curl命令",
|
||||||
|
"clearResultTitle": "清除测试结果",
|
||||||
|
"sendingRequest": "发送请求中...",
|
||||||
|
"errorPathParamRequired": "路径参数 {{name}} 不能为空",
|
||||||
|
"errorQueryParamRequired": "查询参数 {{name}} 不能为空",
|
||||||
|
"errorTokenRequired": "未检测到 Token。请先在前端页面登录,然后刷新此页面。或者手动在请求头中添加 Authorization: Bearer your_token",
|
||||||
|
"errorJsonInvalid": "请求体JSON格式错误: ",
|
||||||
|
"requestFailed": "请求失败: ",
|
||||||
|
"copied": "已复制",
|
||||||
|
"curlCopied": "curl命令已复制到剪贴板!",
|
||||||
|
"copyFailedManual": "复制失败,请手动复制:\n\n",
|
||||||
|
"curlGenFailed": "生成curl命令失败: ",
|
||||||
|
"requestBodyPlaceholder": "请输入JSON格式的请求体",
|
||||||
|
"tags": {
|
||||||
|
"auth": "认证",
|
||||||
|
"conversationManagement": "对话管理",
|
||||||
|
"conversationInteraction": "对话交互",
|
||||||
|
"batchTasks": "批量任务",
|
||||||
|
"conversationGroups": "对话分组",
|
||||||
|
"vulnerabilityManagement": "漏洞管理",
|
||||||
|
"roleManagement": "角色管理",
|
||||||
|
"skillsManagement": "Skills管理",
|
||||||
|
"monitoring": "监控",
|
||||||
|
"configManagement": "配置管理",
|
||||||
|
"externalMCPManagement": "外部MCP管理",
|
||||||
|
"attackChain": "攻击链",
|
||||||
|
"knowledgeBase": "知识库",
|
||||||
|
"mcp": "MCP"
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"login": "用户登录",
|
||||||
|
"logout": "用户登出",
|
||||||
|
"changePassword": "修改密码",
|
||||||
|
"validateToken": "验证Token",
|
||||||
|
"createConversation": "创建对话",
|
||||||
|
"listConversations": "列出对话",
|
||||||
|
"getConversationDetail": "查看对话详情",
|
||||||
|
"updateConversation": "更新对话",
|
||||||
|
"deleteConversation": "删除对话",
|
||||||
|
"getConversationResult": "获取对话结果",
|
||||||
|
"sendMessageNonStream": "发送消息并获取AI回复(非流式)",
|
||||||
|
"sendMessageStream": "发送消息并获取AI回复(流式)",
|
||||||
|
"cancelTask": "取消任务",
|
||||||
|
"listRunningTasks": "列出运行中的任务",
|
||||||
|
"listCompletedTasks": "列出已完成的任务",
|
||||||
|
"createBatchQueue": "创建批量任务队列",
|
||||||
|
"listBatchQueues": "列出批量任务队列",
|
||||||
|
"getBatchQueue": "获取批量任务队列",
|
||||||
|
"deleteBatchQueue": "删除批量任务队列",
|
||||||
|
"startBatchQueue": "启动批量任务队列",
|
||||||
|
"pauseBatchQueue": "暂停批量任务队列",
|
||||||
|
"addTaskToQueue": "添加任务到队列",
|
||||||
|
"sqlInjectionScan": "SQL注入扫描",
|
||||||
|
"portScan": "端口扫描",
|
||||||
|
"updateBatchTask": "更新批量任务",
|
||||||
|
"deleteBatchTask": "删除批量任务",
|
||||||
|
"createGroup": "创建分组",
|
||||||
|
"listGroups": "列出分组",
|
||||||
|
"getGroup": "获取分组",
|
||||||
|
"updateGroup": "更新分组",
|
||||||
|
"deleteGroup": "删除分组",
|
||||||
|
"getGroupConversations": "获取分组中的对话",
|
||||||
|
"addConversationToGroup": "添加对话到分组",
|
||||||
|
"removeConversationFromGroup": "从分组移除对话",
|
||||||
|
"listVulnerabilities": "列出漏洞",
|
||||||
|
"createVulnerability": "创建漏洞",
|
||||||
|
"getVulnerabilityStats": "获取漏洞统计",
|
||||||
|
"getVulnerability": "获取漏洞",
|
||||||
|
"updateVulnerability": "更新漏洞",
|
||||||
|
"deleteVulnerability": "删除漏洞",
|
||||||
|
"listRoles": "列出角色",
|
||||||
|
"createRole": "创建角色",
|
||||||
|
"getRole": "获取角色",
|
||||||
|
"updateRole": "更新角色",
|
||||||
|
"deleteRole": "删除角色",
|
||||||
|
"getAvailableSkills": "获取可用Skills列表",
|
||||||
|
"listSkills": "列出Skills",
|
||||||
|
"createSkill": "创建Skill",
|
||||||
|
"getSkillStats": "获取Skill统计",
|
||||||
|
"clearSkillStats": "清空Skill统计",
|
||||||
|
"getSkill": "获取Skill",
|
||||||
|
"updateSkill": "更新Skill",
|
||||||
|
"deleteSkill": "删除Skill",
|
||||||
|
"getBoundRoles": "获取绑定角色",
|
||||||
|
"clearSkillStatsAlt": "清空Skill统计",
|
||||||
|
"getMonitorInfo": "获取监控信息",
|
||||||
|
"getExecutionRecords": "获取执行记录",
|
||||||
|
"deleteExecutionRecord": "删除执行记录",
|
||||||
|
"batchDeleteExecutionRecords": "批量删除执行记录",
|
||||||
|
"getStats": "获取统计信息",
|
||||||
|
"getConfig": "获取配置",
|
||||||
|
"updateConfig": "更新配置",
|
||||||
|
"getToolConfig": "获取工具配置",
|
||||||
|
"applyConfig": "应用配置",
|
||||||
|
"listExternalMCP": "列出外部MCP",
|
||||||
|
"getExternalMCPStats": "获取外部MCP统计",
|
||||||
|
"getExternalMCP": "获取外部MCP",
|
||||||
|
"addOrUpdateExternalMCP": "添加或更新外部MCP",
|
||||||
|
"stdioModeConfig": "stdio模式配置",
|
||||||
|
"sseModeConfig": "SSE模式配置",
|
||||||
|
"deleteExternalMCP": "删除外部MCP",
|
||||||
|
"startExternalMCP": "启动外部MCP",
|
||||||
|
"stopExternalMCP": "停止外部MCP",
|
||||||
|
"getAttackChain": "获取攻击链",
|
||||||
|
"regenerateAttackChain": "重新生成攻击链",
|
||||||
|
"pinConversation": "设置对话置顶",
|
||||||
|
"pinGroup": "设置分组置顶",
|
||||||
|
"pinGroupConversation": "设置分组中对话的置顶",
|
||||||
|
"getCategories": "获取分类",
|
||||||
|
"listKnowledgeItems": "列出知识项",
|
||||||
|
"createKnowledgeItem": "创建知识项",
|
||||||
|
"getKnowledgeItem": "获取知识项",
|
||||||
|
"updateKnowledgeItem": "更新知识项",
|
||||||
|
"deleteKnowledgeItem": "删除知识项",
|
||||||
|
"getIndexStatus": "获取索引状态",
|
||||||
|
"rebuildIndex": "重建索引",
|
||||||
|
"scanKnowledgeBase": "扫描知识库",
|
||||||
|
"searchKnowledgeBase": "搜索知识库",
|
||||||
|
"basicSearch": "基础搜索",
|
||||||
|
"searchByRiskType": "按风险类型搜索",
|
||||||
|
"getRetrievalLogs": "获取检索日志",
|
||||||
|
"deleteRetrievalLog": "删除检索日志",
|
||||||
|
"mcpEndpoint": "MCP端点",
|
||||||
|
"listAllTools": "列出所有工具",
|
||||||
|
"invokeTool": "调用工具",
|
||||||
|
"initConnection": "初始化连接",
|
||||||
|
"successResponse": "成功响应",
|
||||||
|
"errorResponse": "错误响应"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"getSuccess": "获取成功",
|
||||||
|
"unauthorized": "未授权",
|
||||||
|
"unauthorizedToken": "未授权,需要有效的Token",
|
||||||
|
"createSuccess": "创建成功",
|
||||||
|
"badRequest": "请求参数错误",
|
||||||
|
"conversationNotFound": "对话不存在",
|
||||||
|
"conversationOrResultNotFound": "对话不存在或结果不存在",
|
||||||
|
"badRequestTaskEmpty": "请求参数错误(如task为空)",
|
||||||
|
"badRequestGroupNameExists": "请求参数错误或分组名称已存在",
|
||||||
|
"groupNotFound": "分组不存在",
|
||||||
|
"badRequestConfig": "请求参数错误(如配置格式不正确、缺少必需字段等)",
|
||||||
|
"badRequestQueryEmpty": "请求参数错误(如query为空)",
|
||||||
|
"methodNotAllowed": "方法不允许(仅支持POST请求)",
|
||||||
|
"loginSuccess": "登录成功",
|
||||||
|
"invalidPassword": "密码错误",
|
||||||
|
"logoutSuccess": "登出成功",
|
||||||
|
"passwordChanged": "密码修改成功",
|
||||||
|
"tokenValid": "Token有效",
|
||||||
|
"tokenInvalid": "Token无效或已过期",
|
||||||
|
"conversationCreated": "对话创建成功",
|
||||||
|
"internalError": "服务器内部错误",
|
||||||
|
"updateSuccess": "更新成功",
|
||||||
|
"deleteSuccess": "删除成功",
|
||||||
|
"queueNotFound": "队列不存在",
|
||||||
|
"startSuccess": "启动成功",
|
||||||
|
"pauseSuccess": "暂停成功",
|
||||||
|
"addSuccess": "添加成功",
|
||||||
|
"taskNotFound": "任务不存在",
|
||||||
|
"conversationOrGroupNotFound": "对话或分组不存在",
|
||||||
|
"cancelSubmitted": "取消请求已提交",
|
||||||
|
"noRunningTask": "未找到正在执行的任务",
|
||||||
|
"messageSent": "消息发送成功,返回AI回复",
|
||||||
|
"streamResponse": "流式响应(Server-Sent Events)"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"chatGroup": {
|
"chatGroup": {
|
||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
@@ -799,6 +1051,13 @@
|
|||||||
"execInfo": "执行信息",
|
"execInfo": "执行信息",
|
||||||
"tool": "工具",
|
"tool": "工具",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
|
"statusPending": "等待中",
|
||||||
|
"statusRunning": "执行中",
|
||||||
|
"statusCompleted": "已完成",
|
||||||
|
"statusFailed": "失败",
|
||||||
|
"unknown": "未知",
|
||||||
|
"getDetailFailed": "获取详情失败",
|
||||||
|
"execSuccessNoContent": "执行成功,未返回可展示的文本内容。",
|
||||||
"time": "时间",
|
"time": "时间",
|
||||||
"executionId": "执行 ID",
|
"executionId": "执行 ID",
|
||||||
"requestParams": "请求参数",
|
"requestParams": "请求参数",
|
||||||
|
|||||||
+146
-61
@@ -3,13 +3,74 @@
|
|||||||
let apiSpec = null;
|
let apiSpec = null;
|
||||||
let currentToken = null;
|
let currentToken = null;
|
||||||
|
|
||||||
|
function _t(key, opts) {
|
||||||
|
return typeof window.t === 'function' ? window.t(key, opts) : key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForI18n() {
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
if (window.t) return resolve();
|
||||||
|
var n = 0;
|
||||||
|
var iv = setInterval(function () {
|
||||||
|
if (window.t) { clearInterval(iv); resolve(); return; }
|
||||||
|
n++;
|
||||||
|
if (n >= 100) { clearInterval(iv); resolve(); }
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 OpenAPI spec 的 x-i18n-tags 构建 tag -> i18n key 映射(方案 A:后端提供键)
|
||||||
|
var apiSpecTagToKey = {};
|
||||||
|
function buildApiSpecTagToKey() {
|
||||||
|
apiSpecTagToKey = {};
|
||||||
|
if (!apiSpec || !apiSpec.paths) return;
|
||||||
|
Object.keys(apiSpec.paths).forEach(function (path) {
|
||||||
|
var pathItem = apiSpec.paths[path];
|
||||||
|
if (!pathItem || typeof pathItem !== 'object') return;
|
||||||
|
['get', 'post', 'put', 'delete', 'patch'].forEach(function (method) {
|
||||||
|
var op = pathItem[method];
|
||||||
|
if (!op || !op.tags || !op['x-i18n-tags']) return;
|
||||||
|
var tags = op.tags;
|
||||||
|
var keys = op['x-i18n-tags'];
|
||||||
|
for (var i = 0; i < tags.length && i < keys.length; i++) {
|
||||||
|
apiSpecTagToKey[tags[i]] = typeof keys[i] === 'string' ? keys[i] : keys[i];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function translateApiDocTag(tag) {
|
||||||
|
if (!tag) return tag;
|
||||||
|
var key = apiSpecTagToKey[tag];
|
||||||
|
return key ? _t('apiDocs.tags.' + key) : tag;
|
||||||
|
}
|
||||||
|
function translateApiDocSummaryFromOp(op) {
|
||||||
|
var key = op && op['x-i18n-summary'];
|
||||||
|
if (key) return _t('apiDocs.summary.' + key);
|
||||||
|
return op && op.summary ? op.summary : '';
|
||||||
|
}
|
||||||
|
function translateApiDocResponseDescFromResp(resp) {
|
||||||
|
if (!resp) return '';
|
||||||
|
var key = resp['x-i18n-description'];
|
||||||
|
if (key) return _t('apiDocs.response.' + key);
|
||||||
|
return resp.description || '';
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
await waitForI18n();
|
||||||
await loadToken();
|
await loadToken();
|
||||||
await loadAPISpec();
|
await loadAPISpec();
|
||||||
if (apiSpec) {
|
if (apiSpec) {
|
||||||
renderAPIDocs();
|
renderAPIDocs();
|
||||||
}
|
}
|
||||||
|
document.addEventListener('languagechange', function () {
|
||||||
|
if (typeof window.applyTranslations === 'function') {
|
||||||
|
window.applyTranslations(document);
|
||||||
|
}
|
||||||
|
if (apiSpec) {
|
||||||
|
renderAPIDocs();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载token
|
// 加载token
|
||||||
@@ -43,22 +104,25 @@ async function loadAPISpec() {
|
|||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
showError('需要登录才能查看API文档。请先在前端页面登录,然后刷新此页面。');
|
showError(_t('apiDocs.errorLoginRequired'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error('加载API规范失败: ' + response.status);
|
throw new Error(_t('apiDocs.errorLoadSpec') + response.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
apiSpec = await response.json();
|
apiSpec = await response.json();
|
||||||
|
buildApiSpecTagToKey();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载API规范失败:', error);
|
console.error('加载API规范失败:', error);
|
||||||
showError('加载API文档失败: ' + error.message);
|
showError(_t('apiDocs.errorLoadFailed') + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示错误
|
// 显示错误
|
||||||
function showError(message) {
|
function showError(message) {
|
||||||
const main = document.getElementById('api-docs-main');
|
const main = document.getElementById('api-docs-main');
|
||||||
|
const loadFailed = _t('apiDocs.loadFailed');
|
||||||
|
const backToLogin = _t('apiDocs.backToLogin');
|
||||||
main.innerHTML = `
|
main.innerHTML = `
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
@@ -66,10 +130,10 @@ function showError(message) {
|
|||||||
<line x1="15" y1="9" x2="9" y2="15"/>
|
<line x1="15" y1="9" x2="9" y2="15"/>
|
||||||
<line x1="9" y1="9" x2="15" y2="15"/>
|
<line x1="9" y1="9" x2="15" y2="15"/>
|
||||||
</svg>
|
</svg>
|
||||||
<h3>加载失败</h3>
|
<h3>${escapeHtml(loadFailed)}</h3>
|
||||||
<p>${message}</p>
|
<p>${escapeHtml(message)}</p>
|
||||||
<div style="margin-top: 16px;">
|
<div style="margin-top: 16px;">
|
||||||
<a href="/" style="color: var(--accent-color); text-decoration: none;">返回首页登录</a>
|
<a href="/" style="color: var(--accent-color); text-decoration: none;">${escapeHtml(backToLogin)}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -78,7 +142,7 @@ function showError(message) {
|
|||||||
// 渲染API文档
|
// 渲染API文档
|
||||||
function renderAPIDocs() {
|
function renderAPIDocs() {
|
||||||
if (!apiSpec || !apiSpec.paths) {
|
if (!apiSpec || !apiSpec.paths) {
|
||||||
showError('API规范格式错误');
|
showError(_t('apiDocs.errorSpecInvalid'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +173,7 @@ function renderAuthInfo() {
|
|||||||
tokenStatus.style.display = 'block';
|
tokenStatus.style.display = 'block';
|
||||||
tokenStatus.style.background = 'rgba(255, 152, 0, 0.1)';
|
tokenStatus.style.background = 'rgba(255, 152, 0, 0.1)';
|
||||||
tokenStatus.style.borderLeftColor = '#ff9800';
|
tokenStatus.style.borderLeftColor = '#ff9800';
|
||||||
tokenStatus.innerHTML = '<p style="margin: 0; font-size: 0.8125rem; color: #ff9800;"><strong>⚠ 未检测到 Token</strong> - 请先在前端页面登录,然后刷新此页面。测试接口时需要在请求头中添加 Authorization: Bearer token</p>';
|
tokenStatus.innerHTML = '<p style="margin: 0; font-size: 0.8125rem; color: #ff9800;">' + escapeHtml(_t('apiDocs.tokenNotDetected')) + '</p>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,11 +191,14 @@ function renderSidebar() {
|
|||||||
|
|
||||||
const groupList = document.getElementById('api-group-list');
|
const groupList = document.getElementById('api-group-list');
|
||||||
const allGroups = Array.from(groups).sort();
|
const allGroups = Array.from(groups).sort();
|
||||||
|
while (groupList.children.length > 1) {
|
||||||
|
groupList.removeChild(groupList.lastChild);
|
||||||
|
}
|
||||||
allGroups.forEach(group => {
|
allGroups.forEach(group => {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.className = 'api-group-item';
|
li.className = 'api-group-item';
|
||||||
li.innerHTML = `<a href="#" class="api-group-link" data-group="${group}">${group}</a>`;
|
const groupLabel = translateApiDocTag(group);
|
||||||
|
li.innerHTML = `<a href="#" class="api-group-link" data-group="${escapeHtml(group)}">${escapeHtml(groupLabel)}</a>`;
|
||||||
groupList.appendChild(li);
|
groupList.appendChild(li);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -176,7 +243,7 @@ function renderEndpoints(filterGroup = null) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (endpoints.length === 0) {
|
if (endpoints.length === 0) {
|
||||||
main.innerHTML = '<div class="empty-state"><h3>暂无API</h3><p>该分组下没有API端点</p></div>';
|
main.innerHTML = '<div class="empty-state"><h3>' + escapeHtml(_t('apiDocs.noApis')) + '</h3><p>' + escapeHtml(_t('apiDocs.noEndpointsInGroup')) + '</p></div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,8 +259,8 @@ function createEndpointCard(endpoint) {
|
|||||||
|
|
||||||
const methodClass = endpoint.method.toLowerCase();
|
const methodClass = endpoint.method.toLowerCase();
|
||||||
const tags = endpoint.tags || [];
|
const tags = endpoint.tags || [];
|
||||||
const tagHtml = tags.map(tag => `<span class="api-tag">${tag}</span>`).join('');
|
const tagHtml = tags.map(tag => `<span class="api-tag">${escapeHtml(translateApiDocTag(tag))}</span>`).join('');
|
||||||
|
const summaryText = translateApiDocSummaryFromOp(endpoint);
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
<div class="api-endpoint-header">
|
<div class="api-endpoint-header">
|
||||||
<div class="api-endpoint-title">
|
<div class="api-endpoint-title">
|
||||||
@@ -204,21 +271,21 @@ function createEndpointCard(endpoint) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="api-endpoint-body">
|
<div class="api-endpoint-body">
|
||||||
<div class="api-section">
|
<div class="api-section">
|
||||||
<div class="api-section-title">描述</div>
|
<div class="api-section-title">${escapeHtml(_t('apiDocs.sectionDescription'))}</div>
|
||||||
${endpoint.summary ? `<div class="api-description" style="font-weight: 500; margin-bottom: 8px; color: var(--text-primary);">${escapeHtml(endpoint.summary)}</div>` : ''}
|
${summaryText ? `<div class="api-description" style="font-weight: 500; margin-bottom: 8px; color: var(--text-primary);">${escapeHtml(summaryText)}</div>` : ''}
|
||||||
${endpoint.description ? `
|
${endpoint.description ? `
|
||||||
<div class="api-description-toggle">
|
<div class="api-description-toggle">
|
||||||
<button class="description-toggle-btn" onclick="toggleDescription(this)">
|
<button class="description-toggle-btn" onclick="toggleDescription(this)">
|
||||||
<svg class="description-toggle-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="description-toggle-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polyline points="6 9 12 15 18 9"/>
|
<polyline points="6 9 12 15 18 9"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>查看详细说明</span>
|
<span>${escapeHtml(_t('apiDocs.viewDetailDesc'))}</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="api-description-detail" style="display: none;">
|
<div class="api-description-detail" style="display: none;">
|
||||||
${formatDescription(endpoint.description)}
|
${formatDescription(endpoint.description)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
` : endpoint.summary ? '' : '<div class="api-description">无描述</div>'}
|
` : endpoint.summary ? '' : '<div class="api-description">' + escapeHtml(_t('apiDocs.noDescription')) + '</div>'}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${renderParameters(endpoint)}
|
${renderParameters(endpoint)}
|
||||||
@@ -236,8 +303,10 @@ function renderParameters(endpoint) {
|
|||||||
const params = endpoint.parameters || [];
|
const params = endpoint.parameters || [];
|
||||||
if (params.length === 0) return '';
|
if (params.length === 0) return '';
|
||||||
|
|
||||||
|
const requiredLabel = escapeHtml(_t('apiDocs.required'));
|
||||||
|
const optionalLabel = escapeHtml(_t('apiDocs.optional'));
|
||||||
const rows = params.map(param => {
|
const rows = params.map(param => {
|
||||||
const required = param.required ? '<span class="api-param-required">必需</span>' : '<span class="api-param-optional">可选</span>';
|
const required = param.required ? '<span class="api-param-required">' + requiredLabel + '</span>' : '<span class="api-param-optional">' + optionalLabel + '</span>';
|
||||||
// 处理描述文本,将换行符转换为<br>
|
// 处理描述文本,将换行符转换为<br>
|
||||||
let descriptionHtml = '-';
|
let descriptionHtml = '-';
|
||||||
if (param.description) {
|
if (param.description) {
|
||||||
@@ -255,17 +324,20 @@ function renderParameters(endpoint) {
|
|||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
|
const paramName = escapeHtml(_t('apiDocs.paramName'));
|
||||||
|
const typeLabel = escapeHtml(_t('apiDocs.type'));
|
||||||
|
const descLabel = escapeHtml(_t('apiDocs.description'));
|
||||||
return `
|
return `
|
||||||
<div class="api-section">
|
<div class="api-section">
|
||||||
<div class="api-section-title">参数</div>
|
<div class="api-section-title">${escapeHtml(_t('apiDocs.sectionParams'))}</div>
|
||||||
<div class="api-table-wrapper">
|
<div class="api-table-wrapper">
|
||||||
<table class="api-params-table">
|
<table class="api-params-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>参数名</th>
|
<th>${paramName}</th>
|
||||||
<th>类型</th>
|
<th>${typeLabel}</th>
|
||||||
<th>描述</th>
|
<th>${descLabel}</th>
|
||||||
<th>必需</th>
|
<th>${requiredLabel}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -297,11 +369,13 @@ function renderRequestBody(endpoint) {
|
|||||||
let paramsTable = '';
|
let paramsTable = '';
|
||||||
if (schema.properties) {
|
if (schema.properties) {
|
||||||
const requiredFields = schema.required || [];
|
const requiredFields = schema.required || [];
|
||||||
|
const reqLabel = escapeHtml(_t('apiDocs.required'));
|
||||||
|
const optLabel = escapeHtml(_t('apiDocs.optional'));
|
||||||
const rows = Object.keys(schema.properties).map(key => {
|
const rows = Object.keys(schema.properties).map(key => {
|
||||||
const prop = schema.properties[key];
|
const prop = schema.properties[key];
|
||||||
const required = requiredFields.includes(key)
|
const required = requiredFields.includes(key)
|
||||||
? '<span class="api-param-required">必需</span>'
|
? '<span class="api-param-required">' + reqLabel + '</span>'
|
||||||
: '<span class="api-param-optional">可选</span>';
|
: '<span class="api-param-optional">' + optLabel + '</span>';
|
||||||
|
|
||||||
// 处理嵌套类型
|
// 处理嵌套类型
|
||||||
let typeDisplay = prop.type || 'object';
|
let typeDisplay = prop.type || 'object';
|
||||||
@@ -338,16 +412,20 @@ function renderRequestBody(endpoint) {
|
|||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
if (rows) {
|
if (rows) {
|
||||||
|
const pName = escapeHtml(_t('apiDocs.paramName'));
|
||||||
|
const tLabel = escapeHtml(_t('apiDocs.type'));
|
||||||
|
const dLabel = escapeHtml(_t('apiDocs.description'));
|
||||||
|
const exLabel = escapeHtml(_t('apiDocs.example'));
|
||||||
paramsTable = `
|
paramsTable = `
|
||||||
<div class="api-table-wrapper" style="margin-top: 12px;">
|
<div class="api-table-wrapper" style="margin-top: 12px;">
|
||||||
<table class="api-params-table">
|
<table class="api-params-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>参数名</th>
|
<th>${pName}</th>
|
||||||
<th>类型</th>
|
<th>${tLabel}</th>
|
||||||
<th>描述</th>
|
<th>${dLabel}</th>
|
||||||
<th>必需</th>
|
<th>${reqLabel}</th>
|
||||||
<th>示例</th>
|
<th>${exLabel}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -389,12 +467,12 @@ function renderRequestBody(endpoint) {
|
|||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="api-section">
|
<div class="api-section">
|
||||||
<div class="api-section-title">请求体</div>
|
<div class="api-section-title">${escapeHtml(_t('apiDocs.sectionRequestBody'))}</div>
|
||||||
${endpoint.requestBody.description ? `<div class="api-description">${endpoint.requestBody.description}</div>` : ''}
|
${endpoint.requestBody.description ? `<div class="api-description">${endpoint.requestBody.description}</div>` : ''}
|
||||||
${paramsTable}
|
${paramsTable}
|
||||||
${example ? `
|
${example ? `
|
||||||
<div style="margin-top: 16px;">
|
<div style="margin-top: 16px;">
|
||||||
<div style="font-weight: 500; margin-bottom: 8px; color: var(--text-primary);">示例JSON:</div>
|
<div style="font-weight: 500; margin-bottom: 8px; color: var(--text-primary);">${escapeHtml(_t('apiDocs.exampleJson'))}</div>
|
||||||
<div class="api-response-example">
|
<div class="api-response-example">
|
||||||
<pre>${escapeHtml(example)}</pre>
|
<pre>${escapeHtml(example)}</pre>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,11 +492,11 @@ function renderResponses(endpoint) {
|
|||||||
if (schema.example) {
|
if (schema.example) {
|
||||||
example = JSON.stringify(schema.example, null, 2);
|
example = JSON.stringify(schema.example, null, 2);
|
||||||
}
|
}
|
||||||
|
const descText = translateApiDocResponseDescFromResp(response);
|
||||||
return `
|
return `
|
||||||
<div style="margin-bottom: 16px;">
|
<div style="margin-bottom: 16px;">
|
||||||
<strong style="color: ${status.startsWith('2') ? 'var(--success-color)' : status.startsWith('4') ? 'var(--error-color)' : 'var(--warning-color)'}">${status}</strong>
|
<strong style="color: ${status.startsWith('2') ? 'var(--success-color)' : status.startsWith('4') ? 'var(--error-color)' : 'var(--warning-color)'}">${status}</strong>
|
||||||
${response.description ? `<span style="color: var(--text-secondary); margin-left: 8px;">${response.description}</span>` : ''}
|
${descText ? `<span style="color: var(--text-secondary); margin-left: 8px;">${escapeHtml(descText)}</span>` : ''}
|
||||||
${example ? `
|
${example ? `
|
||||||
<div class="api-response-example" style="margin-top: 8px;">
|
<div class="api-response-example" style="margin-top: 8px;">
|
||||||
<pre>${escapeHtml(example)}</pre>
|
<pre>${escapeHtml(example)}</pre>
|
||||||
@@ -432,7 +510,7 @@ function renderResponses(endpoint) {
|
|||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="api-section">
|
<div class="api-section">
|
||||||
<div class="api-section-title">响应</div>
|
<div class="api-section-title">${escapeHtml(_t('apiDocs.sectionResponse'))}</div>
|
||||||
${responseItems}
|
${responseItems}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -462,8 +540,8 @@ function renderTestSection(endpoint) {
|
|||||||
const bodyInputId = `test-body-${escapeId(path)}-${method}`;
|
const bodyInputId = `test-body-${escapeId(path)}-${method}`;
|
||||||
bodyInput = `
|
bodyInput = `
|
||||||
<div class="api-test-input-group">
|
<div class="api-test-input-group">
|
||||||
<label>请求体 (JSON)</label>
|
<label>${escapeHtml(_t('apiDocs.requestBodyJson'))}</label>
|
||||||
<textarea id="${bodyInputId}" class="test-body-input" placeholder='请输入JSON格式的请求体'>${defaultBody}</textarea>
|
<textarea id="${bodyInputId}" class="test-body-input" placeholder='${escapeHtml(_t('apiDocs.requestBodyPlaceholder'))}'>${defaultBody}</textarea>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -491,7 +569,7 @@ function renderTestSection(endpoint) {
|
|||||||
const inputId = `test-query-${param.name}-${escapeId(path)}-${method}`;
|
const inputId = `test-query-${param.name}-${escapeId(path)}-${method}`;
|
||||||
const defaultValue = param.schema?.default !== undefined ? param.schema.default : '';
|
const defaultValue = param.schema?.default !== undefined ? param.schema.default : '';
|
||||||
const placeholder = param.description || param.name;
|
const placeholder = param.description || param.name;
|
||||||
const required = param.required ? '<span style="color: var(--error-color);">*</span>' : '<span style="color: var(--text-muted);">可选</span>';
|
const required = param.required ? '<span style="color: var(--error-color);">*</span>' : '<span style="color: var(--text-muted);">' + escapeHtml(_t('apiDocs.optional')) + '</span>';
|
||||||
return `
|
return `
|
||||||
<div class="api-test-input-group">
|
<div class="api-test-input-group">
|
||||||
<label>${param.name} ${required}</label>
|
<label>${param.name} ${required}</label>
|
||||||
@@ -505,33 +583,40 @@ function renderTestSection(endpoint) {
|
|||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testSectionTitle = escapeHtml(_t('apiDocs.testSection'));
|
||||||
|
const queryParamsTitle = escapeHtml(_t('apiDocs.queryParams'));
|
||||||
|
const sendRequestLabel = escapeHtml(_t('apiDocs.sendRequest'));
|
||||||
|
const copyCurlLabel = escapeHtml(_t('apiDocs.copyCurl'));
|
||||||
|
const clearResultLabel = escapeHtml(_t('apiDocs.clearResult'));
|
||||||
|
const copyCurlTitle = escapeHtml(_t('apiDocs.copyCurlTitle'));
|
||||||
|
const clearResultTitle = escapeHtml(_t('apiDocs.clearResultTitle'));
|
||||||
return `
|
return `
|
||||||
<div class="api-test-section">
|
<div class="api-test-section">
|
||||||
<div class="api-section-title">测试接口</div>
|
<div class="api-section-title">${testSectionTitle}</div>
|
||||||
<div class="api-test-form">
|
<div class="api-test-form">
|
||||||
${pathParamsInput}
|
${pathParamsInput}
|
||||||
${queryParamsInput ? `<div style="margin-top: 16px;"><div style="font-weight: 500; margin-bottom: 8px; color: var(--text-primary);">查询参数:</div>${queryParamsInput}</div>` : ''}
|
${queryParamsInput ? `<div style="margin-top: 16px;"><div style="font-weight: 500; margin-bottom: 8px; color: var(--text-primary);">${queryParamsTitle}</div>${queryParamsInput}</div>` : ''}
|
||||||
${bodyInput}
|
${bodyInput}
|
||||||
<div class="api-test-buttons">
|
<div class="api-test-buttons">
|
||||||
<button class="api-test-btn primary" onclick="testAPI('${method}', '${escapeHtml(path)}', '${endpoint.operationId || ''}')">
|
<button class="api-test-btn primary" onclick="testAPI('${method}', '${escapeHtml(path)}', '${endpoint.operationId || ''}')">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polygon points="5 3 19 12 5 21 5 3"/>
|
<polygon points="5 3 19 12 5 21 5 3"/>
|
||||||
</svg>
|
</svg>
|
||||||
发送请求
|
${sendRequestLabel}
|
||||||
</button>
|
</button>
|
||||||
<button class="api-test-btn copy-curl" onclick="copyCurlCommand(event, '${method}', '${escapeHtml(path)}')" title="复制curl命令">
|
<button class="api-test-btn copy-curl" onclick="copyCurlCommand(event, '${method}', '${escapeHtml(path)}')" title="${copyCurlTitle}">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" stroke="currentColor" stroke-width="2"/>
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" stroke="currentColor" stroke-width="2"/>
|
||||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" stroke="currentColor" stroke-width="2"/>
|
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" stroke="currentColor" stroke-width="2"/>
|
||||||
</svg>
|
</svg>
|
||||||
复制curl
|
${copyCurlLabel}
|
||||||
</button>
|
</button>
|
||||||
<button class="api-test-btn clear-result" onclick="clearTestResult('${escapeId(path)}-${method}')" title="清除测试结果">
|
<button class="api-test-btn clear-result" onclick="clearTestResult('${escapeId(path)}-${method}')" title="${clearResultTitle}">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polyline points="3 6 5 6 21 6"/>
|
<polyline points="3 6 5 6 21 6"/>
|
||||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
||||||
</svg>
|
</svg>
|
||||||
清除结果
|
${clearResultLabel}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="test-result-${escapeId(path)}-${method}" class="api-test-result" style="display: none;"></div>
|
<div id="test-result-${escapeId(path)}-${method}" class="api-test-result" style="display: none;"></div>
|
||||||
@@ -548,7 +633,7 @@ async function testAPI(method, path, operationId) {
|
|||||||
|
|
||||||
resultDiv.style.display = 'block';
|
resultDiv.style.display = 'block';
|
||||||
resultDiv.className = 'api-test-result loading';
|
resultDiv.className = 'api-test-result loading';
|
||||||
resultDiv.textContent = '发送请求中...';
|
resultDiv.textContent = _t('apiDocs.sendingRequest');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 替换路径参数
|
// 替换路径参数
|
||||||
@@ -561,7 +646,7 @@ async function testAPI(method, path, operationId) {
|
|||||||
if (input && input.value) {
|
if (input && input.value) {
|
||||||
actualPath = actualPath.replace(param, encodeURIComponent(input.value));
|
actualPath = actualPath.replace(param, encodeURIComponent(input.value));
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`路径参数 ${paramName} 不能为空`);
|
throw new Error(_t('apiDocs.errorPathParamRequired', { name: paramName }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -580,7 +665,7 @@ async function testAPI(method, path, operationId) {
|
|||||||
if (input && input.value !== '' && input.value !== null && input.value !== undefined) {
|
if (input && input.value !== '' && input.value !== null && input.value !== undefined) {
|
||||||
queryParams.push(`${encodeURIComponent(param.name)}=${encodeURIComponent(input.value)}`);
|
queryParams.push(`${encodeURIComponent(param.name)}=${encodeURIComponent(input.value)}`);
|
||||||
} else if (param.required) {
|
} else if (param.required) {
|
||||||
throw new Error(`查询参数 ${param.name} 不能为空`);
|
throw new Error(_t('apiDocs.errorQueryParamRequired', { name: param.name }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -602,8 +687,7 @@ async function testAPI(method, path, operationId) {
|
|||||||
if (currentToken) {
|
if (currentToken) {
|
||||||
options.headers['Authorization'] = 'Bearer ' + currentToken;
|
options.headers['Authorization'] = 'Bearer ' + currentToken;
|
||||||
} else {
|
} else {
|
||||||
// 如果没有token,提示用户
|
throw new Error(_t('apiDocs.errorTokenRequired'));
|
||||||
throw new Error('未检测到 Token。请先在前端页面登录,然后刷新此页面。或者手动在请求头中添加 Authorization: Bearer your_token');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加请求体
|
// 添加请求体
|
||||||
@@ -614,7 +698,7 @@ async function testAPI(method, path, operationId) {
|
|||||||
try {
|
try {
|
||||||
options.body = JSON.stringify(JSON.parse(bodyInput.value.trim()));
|
options.body = JSON.stringify(JSON.parse(bodyInput.value.trim()));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('请求体JSON格式错误: ' + e.message);
|
throw new Error(_t('apiDocs.errorJsonInvalid') + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -636,7 +720,7 @@ async function testAPI(method, path, operationId) {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
resultDiv.className = 'api-test-result error';
|
resultDiv.className = 'api-test-result error';
|
||||||
resultDiv.textContent = '请求失败: ' + error.message;
|
resultDiv.textContent = _t('apiDocs.requestFailed') + error.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,17 +811,17 @@ function copyCurlCommand(event, method, path) {
|
|||||||
// 复制到剪贴板
|
// 复制到剪贴板
|
||||||
const button = event ? event.target.closest('button') : null;
|
const button = event ? event.target.closest('button') : null;
|
||||||
navigator.clipboard.writeText(curlCommand).then(() => {
|
navigator.clipboard.writeText(curlCommand).then(() => {
|
||||||
// 显示成功提示
|
|
||||||
if (button) {
|
if (button) {
|
||||||
const originalText = button.innerHTML;
|
const originalText = button.innerHTML;
|
||||||
button.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>已复制';
|
const copiedLabel = escapeHtml(_t('apiDocs.copied'));
|
||||||
|
button.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>' + copiedLabel;
|
||||||
button.style.color = 'var(--success-color)';
|
button.style.color = 'var(--success-color)';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
button.innerHTML = originalText;
|
button.innerHTML = originalText;
|
||||||
button.style.color = '';
|
button.style.color = '';
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
alert('curl命令已复制到剪贴板!');
|
alert(_t('apiDocs.curlCopied'));
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('复制失败:', err);
|
console.error('复制失败:', err);
|
||||||
@@ -752,24 +836,25 @@ function copyCurlCommand(event, method, path) {
|
|||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
if (button) {
|
if (button) {
|
||||||
const originalText = button.innerHTML;
|
const originalText = button.innerHTML;
|
||||||
button.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>已复制';
|
const copiedLabel = escapeHtml(_t('apiDocs.copied'));
|
||||||
|
button.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>' + copiedLabel;
|
||||||
button.style.color = 'var(--success-color)';
|
button.style.color = 'var(--success-color)';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
button.innerHTML = originalText;
|
button.innerHTML = originalText;
|
||||||
button.style.color = '';
|
button.style.color = '';
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
alert('curl命令已复制到剪贴板!');
|
alert(_t('apiDocs.curlCopied'));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert('复制失败,请手动复制:\n\n' + curlCommand);
|
alert(_t('apiDocs.copyFailedManual') + curlCommand);
|
||||||
}
|
}
|
||||||
document.body.removeChild(textarea);
|
document.body.removeChild(textarea);
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('生成curl命令失败:', error);
|
console.error('生成curl命令失败:', error);
|
||||||
alert('生成curl命令失败: ' + error.message);
|
alert(_t('apiDocs.curlGenFailed') + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,10 +1020,10 @@ function toggleDescription(button) {
|
|||||||
if (detail.style.display === 'none') {
|
if (detail.style.display === 'none') {
|
||||||
detail.style.display = 'block';
|
detail.style.display = 'block';
|
||||||
icon.style.transform = 'rotate(180deg)';
|
icon.style.transform = 'rotate(180deg)';
|
||||||
span.textContent = '隐藏详细说明';
|
span.textContent = typeof window.t === 'function' ? window.t('apiDocs.hideDetailDesc') : '隐藏详细说明';
|
||||||
} else {
|
} else {
|
||||||
detail.style.display = 'none';
|
detail.style.display = 'none';
|
||||||
icon.style.transform = 'rotate(0deg)';
|
icon.style.transform = 'rotate(0deg)';
|
||||||
span.textContent = '查看详细说明';
|
span.textContent = typeof window.t === 'function' ? window.t('apiDocs.viewDetailDesc') : '查看详细说明';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -250,13 +250,13 @@ async function bootstrapApp() {
|
|||||||
|
|
||||||
// 通用工具函数
|
// 通用工具函数
|
||||||
function getStatusText(status) {
|
function getStatusText(status) {
|
||||||
const statusMap = {
|
if (typeof window.t !== 'function') {
|
||||||
'pending': '等待中',
|
const fallback = { pending: '等待中', running: '执行中', completed: '已完成', failed: '失败' };
|
||||||
'running': '执行中',
|
return fallback[status] || status;
|
||||||
'completed': '已完成',
|
}
|
||||||
'failed': '失败'
|
const keyMap = { pending: 'mcpDetailModal.statusPending', running: 'mcpDetailModal.statusRunning', completed: 'mcpDetailModal.statusCompleted', failed: 'mcpDetailModal.statusFailed' };
|
||||||
};
|
const key = keyMap[status];
|
||||||
return statusMap[status] || status;
|
return key ? window.t(key) : status;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDuration(ms) {
|
function formatDuration(ms) {
|
||||||
|
|||||||
+30
-31
@@ -755,7 +755,7 @@ function renderMentionSuggestions({ showLoading = false } = {}) {
|
|||||||
const disabledClass = toolEnabled ? '' : 'disabled';
|
const disabledClass = toolEnabled ? '' : 'disabled';
|
||||||
const badge = tool.isExternal ? '<span class="mention-item-badge">外部</span>' : '<span class="mention-item-badge internal">内置</span>';
|
const badge = tool.isExternal ? '<span class="mention-item-badge">外部</span>' : '<span class="mention-item-badge internal">内置</span>';
|
||||||
const nameHtml = escapeHtml(tool.name);
|
const nameHtml = escapeHtml(tool.name);
|
||||||
const description = tool.description && tool.description.length > 0 ? escapeHtml(tool.description) : '暂无描述';
|
const description = tool.description && tool.description.length > 0 ? escapeHtml(tool.description) : (typeof window.t === 'function' ? window.t('chat.noDescription') : '暂无描述');
|
||||||
const descHtml = `<div class="mention-item-desc">${description}</div>`;
|
const descHtml = `<div class="mention-item-desc">${description}</div>`;
|
||||||
// 根据工具在当前角色中的启用状态显示状态标签
|
// 根据工具在当前角色中的启用状态显示状态标签
|
||||||
const statusLabel = toolEnabled ? '可用' : (tool.roleEnabled !== undefined ? '已禁用(当前角色)' : '已禁用');
|
const statusLabel = toolEnabled ? '可用' : (tool.roleEnabled !== undefined ? '已禁用(当前角色)' : '已禁用');
|
||||||
@@ -1209,7 +1209,7 @@ function addMessage(role, content, mcpExecutionIds = null, progressId = null, cr
|
|||||||
mcpExecutionIds.forEach((execId, index) => {
|
mcpExecutionIds.forEach((execId, index) => {
|
||||||
const detailBtn = document.createElement('button');
|
const detailBtn = document.createElement('button');
|
||||||
detailBtn.className = 'mcp-detail-btn';
|
detailBtn.className = 'mcp-detail-btn';
|
||||||
detailBtn.innerHTML = `<span>调用 #${index + 1}</span>`;
|
detailBtn.innerHTML = '<span>' + (typeof window.t === 'function' ? window.t('chat.callNumber', { n: index + 1 }) : '调用 #' + (index + 1)) + '</span>';
|
||||||
detailBtn.onclick = () => showMCPDetail(execId);
|
detailBtn.onclick = () => showMCPDetail(execId);
|
||||||
buttonsContainer.appendChild(detailBtn);
|
buttonsContainer.appendChild(detailBtn);
|
||||||
// 异步获取工具名称并更新按钮文本
|
// 异步获取工具名称并更新按钮文本
|
||||||
@@ -1265,7 +1265,7 @@ function copyMessageToClipboard(messageDiv, button) {
|
|||||||
showCopySuccess(button);
|
showCopySuccess(button);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('复制失败:', err);
|
console.error('复制失败:', err);
|
||||||
alert('复制失败,请手动选择内容复制');
|
alert(typeof window.t === 'function' ? window.t('chat.copyFailedManual') : '复制失败,请手动选择内容复制');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1276,11 +1276,11 @@ function copyMessageToClipboard(messageDiv, button) {
|
|||||||
showCopySuccess(button);
|
showCopySuccess(button);
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('复制失败:', err);
|
console.error('复制失败:', err);
|
||||||
alert('复制失败,请手动选择内容复制');
|
alert(typeof window.t === 'function' ? window.t('chat.copyFailedManual') : '复制失败,请手动选择内容复制');
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('复制消息时出错:', error);
|
console.error('复制消息时出错:', error);
|
||||||
alert('复制失败,请手动选择内容复制');
|
alert(typeof window.t === 'function' ? window.t('chat.copyFailedManual') : '复制失败,请手动选择内容复制');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1408,32 +1408,31 @@ function renderProcessDetails(messageId, processDetails) {
|
|||||||
// 根据事件类型渲染不同的内容
|
// 根据事件类型渲染不同的内容
|
||||||
let itemTitle = title;
|
let itemTitle = title;
|
||||||
if (eventType === 'iteration') {
|
if (eventType === 'iteration') {
|
||||||
itemTitle = `第 ${data.iteration || 1} 轮迭代`;
|
itemTitle = (typeof window.t === 'function' ? window.t('chat.iterationRound', { n: data.iteration || 1 }) : '第 ' + (data.iteration || 1) + ' 轮迭代');
|
||||||
} else if (eventType === 'thinking') {
|
} else if (eventType === 'thinking') {
|
||||||
itemTitle = '🤔 AI思考';
|
itemTitle = '🤔 ' + (typeof window.t === 'function' ? window.t('chat.aiThinking') : 'AI思考');
|
||||||
} else if (eventType === 'tool_calls_detected') {
|
} else if (eventType === 'tool_calls_detected') {
|
||||||
itemTitle = `🔧 检测到 ${data.count || 0} 个工具调用`;
|
itemTitle = '🔧 ' + (typeof window.t === 'function' ? window.t('chat.toolCallsDetected', { count: data.count || 0 }) : '检测到 ' + (data.count || 0) + ' 个工具调用');
|
||||||
} else if (eventType === 'tool_call') {
|
} else if (eventType === 'tool_call') {
|
||||||
const toolName = data.toolName || '未知工具';
|
const toolName = data.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
|
||||||
const index = data.index || 0;
|
const index = data.index || 0;
|
||||||
const total = data.total || 0;
|
const total = data.total || 0;
|
||||||
itemTitle = `🔧 调用工具: ${escapeHtml(toolName)} (${index}/${total})`;
|
itemTitle = '🔧 ' + (typeof window.t === 'function' ? window.t('chat.callTool', { name: escapeHtml(toolName), index: index, total: total }) : '调用工具: ' + escapeHtml(toolName) + ' (' + index + '/' + total + ')');
|
||||||
} else if (eventType === 'tool_result') {
|
} else if (eventType === 'tool_result') {
|
||||||
const toolName = data.toolName || '未知工具';
|
const toolName = data.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
|
||||||
const success = data.success !== false;
|
const success = data.success !== false;
|
||||||
const statusIcon = success ? '✅' : '❌';
|
const statusIcon = success ? '✅' : '❌';
|
||||||
itemTitle = `${statusIcon} 工具 ${escapeHtml(toolName)} 执行${success ? '完成' : '失败'}`;
|
const execText = success ? (typeof window.t === 'function' ? window.t('chat.toolExecComplete', { name: escapeHtml(toolName) }) : '工具 ' + escapeHtml(toolName) + ' 执行完成') : (typeof window.t === 'function' ? window.t('chat.toolExecFailed', { name: escapeHtml(toolName) }) : '工具 ' + escapeHtml(toolName) + ' 执行失败');
|
||||||
|
itemTitle = statusIcon + ' ' + execText;
|
||||||
// 如果是知识检索工具,添加特殊标记
|
|
||||||
if (toolName === BuiltinTools.SEARCH_KNOWLEDGE_BASE && success) {
|
if (toolName === BuiltinTools.SEARCH_KNOWLEDGE_BASE && success) {
|
||||||
itemTitle = `📚 ${itemTitle} - 知识检索`;
|
itemTitle = '📚 ' + itemTitle + ' - ' + (typeof window.t === 'function' ? window.t('chat.knowledgeRetrievalTag') : '知识检索');
|
||||||
}
|
}
|
||||||
} else if (eventType === 'knowledge_retrieval') {
|
} else if (eventType === 'knowledge_retrieval') {
|
||||||
itemTitle = '📚 知识检索';
|
itemTitle = '📚 ' + (typeof window.t === 'function' ? window.t('chat.knowledgeRetrieval') : '知识检索');
|
||||||
} else if (eventType === 'error') {
|
} else if (eventType === 'error') {
|
||||||
itemTitle = '❌ 错误';
|
itemTitle = '❌ ' + (typeof window.t === 'function' ? window.t('chat.error') : '错误');
|
||||||
} else if (eventType === 'cancelled') {
|
} else if (eventType === 'cancelled') {
|
||||||
itemTitle = '⛔ 任务已取消';
|
itemTitle = '⛔ ' + (typeof window.t === 'function' ? window.t('chat.taskCancelled') : '任务已取消');
|
||||||
}
|
}
|
||||||
|
|
||||||
addTimelineItem(timeline, eventType, {
|
addTimelineItem(timeline, eventType, {
|
||||||
@@ -1509,7 +1508,7 @@ async function updateButtonWithToolName(button, executionId, index) {
|
|||||||
const response = await apiFetch(`/api/monitor/execution/${executionId}`);
|
const response = await apiFetch(`/api/monitor/execution/${executionId}`);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const exec = await response.json();
|
const exec = await response.json();
|
||||||
const toolName = exec.toolName || '未知工具';
|
const toolName = exec.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
|
||||||
// 格式化工具名称(如果是 name::toolName 格式,只显示 toolName 部分)
|
// 格式化工具名称(如果是 name::toolName 格式,只显示 toolName 部分)
|
||||||
const displayToolName = toolName.includes('::') ? toolName.split('::')[1] : toolName;
|
const displayToolName = toolName.includes('::') ? toolName.split('::')[1] : toolName;
|
||||||
button.querySelector('span').textContent = `${displayToolName} #${index}`;
|
button.querySelector('span').textContent = `${displayToolName} #${index}`;
|
||||||
@@ -1528,7 +1527,7 @@ async function showMCPDetail(executionId) {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// 填充模态框内容
|
// 填充模态框内容
|
||||||
document.getElementById('detail-tool-name').textContent = exec.toolName || 'Unknown';
|
document.getElementById('detail-tool-name').textContent = exec.toolName || (typeof window.t === 'function' ? window.t('mcpDetailModal.unknown') : 'Unknown');
|
||||||
document.getElementById('detail-execution-id').textContent = exec.id || 'N/A';
|
document.getElementById('detail-execution-id').textContent = exec.id || 'N/A';
|
||||||
const statusEl = document.getElementById('detail-status');
|
const statusEl = document.getElementById('detail-status');
|
||||||
const normalizedStatus = (exec.status || 'unknown').toLowerCase();
|
const normalizedStatus = (exec.status || 'unknown').toLowerCase();
|
||||||
@@ -1598,22 +1597,22 @@ async function showMCPDetail(executionId) {
|
|||||||
successText = content.text;
|
successText = content.text;
|
||||||
}
|
}
|
||||||
if (!successText) {
|
if (!successText) {
|
||||||
successText = '执行成功,未返回可展示的文本内容。';
|
successText = typeof window.t === 'function' ? window.t('mcpDetailModal.execSuccessNoContent') : '执行成功,未返回可展示的文本内容。';
|
||||||
}
|
}
|
||||||
successElement.textContent = successText;
|
successElement.textContent = successText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
responseElement.textContent = '暂无响应数据';
|
responseElement.textContent = typeof window.t === 'function' ? window.t('chat.noResponseData') : '暂无响应数据';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示模态框
|
// 显示模态框
|
||||||
document.getElementById('mcp-detail-modal').style.display = 'block';
|
document.getElementById('mcp-detail-modal').style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
alert('获取详情失败: ' + (exec.error || '未知错误'));
|
alert((typeof window.t === 'function' ? window.t('mcpDetailModal.getDetailFailed') : '获取详情失败') + ': ' + (exec.error || (typeof window.t === 'function' ? window.t('mcpDetailModal.unknown') : '未知错误')));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('获取详情失败: ' + error.message);
|
alert((typeof window.t === 'function' ? window.t('mcpDetailModal.getDetailFailed') : '获取详情失败') + ': ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2255,7 +2254,7 @@ async function showAttackChain(conversationId) {
|
|||||||
// 清空容器
|
// 清空容器
|
||||||
const container = document.getElementById('attack-chain-container');
|
const container = document.getElementById('attack-chain-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
container.innerHTML = '<div class="loading-spinner">加载中...</div>';
|
container.innerHTML = '<div class="loading-spinner">' + (typeof window.t === 'function' ? window.t('chat.loading') : '加载中...') + '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏详情面板
|
// 隐藏详情面板
|
||||||
@@ -2355,7 +2354,7 @@ async function loadAttackChain(conversationId) {
|
|||||||
console.error('加载攻击链失败:', error);
|
console.error('加载攻击链失败:', error);
|
||||||
const container = document.getElementById('attack-chain-container');
|
const container = document.getElementById('attack-chain-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
container.innerHTML = `<div class="error-message">加载失败: ${error.message}</div>`;
|
container.innerHTML = '<div class="error-message">' + (typeof window.t === 'function' ? window.t('chat.loadFailed', { message: error.message }) : '加载失败: ' + error.message) + '</div>';
|
||||||
}
|
}
|
||||||
// 错误时也重置加载状态
|
// 错误时也重置加载状态
|
||||||
setAttackChainLoading(conversationId, false);
|
setAttackChainLoading(conversationId, false);
|
||||||
@@ -2381,7 +2380,7 @@ function renderAttackChain(chainData) {
|
|||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
if (!chainData.nodes || chainData.nodes.length === 0) {
|
if (!chainData.nodes || chainData.nodes.length === 0) {
|
||||||
container.innerHTML = '<div class="empty-message">暂无攻击链数据</div>';
|
container.innerHTML = '<div class="empty-message">' + (typeof window.t === 'function' ? window.t('chat.noAttackChainData') : '暂无攻击链数据') + '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5544,9 +5543,9 @@ async function loadGroupConversations(groupId, searchQuery = '') {
|
|||||||
|
|
||||||
// 显示加载状态
|
// 显示加载状态
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">搜索中...</div>';
|
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">' + (typeof window.t === 'function' ? window.t('chat.searching') : '搜索中...') + '</div>';
|
||||||
} else {
|
} else {
|
||||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">加载中...</div>';
|
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">' + (typeof window.t === 'function' ? window.t('chat.loading') : '加载中...') + '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建URL,如果有搜索关键词则添加search参数
|
// 构建URL,如果有搜索关键词则添加search参数
|
||||||
@@ -5558,7 +5557,7 @@ async function loadGroupConversations(groupId, searchQuery = '') {
|
|||||||
const response = await apiFetch(url);
|
const response = await apiFetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error(`Failed to load conversations for group ${groupId}:`, response.statusText);
|
console.error(`Failed to load conversations for group ${groupId}:`, response.statusText);
|
||||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">加载失败,请重试</div>';
|
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">' + (typeof window.t === 'function' ? window.t('chat.loadFailedRetry') : '加载失败,请重试') + '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5572,7 +5571,7 @@ async function loadGroupConversations(groupId, searchQuery = '') {
|
|||||||
// 验证返回的数据类型
|
// 验证返回的数据类型
|
||||||
if (!Array.isArray(groupConvs)) {
|
if (!Array.isArray(groupConvs)) {
|
||||||
console.error(`Invalid response for group ${groupId}:`, groupConvs);
|
console.error(`Invalid response for group ${groupId}:`, groupConvs);
|
||||||
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">数据格式错误</div>';
|
list.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--text-muted);">' + (typeof window.t === 'function' ? window.t('chat.dataFormatError') : '数据格式错误') + '</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,9 +109,9 @@
|
|||||||
if (!label || typeof i18next === 'undefined') return;
|
if (!label || typeof i18next === 'undefined') return;
|
||||||
const lang = (i18next.language || DEFAULT_LANG).toLowerCase();
|
const lang = (i18next.language || DEFAULT_LANG).toLowerCase();
|
||||||
if (lang.indexOf('zh') === 0) {
|
if (lang.indexOf('zh') === 0) {
|
||||||
label.textContent = '中文';
|
label.textContent = i18next.t('lang.zhCN');
|
||||||
} else {
|
} else {
|
||||||
label.textContent = 'English';
|
label.textContent = i18next.t('lang.enUS');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+108
-80
@@ -3,6 +3,27 @@ let activeTaskInterval = null;
|
|||||||
const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次
|
const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次
|
||||||
const TASK_FINAL_STATUSES = new Set(['failed', 'timeout', 'cancelled', 'completed']);
|
const TASK_FINAL_STATUSES = new Set(['failed', 'timeout', 'cancelled', 'completed']);
|
||||||
|
|
||||||
|
// 将后端下发的进度文案转为当前语言的翻译(已知中文 key 映射)
|
||||||
|
function translateProgressMessage(message) {
|
||||||
|
if (!message || typeof message !== 'string') return message;
|
||||||
|
if (typeof window.t !== 'function') return message;
|
||||||
|
const trim = message.trim();
|
||||||
|
const map = {
|
||||||
|
'正在调用AI模型...': 'progress.callingAI',
|
||||||
|
'最后一次迭代:正在生成总结和下一步计划...': 'progress.lastIterSummary',
|
||||||
|
'总结生成完成': 'progress.summaryDone',
|
||||||
|
'正在生成最终回复...': 'progress.generatingFinalReply',
|
||||||
|
'达到最大迭代次数,正在生成总结...': 'progress.maxIterSummary'
|
||||||
|
};
|
||||||
|
if (map[trim]) return window.t(map[trim]);
|
||||||
|
const callingToolPrefix = '正在调用工具: ';
|
||||||
|
if (trim.indexOf(callingToolPrefix) === 0) {
|
||||||
|
const name = trim.slice(callingToolPrefix.length);
|
||||||
|
return window.t('progress.callingTool', { name: name });
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
// 存储工具调用ID到DOM元素的映射,用于更新执行状态
|
// 存储工具调用ID到DOM元素的映射,用于更新执行状态
|
||||||
const toolCallStatusMap = new Map();
|
const toolCallStatusMap = new Map();
|
||||||
|
|
||||||
@@ -57,11 +78,15 @@ function markProgressCancelling(progressId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function finalizeProgressTask(progressId, finalLabel = '已完成') {
|
function finalizeProgressTask(progressId, finalLabel) {
|
||||||
const stopBtn = document.getElementById(`${progressId}-stop-btn`);
|
const stopBtn = document.getElementById(`${progressId}-stop-btn`);
|
||||||
if (stopBtn) {
|
if (stopBtn) {
|
||||||
stopBtn.disabled = true;
|
stopBtn.disabled = true;
|
||||||
stopBtn.textContent = finalLabel;
|
if (finalLabel !== undefined && finalLabel !== '') {
|
||||||
|
stopBtn.textContent = finalLabel;
|
||||||
|
} else {
|
||||||
|
stopBtn.textContent = typeof window.t === 'function' ? window.t('tasks.statusCompleted') : '已完成';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
progressTaskState.delete(progressId);
|
progressTaskState.delete(progressId);
|
||||||
}
|
}
|
||||||
@@ -94,12 +119,15 @@ function addProgressMessage() {
|
|||||||
|
|
||||||
const bubble = document.createElement('div');
|
const bubble = document.createElement('div');
|
||||||
bubble.className = 'message-bubble progress-container';
|
bubble.className = 'message-bubble progress-container';
|
||||||
|
const progressTitleText = typeof window.t === 'function' ? window.t('chat.progressInProgress') : '渗透测试进行中...';
|
||||||
|
const stopTaskText = typeof window.t === 'function' ? window.t('tasks.stopTask') : '停止任务';
|
||||||
|
const collapseDetailText = typeof window.t === 'function' ? window.t('tasks.collapseDetail') : '收起详情';
|
||||||
bubble.innerHTML = `
|
bubble.innerHTML = `
|
||||||
<div class="progress-header">
|
<div class="progress-header">
|
||||||
<span class="progress-title">🔍 渗透测试进行中...</span>
|
<span class="progress-title">🔍 ${progressTitleText}</span>
|
||||||
<div class="progress-actions">
|
<div class="progress-actions">
|
||||||
<button class="progress-stop" id="${id}-stop-btn" onclick="cancelProgressTask('${id}')">停止任务</button>
|
<button class="progress-stop" id="${id}-stop-btn" onclick="cancelProgressTask('${id}')">${stopTaskText}</button>
|
||||||
<button class="progress-toggle" onclick="toggleProgressDetails('${id}')">收起详情</button>
|
<button class="progress-toggle" onclick="toggleProgressDetails('${id}')">${collapseDetailText}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-timeline expanded" id="${id}-timeline"></div>
|
<div class="progress-timeline expanded" id="${id}-timeline"></div>
|
||||||
@@ -123,10 +151,10 @@ function toggleProgressDetails(progressId) {
|
|||||||
|
|
||||||
if (timeline.classList.contains('expanded')) {
|
if (timeline.classList.contains('expanded')) {
|
||||||
timeline.classList.remove('expanded');
|
timeline.classList.remove('expanded');
|
||||||
toggleBtn.textContent = '展开详情';
|
toggleBtn.textContent = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||||
} else {
|
} else {
|
||||||
timeline.classList.add('expanded');
|
timeline.classList.add('expanded');
|
||||||
toggleBtn.textContent = '收起详情';
|
toggleBtn.textContent = typeof window.t === 'function' ? window.t('tasks.collapseDetail') : '收起详情';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +171,7 @@ function collapseAllProgressDetails(assistantMessageId, progressId) {
|
|||||||
timeline.classList.remove('expanded');
|
timeline.classList.remove('expanded');
|
||||||
const btn = document.querySelector(`#${assistantMessageId} .process-detail-btn`);
|
const btn = document.querySelector(`#${assistantMessageId} .process-detail-btn`);
|
||||||
if (btn) {
|
if (btn) {
|
||||||
btn.innerHTML = '<span>展开详情</span>';
|
btn.innerHTML = '<span>' + (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') + '</span>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +274,7 @@ function integrateProgressToMCPSection(progressId, assistantMessageId) {
|
|||||||
// 设置详情内容(如果有错误,默认折叠;否则默认折叠)
|
// 设置详情内容(如果有错误,默认折叠;否则默认折叠)
|
||||||
detailsContainer.innerHTML = `
|
detailsContainer.innerHTML = `
|
||||||
<div class="process-details-content">
|
<div class="process-details-content">
|
||||||
${hasContent ? `<div class="progress-timeline" id="${detailsId}-timeline">${timelineHTML}</div>` : '<div class="progress-timeline-empty">暂无过程详情</div>'}
|
${hasContent ? `<div class="progress-timeline" id="${detailsId}-timeline">${timelineHTML}</div>` : '<div class="progress-timeline-empty">' + (typeof window.t === 'function' ? window.t('chat.noProcessDetail') : '暂无过程详情(可能执行过快或未触发详细事件)') + '</div>'}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -258,10 +286,9 @@ function integrateProgressToMCPSection(progressId, assistantMessageId) {
|
|||||||
timeline.classList.remove('expanded');
|
timeline.classList.remove('expanded');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新按钮文本为"展开详情"(因为默认折叠)
|
|
||||||
const processDetailBtn = buttonsContainer.querySelector('.process-detail-btn');
|
const processDetailBtn = buttonsContainer.querySelector('.process-detail-btn');
|
||||||
if (processDetailBtn) {
|
if (processDetailBtn) {
|
||||||
processDetailBtn.innerHTML = '<span>展开详情</span>';
|
processDetailBtn.innerHTML = '<span>' + (typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情') + '</span>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,22 +306,23 @@ function toggleProcessDetails(progressId, assistantMessageId) {
|
|||||||
const timeline = detailsContainer.querySelector('.progress-timeline');
|
const timeline = detailsContainer.querySelector('.progress-timeline');
|
||||||
const btn = document.querySelector(`#${assistantMessageId} .process-detail-btn`);
|
const btn = document.querySelector(`#${assistantMessageId} .process-detail-btn`);
|
||||||
|
|
||||||
|
const expandT = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||||
|
const collapseT = typeof window.t === 'function' ? window.t('tasks.collapseDetail') : '收起详情';
|
||||||
if (content && timeline) {
|
if (content && timeline) {
|
||||||
if (timeline.classList.contains('expanded')) {
|
if (timeline.classList.contains('expanded')) {
|
||||||
timeline.classList.remove('expanded');
|
timeline.classList.remove('expanded');
|
||||||
if (btn) btn.innerHTML = '<span>展开详情</span>';
|
if (btn) btn.innerHTML = '<span>' + expandT + '</span>';
|
||||||
} else {
|
} else {
|
||||||
timeline.classList.add('expanded');
|
timeline.classList.add('expanded');
|
||||||
if (btn) btn.innerHTML = '<span>收起详情</span>';
|
if (btn) btn.innerHTML = '<span>' + collapseT + '</span>';
|
||||||
}
|
}
|
||||||
} else if (timeline) {
|
} else if (timeline) {
|
||||||
// 如果只有timeline,直接切换
|
|
||||||
if (timeline.classList.contains('expanded')) {
|
if (timeline.classList.contains('expanded')) {
|
||||||
timeline.classList.remove('expanded');
|
timeline.classList.remove('expanded');
|
||||||
if (btn) btn.innerHTML = '<span>展开详情</span>';
|
if (btn) btn.innerHTML = '<span>' + expandT + '</span>';
|
||||||
} else {
|
} else {
|
||||||
timeline.classList.add('expanded');
|
timeline.classList.add('expanded');
|
||||||
if (btn) btn.innerHTML = '<span>收起详情</span>';
|
if (btn) btn.innerHTML = '<span>' + collapseT + '</span>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,10 +366,10 @@ async function cancelProgressTask(progressId) {
|
|||||||
loadActiveTasks();
|
loadActiveTasks();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('取消任务失败:', error);
|
console.error('取消任务失败:', error);
|
||||||
alert('取消任务失败: ' + error.message);
|
alert((typeof window.t === 'function' ? window.t('tasks.cancelTaskFailed') : '取消任务失败') + ': ' + error.message);
|
||||||
if (stopBtn) {
|
if (stopBtn) {
|
||||||
stopBtn.disabled = false;
|
stopBtn.disabled = false;
|
||||||
stopBtn.textContent = '停止任务';
|
stopBtn.textContent = typeof window.t === 'function' ? window.t('tasks.stopTask') : '停止任务';
|
||||||
}
|
}
|
||||||
const currentState = progressTaskState.get(progressId);
|
const currentState = progressTaskState.get(progressId);
|
||||||
if (currentState) {
|
if (currentState) {
|
||||||
@@ -391,15 +419,17 @@ function convertProgressToDetails(progressId, assistantMessageId) {
|
|||||||
// 如果有错误,默认折叠;否则默认展开
|
// 如果有错误,默认折叠;否则默认展开
|
||||||
const shouldExpand = !hasError;
|
const shouldExpand = !hasError;
|
||||||
const expandedClass = shouldExpand ? 'expanded' : '';
|
const expandedClass = shouldExpand ? 'expanded' : '';
|
||||||
const toggleText = shouldExpand ? '收起详情' : '展开详情';
|
const collapseDetailText = typeof window.t === 'function' ? window.t('tasks.collapseDetail') : '收起详情';
|
||||||
|
const expandDetailText = typeof window.t === 'function' ? window.t('chat.expandDetail') : '展开详情';
|
||||||
// 总是显示详情组件,即使没有内容也显示
|
const toggleText = shouldExpand ? collapseDetailText : expandDetailText;
|
||||||
|
const penetrationDetailText = typeof window.t === 'function' ? window.t('chat.penetrationTestDetail') : '渗透测试详情';
|
||||||
|
const noProcessDetailText = typeof window.t === 'function' ? window.t('chat.noProcessDetail') : '暂无过程详情(可能执行过快或未触发详细事件)';
|
||||||
bubble.innerHTML = `
|
bubble.innerHTML = `
|
||||||
<div class="progress-header">
|
<div class="progress-header">
|
||||||
<span class="progress-title">📋 渗透测试详情</span>
|
<span class="progress-title">📋 ${penetrationDetailText}</span>
|
||||||
${hasContent ? `<button class="progress-toggle" onclick="toggleProgressDetails('${detailsId}')">${toggleText}</button>` : ''}
|
${hasContent ? `<button class="progress-toggle" onclick="toggleProgressDetails('${detailsId}')">${toggleText}</button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
${hasContent ? `<div class="progress-timeline ${expandedClass}" id="${detailsId}-timeline">${timelineHTML}</div>` : '<div class="progress-timeline-empty">暂无过程详情(可能执行过快或未触发详细事件)</div>'}
|
${hasContent ? `<div class="progress-timeline ${expandedClass}" id="${detailsId}-timeline">${timelineHTML}</div>` : '<div class="progress-timeline-empty">' + noProcessDetailText + '</div>'}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
contentWrapper.appendChild(bubble);
|
contentWrapper.appendChild(bubble);
|
||||||
@@ -466,41 +496,37 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
case 'iteration':
|
case 'iteration':
|
||||||
// 添加迭代标记
|
// 添加迭代标记
|
||||||
addTimelineItem(timeline, 'iteration', {
|
addTimelineItem(timeline, 'iteration', {
|
||||||
title: `第 ${event.data?.iteration || 1} 轮迭代`,
|
title: typeof window.t === 'function' ? window.t('chat.iterationRound', { n: event.data?.iteration || 1 }) : '第 ' + (event.data?.iteration || 1) + ' 轮迭代',
|
||||||
message: event.message,
|
message: event.message,
|
||||||
data: event.data
|
data: event.data
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'thinking':
|
case 'thinking':
|
||||||
// 显示AI思考内容
|
|
||||||
addTimelineItem(timeline, 'thinking', {
|
addTimelineItem(timeline, 'thinking', {
|
||||||
title: '🤔 AI思考',
|
title: '🤔 ' + (typeof window.t === 'function' ? window.t('chat.aiThinking') : 'AI思考'),
|
||||||
message: event.message,
|
message: event.message,
|
||||||
data: event.data
|
data: event.data
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'tool_calls_detected':
|
case 'tool_calls_detected':
|
||||||
// 工具调用检测
|
|
||||||
addTimelineItem(timeline, 'tool_calls_detected', {
|
addTimelineItem(timeline, 'tool_calls_detected', {
|
||||||
title: `🔧 检测到 ${event.data?.count || 0} 个工具调用`,
|
title: '🔧 ' + (typeof window.t === 'function' ? window.t('chat.toolCallsDetected', { count: event.data?.count || 0 }) : '检测到 ' + (event.data?.count || 0) + ' 个工具调用'),
|
||||||
message: event.message,
|
message: event.message,
|
||||||
data: event.data
|
data: event.data
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'tool_call':
|
case 'tool_call':
|
||||||
// 显示工具调用信息
|
|
||||||
const toolInfo = event.data || {};
|
const toolInfo = event.data || {};
|
||||||
const toolName = toolInfo.toolName || '未知工具';
|
const toolName = toolInfo.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
|
||||||
const index = toolInfo.index || 0;
|
const index = toolInfo.index || 0;
|
||||||
const total = toolInfo.total || 0;
|
const total = toolInfo.total || 0;
|
||||||
const toolCallId = toolInfo.toolCallId || null;
|
const toolCallId = toolInfo.toolCallId || null;
|
||||||
|
const toolCallTitle = typeof window.t === 'function' ? window.t('chat.callTool', { name: escapeHtml(toolName), index: index, total: total }) : '调用工具: ' + escapeHtml(toolName) + ' (' + index + '/' + total + ')';
|
||||||
// 添加工具调用项,并标记为执行中
|
|
||||||
const toolCallItemId = addTimelineItem(timeline, 'tool_call', {
|
const toolCallItemId = addTimelineItem(timeline, 'tool_call', {
|
||||||
title: `🔧 调用工具: ${escapeHtml(toolName)} (${index}/${total})`,
|
title: '🔧 ' + toolCallTitle,
|
||||||
message: event.message,
|
message: event.message,
|
||||||
data: toolInfo,
|
data: toolInfo,
|
||||||
expanded: false
|
expanded: false
|
||||||
@@ -519,22 +545,18 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'tool_result':
|
case 'tool_result':
|
||||||
// 显示工具执行结果
|
|
||||||
const resultInfo = event.data || {};
|
const resultInfo = event.data || {};
|
||||||
const resultToolName = resultInfo.toolName || '未知工具';
|
const resultToolName = resultInfo.toolName || (typeof window.t === 'function' ? window.t('chat.unknownTool') : '未知工具');
|
||||||
const success = resultInfo.success !== false;
|
const success = resultInfo.success !== false;
|
||||||
const statusIcon = success ? '✅' : '❌';
|
const statusIcon = success ? '✅' : '❌';
|
||||||
const resultToolCallId = resultInfo.toolCallId || null;
|
const resultToolCallId = resultInfo.toolCallId || null;
|
||||||
|
const resultExecText = success ? (typeof window.t === 'function' ? window.t('chat.toolExecComplete', { name: escapeHtml(resultToolName) }) : '工具 ' + escapeHtml(resultToolName) + ' 执行完成') : (typeof window.t === 'function' ? window.t('chat.toolExecFailed', { name: escapeHtml(resultToolName) }) : '工具 ' + escapeHtml(resultToolName) + ' 执行失败');
|
||||||
// 如果有关联的toolCallId,更新工具调用项的状态
|
|
||||||
if (resultToolCallId && toolCallStatusMap.has(resultToolCallId)) {
|
if (resultToolCallId && toolCallStatusMap.has(resultToolCallId)) {
|
||||||
updateToolCallStatus(resultToolCallId, success ? 'completed' : 'failed');
|
updateToolCallStatus(resultToolCallId, success ? 'completed' : 'failed');
|
||||||
// 从映射中移除(已完成)
|
|
||||||
toolCallStatusMap.delete(resultToolCallId);
|
toolCallStatusMap.delete(resultToolCallId);
|
||||||
}
|
}
|
||||||
|
|
||||||
addTimelineItem(timeline, 'tool_result', {
|
addTimelineItem(timeline, 'tool_result', {
|
||||||
title: `${statusIcon} 工具 ${escapeHtml(resultToolName)} 执行${success ? '完成' : '失败'}`,
|
title: statusIcon + ' ' + resultExecText,
|
||||||
message: event.message,
|
message: event.message,
|
||||||
data: resultInfo,
|
data: resultInfo,
|
||||||
expanded: false
|
expanded: false
|
||||||
@@ -542,36 +564,30 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'progress':
|
case 'progress':
|
||||||
// 更新进度状态
|
|
||||||
const progressTitle = document.querySelector(`#${progressId} .progress-title`);
|
const progressTitle = document.querySelector(`#${progressId} .progress-title`);
|
||||||
if (progressTitle) {
|
if (progressTitle) {
|
||||||
progressTitle.textContent = '🔍 ' + event.message;
|
const progressMsg = translateProgressMessage(event.message);
|
||||||
|
progressTitle.textContent = '🔍 ' + progressMsg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'cancelled':
|
case 'cancelled':
|
||||||
// 显示错误
|
const taskCancelledText = typeof window.t === 'function' ? window.t('chat.taskCancelled') : '任务已取消';
|
||||||
addTimelineItem(timeline, 'cancelled', {
|
addTimelineItem(timeline, 'cancelled', {
|
||||||
title: '⛔ 任务已取消',
|
title: '⛔ ' + taskCancelledText,
|
||||||
message: event.message,
|
message: event.message,
|
||||||
data: event.data
|
data: event.data
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新进度标题为取消状态
|
|
||||||
const cancelTitle = document.querySelector(`#${progressId} .progress-title`);
|
const cancelTitle = document.querySelector(`#${progressId} .progress-title`);
|
||||||
if (cancelTitle) {
|
if (cancelTitle) {
|
||||||
cancelTitle.textContent = '⛔ 任务已取消';
|
cancelTitle.textContent = '⛔ ' + taskCancelledText;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新进度容器为已完成状态(添加completed类)
|
|
||||||
const cancelProgressContainer = document.querySelector(`#${progressId} .progress-container`);
|
const cancelProgressContainer = document.querySelector(`#${progressId} .progress-container`);
|
||||||
if (cancelProgressContainer) {
|
if (cancelProgressContainer) {
|
||||||
cancelProgressContainer.classList.add('completed');
|
cancelProgressContainer.classList.add('completed');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 完成进度任务(标记为已取消)
|
|
||||||
if (progressTaskState.has(progressId)) {
|
if (progressTaskState.has(progressId)) {
|
||||||
finalizeProgressTask(progressId, '已取消');
|
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusCancelled') : '已取消');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果取消事件包含messageId,说明有助手消息,需要显示取消内容
|
// 如果取消事件包含messageId,说明有助手消息,需要显示取消内容
|
||||||
@@ -689,7 +705,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
|
|
||||||
// 完成进度任务(标记为失败)
|
// 完成进度任务(标记为失败)
|
||||||
if (progressTaskState.has(progressId)) {
|
if (progressTaskState.has(progressId)) {
|
||||||
finalizeProgressTask(progressId, '已失败');
|
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusFailed') : '执行失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果错误事件包含messageId,说明有助手消息,需要显示错误内容
|
// 如果错误事件包含messageId,说明有助手消息,需要显示错误内容
|
||||||
@@ -753,7 +769,7 @@ function handleStreamEvent(event, progressElement, progressId,
|
|||||||
updateProgressConversation(progressId, event.data.conversationId);
|
updateProgressConversation(progressId, event.data.conversationId);
|
||||||
}
|
}
|
||||||
if (progressTaskState.has(progressId)) {
|
if (progressTaskState.has(progressId)) {
|
||||||
finalizeProgressTask(progressId, '已完成');
|
finalizeProgressTask(progressId, typeof window.t === 'function' ? window.t('tasks.statusCompleted') : '已完成');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查时间线中是否有错误项
|
// 检查时间线中是否有错误项
|
||||||
@@ -807,17 +823,19 @@ function updateToolCallStatus(toolCallId, status) {
|
|||||||
// 移除之前的状态类
|
// 移除之前的状态类
|
||||||
item.classList.remove('tool-call-running', 'tool-call-completed', 'tool-call-failed');
|
item.classList.remove('tool-call-running', 'tool-call-completed', 'tool-call-failed');
|
||||||
|
|
||||||
// 根据状态更新样式和文本
|
const runningLabel = typeof window.t === 'function' ? window.t('timeline.running') : '执行中...';
|
||||||
|
const completedLabel = typeof window.t === 'function' ? window.t('timeline.completed') : '已完成';
|
||||||
|
const failedLabel = typeof window.t === 'function' ? window.t('timeline.execFailed') : '执行失败';
|
||||||
let statusText = '';
|
let statusText = '';
|
||||||
if (status === 'running') {
|
if (status === 'running') {
|
||||||
item.classList.add('tool-call-running');
|
item.classList.add('tool-call-running');
|
||||||
statusText = ' <span class="tool-status-badge tool-status-running">执行中...</span>';
|
statusText = ' <span class="tool-status-badge tool-status-running">' + escapeHtml(runningLabel) + '</span>';
|
||||||
} else if (status === 'completed') {
|
} else if (status === 'completed') {
|
||||||
item.classList.add('tool-call-completed');
|
item.classList.add('tool-call-completed');
|
||||||
statusText = ' <span class="tool-status-badge tool-status-completed">✅ 已完成</span>';
|
statusText = ' <span class="tool-status-badge tool-status-completed">✅ ' + escapeHtml(completedLabel) + '</span>';
|
||||||
} else if (status === 'failed') {
|
} else if (status === 'failed') {
|
||||||
item.classList.add('tool-call-failed');
|
item.classList.add('tool-call-failed');
|
||||||
statusText = ' <span class="tool-status-badge tool-status-failed">❌ 执行失败</span>';
|
statusText = ' <span class="tool-status-badge tool-status-failed">❌ ' + escapeHtml(failedLabel) + '</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新标题(保留原有文本,追加状态)
|
// 更新标题(保留原有文本,追加状态)
|
||||||
@@ -869,11 +887,12 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
} else if (type === 'tool_call' && options.data) {
|
} else if (type === 'tool_call' && options.data) {
|
||||||
const data = options.data;
|
const data = options.data;
|
||||||
const args = data.argumentsObj || (data.arguments ? JSON.parse(data.arguments) : {});
|
const args = data.argumentsObj || (data.arguments ? JSON.parse(data.arguments) : {});
|
||||||
|
const paramsLabel = typeof window.t === 'function' ? window.t('timeline.params') : '参数:';
|
||||||
content += `
|
content += `
|
||||||
<div class="timeline-item-content">
|
<div class="timeline-item-content">
|
||||||
<div class="tool-details">
|
<div class="tool-details">
|
||||||
<div class="tool-arg-section">
|
<div class="tool-arg-section">
|
||||||
<strong>参数:</strong>
|
<strong>${escapeHtml(paramsLabel)}</strong>
|
||||||
<pre class="tool-args">${escapeHtml(JSON.stringify(args, null, 2))}</pre>
|
<pre class="tool-args">${escapeHtml(JSON.stringify(args, null, 2))}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -882,22 +901,25 @@ function addTimelineItem(timeline, type, options) {
|
|||||||
} else if (type === 'tool_result' && options.data) {
|
} else if (type === 'tool_result' && options.data) {
|
||||||
const data = options.data;
|
const data = options.data;
|
||||||
const isError = data.isError || !data.success;
|
const isError = data.isError || !data.success;
|
||||||
const result = data.result || data.error || '无结果';
|
const noResultText = typeof window.t === 'function' ? window.t('timeline.noResult') : '无结果';
|
||||||
// 确保 result 是字符串
|
const result = data.result || data.error || noResultText;
|
||||||
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
const resultStr = typeof result === 'string' ? result : JSON.stringify(result);
|
||||||
|
const execResultLabel = typeof window.t === 'function' ? window.t('timeline.executionResult') : '执行结果:';
|
||||||
|
const execIdLabel = typeof window.t === 'function' ? window.t('timeline.executionId') : '执行ID:';
|
||||||
content += `
|
content += `
|
||||||
<div class="timeline-item-content">
|
<div class="timeline-item-content">
|
||||||
<div class="tool-result-section ${isError ? 'error' : 'success'}">
|
<div class="tool-result-section ${isError ? 'error' : 'success'}">
|
||||||
<strong>执行结果:</strong>
|
<strong>${escapeHtml(execResultLabel)}</strong>
|
||||||
<pre class="tool-result">${escapeHtml(resultStr)}</pre>
|
<pre class="tool-result">${escapeHtml(resultStr)}</pre>
|
||||||
${data.executionId ? `<div class="tool-execution-id">执行ID: <code>${escapeHtml(data.executionId)}</code></div>` : ''}
|
${data.executionId ? `<div class="tool-execution-id">${escapeHtml(execIdLabel)} <code>${escapeHtml(data.executionId)}</code></div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
} else if (type === 'cancelled') {
|
} else if (type === 'cancelled') {
|
||||||
|
const taskCancelledLabel = typeof window.t === 'function' ? window.t('chat.taskCancelled') : '任务已取消';
|
||||||
content += `
|
content += `
|
||||||
<div class="timeline-item-content">
|
<div class="timeline-item-content">
|
||||||
${escapeHtml(options.message || '任务已取消')}
|
${escapeHtml(options.message || taskCancelledLabel)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -964,26 +986,28 @@ function renderActiveTasks(tasks) {
|
|||||||
? startedTime.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
? startedTime.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
// 根据任务状态显示不同的文本
|
const _t = function (k) { return typeof window.t === 'function' ? window.t(k) : k; };
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'running': '执行中',
|
'running': _t('tasks.statusRunning'),
|
||||||
'cancelling': '取消中',
|
'cancelling': _t('tasks.statusCancelling'),
|
||||||
'failed': '执行失败',
|
'failed': _t('tasks.statusFailed'),
|
||||||
'timeout': '执行超时',
|
'timeout': _t('tasks.statusTimeout'),
|
||||||
'cancelled': '已取消',
|
'cancelled': _t('tasks.statusCancelled'),
|
||||||
'completed': '已完成'
|
'completed': _t('tasks.statusCompleted')
|
||||||
};
|
};
|
||||||
const statusText = statusMap[task.status] || '执行中';
|
const statusText = statusMap[task.status] || _t('tasks.statusRunning');
|
||||||
const isFinalStatus = ['failed', 'timeout', 'cancelled', 'completed'].includes(task.status);
|
const isFinalStatus = ['failed', 'timeout', 'cancelled', 'completed'].includes(task.status);
|
||||||
|
const unnamedTaskText = _t('tasks.unnamedTask');
|
||||||
|
const stopTaskBtnText = _t('tasks.stopTask');
|
||||||
|
|
||||||
item.innerHTML = `
|
item.innerHTML = `
|
||||||
<div class="active-task-info">
|
<div class="active-task-info">
|
||||||
<span class="active-task-status">${statusText}</span>
|
<span class="active-task-status">${statusText}</span>
|
||||||
<span class="active-task-message">${escapeHtml(task.message || '未命名任务')}</span>
|
<span class="active-task-message">${escapeHtml(task.message || unnamedTaskText)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="active-task-actions">
|
<div class="active-task-actions">
|
||||||
${timeText ? `<span class="active-task-time">${timeText}</span>` : ''}
|
${timeText ? `<span class="active-task-time">${timeText}</span>` : ''}
|
||||||
${!isFinalStatus ? '<button class="active-task-cancel">停止任务</button>' : ''}
|
${!isFinalStatus ? '<button class="active-task-cancel">' + stopTaskBtnText + '</button>' : ''}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -994,7 +1018,7 @@ function renderActiveTasks(tasks) {
|
|||||||
cancelBtn.onclick = () => cancelActiveTask(task.conversationId, cancelBtn);
|
cancelBtn.onclick = () => cancelActiveTask(task.conversationId, cancelBtn);
|
||||||
if (task.status === 'cancelling') {
|
if (task.status === 'cancelling') {
|
||||||
cancelBtn.disabled = true;
|
cancelBtn.disabled = true;
|
||||||
cancelBtn.textContent = '取消中...';
|
cancelBtn.textContent = typeof window.t === 'function' ? window.t('tasks.cancelling') : '取消中...';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1007,14 +1031,14 @@ async function cancelActiveTask(conversationId, button) {
|
|||||||
if (!conversationId) return;
|
if (!conversationId) return;
|
||||||
const originalText = button.textContent;
|
const originalText = button.textContent;
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
button.textContent = '取消中...';
|
button.textContent = typeof window.t === 'function' ? window.t('tasks.cancelling') : '取消中...';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await requestCancel(conversationId);
|
await requestCancel(conversationId);
|
||||||
loadActiveTasks();
|
loadActiveTasks();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('取消任务失败:', error);
|
console.error('取消任务失败:', error);
|
||||||
alert('取消任务失败: ' + error.message);
|
alert((typeof window.t === 'function' ? window.t('tasks.cancelTaskFailed') : '取消任务失败') + ': ' + error.message);
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
button.textContent = originalText;
|
button.textContent = originalText;
|
||||||
}
|
}
|
||||||
@@ -1522,14 +1546,14 @@ function updateBatchActionsState() {
|
|||||||
if (batchActions) {
|
if (batchActions) {
|
||||||
batchActions.style.display = 'flex';
|
batchActions.style.display = 'flex';
|
||||||
}
|
}
|
||||||
if (selectedCountSpan) {
|
|
||||||
selectedCountSpan.textContent = typeof window.t === 'function' ? window.t('mcp.selectedCount', { count: selectedCount }) : `已选择 ${selectedCount} 项`;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (batchActions) {
|
if (batchActions) {
|
||||||
batchActions.style.display = 'none';
|
batchActions.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (selectedCountSpan) {
|
||||||
|
selectedCountSpan.textContent = typeof window.t === 'function' ? window.t('mcp.selectedCount', { count: selectedCount }) : '已选择 ' + selectedCount + ' 项';
|
||||||
|
}
|
||||||
|
|
||||||
// 更新全选复选框状态
|
// 更新全选复选框状态
|
||||||
const selectAllCheckbox = document.getElementById('monitor-select-all');
|
const selectAllCheckbox = document.getElementById('monitor-select-all');
|
||||||
@@ -1655,3 +1679,7 @@ function formatExecutionDuration(start, end) {
|
|||||||
}
|
}
|
||||||
return typeof window.t === 'function' ? window.t('mcpMonitor.durationHoursOnly', { hours: hours }) : hours + ' 小时';
|
return typeof window.t === 'function' ? window.t('mcpMonitor.durationHoursOnly', { hours: hours }) : hours + ' 小时';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('languagechange', function () {
|
||||||
|
updateBatchActionsState();
|
||||||
|
});
|
||||||
|
|||||||
@@ -1433,6 +1433,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
updateRoleSelectorDisplay();
|
updateRoleSelectorDisplay();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 语言切换后刷新角色选择器显示(默认/自定义角色名)
|
||||||
|
document.addEventListener('languagechange', () => {
|
||||||
|
updateRoleSelectorDisplay();
|
||||||
|
});
|
||||||
|
|
||||||
// 获取当前选中的角色(供chat.js使用)
|
// 获取当前选中的角色(供chat.js使用)
|
||||||
function getCurrentRole() {
|
function getCurrentRole() {
|
||||||
return currentRole || '';
|
return currentRole || '';
|
||||||
|
|||||||
+30
-15
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>API 文档 - CyberStrikeAI</title>
|
<title data-i18n="apiDocs.pageTitle">API 文档 - CyberStrikeAI</title>
|
||||||
<link rel="icon" type="image/png" href="/static/logo.png">
|
<link rel="icon" type="image/png" href="/static/logo.png">
|
||||||
<link rel="stylesheet" href="/static/css/style.css">
|
<link rel="stylesheet" href="/static/css/style.css">
|
||||||
<style>
|
<style>
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.api-docs-header {
|
.api-docs-header {
|
||||||
|
position: relative;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
border-bottom: 2px solid var(--border-color);
|
border-bottom: 2px solid var(--border-color);
|
||||||
@@ -833,9 +834,21 @@
|
|||||||
<line x1="16" y1="13" x2="8" y2="13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<line x1="16" y1="13" x2="8" y2="13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<line x1="16" y1="17" x2="8" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<line x1="16" y1="17" x2="8" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
API 文档
|
<span data-i18n="apiDocs.title">API 文档</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p>CyberStrikeAI 平台 API 接口文档,支持在线测试</p>
|
<p data-i18n="apiDocs.subtitle">CyberStrikeAI 平台 API 接口文档,支持在线测试</p>
|
||||||
|
<div class="api-docs-lang-switcher" style="position: absolute; top: 24px; right: 24px;">
|
||||||
|
<div class="lang-switcher">
|
||||||
|
<button type="button" class="btn-secondary lang-switcher-btn" onclick="typeof toggleLangDropdown === 'function' && toggleLangDropdown()" title="界面语言">
|
||||||
|
<span class="lang-switcher-icon">🌐</span>
|
||||||
|
<span id="current-lang-label">中文</span>
|
||||||
|
</button>
|
||||||
|
<div id="lang-dropdown" class="lang-dropdown" style="display: none;">
|
||||||
|
<div class="lang-option" data-lang="zh-CN" onclick="typeof onLanguageSelect === 'function' && onLanguageSelect('zh-CN')">中文</div>
|
||||||
|
<div class="lang-option" data-lang="en-US" onclick="typeof onLanguageSelect === 'function' && onLanguageSelect('en-US')">English</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="auth-info-section" class="auth-info-section" style="display: none;">
|
<div id="auth-info-section" class="auth-info-section" style="display: none;">
|
||||||
@@ -846,17 +859,17 @@
|
|||||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
|
||||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
<path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||||
</svg>
|
</svg>
|
||||||
<h3 style="margin: 0; font-size: 1rem; font-weight: 600; color: var(--text-primary);">API 认证说明</h3>
|
<h3 style="margin: 0; font-size: 1rem; font-weight: 600; color: var(--text-primary);" data-i18n="apiDocs.authTitle">API 认证说明</h3>
|
||||||
</div>
|
</div>
|
||||||
<svg id="auth-info-arrow" class="auth-info-arrow" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="transition: transform 0.2s ease;">
|
<svg id="auth-info-arrow" class="auth-info-arrow" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="transition: transform 0.2s ease;">
|
||||||
<polyline points="6 9 12 15 18 9"/>
|
<polyline points="6 9 12 15 18 9"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div id="auth-info-body" class="auth-info-body" style="display: none; color: var(--text-secondary); font-size: 0.875rem; line-height: 1.6; margin-top: 16px;">
|
<div id="auth-info-body" class="auth-info-body" style="display: none; color: var(--text-secondary); font-size: 0.875rem; line-height: 1.6; margin-top: 16px;">
|
||||||
<p style="margin: 0 0 12px 0;"><strong>所有 API 接口都需要 Token 认证。</strong></p>
|
<p style="margin: 0 0 12px 0;"><strong data-i18n="apiDocs.authAllNeedToken">所有 API 接口都需要 Token 认证。</strong></p>
|
||||||
<div style="background: var(--bg-secondary); padding: 12px; border-radius: 6px; margin-bottom: 12px;">
|
<div style="background: var(--bg-secondary); padding: 12px; border-radius: 6px; margin-bottom: 12px;">
|
||||||
<p style="margin: 0 0 8px 0; font-weight: 500;">1. 获取 Token:</p>
|
<p style="margin: 0 0 8px 0; font-weight: 500;" data-i18n="apiDocs.authGetToken">1. 获取 Token:</p>
|
||||||
<p style="margin: 0 0 8px 0;">在前端页面登录后,Token 会自动保存。您也可以通过以下方式获取:</p>
|
<p style="margin: 0 0 8px 0;" data-i18n="apiDocs.authGetTokenDesc">在前端页面登录后,Token 会自动保存。您也可以通过以下方式获取:</p>
|
||||||
<pre style="background: var(--bg-primary); padding: 8px; border-radius: 4px; margin: 8px 0; overflow-x: auto; font-size: 0.8125rem;"><code>POST /api/auth/login
|
<pre style="background: var(--bg-primary); padding: 8px; border-radius: 4px; margin: 8px 0; overflow-x: auto; font-size: 0.8125rem;"><code>POST /api/auth/login
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
@@ -871,13 +884,13 @@ Content-Type: application/json
|
|||||||
}</code></pre>
|
}</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div style="background: var(--bg-secondary); padding: 12px; border-radius: 6px; margin-bottom: 12px;">
|
<div style="background: var(--bg-secondary); padding: 12px; border-radius: 6px; margin-bottom: 12px;">
|
||||||
<p style="margin: 0 0 8px 0; font-weight: 500;">2. 使用 Token:</p>
|
<p style="margin: 0 0 8px 0; font-weight: 500;" data-i18n="apiDocs.authUseToken">2. 使用 Token:</p>
|
||||||
<p style="margin: 0 0 8px 0;">在请求头中添加 Authorization 字段:</p>
|
<p style="margin: 0 0 8px 0;" data-i18n="apiDocs.authUseTokenDesc">在请求头中添加 Authorization 字段:</p>
|
||||||
<pre style="background: var(--bg-primary); padding: 8px; border-radius: 4px; margin: 8px 0; overflow-x: auto; font-size: 0.8125rem;"><code>Authorization: Bearer your_token_here</code></pre>
|
<pre style="background: var(--bg-primary); padding: 8px; border-radius: 4px; margin: 8px 0; overflow-x: auto; font-size: 0.8125rem;"><code>Authorization: Bearer your_token_here</code></pre>
|
||||||
<p style="margin: 8px 0 0 0; font-size: 0.8125rem; color: var(--text-muted);">💡 提示:本页面会自动使用您已登录的 Token,无需手动填写。</p>
|
<p style="margin: 8px 0 0 0; font-size: 0.8125rem; color: var(--text-muted);" data-i18n="apiDocs.authTip">💡 提示:本页面会自动使用您已登录的 Token,无需手动填写。</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="token-status" style="display: none; background: rgba(0, 102, 255, 0.1); padding: 8px 12px; border-radius: 6px; border-left: 3px solid var(--accent-color);">
|
<div id="token-status" style="display: none; background: rgba(0, 102, 255, 0.1); padding: 8px 12px; border-radius: 6px; border-left: 3px solid var(--accent-color);">
|
||||||
<p style="margin: 0; font-size: 0.8125rem; color: var(--accent-color);">
|
<p style="margin: 0; font-size: 0.8125rem; color: var(--accent-color);" data-i18n="apiDocs.tokenDetected">
|
||||||
<strong>✓ 已检测到 Token</strong> - 您可以直接测试 API 接口
|
<strong>✓ 已检测到 Token</strong> - 您可以直接测试 API 接口
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -899,10 +912,10 @@ Content-Type: application/json
|
|||||||
|
|
||||||
<div class="api-docs-content">
|
<div class="api-docs-content">
|
||||||
<div class="api-docs-sidebar">
|
<div class="api-docs-sidebar">
|
||||||
<h3>API 分组</h3>
|
<h3 data-i18n="apiDocs.sidebarGroupTitle">API 分组</h3>
|
||||||
<ul class="api-group-list" id="api-group-list">
|
<ul class="api-group-list" id="api-group-list">
|
||||||
<li class="api-group-item">
|
<li class="api-group-item">
|
||||||
<a href="#" class="api-group-link active" data-group="all">全部接口</a>
|
<a href="#" class="api-group-link active" data-group="all" data-i18n="apiDocs.allApis">全部接口</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -914,13 +927,15 @@ Content-Type: application/json
|
|||||||
<line x1="12" y1="8" x2="12" y2="12"/>
|
<line x1="12" y1="8" x2="12" y2="12"/>
|
||||||
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
<line x1="12" y1="16" x2="12.01" y2="16"/>
|
||||||
</svg>
|
</svg>
|
||||||
<h3>加载中...</h3>
|
<h3 data-i18n="apiDocs.loading">加载中...</h3>
|
||||||
<p>正在加载 API 文档</p>
|
<p data-i18n="apiDocs.loadingDesc">正在加载 API 文档</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/i18next@23.11.5/i18next.min.js"></script>
|
||||||
|
<script src="/static/js/i18n.js"></script>
|
||||||
<script src="/static/js/api-docs.js"></script>
|
<script src="/static/js/api-docs.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user