From f793cb3cddc9144d12eb9cd38d1fd62df3613c25 Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Wed, 1 Apr 2026 17:22:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=A1=B5=E9=9D=A2=E5=92=8CSQL=E5=AE=9E?= =?UTF-8?q?=E9=AA=8C=E5=AE=A4=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 bai-chat-index.html 中实现了对话测试功能,支持用户与AI的交互,包含消息发送、接收和显示。 - 在 sql-lab-index.html 中实现了SQL实验室功能,支持数据库表的查询和显示,包含表结构探测、SQL语句执行和结果展示。 - 添加了动态样式和交互效果,提升用户体验。 --- pocket-base/bai-web-main.pb.js | 3 + .../bai-ai-manage-main.pb.js | 122 +++++ pocket-base/bai_chat_alm_hooks/bai-chat.pb.js | 62 +++ .../bai_chat_alm_hooks/bai-sql-lab.pb.js | 79 +++ .../views/ai-manage-index.html | 464 ++++++++++++++++++ .../views/bai-chat-index.html | 203 ++++++++ .../views/sql-lab-index.html | 356 ++++++++++++++ pocket-base/bai_web_pb_hooks/pages/index.js | 58 ++- .../bai_web_pb_hooks/pages/product-manage.js | 46 +- 9 files changed, 1374 insertions(+), 19 deletions(-) create mode 100644 pocket-base/bai_chat_alm_hooks/bai-ai-manage-main.pb.js create mode 100644 pocket-base/bai_chat_alm_hooks/bai-chat.pb.js create mode 100644 pocket-base/bai_chat_alm_hooks/bai-sql-lab.pb.js create mode 100644 pocket-base/bai_chat_alm_hooks/views/ai-manage-index.html create mode 100644 pocket-base/bai_chat_alm_hooks/views/bai-chat-index.html create mode 100644 pocket-base/bai_chat_alm_hooks/views/sql-lab-index.html diff --git a/pocket-base/bai-web-main.pb.js b/pocket-base/bai-web-main.pb.js index c51b5f1..699a94a 100644 --- a/pocket-base/bai-web-main.pb.js +++ b/pocket-base/bai-web-main.pb.js @@ -4,3 +4,6 @@ require(`${__hooks}/bai_web_pb_hooks/pages/document-manage.js`) require(`${__hooks}/bai_web_pb_hooks/pages/product-manage.js`) require(`${__hooks}/bai_web_pb_hooks/pages/dictionary-manage.js`) require(`${__hooks}/bai_web_pb_hooks/pages/sdk-permission-manage.js`) +require(`${__hooks}/bai_chat_alm_hooks/bai-ai-manage-main.pb.js`) +require(`${__hooks}/bai_chat_alm_hooks/bai-chat.pb.js`) +require(`${__hooks}/bai_chat_alm_hooks/bai-sql-lab.pb.js`) diff --git a/pocket-base/bai_chat_alm_hooks/bai-ai-manage-main.pb.js b/pocket-base/bai_chat_alm_hooks/bai-ai-manage-main.pb.js new file mode 100644 index 0000000..a646783 --- /dev/null +++ b/pocket-base/bai_chat_alm_hooks/bai-ai-manage-main.pb.js @@ -0,0 +1,122 @@ +// ============================================================================== +// 模块: BAI 业务审计子系统 (v1.8 完整功能稳态版) +// ============================================================================== + +routerAdd("GET", "/bai-ai-manage", (c) => { + try { + const html = $template.loadFiles(__hooks + "/bai_chat_alm_hooks/views/ai-manage-index.html").render({}); + const safeHtml = String(html || "") + const guardScript = '' + const guardedHtml = safeHtml.indexOf("") !== -1 + ? safeHtml.replace("", guardScript + "") + : guardScript + safeHtml + + return c.html(200, guardedHtml); + } catch (err) { + return c.json(500, { message: "HTML 模板渲染失败: " + err.message }); + } +}); + +// 1. 获取用户列表 +routerAdd("GET", "/pb-api-v1/audit/users", (c) => { + try { + const info = c.requestInfo(); + const days = parseInt(info.query["days"] || "0"); + + // 保持原有的时间过滤逻辑 + let timeClause = (days > 0) ? `AND c.createdAt > ${new Date().getTime() - (days * 24 * 60 * 1000)}` : ""; + + // 【核心修正】SQL 采用 LEFT JOIN 确保 thread_id 为空的 API 对话不被漏掉 + // 注意:这里去掉了可能引起语法错误的注释,使用了探针兼容的 SQL + const sql = ` + SELECT + COALESCE(t.slug, t.name, 'API_USER') as phone, + COUNT(c.id) as chat_count, + MAX(c.createdAt) as last_active + FROM workspace_chats c + LEFT JOIN workspace_threads t ON c.thread_id = t.id + WHERE 1=1 ${timeClause} + GROUP BY phone + ORDER BY last_active DESC + `; + + // 保持对 3010 探针的调用 + const res = $http.send({ + url: "http://bai-alm-audit-api:3010/api/v1/audit/query", + method: "POST", + body: JSON.stringify({ "sql": sql, "params": [] }), + headers: { "Content-Type": "application/json" } + }); + + // 保持原有的 PB 用户姓名映射逻辑 + let phoneToNameMap = {}; + const pbRecords = $app.findAllRecords("tbl_auth_users"); + pbRecords.forEach(r => { + phoneToNameMap[r.getString("users_phone")] = r.getString("users_name"); + }); + + const data = (res.json.data || []).map(u => ({ + ...u, + name: phoneToNameMap[u.phone] || (u.phone === 'API_USER' ? '微信测试用户' : "") + })); + + return c.json(200, { code: 200, data }); + } catch (err) { + return c.json(500, { message: "Users API Error: " + err.message }); + } +}); + +// 2. 获取流水记录 +routerAdd("GET", "/pb-api-v1/audit/chat-list", (c) => { + try { + const info = c.requestInfo(); + const phone = info.query["phone"]; + const page = parseInt(info.query["page"] || "1"); + const days = parseInt(info.query["days"] || "0"); + const offset = (page - 1) * 50; + + let timeClause = (days > 0) ? `AND c.createdAt > ${new Date().getTime() - (days * 24 * 60 * 60 * 1000)}` : ""; + + // 【核心修正】针对 API_USER 的特殊查询逻辑 + let whereClause = ""; + if (phone === "API_USER") { + whereClause = `WHERE c.thread_id IS NULL`; + } else { + whereClause = `WHERE t.slug = '${phone}' OR t.name = '${phone}'`; + } + + const sql = ` + SELECT c.id, w.name AS workspace_name, c.prompt, c.response, c.createdAt + FROM workspace_chats c + LEFT JOIN workspace_threads t ON c.thread_id = t.id + LEFT JOIN workspaces w ON c.workspaceId = w.id + ${whereClause} ${timeClause} + ORDER BY c.createdAt DESC + LIMIT 50 OFFSET ${offset} + `; + + const res = $http.send({ + url: "http://bai-alm-audit-api:3010/api/v1/audit/query", + method: "POST", + body: JSON.stringify({ "sql": sql, "params": [] }), + headers: { "Content-Type": "application/json" } + }); + + return c.json(200, { code: 200, data: res.json.data || [] }); + } catch (err) { + return c.json(500, { message: "Chat List Error: " + err.message }); + } +}); + +// 3. 全量导出保持不变 +routerAdd("GET", "/pb-api-v1/audit/export-all", (c) => { + try { + const info = c.requestInfo(); + const phone = info.query["phone"]; + let whereClause = (phone === "API_USER") ? "WHERE c.thread_id IS NULL" : `WHERE t.slug = '${phone}' OR t.name = '${phone}'`; + + const sql = `SELECT c.*, w.name AS workspace_name FROM workspace_chats c LEFT JOIN workspace_threads t ON c.thread_id = t.id LEFT JOIN workspaces w ON c.workspaceId = w.id ${whereClause} ORDER BY c.createdAt DESC;`; + const res = $http.send({ url: "http://bai-alm-audit-api:3010/api/v1/audit/query", method: "POST", body: JSON.stringify({ "sql": sql, "params": [] }), headers: { "Content-Type": "application/json" } }); + return c.json(200, { code: 200, data: res.json.data || [] }); + } catch (err) { return c.json(500, { message: err.message }); } +}); \ No newline at end of file diff --git a/pocket-base/bai_chat_alm_hooks/bai-chat.pb.js b/pocket-base/bai_chat_alm_hooks/bai-chat.pb.js new file mode 100644 index 0000000..e25c793 --- /dev/null +++ b/pocket-base/bai_chat_alm_hooks/bai-chat.pb.js @@ -0,0 +1,62 @@ +// ============================================================================== +// 模块: BAI 微信风聊天测试页 (v1.4 修复版) +// ============================================================================== + +// 1. 渲染页面路由 +routerAdd("GET", "/bai-chat", (c) => { + try { + // 确保 views/bai-chat-index.html 路径存在 + const html = $template.loadFiles(__hooks + "/bai_chat_alm_hooks/views/bai-chat-index.html").render({}); + const safeHtml = String(html || "") + const guardScript = '' + const guardedHtml = safeHtml.indexOf("") !== -1 + ? safeHtml.replace("", guardScript + "") + : guardScript + safeHtml + return c.html(200, guardedHtml); + } catch (e) { + return c.json(500, { "message": "HTML 模板渲染失败: " + e.message }); + } +}); + +// 2. 聊天转发路由 +routerAdd("POST", "/pb-api-v1/chat/send", (c) => { + try { + const info = c.requestInfo(); + // 增加安全取值逻辑 + const body = info.body || {}; + const userMsg = body.message || ""; + + if (!userMsg) { + return c.json(400, { "message": "消息内容不能为空" }); + } + + // 使用 3001 端口直连 AnythingLLM + // 如果 8888 还没创建成功,API 会自动回落到默认线程,保证不报错 + const res = $http.send({ + url: "http://bai-anythingllm:3001/api/v1/workspace/ai/chat", + method: "POST", + body: JSON.stringify({ + "message": userMsg, + "mode": "chat", + "sessionId": "8888" + }), + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer SEC082V-99D48HT-GB96EZ5-BS34AMS" + }, + timeout: 120 + }); + + // 解析返回结果 + const resData = res.json || {}; + const replyText = resData.textResponse || resData.content || resData.text || "AI 未能返回有效内容"; + + return c.json(200, { + "code": 200, + "reply": replyText + }); + + } catch (err) { + return c.json(500, { "message": "转发请求失败: " + err.message }); + } +}); \ No newline at end of file diff --git a/pocket-base/bai_chat_alm_hooks/bai-sql-lab.pb.js b/pocket-base/bai_chat_alm_hooks/bai-sql-lab.pb.js new file mode 100644 index 0000000..5c45ca5 --- /dev/null +++ b/pocket-base/bai_chat_alm_hooks/bai-sql-lab.pb.js @@ -0,0 +1,79 @@ +// ============================================================================== +// 模块: BAI SQL 实验室 (底层探勘工具) - 拆分独立版 +// 路由: /bai-ai-sql-lab +// 注意: 绝不能引入 dynamic_model,防止引擎崩溃 +// ============================================================================== + +// 1. 页面 UI 挂载 (路径更新为 views 目录) +routerAdd("GET", "/bai-ai-sql-lab", (c) => { + try { + const html = $template.loadFiles(__hooks + "/bai_chat_alm_hooks/views/sql-lab-index.html").render({}); + const safeHtml = String(html || "") + const guardScript = '' + const guardedHtml = safeHtml.indexOf("") !== -1 + ? safeHtml.replace("", guardScript + "") + : guardScript + safeHtml + return c.html(200, guardedHtml); + } catch (err) { + return c.json(500, { "error": "Template Error: " + err.message }); + } +}); + +// 2. 全链路体检探针 (保持原样,属于 SQL Lab 的基础能力) +routerAdd("GET", "/pb-api-v1/diagnostics", (c) => { + let report = { pb_status: "PocketBase OK", py_network: "WAIT", sqlite_storage: "WAIT", sqlite_version: "" }; + try { + const res1 = $http.send({ url: "http://bai-alm-audit-api:3010/api/v1/audit/py-alive-check", method: "GET", timeout: 5 }); + report.py_network = res1.statusCode === 200 ? "ALIVE" : "ERROR"; + + const res2 = $http.send({ url: "http://bai-alm-audit-api:3010/api/v1/audit/sqlite-read-check", method: "GET", timeout: 5 }); + report.sqlite_storage = res2.statusCode === 200 ? "READY" : "ERROR"; + + if (res2.statusCode === 200 && res2.json.message) { + const match = res2.json.message.match(/版本:\s*([\d\.]+)/); + report.sqlite_version = match ? match[1] : "未知"; + report.sqlite_detail = res2.json.message; + } else { + report.sqlite_detail = res2.raw; + } + + return c.json(200, report); + } catch (err) { + report.py_network = "ERROR"; + report.sqlite_storage = "ERROR"; + report.sqlite_detail = err.message; + return c.json(500, report); + } +}); + +// 3. 万能 SQL 执行透传通道 (彻底修复 Body 读取方式) +routerAdd("POST", "/pb-api-v1/sql-ops", (c) => { + try { + // PB v0.23+ 官方推荐的无依赖 Body 读取方式 + const reqBody = c.requestInfo().body; + const sqlStatement = reqBody.sql || ""; + + if (!sqlStatement) { + return c.json(400, { "message": "SQL 指令不能为空" }); + } + + const res = $http.send({ + url: "http://bai-alm-audit-api:3010/api/v1/audit/query", + method: "POST", + body: JSON.stringify({ "sql": sqlStatement, "params": [] }), + headers: { "Content-Type": "application/json" }, + timeout: 15 + }); + + if (res.statusCode !== 200) { + return c.json(res.statusCode, { + code: res.statusCode, + message: res.json?.detail || res.raw || "探针层执行报错" + }); + } + + return c.json(200, { code: 200, data: res.json.data || [] }); + } catch (err) { + return c.json(500, { code: 500, message: "PB穿透异常: " + err.message }); + } +}); \ No newline at end of file diff --git a/pocket-base/bai_chat_alm_hooks/views/ai-manage-index.html b/pocket-base/bai_chat_alm_hooks/views/ai-manage-index.html new file mode 100644 index 0000000..3b9beb5 --- /dev/null +++ b/pocket-base/bai_chat_alm_hooks/views/ai-manage-index.html @@ -0,0 +1,464 @@ + + + + + + BAI 业务审计控制台 + + + + + + + +
+
+ +
+
+ Audit +
+ +
+ +
+
+ +
+ +
+ + +
+ +
+ +

