hiwork-knowledge 0.1.1 → 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 +133 -39
- package/cordis.patch.yml +19 -2
- package/lib/client.js +1599 -45
- package/lib/index.js +27793 -6
- package/lib/prompt.js +31 -0
- package/lib/protocol.js +139 -0
- package/lib/rpc.js +103 -0
- package/lib/service.js +344 -0
- package/lib/tool-names.js +15 -0
- package/lib/tools.js +244 -0
- package/lib/types/client/KnowledgeView.d.ts +19 -5
- package/lib/types/client/SettingsSection.d.ts +42 -0
- package/lib/types/client/ToolCards.d.ts +26 -0
- package/lib/types/client/composer.d.ts +98 -0
- package/lib/types/client/contracts.d.ts +100 -2
- package/lib/types/client/focus.d.ts +19 -0
- package/lib/types/client/format.d.ts +74 -0
- package/lib/types/client/icons.d.ts +26 -0
- package/lib/types/client/index.d.ts +31 -2
- package/lib/types/client/locales.d.ts +213 -6
- package/lib/types/client/runtime.d.ts +66 -0
- package/lib/types/client/tool-result.d.ts +90 -0
- package/lib/types/index.d.ts +49 -10
- package/lib/types/prompt.d.ts +21 -0
- package/lib/types/protocol.d.ts +200 -0
- package/lib/types/rpc.d.ts +18 -0
- package/lib/types/service.d.ts +142 -0
- package/lib/types/tool-names.d.ts +11 -0
- package/lib/types/tools.d.ts +21 -0
- package/lib/types/types.d.ts +109 -0
- package/lib/types/weknora.d.ts +112 -0
- package/lib/types.js +112 -0
- package/lib/weknora.js +366 -0
- package/package.json +25 -5
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 页面展示用的纯格式化函数。
|
|
3
|
+
*
|
|
4
|
+
* 刻意与 React 和宿主完全解耦:这些映射(状态 → 语义色、字节 → 可读大小、时间 → 短日期)
|
|
5
|
+
* 是最容易写错又最容易被测试覆盖的部分,放在独立文件里可以直接单测。
|
|
6
|
+
*
|
|
7
|
+
* 另一条纪律:**不产生任何硬编码文案**。返回空串表示「这段没有内容,调用方别渲染」,
|
|
8
|
+
* 而不是返回 'FILE' / '未知' 这类兜底词——可见文案一律走词典(i18n 门禁要求)。
|
|
9
|
+
*/
|
|
10
|
+
/** 徽标语义色(对应样式表里的 `.hiwork-knowledge-tag.is-<tone>`)。 */
|
|
11
|
+
export type TagTone = 'success' | 'info' | 'warning' | 'danger' | 'neutral';
|
|
12
|
+
/**
|
|
13
|
+
* 解析状态 → 徽标语义色。
|
|
14
|
+
*
|
|
15
|
+
* **已完成刻意不着色**:一份正常的知识库里绝大多数文档都是 `completed`,逐行染绿等于
|
|
16
|
+
* 把颜色变成背景噪声——反而把真正需要一眼看到的 `failed`(红)和解析中(蓝)淹掉。
|
|
17
|
+
* 所以只给异常态上色,正常态走中性(无填充、淡描边的静默徽标)。这也是 CI/构建列表的
|
|
18
|
+
* 通行做法:只在出问题时抢注意力。
|
|
19
|
+
* @param status - 后端原始解析状态。
|
|
20
|
+
*/
|
|
21
|
+
export declare function statusTone(status: string): TagTone;
|
|
22
|
+
/**
|
|
23
|
+
* 解析状态 → 词典键;未知状态返回 `null`。
|
|
24
|
+
* @param status - 后端原始解析状态。
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseStatusKey(status: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* 字节数 → 人类可读大小;无效值返回空串。
|
|
29
|
+
*
|
|
30
|
+
* `0` 也返回空串:后端把未知大小报成 0,而「0 B 的文档」只会让人怀疑数据出错。
|
|
31
|
+
* @param bytes - 字节数(后端可能给 null)。
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatSize(bytes: number | null | undefined): string;
|
|
34
|
+
/**
|
|
35
|
+
* ISO 时间 → `YYYY-MM-DD`;无效值返回空串。
|
|
36
|
+
*
|
|
37
|
+
* 刻意不用 `toLocaleDateString`:它的输出随运行环境的语言与时区变化,
|
|
38
|
+
* 同一份数据在两台机器上会渲染出不同的字符串,让渲染断言变得不可复现。
|
|
39
|
+
* 秒级精度在这里没有意义(用户看的是「哪天更新的」),所以直接截到日。
|
|
40
|
+
* @param iso - 后端返回的时间串。
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatDate(iso: string | null | undefined): string;
|
|
43
|
+
/**
|
|
44
|
+
* 文件类型徽标文字;无法判定时返回空串(调用方退化成通用文件图标)。
|
|
45
|
+
*
|
|
46
|
+
* 优先用后端给的 `fileType`,没有则从文件名取扩展名;统一大写并截到 4 个字符,
|
|
47
|
+
* 这样 32px 的方形徽标里不会溢出(`docx` / `xlsx` 正好 4 个)。
|
|
48
|
+
* @param fileType - 后端的文件类型字段。
|
|
49
|
+
* @param fileName - 文件名(兜底取扩展名)。
|
|
50
|
+
*/
|
|
51
|
+
export declare function fileTypeLabel(fileType: string, fileName: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* 文件类型徽标的色调(对应样式表里的 `.hiwork-knowledge-doc-icon.is-<tone>`)。
|
|
54
|
+
*
|
|
55
|
+
* 用颜色名而不是语义名:这里表达的是「这是哪种文件」,不是「成功/失败」,
|
|
56
|
+
* 借用 success/danger 之类的槽位会让类名与含义对不上。
|
|
57
|
+
*/
|
|
58
|
+
export type IconTone = 'blue' | 'green' | 'amber' | 'red' | 'neutral';
|
|
59
|
+
/**
|
|
60
|
+
* 文件类型 → 徽标色调;未收录的类型走中性色。
|
|
61
|
+
*
|
|
62
|
+
* 键统一用 `fileTypeLabel` 的输出(去掉前导点、大写、截 4 字符),
|
|
63
|
+
* 两种途径(后端 `fileType` 与文件名扩展名)因此落在同一张表上。
|
|
64
|
+
* @param fileType - 后端的文件类型字段。
|
|
65
|
+
* @param fileName - 文件名(兜底取扩展名)。
|
|
66
|
+
*/
|
|
67
|
+
export declare function fileTypeTone(fileType: string, fileName: string): IconTone;
|
|
68
|
+
/**
|
|
69
|
+
* 用「 · 」拼接元数据段,自动跳过空段。
|
|
70
|
+
*
|
|
71
|
+
* 空段不跳过的话,`documentCount` 缺失时会渲染出「 · 3 个分块」这种以分隔符开头的行。
|
|
72
|
+
* @param parts - 各元数据段(空串 / null / undefined 会被丢弃)。
|
|
73
|
+
*/
|
|
74
|
+
export declare function joinMeta(parts: readonly (string | null | undefined)[]): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内联 SVG 图标。
|
|
3
|
+
*
|
|
4
|
+
* 不引第三方图标库:client bundle 必须自包含(打包扫描不允许运行时 import 外部包),
|
|
5
|
+
* 而这里只需要 4 个 16px 线性图标。统一 `stroke="currentColor"`,颜色由所在容器的
|
|
6
|
+
* `color` 决定,因此亮暗主题、选中态、错误态都自动跟随,不需要为图标单独定义颜色。
|
|
7
|
+
*/
|
|
8
|
+
import { type ReactElement } from 'react';
|
|
9
|
+
/** 刷新(页头)。 */
|
|
10
|
+
export declare function IconRefresh(): ReactElement;
|
|
11
|
+
/** 放大镜(检索框)。 */
|
|
12
|
+
export declare function IconSearch(): ReactElement;
|
|
13
|
+
/** 通用文件(未知类型时的兜底)。 */
|
|
14
|
+
export declare function IconFile(): ReactElement;
|
|
15
|
+
/** 告警圆圈(错误 / 警告横幅)。 */
|
|
16
|
+
export declare function IconAlert(): ReactElement;
|
|
17
|
+
/** 空态示意(列表为空时垫在文字上方,避免只有一行灰字悬在空白里)。 */
|
|
18
|
+
export declare function IconInbox(): ReactElement;
|
|
19
|
+
/** 对勾(工具卡片:调用成功)。 */
|
|
20
|
+
export declare function IconCheck(): ReactElement;
|
|
21
|
+
/** 带外链箭头的方框(「在知识库页打开」)。 */
|
|
22
|
+
export declare function IconExternal(): ReactElement;
|
|
23
|
+
/** 对话气泡(「追问」)。 */
|
|
24
|
+
export declare function IconMessage(): ReactElement;
|
|
25
|
+
/** 书本(知识库语义的装饰图标)。 */
|
|
26
|
+
export declare function IconBook(): ReactElement;
|
|
@@ -1,7 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hiwork-knowledge` 的 Web 入口。
|
|
3
|
+
*
|
|
4
|
+
* 两个入口,职责不同,**同时存在**:
|
|
5
|
+
* 1. 中央页 feature(id `knowledge`、order 30):浏览知识库/文档/检索;
|
|
6
|
+
* 依赖 `hiwork-core` 的 `hiworkFeatureCenter`,没有 core 时不注册。
|
|
7
|
+
* 2. 设置页分区 `settings.section#knowledge`:凭据、默认范围、自检。
|
|
8
|
+
* 这块**不随 core 出现而撤下**——它是配置面,不是浏览面的降级替身
|
|
9
|
+
* (M0 起与 hiwork-automation 的"二选一"策略不同,见 README)。
|
|
10
|
+
*
|
|
11
|
+
* 数据来自 Host 的 `/hiwork-knowledge` loopback RPC:Web 半边不发任何 HTTP,
|
|
12
|
+
* 也拿不到凭据(只有 `hasApiKey` 布尔)。
|
|
13
|
+
*
|
|
14
|
+
* client bundle 纯度:本文件对 `@deepseek-ai/*` 只做 type-only import
|
|
15
|
+
* (经 `./contracts.js` 的结构契约),运行时只 import `react` 与本地模块。
|
|
16
|
+
*/
|
|
17
|
+
import { createElement, type ReactElement } from 'react';
|
|
1
18
|
import type { ClientContext } from './contracts.js';
|
|
19
|
+
import { KnowledgeView } from './KnowledgeView.js';
|
|
20
|
+
import { KnowledgeSettings } from './SettingsSection.js';
|
|
2
21
|
/** DSH client 插件名。 */
|
|
3
22
|
export declare const name = "hiwork-knowledge-client";
|
|
4
|
-
/** 必需的 client
|
|
23
|
+
/** 必需的 client 服务(`connection` 提供 loopback RPC)。 */
|
|
5
24
|
export declare const inject: string[];
|
|
6
25
|
/**
|
|
7
26
|
* 挂载 Web 半边。
|
|
@@ -9,4 +28,14 @@ export declare const inject: string[];
|
|
|
9
28
|
*/
|
|
10
29
|
export declare function apply(ctx: ClientContext): void;
|
|
11
30
|
/** 便于验收脚本按导出面断言。 */
|
|
12
|
-
export type { KnowledgeViewProps } from './contracts.js';
|
|
31
|
+
export type { KnowledgeSettingsProps, KnowledgeViewProps } from './contracts.js';
|
|
32
|
+
export type { KnowledgeRuntime } from './runtime.js';
|
|
33
|
+
/** 供测试断言注册面(避免测试里硬编码字符串)。 */
|
|
34
|
+
export declare const KNOWLEDGE_CLIENT_REGISTRATION: {
|
|
35
|
+
readonly featureId: "knowledge";
|
|
36
|
+
readonly featureOrder: 30;
|
|
37
|
+
readonly settingsSectionId: "knowledge";
|
|
38
|
+
};
|
|
39
|
+
/** 便于测试断言渲染出的组件类型。 */
|
|
40
|
+
export { KnowledgeView, KnowledgeSettings, createElement as __createElement };
|
|
41
|
+
export type { ReactElement };
|
|
@@ -5,19 +5,226 @@
|
|
|
5
5
|
* - 页面里所有用户可见文案都必须来自这里,组件内不写死中英文;
|
|
6
6
|
* - `en` 的类型是 `Record<KnowledgeLocaleKey, string>`,少一个键就是编译错误。
|
|
7
7
|
*/
|
|
8
|
-
/** 宿主 locale 服务注册时使用的命名空间。 */
|
|
9
8
|
export declare const NS = "hiwork-knowledge";
|
|
10
9
|
export declare const zh: {
|
|
11
|
-
'view.title':
|
|
12
|
-
'view.subtitle':
|
|
13
|
-
'view.
|
|
14
|
-
'view.
|
|
10
|
+
readonly 'view.title': "知识库";
|
|
11
|
+
readonly 'view.subtitle': "企业知识检索与问答(WeKnora)";
|
|
12
|
+
readonly 'view.close': "关闭设置";
|
|
13
|
+
readonly 'view.refresh': "刷新";
|
|
14
|
+
readonly 'state.loading': "正在读取知识库…";
|
|
15
|
+
readonly 'state.error': "知识库读取失败";
|
|
16
|
+
readonly 'state.retry': "重试";
|
|
17
|
+
readonly 'settings.title': "知识库设置";
|
|
18
|
+
readonly 'settings.subtitle': "凭据保存在本机(Host 半边),不下发给其它插件或网页";
|
|
19
|
+
readonly 'settings.baseUrl': "后端地址";
|
|
20
|
+
readonly 'settings.baseUrlHint': "缺 /api/v1 时会自动补齐;默认指向公司内网部署";
|
|
21
|
+
readonly 'settings.apiKey': "API Key";
|
|
22
|
+
readonly 'settings.apiKeyPlaceholder': "粘贴空间 API Key(形如 sk-…)";
|
|
23
|
+
readonly 'settings.apiKeyKeep': "留空则不修改";
|
|
24
|
+
readonly 'settings.apiKeyConfigured': "已配置";
|
|
25
|
+
readonly 'settings.apiKeyMissing': "未配置";
|
|
26
|
+
readonly 'settings.apiKeySource': "凭据来源";
|
|
27
|
+
readonly 'settings.apiKeySource.settings': "本机设置";
|
|
28
|
+
readonly 'settings.apiKeySource.env': "环境变量";
|
|
29
|
+
readonly 'settings.apiKeySource.none': "无";
|
|
30
|
+
readonly 'settings.tenantId': "空间 ID(平台 Key 才需要)";
|
|
31
|
+
readonly 'settings.defaultBases': "默认检索范围(知识库 ID,逗号分隔)";
|
|
32
|
+
readonly 'settings.defaultBasesHint': "留空表示使用凭据可见的全部知识库";
|
|
33
|
+
readonly 'settings.maxResults': "每次检索条数上限";
|
|
34
|
+
readonly 'settings.maxChunkChars': "单条片段字符上限";
|
|
35
|
+
readonly 'settings.agentId': "问答 Agent ID";
|
|
36
|
+
readonly 'settings.agentIdHint': "必须是自建 Agent(内置 Agent 缺少模型配置,后端会直接拒绝)";
|
|
37
|
+
readonly 'settings.chatModelId': "问答模型 ID";
|
|
38
|
+
readonly 'settings.chatModelIdHint': "请求体里的 summary_model_id;留空则用后端默认";
|
|
39
|
+
readonly 'settings.save': "保存";
|
|
40
|
+
readonly 'settings.saving': "保存中…";
|
|
41
|
+
readonly 'settings.saved': "已保存";
|
|
42
|
+
readonly 'settings.test': "测试连接";
|
|
43
|
+
readonly 'settings.testing': "正在自检…";
|
|
44
|
+
readonly 'settings.testNote': "自检会真实调用后端:列库 + 用文档标题做一次探针检索(用于发现「检索未绑 rerank」这类静默错误)";
|
|
45
|
+
readonly 'selfCheck.ok': "自检通过";
|
|
46
|
+
readonly 'selfCheck.warn': "自检有告警";
|
|
47
|
+
readonly 'selfCheck.error': "自检失败";
|
|
48
|
+
readonly 'selfCheck.baseCount': "可见知识库";
|
|
49
|
+
readonly 'bases.title': "知识库";
|
|
50
|
+
readonly 'bases.empty': "当前凭据看不到任何知识库";
|
|
51
|
+
readonly 'bases.meta.docs': "{count} 篇文档";
|
|
52
|
+
readonly 'bases.meta.updated': "{value} 更新";
|
|
53
|
+
readonly 'bases.select': "查看「{name}」的文档";
|
|
54
|
+
readonly 'docs.title': "文档";
|
|
55
|
+
readonly 'docs.empty': "这个知识库里还没有文档";
|
|
56
|
+
readonly 'docs.loading': "正在读取文档…";
|
|
57
|
+
readonly 'docs.parseStatus': "解析状态";
|
|
58
|
+
readonly 'docs.scope.all': "全部知识库";
|
|
59
|
+
readonly 'docs.parse.pending': "排队中";
|
|
60
|
+
readonly 'docs.parse.processing': "解析中";
|
|
61
|
+
readonly 'docs.parse.finalizing': "生成摘要中";
|
|
62
|
+
readonly 'docs.parse.completed': "已就绪";
|
|
63
|
+
readonly 'docs.parse.failed': "解析失败";
|
|
64
|
+
readonly 'docs.parse.cancelled': "已取消";
|
|
65
|
+
readonly 'card.title': "知识库";
|
|
66
|
+
readonly 'card.search.title': "检索知识库";
|
|
67
|
+
readonly 'card.ask.title': "问知识库";
|
|
68
|
+
readonly 'card.read.title': "读取文档";
|
|
69
|
+
readonly 'card.bases.title': "知识库列表";
|
|
70
|
+
readonly 'card.running': "进行中…";
|
|
71
|
+
readonly 'card.runningWith': "正在检索「{subject}」…";
|
|
72
|
+
readonly 'card.done': "已完成";
|
|
73
|
+
readonly 'card.hitCount': "{count} 段命中";
|
|
74
|
+
readonly 'card.noHit': "无命中";
|
|
75
|
+
readonly 'card.answered': "已给出答案";
|
|
76
|
+
readonly 'card.answeredWith': "答案 · {count} 条引用";
|
|
77
|
+
readonly 'card.read.done': "已读取";
|
|
78
|
+
readonly 'card.read.doneWith': "全文 {count} 段";
|
|
79
|
+
readonly 'card.bases.count': "{count} 个知识库";
|
|
80
|
+
readonly 'card.segment': "第 {index} 段";
|
|
81
|
+
readonly 'card.actionFailed': "没能放进输入框:{reason}";
|
|
82
|
+
readonly 'card.insertFailed.no-target': "当前没有打开的会话";
|
|
83
|
+
readonly 'card.insertFailed.busy': "输入框正忙(正在处理上一轮)";
|
|
84
|
+
readonly 'card.insertFailed.rejected': "输入框拒绝了这次插入";
|
|
85
|
+
readonly 'card.untitled': "未命名文档";
|
|
86
|
+
readonly 'card.clipped': "已截断";
|
|
87
|
+
readonly 'card.open': "在知识库页打开";
|
|
88
|
+
readonly 'card.followUp': "追问";
|
|
89
|
+
readonly 'card.followUpPrompt': "请基于企业知识库《{title}》{segment}继续回答,并注明出处。";
|
|
90
|
+
readonly 'card.more': "展开其余 {count} 段";
|
|
91
|
+
readonly 'card.collapse': "收起";
|
|
92
|
+
readonly 'card.inspect': "在轨迹中查看";
|
|
93
|
+
readonly 'doc.read': "读全文";
|
|
94
|
+
readonly 'doc.back': "返回列表";
|
|
95
|
+
readonly 'doc.loading': "正在读取正文…";
|
|
96
|
+
readonly 'doc.empty': "这篇文档还没有可读的分块";
|
|
97
|
+
readonly 'doc.chunkTotal': "共 {count} 段";
|
|
98
|
+
readonly 'doc.page': "第 {page} / {total} 页";
|
|
99
|
+
readonly 'doc.prev': "上一页";
|
|
100
|
+
readonly 'doc.next': "下一页";
|
|
101
|
+
readonly 'doc.bringToChat': "带进聊天";
|
|
102
|
+
readonly 'doc.chatPrompt': "请基于企业知识库文档《{title}》回答,并注明出处。";
|
|
103
|
+
readonly 'doc.chatSent': "已放进输入框";
|
|
104
|
+
readonly 'doc.chatFailed': "没能放进输入框:{reason}";
|
|
105
|
+
readonly 'doc.chatFailed.no-target': "当前没有打开的会话";
|
|
106
|
+
readonly 'doc.chatFailed.busy': "输入框正忙(正在处理上一轮)";
|
|
107
|
+
readonly 'doc.chatFailed.rejected': "输入框拒绝了这次插入";
|
|
108
|
+
readonly 'doc.chatUnavailable': "当前没有打开的会话";
|
|
109
|
+
readonly 'doc.bringChunkToChat': "带进聊天";
|
|
110
|
+
readonly 'doc.chunkChatPrompt': "请基于企业知识库《{title}》{segment}继续回答,并注明出处。";
|
|
111
|
+
readonly 'search.placeholder': "在知识库里检索…";
|
|
112
|
+
readonly 'search.button': "检索";
|
|
113
|
+
readonly 'search.running': "检索中…";
|
|
114
|
+
readonly 'search.empty': "没有命中。若确认库里有相关内容,请检查后端检索配置:重排模型是否绑定,以及重排阈值(rerank_threshold)是否过高——阈值会把相关但分数偏低的片段整体滤掉。";
|
|
115
|
+
readonly 'search.hitCount': "命中片段";
|
|
116
|
+
readonly 'search.truncated': "已截断";
|
|
117
|
+
readonly 'search.clear': "返回文档列表";
|
|
15
118
|
};
|
|
16
119
|
export declare const en: {
|
|
17
120
|
'view.title': string;
|
|
18
121
|
'view.subtitle': string;
|
|
19
|
-
'view.placeholder': string;
|
|
20
122
|
'view.close': string;
|
|
123
|
+
'view.refresh': string;
|
|
124
|
+
'state.loading': string;
|
|
125
|
+
'state.error': string;
|
|
126
|
+
'state.retry': string;
|
|
127
|
+
'settings.title': string;
|
|
128
|
+
'settings.subtitle': string;
|
|
129
|
+
'settings.baseUrl': string;
|
|
130
|
+
'settings.baseUrlHint': string;
|
|
131
|
+
'settings.apiKey': string;
|
|
132
|
+
'settings.apiKeyPlaceholder': string;
|
|
133
|
+
'settings.apiKeyKeep': string;
|
|
134
|
+
'settings.apiKeyConfigured': string;
|
|
135
|
+
'settings.apiKeyMissing': string;
|
|
136
|
+
'settings.apiKeySource': string;
|
|
137
|
+
'settings.apiKeySource.settings': string;
|
|
138
|
+
'settings.apiKeySource.env': string;
|
|
139
|
+
'settings.apiKeySource.none': string;
|
|
140
|
+
'settings.tenantId': string;
|
|
141
|
+
'settings.defaultBases': string;
|
|
142
|
+
'settings.defaultBasesHint': string;
|
|
143
|
+
'settings.maxResults': string;
|
|
144
|
+
'settings.maxChunkChars': string;
|
|
145
|
+
'settings.agentId': string;
|
|
146
|
+
'settings.agentIdHint': string;
|
|
147
|
+
'settings.chatModelId': string;
|
|
148
|
+
'settings.chatModelIdHint': string;
|
|
149
|
+
'settings.save': string;
|
|
150
|
+
'settings.saving': string;
|
|
151
|
+
'settings.saved': string;
|
|
152
|
+
'settings.test': string;
|
|
153
|
+
'settings.testing': string;
|
|
154
|
+
'settings.testNote': string;
|
|
155
|
+
'selfCheck.ok': string;
|
|
156
|
+
'selfCheck.warn': string;
|
|
157
|
+
'selfCheck.error': string;
|
|
158
|
+
'selfCheck.baseCount': string;
|
|
159
|
+
'bases.title': string;
|
|
160
|
+
'bases.empty': string;
|
|
161
|
+
'bases.meta.docs': string;
|
|
162
|
+
'bases.meta.updated': string;
|
|
163
|
+
'bases.select': string;
|
|
164
|
+
'docs.title': string;
|
|
165
|
+
'docs.empty': string;
|
|
166
|
+
'docs.loading': string;
|
|
167
|
+
'docs.parseStatus': string;
|
|
168
|
+
'docs.scope.all': string;
|
|
169
|
+
'docs.parse.pending': string;
|
|
170
|
+
'docs.parse.processing': string;
|
|
171
|
+
'docs.parse.finalizing': string;
|
|
172
|
+
'docs.parse.completed': string;
|
|
173
|
+
'docs.parse.failed': string;
|
|
174
|
+
'docs.parse.cancelled': string;
|
|
175
|
+
'card.title': string;
|
|
176
|
+
'card.search.title': string;
|
|
177
|
+
'card.ask.title': string;
|
|
178
|
+
'card.read.title': string;
|
|
179
|
+
'card.bases.title': string;
|
|
180
|
+
'card.running': string;
|
|
181
|
+
'card.runningWith': string;
|
|
182
|
+
'card.done': string;
|
|
183
|
+
'card.hitCount': string;
|
|
184
|
+
'card.noHit': string;
|
|
185
|
+
'card.answered': string;
|
|
186
|
+
'card.answeredWith': string;
|
|
187
|
+
'card.read.done': string;
|
|
188
|
+
'card.read.doneWith': string;
|
|
189
|
+
'card.bases.count': string;
|
|
190
|
+
'card.segment': string;
|
|
191
|
+
'card.actionFailed': string;
|
|
192
|
+
'card.insertFailed.no-target': string;
|
|
193
|
+
'card.insertFailed.busy': string;
|
|
194
|
+
'card.insertFailed.rejected': string;
|
|
195
|
+
'card.untitled': string;
|
|
196
|
+
'card.clipped': string;
|
|
197
|
+
'card.open': string;
|
|
198
|
+
'card.followUp': string;
|
|
199
|
+
'card.followUpPrompt': string;
|
|
200
|
+
'card.more': string;
|
|
201
|
+
'card.collapse': string;
|
|
202
|
+
'card.inspect': string;
|
|
203
|
+
'doc.read': string;
|
|
204
|
+
'doc.back': string;
|
|
205
|
+
'doc.loading': string;
|
|
206
|
+
'doc.empty': string;
|
|
207
|
+
'doc.chunkTotal': string;
|
|
208
|
+
'doc.page': string;
|
|
209
|
+
'doc.prev': string;
|
|
210
|
+
'doc.next': string;
|
|
211
|
+
'doc.bringToChat': string;
|
|
212
|
+
'doc.chatPrompt': string;
|
|
213
|
+
'doc.chatSent': string;
|
|
214
|
+
'doc.chatFailed': string;
|
|
215
|
+
'doc.chatFailed.no-target': string;
|
|
216
|
+
'doc.chatFailed.busy': string;
|
|
217
|
+
'doc.chatFailed.rejected': string;
|
|
218
|
+
'doc.chatUnavailable': string;
|
|
219
|
+
'doc.bringChunkToChat': string;
|
|
220
|
+
'doc.chunkChatPrompt': string;
|
|
221
|
+
'search.placeholder': string;
|
|
222
|
+
'search.button': string;
|
|
223
|
+
'search.running': string;
|
|
224
|
+
'search.empty': string;
|
|
225
|
+
'search.hitCount': string;
|
|
226
|
+
'search.truncated': string;
|
|
227
|
+
'search.clear': string;
|
|
21
228
|
};
|
|
22
229
|
/** 词典键(zh 为基准,en 必须键集一致)。 */
|
|
23
230
|
export type KnowledgeLocaleKey = keyof typeof zh;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `hiwork-knowledge` 的客户端状态源。
|
|
3
|
+
*
|
|
4
|
+
* 铁律(对齐设计文档 §D4):
|
|
5
|
+
* - 客户端**不持有**凭据、不直接发 HTTP 到 WeKnora;一切数据来自 Host 的 loopback RPC;
|
|
6
|
+
* - 传输失败保留上一份成功快照,只把 phase 切到 `error` 并带上消息;
|
|
7
|
+
* - `testConnection` 会把自检结果写回快照(`snapshot.selfCheck`),
|
|
8
|
+
* 因为"检索健康"是配置问题、只有跑一次才知道;
|
|
9
|
+
* - 文档列表与检索结果是**视图局部状态**,由调用方自己缓存(不需要全局快照)。
|
|
10
|
+
*
|
|
11
|
+
* client bundle 纯度:本文件只 `import type` `../protocol.js`,运行时依赖为零。
|
|
12
|
+
*/
|
|
13
|
+
import type { KnowledgeDocView, KnowledgeDocumentView, KnowledgeHitView, KnowledgeSnapshotView } from '../protocol.js';
|
|
14
|
+
import type { SelfCheckResult } from '../service.js';
|
|
15
|
+
import type { ClientRpc } from './contracts.js';
|
|
16
|
+
/** RPC 频道;必须与 `protocol.ts` 的 `KNOWLEDGE_RPC_CHANNEL` 一致(由测试锁定)。 */
|
|
17
|
+
export declare const KNOWLEDGE_CHANNEL = "/hiwork-knowledge";
|
|
18
|
+
/** 快照请求携带的 Web 作用域标识(Host 据此把设置页当 hostWide 作用域)。 */
|
|
19
|
+
export declare const SETTINGS_SCOPE_SESSION_ID = "settings";
|
|
20
|
+
/** 宿主返回了无法解析的 RPC 信封时的哨兵消息(由视图本地化)。 */
|
|
21
|
+
export declare const RPC_INVALID_RESPONSE = "hiwork-knowledge:invalid-response";
|
|
22
|
+
/** 宿主返回了空错误消息时的哨兵(同样由视图本地化)。 */
|
|
23
|
+
export declare const RPC_REQUEST_FAILED = "hiwork-knowledge:request-failed";
|
|
24
|
+
/** 是否是"连接层"失败(可重试一次);主动取消不算。 */
|
|
25
|
+
export declare function isTransportError(error: unknown): boolean;
|
|
26
|
+
export interface KnowledgeClientState {
|
|
27
|
+
readonly phase: 'idle' | 'loading' | 'ready' | 'error';
|
|
28
|
+
readonly snapshot?: KnowledgeSnapshotView;
|
|
29
|
+
readonly error?: string;
|
|
30
|
+
readonly refreshedAt?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface KnowledgeStateSource {
|
|
33
|
+
getSnapshot(): KnowledgeClientState;
|
|
34
|
+
/**
|
|
35
|
+
* 服务端渲染时的快照。`useSyncExternalStore` 在 SSR 下要求提供它,
|
|
36
|
+
* 缺了会直接抛 `Missing getServerSnapshot`(静态渲染/冒烟测试会命中)。
|
|
37
|
+
*/
|
|
38
|
+
getServerSnapshot?(): KnowledgeClientState;
|
|
39
|
+
subscribe(listener: () => void): () => void;
|
|
40
|
+
}
|
|
41
|
+
/** 设置补丁(与 Host 的 `configSaveRequestSchema.patch` 逐字段一致)。 */
|
|
42
|
+
export interface KnowledgeConfigPatchWire {
|
|
43
|
+
readonly baseUrl?: string | undefined;
|
|
44
|
+
readonly apiKey?: string | undefined;
|
|
45
|
+
readonly tenantId?: string | undefined;
|
|
46
|
+
readonly defaultBaseIds?: readonly string[] | undefined;
|
|
47
|
+
readonly maxResults?: number | undefined;
|
|
48
|
+
readonly maxChunkChars?: number | undefined;
|
|
49
|
+
readonly agentId?: string | undefined;
|
|
50
|
+
readonly chatModelId?: string | undefined;
|
|
51
|
+
}
|
|
52
|
+
export interface KnowledgeRuntime {
|
|
53
|
+
readonly source: KnowledgeStateSource;
|
|
54
|
+
refresh(): Promise<void>;
|
|
55
|
+
saveConfig(patch: KnowledgeConfigPatchWire): Promise<KnowledgeSnapshotView['config']>;
|
|
56
|
+
testConnection(): Promise<SelfCheckResult>;
|
|
57
|
+
loadDocs(baseId: string): Promise<readonly KnowledgeDocView[]>;
|
|
58
|
+
/** 读一篇文档的正文(分页);「只能搜片段」的解法就在这里。 */
|
|
59
|
+
loadDocument(knowledgeId: string, page?: number): Promise<KnowledgeDocumentView>;
|
|
60
|
+
search(query: string, baseId?: string): Promise<readonly KnowledgeHitView[]>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 创建知识库运行时。
|
|
64
|
+
* @param rpc - 宿主 loopback RPC(`ctx.connection.rpc`)。
|
|
65
|
+
*/
|
|
66
|
+
export declare function createKnowledgeRuntime(rpc: ClientRpc): KnowledgeRuntime;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把一次知识库工具调用(`tool.call.toolview` 收到的 `block`)解析成卡片模型。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独成文件、且不 import React:这是**纯函数**,四种工具各有一套返回形状,
|
|
5
|
+
* 而工具结果在真机上的形态(`argsRaw` 是原始 JSON 字符串、结果在 `content[].text` 里
|
|
6
|
+
* 又是一层 JSON)靠读类型定义是看不出来的——它是实测出来的:
|
|
7
|
+
*
|
|
8
|
+
* - `RunningToolCall` 只有 `argsRaw`(**原始 JSON 字符串**,不是对象),没有结果;
|
|
9
|
+
* - `ToolResultNode` 有 `kind: 'tool-result'`、`content: ContentBlock[]`、`isError`;
|
|
10
|
+
* - 我们的工具声明的是 `output: { schema: { type: 'json' } }` + 一个 `render`,所以
|
|
11
|
+
* 结果落在 `content[0].text` 里,内容是 `JSON.stringify(value)` 后的字符串;
|
|
12
|
+
* - 因此「读结果」= 取 text block → `JSON.parse` → 得到 `{ ok, ... }`。
|
|
13
|
+
*
|
|
14
|
+
* 解析**永不抛错**:任何形状不对的输入都退化成「有 args、没有结果」的最小模型,
|
|
15
|
+
* 卡片渲染成一行标题而不是让 slot 的 error boundary 把整张卡片摘掉。
|
|
16
|
+
*/
|
|
17
|
+
import type { ToolCallBlockLike } from './contracts.js';
|
|
18
|
+
/** 卡片生命周期状态(由 block 自身推导,不查任何实时状态)。 */
|
|
19
|
+
export type ToolCardState = 'running' | 'ok' | 'error' | 'stopped';
|
|
20
|
+
/** 一条检索命中 / 引用。 */
|
|
21
|
+
export interface HitRowView {
|
|
22
|
+
readonly knowledgeId: string;
|
|
23
|
+
readonly chunkId: string;
|
|
24
|
+
readonly knowledgeTitle: string;
|
|
25
|
+
readonly fileName: string;
|
|
26
|
+
readonly chunkIndex: number | null;
|
|
27
|
+
readonly chunkType: string;
|
|
28
|
+
readonly content: string;
|
|
29
|
+
readonly truncated: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** 一个知识库。 */
|
|
32
|
+
export interface BaseRowView {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly description: string;
|
|
36
|
+
readonly documentCount: number | null;
|
|
37
|
+
}
|
|
38
|
+
/** 文档正文里的一个分块。 */
|
|
39
|
+
export interface ChunkRowView {
|
|
40
|
+
readonly index: number | null;
|
|
41
|
+
readonly chunkType: string;
|
|
42
|
+
readonly content: string;
|
|
43
|
+
readonly truncated: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** 四张卡片共用的头部信息。 */
|
|
46
|
+
interface CardCommon {
|
|
47
|
+
readonly state: ToolCardState;
|
|
48
|
+
/** 调用参数里的主要文本(检索词 / 问题 / 文档 ID),running 阶段只有它可显示。 */
|
|
49
|
+
readonly subject: string;
|
|
50
|
+
/** 失败原因(`isError` 或工具自己返回 `{ ok: false, message }`)。 */
|
|
51
|
+
readonly error: string | null;
|
|
52
|
+
}
|
|
53
|
+
export interface SearchCard extends CardCommon {
|
|
54
|
+
readonly kind: 'search';
|
|
55
|
+
readonly hitCount: number;
|
|
56
|
+
readonly hits: readonly HitRowView[];
|
|
57
|
+
/** 工具在「0 条」时给出的可执行提示(配置类原因),有则优先显示。 */
|
|
58
|
+
readonly hint: string | null;
|
|
59
|
+
}
|
|
60
|
+
export interface AskCard extends CardCommon {
|
|
61
|
+
readonly kind: 'ask';
|
|
62
|
+
readonly answer: string;
|
|
63
|
+
readonly references: readonly HitRowView[];
|
|
64
|
+
readonly elapsedMs: number | null;
|
|
65
|
+
}
|
|
66
|
+
export interface ReadCard extends CardCommon {
|
|
67
|
+
readonly kind: 'read';
|
|
68
|
+
readonly title: string;
|
|
69
|
+
readonly chunkTotal: number | null;
|
|
70
|
+
readonly page: number | null;
|
|
71
|
+
readonly chunks: readonly ChunkRowView[];
|
|
72
|
+
}
|
|
73
|
+
export interface BasesCard extends CardCommon {
|
|
74
|
+
readonly kind: 'bases';
|
|
75
|
+
readonly bases: readonly BaseRowView[];
|
|
76
|
+
}
|
|
77
|
+
export interface UnknownCard extends CardCommon {
|
|
78
|
+
readonly kind: 'unknown';
|
|
79
|
+
}
|
|
80
|
+
export type ToolCardModel = SearchCard | AskCard | ReadCard | BasesCard | UnknownCard;
|
|
81
|
+
/** 卡片一次最多展示多少条命中(多的靠展开,见组件)。 */
|
|
82
|
+
export declare const CARD_HIT_PREVIEW = 3;
|
|
83
|
+
/**
|
|
84
|
+
* 解析一次工具调用 → 卡片模型。
|
|
85
|
+
* @param toolName - 线上工具名(决定返回体的形状)。
|
|
86
|
+
* @param block - `tool.call.toolview` 的 `block`(running 或 settled)。
|
|
87
|
+
* @returns 卡片模型;未知工具名退化为 `unknown`。
|
|
88
|
+
*/
|
|
89
|
+
export declare function toToolCardModel(toolName: string, block: ToolCallBlockLike): ToolCardModel;
|
|
90
|
+
export {};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,22 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `hiwork-knowledge` 的 Host 入口。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
* 所有贡献都登记在 `ctx.effect`
|
|
4
|
+
* 职责边界(与设计文档 §D4 一致,Host 是唯一事实来源):
|
|
5
|
+
* - 打开 `KnowledgeService`(存储域 `hiwork_knowledge` + 凭据解析 + 出网客户端);
|
|
6
|
+
* - 给每个**根 Agent** 挂 4 个只读工具(创建时补挂、销毁时摘除);
|
|
7
|
+
* - 注册系统提示词片段与 `/hiwork-knowledge` loopback RPC(Web 半边只读、只提交意图);
|
|
8
|
+
* - 所有贡献都登记在 `ctx.effect` 的清理函数里,清理幂等。
|
|
9
9
|
*
|
|
10
10
|
* 打包注意:桌面端补种插件时会清空 profile 的 `node_modules/@deepseek-ai/*`,
|
|
11
|
-
* 因此 `lib/index.js` 必须自包含;本文件对官方包只做 **type-only** import
|
|
11
|
+
* 因此 `lib/index.js` 必须自包含;本文件对官方包只做 **type-only** import
|
|
12
|
+
* (下面几个 `import type {}` 仅用于加载 Context 声明合并,编译后被完全擦除)。
|
|
12
13
|
*/
|
|
13
14
|
import type { Context } from '@deepseek-ai/cordis';
|
|
15
|
+
import z from '@deepseek-ai/schemastery';
|
|
14
16
|
/** DSH 插件名(与 `cordis.patch.yml` 的 bundle id 一致)。 */
|
|
15
17
|
export declare const name = "hiwork-knowledge";
|
|
16
|
-
/**
|
|
17
|
-
export declare const inject: readonly
|
|
18
|
+
/** 必需的 Host 服务。 */
|
|
19
|
+
export declare const inject: readonly ["storageDomain", "agents", "tools", "connection"];
|
|
20
|
+
/** bundle 侧可覆盖的默认值(与 `cordis.patch.yml` 的 config 字段一一对应)。 */
|
|
21
|
+
export interface KnowledgeBundleConfig {
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
apiKey: string;
|
|
24
|
+
tenantId: string;
|
|
25
|
+
defaultBaseIds: string[];
|
|
26
|
+
maxResults: number;
|
|
27
|
+
maxChunkChars: number;
|
|
28
|
+
agentId: string;
|
|
29
|
+
chatModelId: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 配置 schema:默认值与 `types.ts` 的常量一致。
|
|
33
|
+
*
|
|
34
|
+
* 用推断类型而不是 `Schemastery<...>`:cordis 会用**部分**配置调用 schema,
|
|
35
|
+
* 标注成完整类型会让 `Config({})` 在类型层被拒绝。
|
|
36
|
+
*/
|
|
37
|
+
export declare const Config: z<Schemastery.ObjectS<{
|
|
38
|
+
baseUrl: z<string, string>;
|
|
39
|
+
apiKey: z<string, string>;
|
|
40
|
+
tenantId: z<string, string>;
|
|
41
|
+
defaultBaseIds: z<string[], string[]>;
|
|
42
|
+
maxResults: z<number, number>;
|
|
43
|
+
maxChunkChars: z<number, number>;
|
|
44
|
+
agentId: z<string, string>;
|
|
45
|
+
chatModelId: z<string, string>;
|
|
46
|
+
}>, Schemastery.ObjectT<{
|
|
47
|
+
baseUrl: z<string, string>;
|
|
48
|
+
apiKey: z<string, string>;
|
|
49
|
+
tenantId: z<string, string>;
|
|
50
|
+
defaultBaseIds: z<string[], string[]>;
|
|
51
|
+
maxResults: z<number, number>;
|
|
52
|
+
maxChunkChars: z<number, number>;
|
|
53
|
+
agentId: z<string, string>;
|
|
54
|
+
chatModelId: z<string, string>;
|
|
55
|
+
}>>;
|
|
18
56
|
/**
|
|
19
57
|
* 挂载 Host 半边。
|
|
20
|
-
* @param ctx -
|
|
58
|
+
* @param ctx - Host 插件上下文(必须提供 `inject` 声明的全部服务)。
|
|
59
|
+
* @param config - bundle 配置;缺省字段已由 {@link Config} 补齐。
|
|
21
60
|
*/
|
|
22
|
-
export declare function apply(ctx: Context): void
|
|
61
|
+
export declare function apply(ctx: Context, config: KnowledgeBundleConfig): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 知识库相关的系统提示词片段与工具使用指引。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要这段:DSH 自带的能力只覆盖工作区(`grep`/`glob`)与公网(`web_search`),
|
|
5
|
+
* 企业文档既不在工作区、也不该走公网。模型不知道有企业知识库时,会直接回答"我查不到"
|
|
6
|
+
* 或者凭常识编。片段的作用是**告诉它先去查**,以及别把「配置问题导致的 0 命中」当成
|
|
7
|
+
* 「企业没有这个资料」。
|
|
8
|
+
*
|
|
9
|
+
* 引用格式是**强制**的(`CITATION_RULE`):知识库的答案是给同事看的,没有出处的答案在
|
|
10
|
+
* 内部场景里等于不可用——用户没法核对,也没法顺着找原文。格式刻意对齐聊天卡片展示的
|
|
11
|
+
* 信息(文档标题 + 段号),两者能直接对上。
|
|
12
|
+
*/
|
|
13
|
+
/** 系统提示词段落名(稳定值,便于用户按名字关闭)。 */
|
|
14
|
+
export declare const KNOWLEDGE_PROMPT_NAME = "hiwork-knowledge";
|
|
15
|
+
/** 排序权重:排在通用工作区规则之后、具体业务插件之前。 */
|
|
16
|
+
export declare const KNOWLEDGE_PROMPT_ORDER = 62;
|
|
17
|
+
/** 引用格式:与聊天卡片展示的「文档标题 · 段号」一致,用户据此核对原文。 */
|
|
18
|
+
export declare const CITATION_RULE: string;
|
|
19
|
+
export declare const KNOWLEDGE_PROMPT_TEXT: string;
|
|
20
|
+
/** 工具说明里引用的行为约束(测试会断言它出现在工具描述中)。 */
|
|
21
|
+
export declare const KNOWLEDGE_TOOL_GUIDE = "\u77E5\u8BC6\u5E93\u68C0\u7D22\u8FD4\u56DE 0 \u6761\u65F6\uFF0C\u5982\u5B9E\u8BF4\u660E\u60C5\u51B5\uFF0C\u4E0D\u8981\u51ED\u5E38\u8BC6\u4F5C\u7B54\uFF1B\u5F15\u7528\u7ED3\u8BBA\u5FC5\u987B\u9644\u51FA\u5904\u3002";
|