From abd188d344197dd976c7737c6d2a1afbdbee25cd Mon Sep 17 00:00:00 2001 From: wolfkill <27876473+wolfkill@users.noreply.github.com> Date: Mon, 27 Apr 2026 19:55:57 +0800 Subject: [PATCH] fix(miniprogram): improve knowledge base selection --- miniprogram/app.json | 8 +- miniprogram/app.wxss | 19 ++-- miniprogram/pages/chat/chat.js | 3 +- miniprogram/pages/chat/chat.wxss | 2 +- miniprogram/pages/index/index.js | 50 ++++++++++- miniprogram/pages/index/index.wxml | 19 +++- miniprogram/pages/index/index.wxss | 52 ++++++++++- miniprogram/utils/request.js | 9 +- tests/miniprogram/miniprogram.test.js | 124 +++++++++++++++++++++++++- 9 files changed, 262 insertions(+), 24 deletions(-) diff --git a/miniprogram/app.json b/miniprogram/app.json index b0bd0fb3..ac8c34a7 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -6,13 +6,13 @@ ], "window": { "navigationBarTitleText": "WeKnora", - "navigationBarBackgroundColor": "#102a43", + "navigationBarBackgroundColor": "#0d3b2a", "navigationBarTextStyle": "white", - "backgroundColor": "#f4f7fb" + "backgroundColor": "#f3f3f3" }, "tabBar": { - "color": "#667085", - "selectedColor": "#1264a3", + "color": "#8b8b8b", + "selectedColor": "#07c05f", "backgroundColor": "#ffffff", "borderStyle": "white", "list": [ diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss index 64500b6f..9fbd9c44 100644 --- a/miniprogram/app.wxss +++ b/miniprogram/app.wxss @@ -1,13 +1,14 @@ page { min-height: 100%; - background: #f4f7fb; - color: #102a43; + background: #f3f3f3; + color: #1a1a1a; font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif; } .page { min-height: 100vh; box-sizing: border-box; + background: linear-gradient(180deg, #f8faf9 0%, #f3f3f3 100%); padding: 32rpx; } @@ -15,7 +16,8 @@ page { padding: 28rpx; border-radius: 24rpx; background: #ffffff; - box-shadow: 0 12rpx 36rpx rgba(16, 42, 67, 0.08); + border: 1rpx solid #e7ebf0; + box-shadow: 0 12rpx 36rpx rgba(7, 192, 95, 0.08); } .title { @@ -25,7 +27,7 @@ page { } .muted { - color: #667085; + color: rgba(0, 0, 0, 0.6); font-size: 26rpx; } @@ -36,7 +38,7 @@ page { .label { display: block; margin-bottom: 12rpx; - color: #344054; + color: rgba(0, 0, 0, 0.9); font-size: 26rpx; font-weight: 600; } @@ -48,9 +50,10 @@ picker { width: 100%; min-height: 88rpx; padding: 20rpx 24rpx; - border: 1rpx solid #d0d5dd; + border: 1rpx solid #dcdcdc; border-radius: 18rpx; background: #ffffff; + color: #1a1a1a; font-size: 28rpx; } @@ -61,11 +64,11 @@ textarea { button { margin-top: 24rpx; border-radius: 18rpx; - background: #1264a3; + background: #07c05f; color: #ffffff; font-weight: 700; } button[disabled] { - background: #98a2b3; + background: #c5c5c5; } diff --git a/miniprogram/pages/chat/chat.js b/miniprogram/pages/chat/chat.js index ad9da0f0..095d6a1e 100644 --- a/miniprogram/pages/chat/chat.js +++ b/miniprogram/pages/chat/chat.js @@ -34,7 +34,8 @@ Page({ this.setData({ answer: "", rawResponse: "", loading: true }); try { const sessionId = await this.ensureSession(); - const response = await knowledgeChat(sessionId, this.data.query.trim()); + const settings = getSettings(); + const response = await knowledgeChat(sessionId, this.data.query.trim(), settings.selectedKnowledgeBaseId); const rawResponse = typeof response === "string" ? response : JSON.stringify(response); const answer = collectAnswerFromSSE(rawResponse); this.setData({ diff --git a/miniprogram/pages/chat/chat.wxss b/miniprogram/pages/chat/chat.wxss index 7ee55715..3e0161f0 100644 --- a/miniprogram/pages/chat/chat.wxss +++ b/miniprogram/pages/chat/chat.wxss @@ -5,6 +5,6 @@ .answer { white-space: pre-wrap; line-height: 1.7; - color: #102a43; + color: #1a1a1a; font-size: 28rpx; } diff --git a/miniprogram/pages/index/index.js b/miniprogram/pages/index/index.js index 5faa802f..7013c9bd 100644 --- a/miniprogram/pages/index/index.js +++ b/miniprogram/pages/index/index.js @@ -1,22 +1,43 @@ const { saveSettings, getSettings } = require("../../utils/config"); const { createKnowledgeFromURL, listKnowledgeBases } = require("../../utils/request"); +function normalizeKnowledgeBases(response) { + if (Array.isArray(response?.data)) { + return response.data; + } + if (Array.isArray(response?.data?.list)) { + return response.data.list; + } + if (Array.isArray(response?.knowledge_bases)) { + return response.knowledge_bases; + } + return []; +} + Page({ data: { importing: false, knowledgeBases: [], + knowledgeBaseNames: [], loading: false, + needsSettings: false, selectedIndex: 0, selectedKnowledgeBaseId: "", selectedKnowledgeBaseName: "", + statusMessage: "", url: "" }, onShow() { const settings = getSettings(); + const needsSettings = !settings.baseUrl || !settings.apiKey; if (settings.selectedKnowledgeBaseId) { this.setData({ selectedKnowledgeBaseId: settings.selectedKnowledgeBaseId }); } + this.setData({ needsSettings }); + if (needsSettings) { + return; + } this.loadKnowledgeBases(); }, @@ -26,6 +47,14 @@ Page({ onKnowledgeBaseChange(event) { const selectedIndex = Number(event.detail.value); + this.selectKnowledgeBase(selectedIndex); + }, + + onKnowledgeBaseTap(event) { + this.selectKnowledgeBase(Number(event.currentTarget.dataset.index)); + }, + + selectKnowledgeBase(selectedIndex) { const selected = this.data.knowledgeBases[selectedIndex]; if (!selected) return; @@ -37,11 +66,22 @@ Page({ }); }, + openSettings() { + wx.switchTab({ url: "/pages/settings/settings" }); + }, + async loadKnowledgeBases() { - this.setData({ loading: true }); + const settings = getSettings(); + if (!settings.baseUrl || !settings.apiKey) { + this.setData({ needsSettings: true }); + return; + } + + this.setData({ loading: true, statusMessage: "" }); try { const response = await listKnowledgeBases(); - const knowledgeBases = response.data || []; + const knowledgeBases = normalizeKnowledgeBases(response); + const knowledgeBaseNames = knowledgeBases.map((item) => item.name || item.id); const settings = getSettings(); const selectedIndex = Math.max( 0, @@ -50,9 +90,13 @@ Page({ const selected = knowledgeBases[selectedIndex]; this.setData({ knowledgeBases, + knowledgeBaseNames, selectedIndex, selectedKnowledgeBaseId: selected?.id || "", - selectedKnowledgeBaseName: selected?.name || "" + selectedKnowledgeBaseName: selected?.name || "", + statusMessage: knowledgeBases.length + ? `Loaded ${knowledgeBases.length} knowledge bases.` + : "No knowledge bases found." }); if (selected?.id) { saveSettings({ selectedKnowledgeBaseId: selected.id }); diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index fd1c1734..8fe26476 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -5,11 +5,28 @@ + + Configure the WeKnora API base URL and API key before loading knowledge bases. + + + Knowledge Base - + {{selectedKnowledgeBaseName || "Tap to select"}} + {{statusMessage}} + + + {{item}} + + diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss index f5b790e7..df53beb0 100644 --- a/miniprogram/pages/index/index.wxss +++ b/miniprogram/pages/index/index.wxss @@ -1,5 +1,5 @@ .hero { - background: linear-gradient(135deg, #102a43 0%, #1264a3 100%); + background: linear-gradient(135deg, #0d3b2a 0%, #05a04f 54%, #07c05f 100%); color: #ffffff; } @@ -11,6 +11,52 @@ margin-top: 28rpx; } -.picker-value { - color: #102a43; +.notice { + box-sizing: border-box; + padding: 24rpx; + border: 1rpx solid #d0f2de; + border-radius: 18rpx; + background: #e9f8ec; + color: #1f5a3f; + font-size: 26rpx; + line-height: 1.5; +} + +.picker-value { + color: #1a1a1a; +} + +.status-message { + margin-top: 12rpx; + color: #6b7280; + font-size: 24rpx; +} + +.knowledge-list { + display: flex; + flex-direction: column; + gap: 12rpx; + margin-top: 16rpx; +} + +.knowledge-item { + padding: 18rpx 20rpx; + border: 1rpx solid #dcdcdc; + border-radius: 14rpx; + background: #ffffff; + color: #1a1a1a; + font-size: 26rpx; +} + +.knowledge-item.active { + border-color: #07c05f; + background: #e9f8ec; + color: #05a04f; + font-weight: 600; +} + +.secondary { + background: #ffffff; + color: #07c05f; + border: 1rpx solid #b8ebcc; } diff --git a/miniprogram/utils/request.js b/miniprogram/utils/request.js index d0fbe97a..b53ef96f 100644 --- a/miniprogram/utils/request.js +++ b/miniprogram/utils/request.js @@ -55,10 +55,15 @@ function createSession(knowledgeBaseId) { }); } -function knowledgeChat(sessionId, query) { +function knowledgeChat(sessionId, query, knowledgeBaseId) { + const data = { query }; + if (knowledgeBaseId) { + data.knowledge_base_ids = [knowledgeBaseId]; + } + return request(`/api/v1/knowledge-chat/${sessionId}`, { method: "POST", - data: { query } + data }); } diff --git a/tests/miniprogram/miniprogram.test.js b/tests/miniprogram/miniprogram.test.js index 29d4648e..4c61657f 100644 --- a/tests/miniprogram/miniprogram.test.js +++ b/tests/miniprogram/miniprogram.test.js @@ -1,6 +1,6 @@ const assert = require("node:assert/strict"); const test = require("node:test"); -const { createKnowledgeFromURL, listKnowledgeBases } = require("../../miniprogram/utils/request"); +const { createKnowledgeFromURL, knowledgeChat, listKnowledgeBases } = require("../../miniprogram/utils/request"); const { collectAnswerFromSSE, parseSSE } = require("../../miniprogram/utils/sse"); const { normalizeBaseUrl } = require("../../miniprogram/utils/config"); @@ -84,3 +84,125 @@ test("URL import helper posts the selected URL payload", async () => { enable_multimodel: true }); }); + +test("chat helper includes selected knowledge base ids", async () => { + let capturedRequest; + global.wx = { + getStorageSync() { + return { + apiKey: "sk-test", + baseUrl: "https://weknora.example.com" + }; + }, + request(options) { + capturedRequest = options; + options.success({ + statusCode: 200, + data: "event: message\ndata: {}\n\n" + }); + } + }; + + await knowledgeChat("session-1", "hello", "kb-1"); + + assert.equal(capturedRequest.method, "POST"); + assert.equal(capturedRequest.url, "https://weknora.example.com/api/v1/knowledge-chat/session-1"); + assert.deepEqual(capturedRequest.data, { + query: "hello", + knowledge_base_ids: ["kb-1"] + }); +}); + +test("knowledge page skips API loading until settings are configured", async () => { + const calls = []; + const pageDefinitions = []; + const originalPage = global.Page; + const originalWx = global.wx; + + try { + global.Page = (definition) => { + pageDefinitions.push(definition); + }; + global.wx = { + getStorageSync() { + return {}; + }, + request() { + calls.push("request"); + }, + switchTab() {} + }; + + delete require.cache[require.resolve("../../miniprogram/pages/index/index.js")]; + require("../../miniprogram/pages/index/index.js"); + const page = { + data: { ...pageDefinitions[0].data }, + setData(nextData) { + this.data = { ...this.data, ...nextData }; + } + }; + + await pageDefinitions[0].onShow.call(page); + + assert.equal(page.data.needsSettings, true); + assert.deepEqual(calls, []); + } finally { + global.Page = originalPage; + global.wx = originalWx; + } +}); + +test("knowledge page maps API results to picker labels", async () => { + const pageDefinitions = []; + const originalPage = global.Page; + const originalWx = global.wx; + let savedSettings; + + try { + global.Page = (definition) => { + pageDefinitions.push(definition); + }; + global.wx = { + getStorageSync() { + return { + apiKey: "sk-test", + baseUrl: "https://weknora.example.com" + }; + }, + request(options) { + options.success({ + statusCode: 200, + data: { + data: [ + { id: "kb-1", name: "Compliance KB" }, + { id: "kb-2", name: "Docs KB" } + ] + } + }); + }, + setStorageSync(key, value) { + savedSettings = { key, value }; + }, + switchTab() {} + }; + + delete require.cache[require.resolve("../../miniprogram/pages/index/index.js")]; + require("../../miniprogram/pages/index/index.js"); + const page = { + data: { ...pageDefinitions[0].data }, + setData(nextData) { + this.data = { ...this.data, ...nextData }; + } + }; + + await pageDefinitions[0].loadKnowledgeBases.call(page); + + assert.deepEqual(page.data.knowledgeBaseNames, ["Compliance KB", "Docs KB"]); + assert.equal(page.data.selectedKnowledgeBaseId, "kb-1"); + assert.equal(page.data.selectedKnowledgeBaseName, "Compliance KB"); + assert.equal(savedSettings.value.selectedKnowledgeBaseId, "kb-1"); + } finally { + global.Page = originalPage; + global.wx = originalWx; + } +});