Select to begin

+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/pocket-base/bai_chat_alm_hooks/views/bai-chat-index.html b/pocket-base/bai_chat_alm_hooks/views/bai-chat-index.html new file mode 100644 index 0000000..2985150 --- /dev/null +++ b/pocket-base/bai_chat_alm_hooks/views/bai-chat-index.html @@ -0,0 +1,203 @@ + + + + + + 对话测试 + + + + + + + +
+
+ + 对话测试 +
+
+ +
+
+ +
+
+ +
+ + + +
+
AI
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/pocket-base/bai_chat_alm_hooks/views/sql-lab-index.html b/pocket-base/bai_chat_alm_hooks/views/sql-lab-index.html new file mode 100644 index 0000000..d42c53e --- /dev/null +++ b/pocket-base/bai_chat_alm_hooks/views/sql-lab-index.html @@ -0,0 +1,356 @@ + + + + + BAI SQL 实验室 - 数据库探勘 + + + + + + +
+
+ BAI SQL LAB + +
+
+ + PYTHON +
+
+ + SQLITE +
+ +
+ + +
+
+
+
+ +
+ + + +
+
+ +
+ 快捷键: Ctrl + Enter 执行 + +
+
+ +
+
+
+ +
+
+ 本页返回: + +
+ +
+ + + + + + + + + +
+
+
+ +
+ SQL 穿透成功,但返回了 0 行数据。 +
+
+ +
+ 当前表: | 第 +
+ + +
+
+
+
+ + + +
+ + + + \ No newline at end of file diff --git a/pocket-base/bai_web_pb_hooks/pages/index.js b/pocket-base/bai_web_pb_hooks/pages/index.js index 3db9d5e..0fc5caa 100644 --- a/pocket-base/bai_web_pb_hooks/pages/index.js +++ b/pocket-base/bai_web_pb_hooks/pages/index.js @@ -19,6 +19,8 @@ routerAdd('GET', '/manage', function (e) { .wrap { max-width: 1440px; margin: 0 auto; padding: 24px 14px; } .hero { background: #ffffff; border-radius: 20px; box-shadow: 0 18px 50px rgba(15, 23, 42, 0.08); padding: 22px; border: 1px solid #e5e7eb; } h1 { margin: 0 0 14px; font-size: 30px; } + .module + .module { margin-top: 18px; } + .module-title { margin: 0 0 10px; font-size: 22px; color: #0f172a; } .grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; } .card { background: #f8fafc; border: 1px solid #dbe3f0; border-radius: 16px; padding: 16px; text-align: center; } .card h2 { margin: 0 0 8px; font-size: 19px; } @@ -33,24 +35,44 @@ routerAdd('GET', '/manage', function (e) {

管理主页

-
- - - - -
+
+

平台管理

+
+ + + + +
+
+
+

AI 管理

+
+ + + +
+
diff --git a/pocket-base/bai_web_pb_hooks/pages/product-manage.js b/pocket-base/bai_web_pb_hooks/pages/product-manage.js index d06a88b..2f84922 100644 --- a/pocket-base/bai_web_pb_hooks/pages/product-manage.js +++ b/pocket-base/bai_web_pb_hooks/pages/product-manage.js @@ -197,13 +197,14 @@ routerAdd('GET', '/manage/product-manage', function (e) {
- +
仅支持 JSON 对象格式。导入时按属性名增量合并:同名覆盖、不同名新增,不会清空当前参数表。
+
@@ -878,6 +879,42 @@ routerAdd('GET', '/manage/product-manage', function (e) { return result } + async function exportParametersToJson() { + const rows = collectParameterArray() + const exportObject = {} + + for (let i = 0; i < rows.length; i += 1) { + const name = normalizeText(rows[i].name) + if (!name) { + continue + } + exportObject[name] = rows[i].value === null || typeof rows[i].value === 'undefined' + ? '' + : String(rows[i].value) + } + + const keys = Object.keys(exportObject) + if (!keys.length) { + setStatus('当前参数表为空,暂无可导出的内容。', 'error') + return + } + + const jsonText = JSON.stringify(exportObject, null, 2) + fields.paramsJsonInput.value = jsonText + + let copied = false + try { + if (navigator && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { + await navigator.clipboard.writeText(jsonText) + copied = true + } + } catch (_error) {} + + setStatus(copied + ? '参数已批量导出,已写入导入框并复制到剪贴板。' + : '参数已批量导出到导入框,可直接复制后用于一键导入。', 'success') + } + function normalizeParameterRows(value) { if (value === null || typeof value === 'undefined' || value === '') { return [] @@ -1367,6 +1404,13 @@ routerAdd('GET', '/manage/product-manage', function (e) { importParametersFromJson() }) + const exportParamsBtn = document.getElementById('exportParamsBtn') + if (exportParamsBtn) { + exportParamsBtn.addEventListener('click', function () { + exportParametersToJson() + }) + } + if (fields.sort) { fields.sort.addEventListener('input', function () { renderSortRankHint()