diff --git a/.gitignore b/.gitignore index b1043e38..ca0e52d4 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,7 @@ Claude.md # 本地测试脚本 configure-ollama.sh -fix-docker.sh \ No newline at end of file +fix-docker.sh + +# WeChat Mini Program local/private configuration +miniprogram/project.private.config.json diff --git a/README.md b/README.md index 096e6df1..22e93f3b 100644 --- a/README.md +++ b/README.md @@ -237,7 +237,7 @@ Fully modular pipeline from document parsing, vectorization, and retrieval to LL | Capability | Details | |------------|---------| | Deployment | Local / Docker / Kubernetes (Helm) with private and offline support | -| UI | Web UI / RESTful API / Chrome Extension | +| UI | Web UI / RESTful API / Chrome Extension / WeChat Mini Program | | Observability | Integrated Langfuse for ReAct loops, token tracking, tool calls, and pipeline tracing | | Task Management | MQ async tasks, automatic database migration on version upgrade | | Model Management | Centralized config, per-knowledge-base model selection, multi-tenant built-in model sharing, WeKnora Cloud hosted models and parsing | @@ -247,6 +247,11 @@ Fully modular pipeline from document parsing, vectorization, and retrieval to LL [**WeKnora Chrome Extension**](https://chromewebstore.google.com/detail/jpemjbopikggjlmikmclgbmkhhopjdgd) lets you capture web content directly into your WeKnora knowledge base. Select text, images, or entire pages in the browser and save them as knowledge entries with one click — no copy-paste or file upload needed. +## 📱 WeChat Mini Program + +The [WeKnora Mini Program](./miniprogram/README.md) provides a lightweight mobile client for configuring WeKnora API access, selecting knowledge bases, importing URLs, and asking knowledge chat from WeChat. + + ## 🦞 ClawHub Skill [**WeKnora ClawHub Skill**](https://clawhub.ai/lyingbug/weknora) is a WeKnora skill published on the ClawHub platform. Once installed, it enables document import (file / URL / Markdown), hybrid search (vector + keyword) across knowledge bases, and knowledge entry management — all through the WeKnora REST API. @@ -255,7 +260,6 @@ Fully modular pipeline from document parsing, vectorization, and retrieval to LL - **Hybrid Search** — Search within or across knowledge bases with vector + keyword retrieval - **Knowledge Management** — List, browse, edit, and delete knowledge entries programmatically - ## 🚀 Getting Started ### 🛠 Prerequisites diff --git a/README_CN.md b/README_CN.md index 81a4946c..2913f033 100644 --- a/README_CN.md +++ b/README_CN.md @@ -235,7 +235,7 @@ | 能力 | 详情 | |------|------| | 部署 | 本地 / Docker / Kubernetes (Helm),支持私有化离线部署 | -| 界面 | Web UI / RESTful API / Chrome Extension| +| 界面 | Web UI / RESTful API / Chrome Extension / 微信小程序 | | 可观测性 | 集成 Langfuse 以追踪 ReAct 循环、Token 消耗、工具调用和任务流水线 | | 任务管理 | MQ 异步任务,版本升级自动数据库迁移 | | 模型管理 | 集中配置,知识库级别模型选择,多租户共享内置模型,WeKnora Cloud 托管模型与文档解析 | @@ -245,6 +245,11 @@ [**WeKnora Chrome 插件**](https://chromewebstore.google.com/detail/jpemjbopikggjlmikmclgbmkhhopjdgd)支持在浏览器中直接将网页内容采集到 WeKnora 知识库。选中文本、图片或整个页面,一键保存为知识条目,无需复制粘贴或手动上传文件。 +## 📱 微信小程序 + +[**WeKnora 微信小程序**](./miniprogram/README.md) 提供轻量移动端客户端,支持配置 WeKnora API、选择知识库、导入 URL,并在微信内向知识库提问。 + + ## 🦞 ClawHub Skill [**WeKnora ClawHub Skill**](https://clawhub.ai/lyingbug/weknora) 是 WeKnora 发布在 ClawHub 平台上的技能。安装后,可通过 WeKnora REST API 上传文档(文件 / URL / Markdown)、执行混合检索(向量 + 关键词)以及管理知识条目。 @@ -253,7 +258,6 @@ - **混合检索** — 在单个或多个知识库中进行向量 + 关键词混合搜索 - **知识管理** — 以编程方式浏览、编辑和删除知识条目 - ## 🚀 快速开始 ### 🛠 环境要求 @@ -405,4 +409,3 @@ WeKnora/ Star History Chart - diff --git a/miniprogram/README.md b/miniprogram/README.md new file mode 100644 index 00000000..823a65f2 --- /dev/null +++ b/miniprogram/README.md @@ -0,0 +1,31 @@ +# WeKnora Mini Program + +This directory contains a WeChat Mini Program plugin for WeKnora. It gives mobile users a lightweight entry point to: + +- configure a WeKnora API endpoint and tenant API key; +- list available knowledge bases; +- import a URL into a selected knowledge base; +- ask a selected knowledge base through WeKnora knowledge chat. + +## Getting started + +1. Open `miniprogram/` in WeChat DevTools. +2. Copy `project.private.config.json.example` to `project.private.config.json` and set your real Mini Program AppID. The shared `project.config.json` intentionally does not include an AppID to avoid forcing maintainers into a placeholder project. +3. Open the **Settings** tab and fill in: + - API Base URL, for example `https://weknora.example.com`; + - API Key from the WeKnora tenant settings page. +4. Open the **Knowledge** tab, refresh knowledge bases, and select the target knowledge base. +5. Import a URL or switch to **Chat** to ask questions. + +## Local development notes + +- WeChat DevTools may block `localhost` requests when URL validation is enabled. For local testing, either disable domain validation in DevTools or expose WeKnora through a HTTPS development domain. +- In production Mini Programs, add the WeKnora API domain to the Mini Program request domain allowlist. +- The chat endpoint returns Server-Sent Events. The Mini Program client parses completed SSE text responses and displays accumulated `answer` chunks. + +## Test + +```bash +cd miniprogram +npm test +``` diff --git a/miniprogram/app.js b/miniprogram/app.js new file mode 100644 index 00000000..9543f8e5 --- /dev/null +++ b/miniprogram/app.js @@ -0,0 +1,12 @@ +App({ + onLaunch() { + const settings = wx.getStorageSync("weknora_settings"); + if (!settings) { + wx.setStorageSync("weknora_settings", { + baseUrl: "http://localhost:8080", + apiKey: "", + selectedKnowledgeBaseId: "" + }); + } + } +}); diff --git a/miniprogram/app.json b/miniprogram/app.json new file mode 100644 index 00000000..b0bd0fb3 --- /dev/null +++ b/miniprogram/app.json @@ -0,0 +1,33 @@ +{ + "pages": [ + "pages/index/index", + "pages/chat/chat", + "pages/settings/settings" + ], + "window": { + "navigationBarTitleText": "WeKnora", + "navigationBarBackgroundColor": "#102a43", + "navigationBarTextStyle": "white", + "backgroundColor": "#f4f7fb" + }, + "tabBar": { + "color": "#667085", + "selectedColor": "#1264a3", + "backgroundColor": "#ffffff", + "borderStyle": "white", + "list": [ + { + "pagePath": "pages/index/index", + "text": "Knowledge" + }, + { + "pagePath": "pages/chat/chat", + "text": "Chat" + }, + { + "pagePath": "pages/settings/settings", + "text": "Settings" + } + ] + } +} diff --git a/miniprogram/app.wxss b/miniprogram/app.wxss new file mode 100644 index 00000000..64500b6f --- /dev/null +++ b/miniprogram/app.wxss @@ -0,0 +1,71 @@ +page { + min-height: 100%; + background: #f4f7fb; + color: #102a43; + font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", sans-serif; +} + +.page { + min-height: 100vh; + box-sizing: border-box; + padding: 32rpx; +} + +.card { + padding: 28rpx; + border-radius: 24rpx; + background: #ffffff; + box-shadow: 0 12rpx 36rpx rgba(16, 42, 67, 0.08); +} + +.title { + font-size: 40rpx; + font-weight: 700; + margin-bottom: 12rpx; +} + +.muted { + color: #667085; + font-size: 26rpx; +} + +.field { + margin-top: 24rpx; +} + +.label { + display: block; + margin-bottom: 12rpx; + color: #344054; + font-size: 26rpx; + font-weight: 600; +} + +input, +textarea, +picker { + box-sizing: border-box; + width: 100%; + min-height: 88rpx; + padding: 20rpx 24rpx; + border: 1rpx solid #d0d5dd; + border-radius: 18rpx; + background: #ffffff; + font-size: 28rpx; +} + +textarea { + min-height: 180rpx; +} + +button { + margin-top: 24rpx; + border-radius: 18rpx; + background: #1264a3; + color: #ffffff; + font-weight: 700; +} + +button[disabled] { + background: #98a2b3; +} diff --git a/miniprogram/package.json b/miniprogram/package.json new file mode 100644 index 00000000..1dfa1218 --- /dev/null +++ b/miniprogram/package.json @@ -0,0 +1,9 @@ +{ + "name": "weknora-miniprogram", + "version": "0.1.0", + "private": true, + "description": "WeChat Mini Program plugin for WeKnora", + "scripts": { + "test": "node --test ../tests/miniprogram/*.test.js" + } +} diff --git a/miniprogram/pages/chat/chat.js b/miniprogram/pages/chat/chat.js new file mode 100644 index 00000000..ad9da0f0 --- /dev/null +++ b/miniprogram/pages/chat/chat.js @@ -0,0 +1,54 @@ +const { getSettings } = require("../../utils/config"); +const { createSession, knowledgeChat } = require("../../utils/request"); +const { collectAnswerFromSSE } = require("../../utils/sse"); + +Page({ + data: { + answer: "", + loading: false, + query: "", + rawResponse: "", + sessionId: "" + }, + + onQueryInput(event) { + this.setData({ query: event.detail.value }); + }, + + async ensureSession() { + if (this.data.sessionId) { + return this.data.sessionId; + } + + const settings = getSettings(); + const response = await createSession(settings.selectedKnowledgeBaseId); + const sessionId = response.data?.id; + if (!sessionId) { + throw new Error("The session API did not return a session id."); + } + this.setData({ sessionId }); + return sessionId; + }, + + async ask() { + this.setData({ answer: "", rawResponse: "", loading: true }); + try { + const sessionId = await this.ensureSession(); + const response = await knowledgeChat(sessionId, this.data.query.trim()); + const rawResponse = typeof response === "string" ? response : JSON.stringify(response); + const answer = collectAnswerFromSSE(rawResponse); + this.setData({ + answer, + rawResponse: answer ? "" : rawResponse + }); + } catch (error) { + wx.showModal({ + title: "Chat failed", + content: error.message, + showCancel: false + }); + } finally { + this.setData({ loading: false }); + } + } +}); diff --git a/miniprogram/pages/chat/chat.json b/miniprogram/pages/chat/chat.json new file mode 100644 index 00000000..baf81b0e --- /dev/null +++ b/miniprogram/pages/chat/chat.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "Chat" +} diff --git a/miniprogram/pages/chat/chat.wxml b/miniprogram/pages/chat/chat.wxml new file mode 100644 index 00000000..f0f0b7dc --- /dev/null +++ b/miniprogram/pages/chat/chat.wxml @@ -0,0 +1,18 @@ + + + Knowledge Chat + Ask the selected WeKnora knowledge base. + + + Question +