@puwenhui/dsh-rag-kb 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -2
- package/USAGE.zh.md +110 -0
- package/cordis.patch.yml +4 -0
- package/lib/client.js +443 -0
- package/lib/plugin.mjs +793 -0
- package/package.json +55 -4
package/README.md
CHANGED
|
@@ -1,3 +1,86 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @puwenhui/dsh-rag-kb
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DSH RAG 知识库插件:**文档导入 → 智能切块 → 本地向量化 → 混合检索(向量+FTS5)→ Reranker 精排 → 引用溯源**。
|
|
4
|
+
|
|
5
|
+
> 面向公司非技术人员的使用帮助请阅读 **[USAGE.zh.md](./USAGE.zh.md)**。
|
|
6
|
+
|
|
7
|
+
## 核心特性
|
|
8
|
+
|
|
9
|
+
- **混合检索**:语义向量(bge-small-zh)+ FTS5 trigram 关键词双路合并,同义/改述和精确词/错误码都能找到
|
|
10
|
+
- **Reranker 精排**(可选):bge-reranker-base cross-encoder,对混合检索结果做语义精排,噪音过滤更准
|
|
11
|
+
- **GPU 加速**:DirectML(RTX 4060 实测批量索引快 4 倍),无 GPU 自动回退 CPU
|
|
12
|
+
- **引用溯源**:脚注式引用(正文上标¹²³ + 末尾参考来源清单),agent 回答自带出处
|
|
13
|
+
- **会话语义管理**:所有操作(添加/删除/保存/检索)直接在对话里用大白话完成
|
|
14
|
+
- **多格式支持**:pdf / docx / md / txt / csv / json
|
|
15
|
+
- **完全本地**:模型本地运行,数据不出机器
|
|
16
|
+
- **目录自动索引**:配置目录后文件放入/变更自动入库
|
|
17
|
+
- **对话内容保存**:把对话中的结论/总结一键存入知识库
|
|
18
|
+
|
|
19
|
+
## 版本要求
|
|
20
|
+
|
|
21
|
+
- **DSH ≥ 0.2.0**
|
|
22
|
+
|
|
23
|
+
## 安装
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
dsh plugin --profile web add @puwenhui/dsh-rag-kb
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 配置(设置 → 插件 →「知识库」标签页)
|
|
30
|
+
|
|
31
|
+
| 字段 | 默认 | 说明 |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| 知识库描述 | 空 | agent 检索前阅读,知道库里有什么 |
|
|
34
|
+
| 自动索引目录 | 空 | 放入文档自动入库(可选) |
|
|
35
|
+
| 检索返回条数 | 5 | 混合检索召回 top-K |
|
|
36
|
+
| 相似度阈值 | 0.3 | 0~1,过低噪音多 |
|
|
37
|
+
| 启用 Reranker | 关 | 开启后需下载 ~1GB 模型 |
|
|
38
|
+
| 重排后返回条数 | 3 | Reranker 精排后返回 top-N |
|
|
39
|
+
|
|
40
|
+
## 会话语义操作
|
|
41
|
+
|
|
42
|
+
| 你说 | agent 调用 |
|
|
43
|
+
|---|---|
|
|
44
|
+
| 「把 D:\docs 加进知识库」 | `knowledge_manage add` |
|
|
45
|
+
| 「把刚才的总结存到知识库」 | `knowledge_manage save` |
|
|
46
|
+
| 「知识库里有什么?」 | `knowledge_status` |
|
|
47
|
+
| 「删掉旧版手册」 | `knowledge_manage remove` |
|
|
48
|
+
| 「根据知识库回答:XXX」 | `knowledge_search` |
|
|
49
|
+
|
|
50
|
+
## 检索架构
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
文档 → 解析(pdf/docx/md) → 标题感知切块(400 token + 10% 重叠)
|
|
54
|
+
→ 向量化(transformers.js bge-small-zh-v1.5, 512维, GPU/CPU)
|
|
55
|
+
→ FTS5 trigram 关键词索引(与向量索引同步写入)
|
|
56
|
+
|
|
57
|
+
检索: 混合检索(向量余弦 + FTS5 bm25 双路合并, via=vector/keyword/both)
|
|
58
|
+
→ [可选] Reranker 精排(bge-reranker-base cross-encoder, 召回×3 → 精排 → top-N)
|
|
59
|
+
→ 引用溯源(citation + position 字段)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 技术栈
|
|
63
|
+
|
|
64
|
+
| 层 | 选型 | 说明 |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| Embedding | transformers.js + bge-small-zh-v1.5 ONNX | 512 维中文,本地 CPU 3.6ms/条 |
|
|
67
|
+
| Reranker | transformers.js + bge-reranker-base ONNX | cross-encoder 精排 |
|
|
68
|
+
| GPU 加速 | DirectML (device: 'dml') | 批量索引快 4 倍,无 GPU 回退 CPU |
|
|
69
|
+
| 向量库 | node:sqlite BLOB + 内存余弦 | 万级 chunk <50ms |
|
|
70
|
+
| FTS | SQLite FTS5 + trigram tokenizer | 中文 3 字滑窗精确匹配 |
|
|
71
|
+
| PDF 解析 | unpdf(纯 JS) | 无原生依赖 |
|
|
72
|
+
| DOCX 解析 | mammoth(纯 JS) | 无原生依赖 |
|
|
73
|
+
| 数据目录 | $DSH_HOME/rag-kb/ | index.db + hf-cache/ |
|
|
74
|
+
|
|
75
|
+
## 模型缓存
|
|
76
|
+
|
|
77
|
+
| 模型 | 大小 | 位置 |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| bge-small-zh-v1.5 | ~91MB | $DSH_HOME/rag-kb/hf-cache/ |
|
|
80
|
+
| bge-reranker-base | ~1GB | 同上(启用 Reranker 时自动下载) |
|
|
81
|
+
|
|
82
|
+
离线部署:管理员把整个 `hf-cache/` 目录拷贝到同事机器的相同位置即可。
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/USAGE.zh.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# DSH 知识库 · 使用帮助
|
|
2
|
+
|
|
3
|
+
DSH 知识库让你**在对话里拥有自己的私域知识**——把公司文档、技术手册、会议纪要放进去,直接用大白话提问,AI 自动从知识库里找答案。不需要任何技术操作。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 首次使用:两步配置(约 1 分钟 + 模型下载等待)
|
|
8
|
+
|
|
9
|
+
管理员执行 `dsh plugin --profile web add @puwenhui/dsh-rag-kb` 安装后:
|
|
10
|
+
|
|
11
|
+
### 第一步:填写知识库描述(所有人必做)
|
|
12
|
+
|
|
13
|
+
1. 打开 DSH 网页(`dsh web` 启动后浏览器打开的那个地址)
|
|
14
|
+
2. 点左侧栏底部的 **⚙ 设置** → 在左侧列表点 **插件**
|
|
15
|
+
3. 在标签页行点 **「知识库」**
|
|
16
|
+
4. 在「知识库描述」里写一段话,说明知识库里放的是什么类型的内容。例如:
|
|
17
|
+
|
|
18
|
+
> 公司产品手册、技术规范、常见问题解答
|
|
19
|
+
|
|
20
|
+
这段话 AI 每次检索前都会读到——写得越清楚,AI 越知道什么时候该查知识库。
|
|
21
|
+
|
|
22
|
+
5. 点 **保存**
|
|
23
|
+
|
|
24
|
+
> **首次保存后**会自动下载中文向量化模型(约 100MB),下载完成后**完全离线运行**,后续不再需要网络。
|
|
25
|
+
|
|
26
|
+
### 第二步(可选):设置自动索引目录
|
|
27
|
+
|
|
28
|
+
如果你有一个固定的文档目录(如 `D:\公司文档`),可以填在「自动索引目录」里——之后往目录里丢文件就自动入库,不用手动添加。
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 日常使用:直接在对话里说
|
|
33
|
+
|
|
34
|
+
所有知识库操作**不需要打开设置页面**,直接在对话里用大白话说就行:
|
|
35
|
+
|
|
36
|
+
### 📥 添加文档
|
|
37
|
+
|
|
38
|
+
> 💬 把 D:\公司文档 目录加进知识库
|
|
39
|
+
> 💬 帮我把这份 PDF 加入知识库:D:\docs\产品手册.pdf
|
|
40
|
+
|
|
41
|
+
### 💾 保存对话内容
|
|
42
|
+
|
|
43
|
+
对话中产出了有价值的结论、总结、流程说明?直接说:
|
|
44
|
+
|
|
45
|
+
> 💬 把刚才的报销流程总结存到知识库
|
|
46
|
+
> 💬 这段分析很好,保存到知识库里
|
|
47
|
+
|
|
48
|
+
AI 会自动提取内容、起标题、入库。
|
|
49
|
+
|
|
50
|
+
### 🔍 检索知识
|
|
51
|
+
|
|
52
|
+
> 💬 根据知识库回答:公司的报销流程是什么?
|
|
53
|
+
> 💬 知识库里有没有关于 XX 系统部署的说明?
|
|
54
|
+
> 💬 查一下产品手册里关于安全设置的部分
|
|
55
|
+
|
|
56
|
+
### 📋 查看知识库状态
|
|
57
|
+
|
|
58
|
+
> 💬 知识库里有哪些文档?
|
|
59
|
+
> 💬 知识库里现在有多少个知识块?
|
|
60
|
+
|
|
61
|
+
### 🗑️ 删除文档
|
|
62
|
+
|
|
63
|
+
> 💬 删掉知识库里的旧版手册
|
|
64
|
+
> 💬 把那个「对话保存」的文档从知识库移除
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 知识库支持哪些文件?
|
|
69
|
+
|
|
70
|
+
| 格式 | 说明 |
|
|
71
|
+
|---|---|
|
|
72
|
+
| PDF | 扫描件暂不支持(需文字版 PDF) |
|
|
73
|
+
| Word (.docx) | 自动提取正文 |
|
|
74
|
+
| Markdown (.md) | 保留标题结构 |
|
|
75
|
+
| 纯文本 (.txt) | 自动分段 |
|
|
76
|
+
| CSV / JSON | 结构化数据 |
|
|
77
|
+
|
|
78
|
+
**不支持的**:图片、音视频、加密文档、扫描版 PDF(无文字层)。
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 常见问题
|
|
83
|
+
|
|
84
|
+
**Q:刚添加的文档搜不到?**
|
|
85
|
+
添加后需要几秒钟建索引(切块+向量化)。如果文档很大(如 200 页 PDF),可能需要 10-30 秒。等一下再搜。
|
|
86
|
+
|
|
87
|
+
**Q:模型下载很慢或失败了?**
|
|
88
|
+
首次使用需要从 HuggingFace 下载约 100MB 模型。如果网络不通,联系管理员获取离线模型包。
|
|
89
|
+
|
|
90
|
+
**Q:检索结果不准确?**
|
|
91
|
+
尝试以下方法:
|
|
92
|
+
- 把「相似度阈值」调低(如 0.2)——能搜到更多结果
|
|
93
|
+
- 把「相似度阈值」调高(如 0.5)——结果更精准但可能漏掉
|
|
94
|
+
- 在「知识库描述」里写得更具体,帮助 AI 判断什么时候该检索
|
|
95
|
+
|
|
96
|
+
**Q:什么是 Reranker?要不要开?**
|
|
97
|
+
Reranker 是对检索结果做二次精排的技术,能过滤噪音让结果更准。如果知识库内容多且检索有噪音,建议开启(需下载约 1GB 模型)。文档少(<50篇)时不开也够用。
|
|
98
|
+
|
|
99
|
+
**Q:能同时存多少文档?**
|
|
100
|
+
建议 1000 篇以内。超过后检索仍可用,但不同主题的文档可能互相干扰。
|
|
101
|
+
|
|
102
|
+
**Q:数据存在哪里?安全吗?**
|
|
103
|
+
所有数据(文档内容 + 向量索引)存储在你本机的 `$DSH_HOME/rag-kb/` 目录。向量化模型也运行在本机,**文档内容不会发送到任何外部服务器**。
|
|
104
|
+
|
|
105
|
+
**Q:换电脑怎么办?**
|
|
106
|
+
把 `$DSH_HOME/rag-kb/` 目录整个复制到新电脑的相同位置即可(包含索引库和模型缓存)。
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
*文档随 `@puwenhui/dsh-rag-kb` 插件提供 · 有问题找管理员*
|
package/cordis.patch.yml
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "@puwenhui/dsh-rag-kb", factory: (require) => {
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
//#region settings-tab.tsx
|
|
10
|
+
/**
|
|
11
|
+
* 设置 → 插件 →「知识库」标签页:配置项 + 使用指引。
|
|
12
|
+
* 复杂操作按设计走会话语义(对话里说「把 D:\docs 加入知识库」即可),
|
|
13
|
+
* 面板只承载:检索参数、知识库描述、监视目录 + 操作提示。
|
|
14
|
+
*/
|
|
15
|
+
const FIELDS = [
|
|
16
|
+
{
|
|
17
|
+
key: "description",
|
|
18
|
+
label: "知识库描述",
|
|
19
|
+
type: "string",
|
|
20
|
+
placeholder: "如:公司产品手册、技术规范、常见问题解答",
|
|
21
|
+
hint: "agent 检索前会阅读这段描述,知道库里有什么"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: "watchDir",
|
|
25
|
+
label: "自动索引目录",
|
|
26
|
+
type: "string",
|
|
27
|
+
placeholder: "D:\\docs",
|
|
28
|
+
hint: "放入此目录的文档自动监视索引(可选)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: "topK",
|
|
32
|
+
label: "检索返回条数",
|
|
33
|
+
type: "number",
|
|
34
|
+
hint: "默认 5"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
key: "minScore",
|
|
38
|
+
label: "相似度阈值",
|
|
39
|
+
type: "number",
|
|
40
|
+
hint: "0~1,默认 0.3,过低噪音多"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: "enableReranker",
|
|
44
|
+
label: "启用 Reranker 重排序",
|
|
45
|
+
type: "boolean",
|
|
46
|
+
hint: "首次使用会下载约 500MB 模型"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "rerankTopN",
|
|
50
|
+
label: "重排后返回条数",
|
|
51
|
+
type: "number",
|
|
52
|
+
hint: "默认 3"
|
|
53
|
+
}
|
|
54
|
+
];
|
|
55
|
+
function KbSettingsTab(props) {
|
|
56
|
+
const face = props.face;
|
|
57
|
+
const [ns, setNs] = (0, react.useState)(null);
|
|
58
|
+
const [view, setView] = (0, react.useState)(null);
|
|
59
|
+
const [error, setError] = (0, react.useState)(null);
|
|
60
|
+
const [draft, setDraft] = (0, react.useState)(null);
|
|
61
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
62
|
+
const [message, setMessage] = (0, react.useState)(null);
|
|
63
|
+
const [failed, setFailed] = (0, react.useState)(false);
|
|
64
|
+
const reload = (0, react.useCallback)(async () => {
|
|
65
|
+
setError(null);
|
|
66
|
+
if (face === void 0 || typeof face.describe !== "function") {
|
|
67
|
+
setError("组件未收到设置读写面");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const timeout = new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("settings.describe 8 秒未响应")), 8e3));
|
|
71
|
+
let result;
|
|
72
|
+
try {
|
|
73
|
+
result = await Promise.race([face.describe(), timeout]);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
setError(`读取设置失败:${String(e.message)}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if ("error" in result) {
|
|
79
|
+
setError(result.error);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const rows = result.namespaces ?? [];
|
|
83
|
+
const hit = rows.find((row) => row.ns === "rag-kb");
|
|
84
|
+
if (hit === void 0) {
|
|
85
|
+
setError(`设置视图中未找到 rag-kb 命名空间(共 ${String(rows.length)} 个)`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
setNs(hit.ns);
|
|
89
|
+
setView(hit);
|
|
90
|
+
setDraft(null);
|
|
91
|
+
}, [face]);
|
|
92
|
+
(0, react.useEffect)(() => {
|
|
93
|
+
reload();
|
|
94
|
+
}, [reload]);
|
|
95
|
+
const current = view?.value ?? {};
|
|
96
|
+
const userLayer = view?.user ?? {};
|
|
97
|
+
const shown = draft ?? current;
|
|
98
|
+
const edit = (key, value) => {
|
|
99
|
+
setMessage(null);
|
|
100
|
+
setFailed(false);
|
|
101
|
+
setDraft((prev) => ({
|
|
102
|
+
...prev ?? { ...current },
|
|
103
|
+
[key]: value
|
|
104
|
+
}));
|
|
105
|
+
};
|
|
106
|
+
const save = async () => {
|
|
107
|
+
if (draft === null || ns === null || view === void 0) return;
|
|
108
|
+
setSaving(true);
|
|
109
|
+
try {
|
|
110
|
+
const ops = [];
|
|
111
|
+
for (const f of FIELDS) {
|
|
112
|
+
const after = draft[f.key];
|
|
113
|
+
if (after === void 0 || after === "") continue;
|
|
114
|
+
const value = f.type === "number" ? Number(after) : after;
|
|
115
|
+
if (Number.isNaN(value)) continue;
|
|
116
|
+
if (JSON.stringify(value) === JSON.stringify(current[f.key])) continue;
|
|
117
|
+
ops.push({
|
|
118
|
+
op: "set",
|
|
119
|
+
path: [f.key],
|
|
120
|
+
value
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (ops.length > 0) {
|
|
124
|
+
const result = await face.mutate(ns, ops, view.revision);
|
|
125
|
+
if (!result.ok) throw new Error(result.error ?? "写入失败");
|
|
126
|
+
}
|
|
127
|
+
setDraft(null);
|
|
128
|
+
setFailed(false);
|
|
129
|
+
setMessage(ops.length > 0 ? "已保存,插件已按新配置自动重载" : "无更改");
|
|
130
|
+
await reload();
|
|
131
|
+
} catch (e) {
|
|
132
|
+
setFailed(true);
|
|
133
|
+
setMessage(`保存失败:${String(e.message)}`);
|
|
134
|
+
} finally {
|
|
135
|
+
setSaving(false);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const inputStyle = {
|
|
139
|
+
width: "100%",
|
|
140
|
+
padding: "6px 10px",
|
|
141
|
+
font: "inherit",
|
|
142
|
+
boxSizing: "border-box",
|
|
143
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
144
|
+
borderRadius: "6px",
|
|
145
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
146
|
+
color: "var(--dsw-alias-label-primary)"
|
|
147
|
+
};
|
|
148
|
+
if (error !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
149
|
+
style: {
|
|
150
|
+
...cardStyle,
|
|
151
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
152
|
+
},
|
|
153
|
+
children: [
|
|
154
|
+
"知识库配置加载失败:",
|
|
155
|
+
error,
|
|
156
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
157
|
+
type: "button",
|
|
158
|
+
onClick: () => {
|
|
159
|
+
reload();
|
|
160
|
+
},
|
|
161
|
+
style: {
|
|
162
|
+
...btnStyle,
|
|
163
|
+
marginLeft: "10px"
|
|
164
|
+
},
|
|
165
|
+
children: "重试"
|
|
166
|
+
})
|
|
167
|
+
]
|
|
168
|
+
});
|
|
169
|
+
if (view === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
170
|
+
style: {
|
|
171
|
+
...cardStyle,
|
|
172
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
173
|
+
},
|
|
174
|
+
children: "知识库配置加载中…"
|
|
175
|
+
});
|
|
176
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
177
|
+
style: cardStyle,
|
|
178
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
179
|
+
style: { fontSize: "14px" },
|
|
180
|
+
children: "知识库"
|
|
181
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
182
|
+
style: {
|
|
183
|
+
display: "grid",
|
|
184
|
+
gridTemplateColumns: "1fr 1fr",
|
|
185
|
+
gap: "10px 14px",
|
|
186
|
+
marginTop: "12px"
|
|
187
|
+
},
|
|
188
|
+
children: [FIELDS.map((f) => {
|
|
189
|
+
const overridden = Object.hasOwn(userLayer, f.key);
|
|
190
|
+
const isBoolean = f.type === "boolean";
|
|
191
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
192
|
+
style: {
|
|
193
|
+
display: "flex",
|
|
194
|
+
flexDirection: "column",
|
|
195
|
+
gap: "4px",
|
|
196
|
+
fontSize: "13px",
|
|
197
|
+
...isBoolean ? {
|
|
198
|
+
gridColumn: "span 1",
|
|
199
|
+
justifyContent: "center"
|
|
200
|
+
} : {}
|
|
201
|
+
},
|
|
202
|
+
children: [
|
|
203
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
204
|
+
style: { color: "var(--dsw-alias-label-secondary)" },
|
|
205
|
+
children: [f.label, overridden && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
206
|
+
style: {
|
|
207
|
+
marginLeft: "6px",
|
|
208
|
+
color: "var(--dsw-alias-brand-primary)"
|
|
209
|
+
},
|
|
210
|
+
children: "·已覆盖"
|
|
211
|
+
})]
|
|
212
|
+
}),
|
|
213
|
+
isBoolean ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
214
|
+
style: {
|
|
215
|
+
display: "flex",
|
|
216
|
+
alignItems: "center",
|
|
217
|
+
gap: "12px",
|
|
218
|
+
padding: "4px 0"
|
|
219
|
+
},
|
|
220
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
221
|
+
style: {
|
|
222
|
+
display: "flex",
|
|
223
|
+
alignItems: "center",
|
|
224
|
+
gap: "4px",
|
|
225
|
+
cursor: "pointer"
|
|
226
|
+
},
|
|
227
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
228
|
+
type: "radio",
|
|
229
|
+
name: f.key,
|
|
230
|
+
checked: shown[f.key] === true,
|
|
231
|
+
onChange: () => {
|
|
232
|
+
edit(f.key, true);
|
|
233
|
+
}
|
|
234
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "开" })]
|
|
235
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
236
|
+
style: {
|
|
237
|
+
display: "flex",
|
|
238
|
+
alignItems: "center",
|
|
239
|
+
gap: "4px",
|
|
240
|
+
cursor: "pointer"
|
|
241
|
+
},
|
|
242
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
243
|
+
type: "radio",
|
|
244
|
+
name: f.key,
|
|
245
|
+
checked: shown[f.key] !== true,
|
|
246
|
+
onChange: () => {
|
|
247
|
+
edit(f.key, false);
|
|
248
|
+
}
|
|
249
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "关" })]
|
|
250
|
+
})]
|
|
251
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
252
|
+
value: draft !== null && draft[f.key] !== void 0 ? String(draft[f.key]) : String(shown[f.key] ?? ""),
|
|
253
|
+
placeholder: f.placeholder ?? "",
|
|
254
|
+
onChange: (e) => {
|
|
255
|
+
edit(f.key, e.target.value);
|
|
256
|
+
},
|
|
257
|
+
style: inputStyle
|
|
258
|
+
}),
|
|
259
|
+
f.hint !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
260
|
+
style: {
|
|
261
|
+
fontSize: "11px",
|
|
262
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
263
|
+
},
|
|
264
|
+
children: f.hint
|
|
265
|
+
})
|
|
266
|
+
]
|
|
267
|
+
}, f.key);
|
|
268
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
269
|
+
style: {
|
|
270
|
+
gridColumn: "1 / -1",
|
|
271
|
+
display: "flex",
|
|
272
|
+
alignItems: "center",
|
|
273
|
+
gap: "10px",
|
|
274
|
+
marginTop: "4px"
|
|
275
|
+
},
|
|
276
|
+
children: [
|
|
277
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
278
|
+
type: "button",
|
|
279
|
+
disabled: saving || draft === null,
|
|
280
|
+
onClick: () => {
|
|
281
|
+
save();
|
|
282
|
+
},
|
|
283
|
+
style: {
|
|
284
|
+
padding: "6px 16px",
|
|
285
|
+
borderRadius: "6px",
|
|
286
|
+
font: "inherit",
|
|
287
|
+
border: "1px solid var(--dsw-alias-brand-primary)",
|
|
288
|
+
background: "var(--dsw-alias-brand-primary)",
|
|
289
|
+
color: "var(--dsw-alias-label-primary-foreground)",
|
|
290
|
+
cursor: draft === null ? "default" : "pointer",
|
|
291
|
+
opacity: draft === null ? .6 : 1
|
|
292
|
+
},
|
|
293
|
+
children: saving ? "保存中…" : "保存"
|
|
294
|
+
}),
|
|
295
|
+
draft !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
296
|
+
type: "button",
|
|
297
|
+
disabled: saving,
|
|
298
|
+
onClick: () => {
|
|
299
|
+
setDraft(null);
|
|
300
|
+
setMessage(null);
|
|
301
|
+
setFailed(false);
|
|
302
|
+
},
|
|
303
|
+
style: {
|
|
304
|
+
padding: "6px 16px",
|
|
305
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
306
|
+
borderRadius: "6px",
|
|
307
|
+
background: "transparent",
|
|
308
|
+
font: "inherit",
|
|
309
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
310
|
+
cursor: "pointer"
|
|
311
|
+
},
|
|
312
|
+
children: "放弃更改"
|
|
313
|
+
}),
|
|
314
|
+
message !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
315
|
+
style: {
|
|
316
|
+
fontSize: "12px",
|
|
317
|
+
color: failed ? "var(--dsw-alias-label-error)" : "var(--dsw-alias-label-secondary)"
|
|
318
|
+
},
|
|
319
|
+
children: message
|
|
320
|
+
})
|
|
321
|
+
]
|
|
322
|
+
})]
|
|
323
|
+
})]
|
|
324
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
325
|
+
style: {
|
|
326
|
+
...cardStyle,
|
|
327
|
+
marginTop: "12px",
|
|
328
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
329
|
+
},
|
|
330
|
+
children: [
|
|
331
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
|
|
332
|
+
style: { fontSize: "13px" },
|
|
333
|
+
children: "💡 知识库管理:直接在对话里说"
|
|
334
|
+
}),
|
|
335
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
336
|
+
style: {
|
|
337
|
+
marginTop: "8px",
|
|
338
|
+
lineHeight: 1.8,
|
|
339
|
+
fontSize: "13px"
|
|
340
|
+
},
|
|
341
|
+
children: [
|
|
342
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
343
|
+
"💬 「把 ",
|
|
344
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
345
|
+
style: {
|
|
346
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
347
|
+
padding: "1px 4px",
|
|
348
|
+
borderRadius: "3px"
|
|
349
|
+
},
|
|
350
|
+
children: "D:\\docs"
|
|
351
|
+
}),
|
|
352
|
+
" 目录加入知识库」"
|
|
353
|
+
] }),
|
|
354
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "💬 「知识库里有哪些文档?」" }),
|
|
355
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "💬 「删掉知识库里的旧版手册」" }),
|
|
356
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: "💬 「根据知识库回答:报销流程是什么?」" })
|
|
357
|
+
]
|
|
358
|
+
}),
|
|
359
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
360
|
+
style: {
|
|
361
|
+
marginTop: "8px",
|
|
362
|
+
fontSize: "11px",
|
|
363
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
364
|
+
},
|
|
365
|
+
children: "支持 pdf / docx / md / txt / csv / json;文件内容变化自动重新索引。 首次使用会自动下载中文向量化模型(约 100MB,下载后完全离线)。"
|
|
366
|
+
})
|
|
367
|
+
]
|
|
368
|
+
})] });
|
|
369
|
+
}
|
|
370
|
+
const cardStyle = {
|
|
371
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
372
|
+
borderRadius: "10px",
|
|
373
|
+
padding: "14px 16px",
|
|
374
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
375
|
+
font: "13px/1.5 system-ui, \"Segoe UI\", \"Microsoft YaHei\", sans-serif",
|
|
376
|
+
color: "var(--dsw-alias-label-primary)"
|
|
377
|
+
};
|
|
378
|
+
const btnStyle = {
|
|
379
|
+
padding: "6px 12px",
|
|
380
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
381
|
+
borderRadius: "6px",
|
|
382
|
+
background: "transparent",
|
|
383
|
+
color: "inherit",
|
|
384
|
+
font: "inherit",
|
|
385
|
+
cursor: "pointer",
|
|
386
|
+
whiteSpace: "nowrap"
|
|
387
|
+
};
|
|
388
|
+
//#endregion
|
|
389
|
+
//#region index.tsx
|
|
390
|
+
const name = "dsh-rag-kb";
|
|
391
|
+
const inject = [
|
|
392
|
+
"slots",
|
|
393
|
+
"locale",
|
|
394
|
+
"remote",
|
|
395
|
+
"remote.settings"
|
|
396
|
+
];
|
|
397
|
+
const NS = "dsh-rag-kb";
|
|
398
|
+
const zh = { tab: "知识库" };
|
|
399
|
+
const en = { tab: "Knowledge" };
|
|
400
|
+
function apply(ctx) {
|
|
401
|
+
ctx.effect(() => {
|
|
402
|
+
try {
|
|
403
|
+
ctx.locale.register(NS, {
|
|
404
|
+
zh,
|
|
405
|
+
en
|
|
406
|
+
});
|
|
407
|
+
} catch {}
|
|
408
|
+
const t = (key) => zh[key] ?? String(key);
|
|
409
|
+
const face = {
|
|
410
|
+
describe: async () => {
|
|
411
|
+
const response = await ctx.remote.settings.describe();
|
|
412
|
+
return response.ok && response.value !== void 0 ? { namespaces: response.value.namespaces } : { error: response.error?.message ?? "settings.describe failed" };
|
|
413
|
+
},
|
|
414
|
+
mutate: async (ns, ops, revision) => {
|
|
415
|
+
const response = await ctx.remote.settings.mutate(ns, ops, revision);
|
|
416
|
+
return response.ok ? { ok: true } : {
|
|
417
|
+
ok: false,
|
|
418
|
+
error: response.error?.message ?? "settings.mutate failed"
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
const offTab = ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
|
|
423
|
+
name: "settings.plugins.tab",
|
|
424
|
+
id: "rag-kb",
|
|
425
|
+
order: 50,
|
|
426
|
+
label: () => t("tab"),
|
|
427
|
+
locale: NS,
|
|
428
|
+
inject: () => ({ face })
|
|
429
|
+
}, KbSettingsTab));
|
|
430
|
+
return () => {
|
|
431
|
+
offTab();
|
|
432
|
+
};
|
|
433
|
+
}, "dsh-rag-kb: settings tab");
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
436
|
+
exports.apply = apply;
|
|
437
|
+
exports.inject = inject;
|
|
438
|
+
exports.name = name;
|
|
439
|
+
return module.exports;
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
//# sourceMappingURL=client.js.map
|