dsh-data-cleaning-agent 0.5.2 → 0.5.3
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/CHANGELOG.md +21 -0
- package/README.en.md +14 -9
- package/README.md +11 -7
- package/docs/COMPATIBILITY.md +12 -1
- package/docs/RELEASE-0.5.2.md +6 -2
- package/docs/RELEASE-0.5.3.md +59 -0
- package/docs/USER-GUIDE.md +14 -2
- package/lib/client.js +679 -29
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.3] - 2026-09-03
|
|
8
|
+
|
|
9
|
+
> UI 二次对齐版本;不改变 Host/QCC 工具契约或计费安全门。
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- 为插件创建的 blank 会话增加「数据清洗补全智能体」业务首页、产品说明、四阶段工作流与安全边界说明。
|
|
13
|
+
- 在原生输入框左上角增加提示词生成器:支持粘贴企业名/统一社会信用代码、解析 Excel/CSV/JSON、
|
|
14
|
+
附加图片、选择清洗动作和补全维度,并将自然语言任务描述回填原生输入框供人工修改。
|
|
15
|
+
- 表格由本地同源解析端点处理,完整数据经 session→root 事件桥载入工作台;提示词仅带主体预览,
|
|
16
|
+
避免把整表无边界写入模型上下文。
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- 侧栏菜单由「数据清洗」改为「数据清洗补全」,点击后先进入中央业务首页,不再自动拉开右侧工作台。
|
|
20
|
+
- 五个流程入口从输入框内部 `conversation.input.left` 迁到公开 `conversation.input.dock`,并仅移动
|
|
21
|
+
本插件 cell 到输入框下方;普通 DSH 会话不显示业务首页、提示词按钮或流程入口。
|
|
22
|
+
|
|
23
|
+
### Compatibility
|
|
24
|
+
- DSH 没有公开 Hero headline 替换槽位;0.5.3 使用精确匹配中英文原生标题、卸载时恢复的隔离 DOM Bridge。
|
|
25
|
+
- 图片接入只在运行时探测到 `conversation.createDraftImages` 与 `input.shell().addImages` 后启用;
|
|
26
|
+
不硬编码未验证的企查查智能文档解析工具名,任务描述只要求模型使用当前已连接且可用的能力。
|
|
27
|
+
|
|
7
28
|
## [0.5.2] - 2026-09-03
|
|
8
29
|
|
|
9
30
|
> DSH 原生 UI 对齐版本;不改变 QCC 工具范围、OAuth、计费确认或数据契约。
|
package/README.en.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> A data cleaning & completion agent plugin for DeepSeek Harness: local CSV/XLSX/JSON engine plus optional Qichacha (QCC) MCP enterprise-data enrichment. Initiated and maintained by the Qichacha (QCC) team.
|
|
4
4
|
>
|
|
5
|
-
> Current source version / 当前源码版本: **0.5.
|
|
5
|
+
> Current source version / 当前源码版本: **0.5.3** (stable release)
|
|
6
6
|
|
|
7
7
|
[](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/dsh-data-cleaning-agent)
|
|
@@ -20,9 +20,10 @@ clean name / phone / amount columns (trim, phone normalization, drop missing-req
|
|
|
20
20
|
negative-amount / duplicate rows), deterministically complete gaps, profile the batch, and
|
|
21
21
|
export clean CSV.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
When spreadsheet data is handled in the right-side workbench, the model receives aggregate
|
|
24
|
+
summaries by default while full rows stay in the same-origin UI. If the user explicitly pastes
|
|
25
|
+
entities, attaches an image, or sends a spreadsheet entity preview through the prompt builder,
|
|
26
|
+
that selected content enters model context; the full spreadsheet still remains local to the workbench.
|
|
26
27
|
|
|
27
28
|
QCC enrichment follows a **bring-your-own connection and account (BYO QCC)** model. Each customer
|
|
28
29
|
connects QCC MCP in their own DSH environment and uses quota or billing attached to their own QCC
|
|
@@ -40,10 +41,12 @@ Fully restart DeepSeek Harness afterwards (stop and re-run `dsh web`). Then say
|
|
|
40
41
|
"help me clean this batch of company list data" and the plugin loads its built-in Skill and
|
|
41
42
|
drives the clean / complete / profile tools.
|
|
42
43
|
|
|
43
|
-
After restart, a "Data Cleaning" entry appears between "New Session" and "Workspaces"
|
|
44
|
-
of the sidebar. It opens a native DSH
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
After restart, a "Data Cleaning & Completion" entry appears between "New Session" and "Workspaces"
|
|
45
|
+
near the top of the sidebar. It opens a dedicated native DSH session with a business landing view,
|
|
46
|
+
product/workflow introduction, and a prompt builder at the upper-left of the composer. The builder
|
|
47
|
+
accepts pasted entities, locally parsed spreadsheet data, or image attachments and writes an editable
|
|
48
|
+
task brief back to the native composer. Five workflow actions (upload, profile, match, enrich, history)
|
|
49
|
+
sit below the composer and open the four-step workbench on demand.
|
|
47
50
|
|
|
48
51
|
Without the `dsh` CLI, use the install script:
|
|
49
52
|
|
|
@@ -65,7 +68,8 @@ Or let an agent install it for you:
|
|
|
65
68
|
| Parse | web `/data-cleaning/api/mvp/parse` | CSV / XLSX / JSON |
|
|
66
69
|
| Async jobs | web `/data-cleaning/api/mvp/jobs` | job state machine + persistent storage |
|
|
67
70
|
| UI | web `/data-cleaning/` | upload → clean/complete → export |
|
|
68
|
-
| In-app entry | top
|
|
71
|
+
| In-app entry | top "Data Cleaning & Completion" entry + five actions below the composer | dedicated business home in the center; opens the Mockup-aligned workbench on demand |
|
|
72
|
+
| Prompt builder | `conversation.input.overlay` | text / spreadsheet / image intake, cleaning and enrichment selection, editable native-composer draft |
|
|
69
73
|
| Tool cards | `tool.call.toolview` (`data_clean_rows`/`data_complete_rows`/`data_profile`) | render clean/complete/profile result cards in-conversation with running/done/failed state |
|
|
70
74
|
| Task progress | workbench header jobs pill | polls `/data-cleaning/api/mvp/jobs`; shows queued / running tasks |
|
|
71
75
|
| Skill | `data-cleaning` | guides the model through the workflow |
|
|
@@ -107,6 +111,7 @@ See [the Phase-3 acceptance record](docs/PHASE3-ACCEPTANCE.md) and
|
|
|
107
111
|
[the 0.5.0 release record](docs/RELEASE-0.5.0.md) for verification, upgrade, and rollback.
|
|
108
112
|
See [the 0.5.1 release record](docs/RELEASE-0.5.1.md) for the README fix and release-text gate.
|
|
109
113
|
See [the 0.5.2 release record](docs/RELEASE-0.5.2.md) for native DSH UI alignment, verification, and rollback.
|
|
114
|
+
See [the 0.5.3 release record](docs/RELEASE-0.5.3.md) for the business landing view and prompt builder.
|
|
110
115
|
|
|
111
116
|
See [docs/PLAN-OSS.md](docs/PLAN-OSS.md) for details.
|
|
112
117
|
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 在 DeepSeek Harness 中清洗、补全、画像企业名单数据的智能体插件:本地 CSV/XLSX/JSON 引擎 + 可选企查查(Qichacha/QCC)MCP 企业数据补全,由企查查(Qichacha/QCC)团队发起并维护。
|
|
4
4
|
>
|
|
5
|
-
> 当前源码版本 / Current source version: **0.5.
|
|
5
|
+
> 当前源码版本 / Current source version: **0.5.3**(正式版本)
|
|
6
6
|
|
|
7
7
|
[](https://github.com/duhu2000/dsh-data-cleaning-agent/actions/workflows/ci.yml)
|
|
8
8
|
[](https://www.npmjs.com/package/dsh-data-cleaning-agent)
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
对姓名、手机号、金额等列做清洗(去空格、手机号规范化、剔除缺失/负金额/重复行)、
|
|
20
20
|
确定性补全与概览画像,并导出干净的 CSV。
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
通过右侧工作台处理表格时,模型(LLM)默认只拿到统计摘要,完整明细仅在同源 web 界面查看与导出。
|
|
23
|
+
如果用户主动用提示词生成器粘贴名单、附加图片或把表格主体预览回填对话框,这些用户明确选择发送的
|
|
24
|
+
内容会进入模型上下文;表格完整数据仍只保留在本机工作台。
|
|
24
25
|
|
|
25
26
|
企查查补全采用 **客户自带连接与账号(BYO QCC)**:每位客户在自己的 DSH 中开通企查查 MCP,
|
|
26
27
|
调用消耗其自有账号的套餐额度或按其与企查查的合同计费。本插件不内置、不分发、不共享开发者 Key,
|
|
@@ -35,9 +36,10 @@ dsh plugin --profile web add dsh-data-cleaning-agent
|
|
|
35
36
|
安装后**完全重启** DeepSeek Harness(停止后重新运行 `dsh web`)。
|
|
36
37
|
之后在对话中说「帮我清洗这批企业名单数据」,插件会自动加载内嵌 Skill 并调度清洗/补全/画像工具。
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
重启后,「数据清洗补全」入口会显示在侧边栏顶部的「新会话」与「工作区」之间。点击后打开专属的
|
|
40
|
+
DSH 原生会话:首页标题、产品说明与工作流会替换通用探索页;输入框左上角提供提示词生成器,支持
|
|
41
|
+
粘贴名单、解析 Excel/CSV/JSON 或附加图片并选择清洗动作、补全字段。上传清洗、质量体检、匹配核验、
|
|
42
|
+
字段补全和任务历史五个入口位于输入框下方,右侧工作台承载完整四步闭环。
|
|
41
43
|
|
|
42
44
|
没有 `dsh` CLI 时,也可以用安装脚本:
|
|
43
45
|
|
|
@@ -59,7 +61,8 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
|
|
|
59
61
|
| 解析 | web `/data-cleaning/api/mvp/parse` | CSV / XLSX / JSON |
|
|
60
62
|
| 异步任务 | web `/data-cleaning/api/mvp/jobs` | 任务状态机 + 持久化存储 |
|
|
61
63
|
| UI | web `/data-cleaning/` | 上传 → 清洗/补全 → 导出 |
|
|
62
|
-
| 应用内入口 |
|
|
64
|
+
| 应用内入口 | 侧栏顶部「数据清洗补全」+ 输入框下方五能力按钮 | 中央业务首页与原生对话,右侧按需打开 Mockup 对齐工作台 |
|
|
65
|
+
| 提示词生成 | `conversation.input.overlay` | 文本 / Excel / 图片录入、清洗项与补全维度选择,生成后回填原生输入框供人工修改 |
|
|
63
66
|
| 工具卡片 | `tool.call.toolview`(`data_clean_rows`/`data_complete_rows`/`data_profile`) | 对话内渲染清洗/补全/画像结果卡,含运行/已完成/失败状态 |
|
|
64
67
|
| 任务进度 | 工作台头部任务 pill | 轮询 `/data-cleaning/api/mvp/jobs`,展示排队/运行中任务 |
|
|
65
68
|
| Skill | `data-cleaning` | 引导模型按工作流调度上述工具 |
|
|
@@ -104,6 +107,7 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
|
|
|
104
107
|
发布记录、升级与回滚见 [docs/RELEASE-0.5.0.md](docs/RELEASE-0.5.0.md)。
|
|
105
108
|
0.5.1 文档补丁与防回归发布门见 [docs/RELEASE-0.5.1.md](docs/RELEASE-0.5.1.md)。
|
|
106
109
|
0.5.2 DSH 原生 UI 对齐范围、验收与回滚见 [docs/RELEASE-0.5.2.md](docs/RELEASE-0.5.2.md)。
|
|
110
|
+
0.5.3 业务首页、提示词生成和输入框下方流程栏见 [docs/RELEASE-0.5.3.md](docs/RELEASE-0.5.3.md)。
|
|
107
111
|
|
|
108
112
|
## 本地开发
|
|
109
113
|
|
package/docs/COMPATIBILITY.md
CHANGED
|
@@ -21,11 +21,18 @@ MVP 路由、Phase-3 capabilities、estimate 与未确认 enrich 阻断均通过
|
|
|
21
21
|
0.5.1 为已发布的 README 状态与发布 Gate 文档补丁,不修改 Host/Client、QCC 契约或运行时依赖,
|
|
22
22
|
因此继承 0.5.0 的 DSH、Node 与 OAuth 兼容矩阵。
|
|
23
23
|
|
|
24
|
-
0.5.2
|
|
24
|
+
0.5.2 已发布版本只调整 Client UI:入口 Portal 到 `sidebar.workspaces` 前,中央区域复用 DSH 原生会话,
|
|
25
25
|
五能力入口使用公开 `conversation.input.left`,会话头恢复入口使用公开
|
|
26
26
|
`conversation.session.header.actions`,右侧面板继续使用 additive `shell.overlay`。不替换单占位
|
|
27
27
|
`details` / `conversation.session.header`,也不修改 Host、QCC 工具契约或计费安全门。
|
|
28
28
|
|
|
29
|
+
0.5.3 正式版本修正第二轮 UI 位置:五能力入口迁到公开 `conversation.input.dock`,只使用稳定
|
|
30
|
+
`data-slot` 和 `:has()` 把本插件 cell 排到输入框下方;提示词生成器使用公开
|
|
31
|
+
`conversation.input.overlay` 和标准 `inputActions.setDraft`。DSH 未提供公开 Hero headline 槽位,
|
|
32
|
+
因此仅对插件创建且仍为空白的会话使用精确中英文文本匹配、卸载恢复的 DOM Bridge。图片接入对
|
|
33
|
+
`conversation.createDraftImages` / `input.shell(sessionId).addImages` 做运行时探测,视为隔离兼容层,
|
|
34
|
+
不承诺 alpha 实验面稳定。
|
|
35
|
+
|
|
29
36
|
## 2. Node 运行时
|
|
30
37
|
|
|
31
38
|
- 本包 `engines.node` 声明 `>=20`。
|
|
@@ -95,6 +102,10 @@ Portal、原生会话、五能力按钮、跨 scope 状态桥、窄桌面布局
|
|
|
95
102
|
插件只做运行时能力探测并保留 `sessions.create` 安全降级,不承诺 alpha 实验面稳定。Portal 失败时
|
|
96
103
|
仍保留 footer 降级按钮。
|
|
97
104
|
|
|
105
|
+
0.5.3 新增业务首页和提示词生成器,并把流程栏移出输入框。本地 tarball 已完成 rc.2 隔离
|
|
106
|
+
Profile 装载与 Host 注册冒烟;受应用内浏览器本机 URL 策略限制,深浅色、窄屏和精确像素位置仍作为
|
|
107
|
+
发布后人工观察项,当前单元测试与源码契约测试不能替代该视觉检查。
|
|
108
|
+
|
|
98
109
|
### 4.3 0.5.0 三域兼容面
|
|
99
110
|
|
|
100
111
|
| 能力 | rc.2 | alpha.2 | 备注 |
|
package/docs/RELEASE-0.5.2.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
> 版本:**0.5.2**
|
|
4
4
|
>
|
|
5
|
-
>
|
|
5
|
+
> 状态:**✅ 已发布;npm `latest`、Git tag 与 GitHub Latest 均为 `0.5.2`。**
|
|
6
6
|
>
|
|
7
|
-
>
|
|
7
|
+
> 发布时间:2026-09-03T06:10:34Z
|
|
8
8
|
|
|
9
9
|
## 1. 目的与范围
|
|
10
10
|
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
- DSH `0.1.1-rc.2` / `0.1.2-alpha.2` 全新隔离安装与 UI 冒烟通过;
|
|
26
26
|
- 已验证顶部入口、中央原生会话、五能力按钮、非模态右栏、布局避让、上传解析和质量体检;
|
|
27
27
|
- 本轮 UI 验收没有调用 QCC,也没有触碰生产 DSH Host。
|
|
28
|
+
- `v0.5.2` annotated tag 指向 `a1bb68a`;Release workflow `33722000758` 全绿;
|
|
29
|
+
- npm 公共 Registry 返回 `latest=0.5.2`,provenance predicate 为 SLSA v1;
|
|
30
|
+
- 公共 Registry 全新安装成功,ESM 导出为 `apply / inject / name`,包内中英文 README 均为 0.5.2 正式态;
|
|
31
|
+
- GitHub Release 为非 draft、非 prerelease:https://github.com/duhu2000/dsh-data-cleaning-agent/releases/tag/v0.5.2 。
|
|
28
32
|
|
|
29
33
|
## 3. 升级与回滚
|
|
30
34
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# 0.5.3 UI 二次对齐发布记录
|
|
2
|
+
|
|
3
|
+
> 版本:**0.5.3**
|
|
4
|
+
>
|
|
5
|
+
> 状态:**正式版本;由 `v0.5.3` 标签触发 npm OIDC Trusted Publishing 与 GitHub Release。**
|
|
6
|
+
>
|
|
7
|
+
> 发布日期:2026-09-03
|
|
8
|
+
|
|
9
|
+
## 目标
|
|
10
|
+
|
|
11
|
+
0.5.3 继续对齐原始高保真 Mockup 与招投标智能体的入口模式:
|
|
12
|
+
|
|
13
|
+
1. 侧栏名称为「数据清洗补全」,位置仍在「新会话」与「工作区」之间;
|
|
14
|
+
2. 点击菜单先进入中央业务首页,不自动展开右侧工作台;
|
|
15
|
+
3. 五个流程入口位于原生输入框下方;
|
|
16
|
+
4. 输入框左上角提供提示词生成器,生成内容回填原生输入框,用户可修改后发送;
|
|
17
|
+
5. 右侧工作台继续负责上传映射、质量体检、匹配核验、补全导出与任务历史。
|
|
18
|
+
|
|
19
|
+
## 已验证的 DSH 契约
|
|
20
|
+
|
|
21
|
+
本实现依据本机 DSH Desktop 随附的一手槽位目录与运行时代码:
|
|
22
|
+
|
|
23
|
+
| 能力 | 已验证事实 | 0.5.3 用法 |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `conversation.input.left` | 输入框卡片内部工具行 | 不再承载五流程按钮 |
|
|
26
|
+
| `conversation.input.dock` | session 级、独立整行,默认位于输入框上方 | 承载业务首页和五流程按钮,仅通过稳定 `data-slot` 把本 cell 排到输入框下方 |
|
|
27
|
+
| `conversation.input.overlay` | session 级浮层锚点,标准 props 含 `sessionId` / `inputActions` | 承载提示词生成触发器与弹层 |
|
|
28
|
+
| `inputActions.setDraft` | 原生输入机动作 | 把生成的任务描述回填对话框 |
|
|
29
|
+
| `conversation.createDraftImages` + `input.shell(id).addImages` | 本机 rc.2 运行时存在的浏览器 Draft 图片链 | 仅经能力探测 Bridge 使用;失败时释放临时图片并提示使用原生“+” |
|
|
30
|
+
| Hero headline | 当前没有公开的 headline 替换槽位 | 仅在本插件 blank 会话精确匹配中英文标题并可恢复;普通会话不变 |
|
|
31
|
+
|
|
32
|
+
## 提示词生成器范围
|
|
33
|
+
|
|
34
|
+
- 输入:逐行企业名称/统一社会信用代码、本地 Excel/XLS/CSV/JSON、PNG/JPEG/WebP 图片;
|
|
35
|
+
- 清洗:名称规范、旧名补全/修正、信用代码校验/补全、去重、模糊候选人工确认;
|
|
36
|
+
- 补全:v1 工商字段,以及已经存在的企业全景、股权、组织、风险、知产、经营维度组;
|
|
37
|
+
- 输出:自然语言任务描述,包含精确优先、多候选暂停、缺失不编造、来源保留与客户自带 QCC 费用边界;
|
|
38
|
+
- 表格:完整解析结果留在本机工作台,提示词只包含最多 50 条主体预览;
|
|
39
|
+
- 图片:插件只附加图片并生成能力要求,不假设或硬编码尚未验证的“智能文档解析”具体工具名。
|
|
40
|
+
|
|
41
|
+
## 验收门
|
|
42
|
+
|
|
43
|
+
- [x] `npm run check` 全通过(2026-09-03:138/138);
|
|
44
|
+
- [x] rc.2 隔离 Profile 安装本地 tarball,Host `apply()`、MVP seam、3 tools 与 2 skills 注册成功;
|
|
45
|
+
- [ ] 侧栏文案、业务首页标题和四段产品流程与设计稿一致;
|
|
46
|
+
- [ ] 五流程按钮位于输入框下方,打开对应右侧工作台步骤;
|
|
47
|
+
- [ ] 文本、Excel、图片三种提示词生成路径均能回填且可编辑;
|
|
48
|
+
- [x] 组件/事件契约确认 Excel 完整数据进入工作台,普通会话不出现本插件业务 UI;
|
|
49
|
+
- [ ] 右栏关闭/展开、窄桌面与深色模式无明显遮挡;
|
|
50
|
+
- [ ] 无真实 QCC 调用即可完成 UI 验收;真实调用仍受既有估算、确认、幂等与上限门保护。
|
|
51
|
+
|
|
52
|
+
> 本轮应用内浏览器首次打开时已确认顶部菜单文案;隔离 Host 重启后,本机 URL 被浏览器安全策略阻止二次访问,
|
|
53
|
+
> 因此标题、流程栏位置、弹层及深浅色/窄屏保留为发布后人工观察项,未用替代浏览器绕过。
|
|
54
|
+
|
|
55
|
+
## 升级与回滚
|
|
56
|
+
|
|
57
|
+
- 升级候选:安装本地 `npm pack` 产物并完全重启 DSH;不覆盖客户 OAuth grant 或 Key。
|
|
58
|
+
- 回滚:重新安装 `dsh-data-cleaning-agent@0.5.2` 并完全重启 DSH。
|
|
59
|
+
- 0.5.3 不修改 Host 路由、QCC 工具契约、存储 schema 或导出格式,回滚无需数据迁移。
|
package/docs/USER-GUIDE.md
CHANGED
|
@@ -24,15 +24,27 @@ bash <(curl -fsSL https://raw.githubusercontent.com/duhu2000/dsh-data-cleaning-a
|
|
|
24
24
|
|
|
25
25
|
插件会加载内嵌 Skill `data-cleaning`,自动按 `data_profile → data_clean_rows → data_complete_rows` 工作流调度。
|
|
26
26
|
|
|
27
|
-
### 2.2
|
|
27
|
+
### 2.2 应用内入口(侧边栏「数据清洗补全」)
|
|
28
28
|
|
|
29
|
-
重启后,DeepSeek Harness
|
|
29
|
+
重启后,DeepSeek Harness 侧栏顶部的「新会话」与「工作区」之间会出现「数据清洗补全」。点击后先进入
|
|
30
|
+
中央业务首页,右侧工作台保持关闭;输入框下方的五个入口分别定位到上传清洗、质量体检、匹配核验、
|
|
31
|
+
字段补全和任务历史。处理步骤为:
|
|
30
32
|
|
|
31
33
|
1. **上传与映射**:粘贴或上传 CSV / XLSX / JSON,确认企业名称字段;
|
|
32
34
|
2. **数据体检**:查看列概览、缺失和金额分布;
|
|
33
35
|
3. **匹配核验**:先做本地清洗,再选择风险/知产/经营域;多候选由人工确认;
|
|
34
36
|
4. **补全与导出**:恢复任务、重试可重试失败,下载结果 CSV 和复核 CSV。
|
|
35
37
|
|
|
38
|
+
输入框左上角的「提示词生成」提供三种名单录入方式:
|
|
39
|
+
|
|
40
|
+
- **粘贴名单**:每行一个企业名称或统一社会信用代码;
|
|
41
|
+
- **上传 Excel**:支持 XLSX/XLS/CSV/JSON,识别企业名称/信用代码列并把完整数据载入右侧工作台;
|
|
42
|
+
- **上传图片**:把 PNG/JPEG/WebP 附加到当前原生对话,并生成“使用当前已连接且可用的企查查智能文档解析 MCP”要求。
|
|
43
|
+
|
|
44
|
+
随后可选择名称规范、信用代码校验、去重、模糊候选复核等清洗动作,以及工商字段或已支持的维度组。
|
|
45
|
+
点击「生成并回填」后,任务描述进入原生对话框;发送前仍可人工修改。图片能力不可用时,请改用输入框
|
|
46
|
+
原生“+”或文本/Excel。提示词生成器不会硬编码未验证的 QCC 工具名,也不会绕过付费确认门。
|
|
47
|
+
|
|
36
48
|
模型在对话中调用 `data_clean_rows` / `data_complete_rows` / `data_profile` 时,
|
|
37
49
|
对话内会渲染对应的工具结果卡片(含运行中 / 已完成 / 失败状态);工作台头部用任务 pill
|
|
38
50
|
实时展示排队 / 运行中的后台任务(轮询 `/data-cleaning/api/mvp/jobs`)。
|
package/lib/client.js
CHANGED
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
* - `defineStore` 首选 `@deepseek-ai/dsh-client-store`,回退 `@deepseek-ai/dsh-client-runtime/client`。
|
|
9
9
|
* - `sidebar.footer.action` 仅托管入口生命周期和降级渲染;实际入口 Portal 到
|
|
10
10
|
* `sidebar.workspaces` 前,位于「新会话」与「工作区」之间。
|
|
11
|
-
* - DSH 原生会话保持在中间,能力入口使用 `conversation.input.
|
|
12
|
-
*
|
|
11
|
+
* - DSH 原生会话保持在中间,能力入口使用 `conversation.input.dock` 并通过稳定
|
|
12
|
+
* `data-slot` 排到输入框下方;右侧非模态工作台使用 `shell.overlay`。
|
|
13
|
+
* - 提示词生成器使用 `conversation.input.overlay`,只对本插件创建的会话生效。
|
|
14
|
+
* Hero 标题没有公开替换槽位,故以可恢复、精确文本匹配的 DOM Bridge 对齐业务首页。
|
|
13
15
|
*
|
|
14
16
|
* 0.5.0 工作台复用本地 `/mvp/*` 与三域 `/phase3/*` 后端,完整覆盖上传映射、
|
|
15
17
|
* 数据体检、匹配核验、补全导出;计费调用仍由 Host Bridge 的确认/幂等/上限门约束。
|
|
@@ -124,9 +126,14 @@ window.__ModuleLoader__.load({
|
|
|
124
126
|
|
|
125
127
|
.dcAgentCapabilities {
|
|
126
128
|
display: flex;
|
|
129
|
+
justify-content: center;
|
|
127
130
|
align-items: center;
|
|
128
|
-
gap:
|
|
129
|
-
|
|
131
|
+
gap: 8px;
|
|
132
|
+
width: 100%;
|
|
133
|
+
max-width: var(--dsh-composer-card-max-width, 780px);
|
|
134
|
+
box-sizing: border-box;
|
|
135
|
+
margin: 0 auto;
|
|
136
|
+
padding: 2px 16px 0;
|
|
130
137
|
overflow-x: auto;
|
|
131
138
|
scrollbar-width: none;
|
|
132
139
|
transform: translateX(var(--dc-agent-workbench-shift, 0px));
|
|
@@ -138,11 +145,14 @@ window.__ModuleLoader__.load({
|
|
|
138
145
|
align-items: center;
|
|
139
146
|
gap: 5px;
|
|
140
147
|
flex: 0 0 auto;
|
|
141
|
-
min-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
min-width: 108px;
|
|
149
|
+
min-height: 54px;
|
|
150
|
+
padding: 7px 12px;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
justify-content: center;
|
|
153
|
+
border: 1px solid light-dark(#e4e8f0, #303947);
|
|
154
|
+
border-radius: 12px;
|
|
155
|
+
background: light-dark(rgba(255,255,255,.86), rgba(22,27,35,.86));
|
|
146
156
|
color: light-dark(#5d687a, #aab5c7);
|
|
147
157
|
font: inherit;
|
|
148
158
|
font-size: 12px;
|
|
@@ -157,6 +167,212 @@ window.__ModuleLoader__.load({
|
|
|
157
167
|
}
|
|
158
168
|
.dcAgentCapability svg { flex: 0 0 auto; }
|
|
159
169
|
|
|
170
|
+
/* DSH 官方 input.dock 默认在 composer 上方;只把本插件自己的 cell 排到下方。 */
|
|
171
|
+
[data-slot="conversation.input.dock"]:has(.dcAgentExperience) {
|
|
172
|
+
order: 20;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.dcAgentExperience {
|
|
176
|
+
width: 100%;
|
|
177
|
+
box-sizing: border-box;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.dcAgentProductHome {
|
|
181
|
+
width: 100%;
|
|
182
|
+
max-width: var(--dsh-composer-card-max-width, 780px);
|
|
183
|
+
box-sizing: border-box;
|
|
184
|
+
margin: 10px auto 0;
|
|
185
|
+
padding: 16px 18px;
|
|
186
|
+
border: 1px solid light-dark(#e4e8f0, #303947);
|
|
187
|
+
border-radius: 16px;
|
|
188
|
+
background: light-dark(rgba(255,255,255,.82), rgba(20,25,33,.86));
|
|
189
|
+
box-shadow: light-dark(0 10px 30px rgba(42,75,120,.06), 0 14px 34px rgba(0,0,0,.14));
|
|
190
|
+
}
|
|
191
|
+
.dcAgentProductHome h2 {
|
|
192
|
+
margin: 0;
|
|
193
|
+
color: light-dark(#172033, #edf2fa);
|
|
194
|
+
font-size: 16px;
|
|
195
|
+
line-height: 24px;
|
|
196
|
+
}
|
|
197
|
+
.dcAgentProductHome > p {
|
|
198
|
+
margin: 4px 0 12px;
|
|
199
|
+
color: light-dark(#697386, #aab5c7);
|
|
200
|
+
font-size: 12px;
|
|
201
|
+
line-height: 18px;
|
|
202
|
+
}
|
|
203
|
+
.dcAgentHomeFlow {
|
|
204
|
+
display: grid;
|
|
205
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
206
|
+
gap: 8px;
|
|
207
|
+
}
|
|
208
|
+
.dcAgentHomeStep {
|
|
209
|
+
min-width: 0;
|
|
210
|
+
padding: 9px 8px;
|
|
211
|
+
border-radius: 10px;
|
|
212
|
+
background: light-dark(#f4f7fc, #1c2330);
|
|
213
|
+
color: light-dark(#33415a, #c9d4e5);
|
|
214
|
+
font-size: 12px;
|
|
215
|
+
line-height: 18px;
|
|
216
|
+
}
|
|
217
|
+
.dcAgentHomeStep b {
|
|
218
|
+
display: block;
|
|
219
|
+
margin-bottom: 2px;
|
|
220
|
+
color: light-dark(#1556cf, #8cb3ff);
|
|
221
|
+
font-size: 11px;
|
|
222
|
+
}
|
|
223
|
+
.dcAgentHomeTrust {
|
|
224
|
+
display: flex;
|
|
225
|
+
flex-wrap: wrap;
|
|
226
|
+
gap: 6px;
|
|
227
|
+
margin-top: 10px;
|
|
228
|
+
}
|
|
229
|
+
.dcAgentHomeTrust span {
|
|
230
|
+
padding: 3px 8px;
|
|
231
|
+
border: 1px solid light-dark(#dce4f1, #344052);
|
|
232
|
+
border-radius: 999px;
|
|
233
|
+
color: light-dark(#59677c, #b1bdd0);
|
|
234
|
+
font-size: 11px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* input.overlay 是 DSH 官方浮层锚点;给触发器让出输入卡片顶部空间。 */
|
|
238
|
+
[data-composer-card]:has(.dcAgentPromptTrigger) {
|
|
239
|
+
padding-top: 48px;
|
|
240
|
+
}
|
|
241
|
+
.dcAgentPromptLayer {
|
|
242
|
+
position: absolute;
|
|
243
|
+
inset: 0;
|
|
244
|
+
z-index: 40;
|
|
245
|
+
pointer-events: none;
|
|
246
|
+
}
|
|
247
|
+
.dcAgentPromptTrigger {
|
|
248
|
+
position: absolute;
|
|
249
|
+
top: 10px;
|
|
250
|
+
left: 16px;
|
|
251
|
+
min-height: 28px;
|
|
252
|
+
padding: 3px 10px;
|
|
253
|
+
border: 1px solid light-dark(#c9d9f8, #365681);
|
|
254
|
+
border-radius: 999px;
|
|
255
|
+
background: light-dark(#edf4ff, #172841);
|
|
256
|
+
color: light-dark(#1556cf, #8cb3ff);
|
|
257
|
+
font: inherit;
|
|
258
|
+
font-size: 12px;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
pointer-events: auto;
|
|
261
|
+
}
|
|
262
|
+
.dcAgentPromptPanel {
|
|
263
|
+
position: absolute;
|
|
264
|
+
top: 44px;
|
|
265
|
+
left: 16px;
|
|
266
|
+
width: min(660px, calc(100vw - 120px));
|
|
267
|
+
max-height: min(68vh, 660px);
|
|
268
|
+
box-sizing: border-box;
|
|
269
|
+
overflow: auto;
|
|
270
|
+
padding: 16px;
|
|
271
|
+
border: 1px solid light-dark(#d5dae4, #303947);
|
|
272
|
+
border-radius: 16px;
|
|
273
|
+
background: light-dark(#ffffff, #161b23);
|
|
274
|
+
color: light-dark(#172033, #edf2fa);
|
|
275
|
+
box-shadow: light-dark(0 18px 48px rgba(33,55,88,.20), 0 20px 54px rgba(0,0,0,.40));
|
|
276
|
+
pointer-events: auto;
|
|
277
|
+
}
|
|
278
|
+
.dcAgentPromptHead {
|
|
279
|
+
display: flex;
|
|
280
|
+
align-items: flex-start;
|
|
281
|
+
justify-content: space-between;
|
|
282
|
+
gap: 12px;
|
|
283
|
+
margin-bottom: 12px;
|
|
284
|
+
}
|
|
285
|
+
.dcAgentPromptHead h3 { margin: 0; font-size: 16px; line-height: 22px; }
|
|
286
|
+
.dcAgentPromptHead p { margin: 3px 0 0; color: light-dark(#697386, #aab5c7); font-size: 11px; line-height: 17px; }
|
|
287
|
+
.dcAgentPromptClose {
|
|
288
|
+
width: 28px;
|
|
289
|
+
height: 28px;
|
|
290
|
+
flex: none;
|
|
291
|
+
border: 0;
|
|
292
|
+
border-radius: 50%;
|
|
293
|
+
background: transparent;
|
|
294
|
+
color: inherit;
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
}
|
|
297
|
+
.dcAgentPromptClose:hover { background: light-dark(#f1f4f8, #222a36); }
|
|
298
|
+
.dcAgentPromptTabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
|
|
299
|
+
.dcAgentPromptTab,
|
|
300
|
+
.dcAgentPromptChoice {
|
|
301
|
+
display: inline-flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
gap: 5px;
|
|
304
|
+
min-height: 32px;
|
|
305
|
+
padding: 5px 10px;
|
|
306
|
+
border: 1px solid light-dark(#d9dee8, #394352);
|
|
307
|
+
border-radius: 9px;
|
|
308
|
+
background: transparent;
|
|
309
|
+
color: inherit;
|
|
310
|
+
font: inherit;
|
|
311
|
+
font-size: 12px;
|
|
312
|
+
cursor: pointer;
|
|
313
|
+
}
|
|
314
|
+
.dcAgentPromptTab.is-active,
|
|
315
|
+
.dcAgentPromptChoice.is-selected {
|
|
316
|
+
border-color: light-dark(#2869e6, #6d9eff);
|
|
317
|
+
background: light-dark(#edf4ff, #172841);
|
|
318
|
+
color: light-dark(#1556cf, #8cb3ff);
|
|
319
|
+
}
|
|
320
|
+
.dcAgentPromptField { display: grid; gap: 6px; margin: 10px 0; }
|
|
321
|
+
.dcAgentPromptField > span { font-size: 12px; font-weight: 600; }
|
|
322
|
+
.dcAgentPromptText {
|
|
323
|
+
box-sizing: border-box;
|
|
324
|
+
width: 100%;
|
|
325
|
+
min-height: 104px;
|
|
326
|
+
resize: vertical;
|
|
327
|
+
padding: 10px 12px;
|
|
328
|
+
border: 1px solid light-dark(#d9dee8, #394352);
|
|
329
|
+
border-radius: 10px;
|
|
330
|
+
background: light-dark(#fbfcfe, #11171f);
|
|
331
|
+
color: inherit;
|
|
332
|
+
font: inherit;
|
|
333
|
+
font-size: 12px;
|
|
334
|
+
line-height: 20px;
|
|
335
|
+
}
|
|
336
|
+
.dcAgentPromptFile {
|
|
337
|
+
display: flex;
|
|
338
|
+
align-items: center;
|
|
339
|
+
gap: 10px;
|
|
340
|
+
padding: 12px;
|
|
341
|
+
border: 1px dashed light-dark(#b9c5d8, #43516a);
|
|
342
|
+
border-radius: 10px;
|
|
343
|
+
color: light-dark(#59677c, #b1bdd0);
|
|
344
|
+
font-size: 12px;
|
|
345
|
+
}
|
|
346
|
+
.dcAgentPromptFile input { max-width: 100%; }
|
|
347
|
+
.dcAgentPromptGroup { margin-top: 12px; }
|
|
348
|
+
.dcAgentPromptGroup > b { display: block; margin-bottom: 7px; font-size: 12px; }
|
|
349
|
+
.dcAgentPromptChoices { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
350
|
+
.dcAgentPromptChoice input { margin: 0; }
|
|
351
|
+
.dcAgentPromptNote {
|
|
352
|
+
margin: 10px 0 0;
|
|
353
|
+
padding: 8px 10px;
|
|
354
|
+
border-radius: 8px;
|
|
355
|
+
background: light-dark(#f6f8fb, #1c2330);
|
|
356
|
+
color: light-dark(#697386, #aab5c7);
|
|
357
|
+
font-size: 11px;
|
|
358
|
+
line-height: 17px;
|
|
359
|
+
}
|
|
360
|
+
.dcAgentPromptError { margin: 8px 0 0; color: light-dark(#b42318, #ff8d86); font-size: 11px; }
|
|
361
|
+
.dcAgentPromptActions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
|
|
362
|
+
.dcAgentPromptAction {
|
|
363
|
+
min-height: 34px;
|
|
364
|
+
padding: 6px 13px;
|
|
365
|
+
border: 1px solid light-dark(#d9dee8, #394352);
|
|
366
|
+
border-radius: 9px;
|
|
367
|
+
background: transparent;
|
|
368
|
+
color: inherit;
|
|
369
|
+
font: inherit;
|
|
370
|
+
font-size: 12px;
|
|
371
|
+
cursor: pointer;
|
|
372
|
+
}
|
|
373
|
+
.dcAgentPromptAction.is-primary { border-color: #2869e6; background: #2869e6; color: white; }
|
|
374
|
+
.dcAgentPromptAction:disabled { opacity: .5; cursor: default; }
|
|
375
|
+
|
|
160
376
|
.dcAgentHeaderAction {
|
|
161
377
|
display: inline-flex;
|
|
162
378
|
align-items: center;
|
|
@@ -465,7 +681,10 @@ window.__ModuleLoader__.load({
|
|
|
465
681
|
.dcAgentStepper { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
466
682
|
.dcAgentQccBadge { display: none; }
|
|
467
683
|
.dcAgentCandidate { grid-template-columns: 1fr; }
|
|
468
|
-
.
|
|
684
|
+
.dcAgentCapabilities { justify-content: flex-start; padding-inline: 12px; }
|
|
685
|
+
.dcAgentCapability { min-width: 92px; }
|
|
686
|
+
.dcAgentPromptPanel { left: 8px; width: calc(100vw - 88px); max-height: 72vh; }
|
|
687
|
+
.dcAgentHomeFlow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
469
688
|
}
|
|
470
689
|
`;
|
|
471
690
|
|
|
@@ -498,7 +717,111 @@ window.__ModuleLoader__.load({
|
|
|
498
717
|
{ key: 'history', label: '任务历史', icon: IconArchiveOutline20, fallback: '◷' },
|
|
499
718
|
];
|
|
500
719
|
|
|
501
|
-
const
|
|
720
|
+
const CLEANING_OPTIONS = [
|
|
721
|
+
['normalize_name', '规范企业名称'],
|
|
722
|
+
['complete_name', '补全 / 修正旧名称'],
|
|
723
|
+
['verify_credit', '校验 / 补全信用代码'],
|
|
724
|
+
['deduplicate', '重复企业去重'],
|
|
725
|
+
['manual_review', '模糊候选人工确认'],
|
|
726
|
+
];
|
|
727
|
+
const ENRICHMENT_OPTIONS = [
|
|
728
|
+
['credit_no', '统一社会信用代码'],
|
|
729
|
+
['legal_rep', '法定代表人'],
|
|
730
|
+
['reg_capital', '注册资本'],
|
|
731
|
+
['establish_date', '成立日期'],
|
|
732
|
+
['reg_status', '登记 / 经营状态'],
|
|
733
|
+
['panorama', '企业全景与联系方式'],
|
|
734
|
+
['ownership', '股权与对外投资'],
|
|
735
|
+
['governance', '主要人员与变更年报'],
|
|
736
|
+
['risk', '风险信息'],
|
|
737
|
+
['ipr', '知识产权'],
|
|
738
|
+
['operation', '经营信息'],
|
|
739
|
+
];
|
|
740
|
+
const DEFAULT_CLEANING_KEYS = ['normalize_name', 'complete_name', 'verify_credit', 'deduplicate', 'manual_review'];
|
|
741
|
+
const DEFAULT_ENRICHMENT_KEYS = ['credit_no', 'legal_rep', 'reg_capital', 'establish_date', 'reg_status'];
|
|
742
|
+
const DEFAULT_SESSION_PROMPT = '请帮我清洗并补全企业名单。可点击输入框左上角「提示词生成」录入名单、上传 Excel 或图片,也可直接修改本段任务说明后开始。';
|
|
743
|
+
const CLEANING_SESSION_STORAGE_KEY = 'dsh.data-cleaning-agent.sessions.v1';
|
|
744
|
+
const CLEANING_SESSION_EVENT = 'dsh:data-cleaning-session-marked';
|
|
745
|
+
const cleaningSessionIds = new Set();
|
|
746
|
+
|
|
747
|
+
try {
|
|
748
|
+
const stored = window.sessionStorage?.getItem(CLEANING_SESSION_STORAGE_KEY);
|
|
749
|
+
for (const id of JSON.parse(stored || '[]')) {
|
|
750
|
+
if (typeof id === 'string' && id) cleaningSessionIds.add(id);
|
|
751
|
+
}
|
|
752
|
+
} catch (_error) {
|
|
753
|
+
// sessionStorage 可能被禁用;当前页面内的 Set 仍可工作。
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function isCleaningSession(sessionId) {
|
|
757
|
+
return typeof sessionId === 'string' && cleaningSessionIds.has(sessionId);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function markCleaningSession(sessionId) {
|
|
761
|
+
if (typeof sessionId !== 'string' || !sessionId) return;
|
|
762
|
+
cleaningSessionIds.add(sessionId);
|
|
763
|
+
try {
|
|
764
|
+
window.sessionStorage?.setItem(CLEANING_SESSION_STORAGE_KEY, JSON.stringify([...cleaningSessionIds].slice(-20)));
|
|
765
|
+
} catch (_error) {
|
|
766
|
+
// 不因浏览器存储策略阻断会话。
|
|
767
|
+
}
|
|
768
|
+
if (typeof window.CustomEvent === 'function' && typeof window.dispatchEvent === 'function') {
|
|
769
|
+
window.dispatchEvent(new window.CustomEvent(CLEANING_SESSION_EVENT, { detail: { sessionId } }));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function useCleaningSession(sessionId) {
|
|
774
|
+
const [markedSessionId, setMarkedSessionId] = react.useState(
|
|
775
|
+
isCleaningSession(sessionId) ? sessionId : null,
|
|
776
|
+
);
|
|
777
|
+
react.useEffect(() => {
|
|
778
|
+
const handleMarked = (event) => {
|
|
779
|
+
if (event?.detail?.sessionId === sessionId) setMarkedSessionId(sessionId);
|
|
780
|
+
};
|
|
781
|
+
window.addEventListener?.(CLEANING_SESSION_EVENT, handleMarked);
|
|
782
|
+
return () => window.removeEventListener?.(CLEANING_SESSION_EVENT, handleMarked);
|
|
783
|
+
}, [sessionId]);
|
|
784
|
+
// DSH 可能复用同一个 slot component 切换会话;状态必须绑定具体 sessionId,
|
|
785
|
+
// 避免从清洗会话切回普通会话后仍残留业务首页与提示词入口。
|
|
786
|
+
return markedSessionId === sessionId || isCleaningSession(sessionId);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function optionLabels(options, keys) {
|
|
790
|
+
const selected = new Set(Array.isArray(keys) ? keys : []);
|
|
791
|
+
return options.filter(([key]) => selected.has(key)).map(([, label]) => label);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** 纯函数:把向导选择转换成可编辑、可审计的自然语言任务描述。 */
|
|
795
|
+
function buildTaskPrompt(config = {}) {
|
|
796
|
+
const mode = config.mode ?? 'text';
|
|
797
|
+
const cleanLabels = optionLabels(CLEANING_OPTIONS, config.cleaningKeys);
|
|
798
|
+
const enrichLabels = optionLabels(ENRICHMENT_OPTIONS, config.enrichmentKeys);
|
|
799
|
+
const entries = Array.isArray(config.entries)
|
|
800
|
+
? config.entries.map((value) => String(value ?? '').trim()).filter(Boolean)
|
|
801
|
+
: [];
|
|
802
|
+
const entryCount = Number.isFinite(config.entryCount) ? config.entryCount : entries.length;
|
|
803
|
+
const source = mode === 'image'
|
|
804
|
+
? `已附加图片${config.fileName ? `「${config.fileName}」` : ''}`
|
|
805
|
+
: mode === 'excel'
|
|
806
|
+
? `本地表格${config.fileName ? `「${config.fileName}」` : ''},已解析 ${entryCount} 条主体标识,完整数据已载入数据清洗补全工作台`
|
|
807
|
+
: `手工录入,共 ${entryCount} 条主体标识`;
|
|
808
|
+
const lines = [
|
|
809
|
+
'请执行一项企业名单数据清洗补全任务。',
|
|
810
|
+
`输入来源:${source}。`,
|
|
811
|
+
];
|
|
812
|
+
if (mode === 'image') {
|
|
813
|
+
lines.push('请先使用当前已连接且可用的企查查智能文档解析 MCP 能力,从图片中提取企业全称或统一社会信用代码;若该能力未连接或不可用,请先提示我改用文本或 Excel。');
|
|
814
|
+
}
|
|
815
|
+
if (entries.length) {
|
|
816
|
+
lines.push(`${mode === 'excel' ? '主体预览' : '待处理主体'}:\n${entries.map((entry, index) => `${index + 1}. ${entry}`).join('\n')}`);
|
|
817
|
+
}
|
|
818
|
+
lines.push(`清洗要求:${cleanLabels.length ? cleanLabels.join('、') : '仅解析,不自动修改'}。`);
|
|
819
|
+
lines.push(`需要补全的字段 / 维度:${enrichLabels.length ? enrichLabels.join('、') : '不调用外部补全,仅输出本地清洗结果'}。`);
|
|
820
|
+
lines.push('处理规则:企业全称、统一社会信用代码或注册号优先精确匹配;精确匹配失败时再进入模糊候选,存在多个候选必须暂停并让我确认,不得默认选择第一项。');
|
|
821
|
+
lines.push('请先生成数据质量体检与匹配摘要,经我确认后再执行需要消耗额度的企查查 MCP 调用;企查查连接、套餐额度和费用均由当前用户自己的账号承担。缺失或无权限字段请留空并标记原因,不得编造。');
|
|
822
|
+
lines.push('完成后保留来源原值、标准主体、匹配状态与字段来源,并提供结果和待复核清单的导出。');
|
|
823
|
+
return lines.join('\n\n');
|
|
824
|
+
}
|
|
502
825
|
|
|
503
826
|
/** 后端往返:POST JSON,返回解析后的对象。 */
|
|
504
827
|
async function api(path, body) {
|
|
@@ -598,7 +921,7 @@ window.__ModuleLoader__.load({
|
|
|
598
921
|
: items.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.includes(current))?.workspaceId;
|
|
599
922
|
const targetWorkspaceId = currentWorkspaceId ?? workspace.recentWorkspaceId ?? items[0]?.workspaceId;
|
|
600
923
|
if (targetWorkspaceId === undefined) {
|
|
601
|
-
throw new Error('
|
|
924
|
+
throw new Error('请先选择一个工作空间,再打开数据清洗补全智能体');
|
|
602
925
|
}
|
|
603
926
|
// rc.2 暴露 workspaces.connectWorkspace;alpha.2 把同一导航策略迁移到
|
|
604
927
|
// uiWorkspace.connectWorkspace,纯 workspaces controller 不再带该方法。
|
|
@@ -617,11 +940,33 @@ window.__ModuleLoader__.load({
|
|
|
617
940
|
if (!sessionId) throw new Error('当前 DSH 版本没有可用的工作区会话创建能力');
|
|
618
941
|
const conversation = typeof ctx.get === 'function' ? ctx.get('conversation') : ctx.conversation;
|
|
619
942
|
if (!conversation) throw new Error('DSH 对话服务尚未就绪,请稍后重试');
|
|
943
|
+
markCleaningSession(sessionId);
|
|
620
944
|
conversation.input.shell(sessionId).setDraft(DEFAULT_SESSION_PROMPT);
|
|
621
945
|
ctx.sessions.open(sessionId);
|
|
622
946
|
return sessionId;
|
|
623
947
|
}
|
|
624
948
|
|
|
949
|
+
/**
|
|
950
|
+
* 图片接入兼容 Bridge。仅在运行时同时探测到 createDraftImages / input.shell.addImages
|
|
951
|
+
* 时使用;失败会释放浏览器临时对象,不假设 alpha 实验 API 稳定。
|
|
952
|
+
*/
|
|
953
|
+
function attachPromptImages(ctx, sessionId, files) {
|
|
954
|
+
const conversation = typeof ctx.get === 'function' ? ctx.get('conversation') : ctx.conversation;
|
|
955
|
+
if (!conversation || typeof conversation.createDraftImages !== 'function') {
|
|
956
|
+
throw new Error('当前 DSH 版本不支持从插件附加图片,请使用输入框原生“+”按钮上传。');
|
|
957
|
+
}
|
|
958
|
+
const shell = conversation.input?.shell?.(sessionId);
|
|
959
|
+
if (!shell || typeof shell.addImages !== 'function') {
|
|
960
|
+
throw new Error('当前会话的图片输入能力尚未就绪,请稍后重试。');
|
|
961
|
+
}
|
|
962
|
+
const images = conversation.createDraftImages(files);
|
|
963
|
+
if (!shell.addImages(images.map((image) => image.id))) {
|
|
964
|
+
conversation.releaseDraftImages?.(images);
|
|
965
|
+
throw new Error('图片未能加入当前对话,请检查格式或数量限制。');
|
|
966
|
+
}
|
|
967
|
+
return images.length;
|
|
968
|
+
}
|
|
969
|
+
|
|
625
970
|
function capabilityIcon(item, size = 16) {
|
|
626
971
|
return typeof item.icon === 'function'
|
|
627
972
|
? h(item.icon, { size, 'aria-hidden': 'true' })
|
|
@@ -634,6 +979,7 @@ window.__ModuleLoader__.load({
|
|
|
634
979
|
}
|
|
635
980
|
|
|
636
981
|
const WORKBENCH_OPEN_EVENT = 'dsh:data-cleaning-workbench-open';
|
|
982
|
+
const WORKBENCH_DATASET_EVENT = 'dsh:data-cleaning-workbench-dataset';
|
|
637
983
|
|
|
638
984
|
// session scope 不能复用 root scope 的 store handle。DSH 当前会分别物化 root/session
|
|
639
985
|
// 插槽组件,因此以 document 事件跨 scope 通知;闭包引用仅作为无 DOM 测试降级。
|
|
@@ -651,10 +997,25 @@ window.__ModuleLoader__.load({
|
|
|
651
997
|
if (rootWorkbenchActions) openWorkbench(rootWorkbenchActions, step, sessionId);
|
|
652
998
|
}
|
|
653
999
|
|
|
1000
|
+
/** 把提示词生成器解析出的完整表格交给 root 工作台,不把整表塞进模型上下文。 */
|
|
1001
|
+
function requestWorkbenchDataset(result, sessionId) {
|
|
1002
|
+
if (typeof document !== 'undefined'
|
|
1003
|
+
&& typeof document.createEvent === 'function'
|
|
1004
|
+
&& typeof document.dispatchEvent === 'function') {
|
|
1005
|
+
const event = document.createEvent('CustomEvent');
|
|
1006
|
+
event.initCustomEvent(WORKBENCH_DATASET_EVENT, false, true, { result, sessionId });
|
|
1007
|
+
if (!document.dispatchEvent(event)) return;
|
|
1008
|
+
}
|
|
1009
|
+
if (rootWorkbenchActions) {
|
|
1010
|
+
applyParsed(result, rootWorkbenchActions);
|
|
1011
|
+
rootWorkbenchActions.setActiveSession(sessionId);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
654
1015
|
/** 左栏入口按钮:公开 footer 托管,真实按钮 Portal 到「新会话 / 工作区」之间。 */
|
|
655
1016
|
function SidebarEntry(props) {
|
|
656
1017
|
const { wide, useStore, actions, startSession } = props;
|
|
657
|
-
|
|
1018
|
+
useStore((state) => state.open);
|
|
658
1019
|
const [topMount, setTopMount] = react.useState(null);
|
|
659
1020
|
|
|
660
1021
|
react.useEffect(() => {
|
|
@@ -683,12 +1044,9 @@ window.__ModuleLoader__.load({
|
|
|
683
1044
|
variant: 'ghost',
|
|
684
1045
|
className: 'dcAgentLauncher',
|
|
685
1046
|
'data-wide': wide,
|
|
686
|
-
'aria-label': '
|
|
687
|
-
'aria-haspopup': 'dialog',
|
|
688
|
-
'aria-expanded': open,
|
|
1047
|
+
'aria-label': '数据清洗补全',
|
|
689
1048
|
onClick: async () => {
|
|
690
1049
|
try {
|
|
691
|
-
openWorkbench(actions, 'upload');
|
|
692
1050
|
if (typeof startSession === 'function') {
|
|
693
1051
|
const sessionId = await startSession();
|
|
694
1052
|
actions.setActiveSession(sessionId);
|
|
@@ -698,7 +1056,7 @@ window.__ModuleLoader__.load({
|
|
|
698
1056
|
console.error('[dc-agent] open failed:', error);
|
|
699
1057
|
}
|
|
700
1058
|
},
|
|
701
|
-
children: wide ? '🧹
|
|
1059
|
+
children: wide ? '🧹 数据清洗补全' : '🧹',
|
|
702
1060
|
});
|
|
703
1061
|
if (!topMount || typeof reactDom.createPortal !== 'function') return launcher;
|
|
704
1062
|
return reactDom.createPortal(h('div', {
|
|
@@ -707,10 +1065,10 @@ window.__ModuleLoader__.load({
|
|
|
707
1065
|
}, launcher), topMount);
|
|
708
1066
|
}
|
|
709
1067
|
|
|
710
|
-
/** Mockup
|
|
1068
|
+
/** Mockup 五能力入口:由 input.dock 承载,并在 CSS 中排到原生 composer 下方。 */
|
|
711
1069
|
function CapabilityBar(props) {
|
|
712
1070
|
const { sessionId } = props;
|
|
713
|
-
return h('div', { className: 'dcAgentCapabilities', 'aria-label': '
|
|
1071
|
+
return h('div', { className: 'dcAgentCapabilities', 'aria-label': '数据清洗补全智能体能力' },
|
|
714
1072
|
CAPABILITIES.map((item) => h('button', {
|
|
715
1073
|
key: item.key,
|
|
716
1074
|
type: 'button',
|
|
@@ -725,20 +1083,292 @@ window.__ModuleLoader__.load({
|
|
|
725
1083
|
);
|
|
726
1084
|
}
|
|
727
1085
|
|
|
1086
|
+
function ProductHome() {
|
|
1087
|
+
const flow = [
|
|
1088
|
+
['01', '录入名单', '文本、Excel 或图片'],
|
|
1089
|
+
['02', '质量体检', '缺失、重复与格式诊断'],
|
|
1090
|
+
['03', '主体匹配', '精确优先,多候选复核'],
|
|
1091
|
+
['04', '补全导出', '按需选字段,保留来源'],
|
|
1092
|
+
];
|
|
1093
|
+
return h('section', { className: 'dcAgentProductHome', 'aria-label': '数据清洗补全产品介绍' },
|
|
1094
|
+
h('h2', null, '把企业名单变成可核验、可回写的标准数据'),
|
|
1095
|
+
h('p', null, '面向销售线索、客户尽调、供应商管理与 CRM / ERP 数据治理,先本地体检清洗,再按需使用当前用户连接的企查查 MCP 补全。'),
|
|
1096
|
+
h('div', { className: 'dcAgentHomeFlow', 'aria-label': '数据清洗补全工作流' },
|
|
1097
|
+
flow.map(([index, title, hint]) => h('div', { className: 'dcAgentHomeStep', key: index },
|
|
1098
|
+
h('b', null, index),
|
|
1099
|
+
h('span', null, title),
|
|
1100
|
+
h('small', { style: { display: 'block', opacity: .72 } }, hint),
|
|
1101
|
+
)),
|
|
1102
|
+
),
|
|
1103
|
+
h('div', { className: 'dcAgentHomeTrust' },
|
|
1104
|
+
h('span', null, '本地解析与确定性清洗'),
|
|
1105
|
+
h('span', null, '匹配候选人工确认'),
|
|
1106
|
+
h('span', null, '客户自带企查查连接与额度'),
|
|
1107
|
+
h('span', null, '结果与待复核清单可导出'),
|
|
1108
|
+
),
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* DSH 当前只公开 hero brand.mark,没有 headline slot。这里仅在本插件会话且 blank hero
|
|
1114
|
+
* 阶段精确替换原生中英文标题,并在卸载时恢复,避免污染普通会话。
|
|
1115
|
+
*/
|
|
1116
|
+
function rewriteHeroChrome(sessionId, enabled) {
|
|
1117
|
+
if (!enabled || typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') return () => {};
|
|
1118
|
+
const marker = [...document.querySelectorAll('.dcAgentExperience')]
|
|
1119
|
+
.find((element) => element?.dataset?.sessionId === sessionId);
|
|
1120
|
+
const hero = marker?.closest?.('[data-phase="hero"]');
|
|
1121
|
+
if (!hero) return () => {};
|
|
1122
|
+
const spans = [...hero.querySelectorAll('span')];
|
|
1123
|
+
const title = spans.find((element) => ['探索未至之境', 'Into the Unknown'].includes(element.textContent?.trim()))
|
|
1124
|
+
?? spans.find((element) => element.dataset?.dcAgentHeroTitle === 'true');
|
|
1125
|
+
const badge = spans.find((element) => ['预览版', 'Preview'].includes(element.textContent?.trim()))
|
|
1126
|
+
?? spans.find((element) => element.dataset?.dcAgentHeroBadge === 'true');
|
|
1127
|
+
if (title) {
|
|
1128
|
+
title.dataset.dcAgentHeroTitle = 'true';
|
|
1129
|
+
title.dataset.dcAgentOriginalText ??= title.textContent ?? '';
|
|
1130
|
+
title.textContent = '数据清洗补全智能体';
|
|
1131
|
+
}
|
|
1132
|
+
if (badge) {
|
|
1133
|
+
badge.dataset.dcAgentHeroBadge = 'true';
|
|
1134
|
+
badge.dataset.dcAgentOriginalDisplay ??= badge.style?.display ?? '';
|
|
1135
|
+
if (badge.style) badge.style.display = 'none';
|
|
1136
|
+
}
|
|
1137
|
+
return () => {
|
|
1138
|
+
if (title?.dataset?.dcAgentOriginalText !== undefined) {
|
|
1139
|
+
title.textContent = title.dataset.dcAgentOriginalText;
|
|
1140
|
+
delete title.dataset.dcAgentOriginalText;
|
|
1141
|
+
delete title.dataset.dcAgentHeroTitle;
|
|
1142
|
+
}
|
|
1143
|
+
if (badge?.dataset?.dcAgentOriginalDisplay !== undefined) {
|
|
1144
|
+
if (badge.style) badge.style.display = badge.dataset.dcAgentOriginalDisplay;
|
|
1145
|
+
delete badge.dataset.dcAgentOriginalDisplay;
|
|
1146
|
+
delete badge.dataset.dcAgentHeroBadge;
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function DataCleaningExperience(props) {
|
|
1152
|
+
const { sessionId, session: ownerSession } = props;
|
|
1153
|
+
const enabled = useCleaningSession(sessionId);
|
|
1154
|
+
const hero = ownerSession?.composerPhase === 'blank';
|
|
1155
|
+
react.useEffect(
|
|
1156
|
+
() => rewriteHeroChrome(sessionId, enabled && hero),
|
|
1157
|
+
[sessionId, enabled, hero, ownerSession?.openState]
|
|
1158
|
+
);
|
|
1159
|
+
if (!enabled) return null;
|
|
1160
|
+
return h('div', {
|
|
1161
|
+
className: `dcAgentExperience${hero ? ' is-home' : ''}`,
|
|
1162
|
+
'data-session-id': sessionId,
|
|
1163
|
+
},
|
|
1164
|
+
CapabilityBar({ sessionId }),
|
|
1165
|
+
hero ? ProductHome() : null,
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function extractPromptEntries(result) {
|
|
1170
|
+
const rows = Array.isArray(result?.rows) ? result.rows : [];
|
|
1171
|
+
const headers = Array.isArray(result?.headers) && result.headers.length
|
|
1172
|
+
? result.headers
|
|
1173
|
+
: rows.length ? Object.keys(rows[0]) : [];
|
|
1174
|
+
const nameField = headers.find((name) => /^(name|company|company_name|企业名称|公司名称)$/i.test(name))
|
|
1175
|
+
?? headers.find((name) => /企业|公司|名称|name/i.test(name));
|
|
1176
|
+
const creditField = headers.find((name) => /^(credit_no|creditCode|unified_credit_code|统一社会信用代码|信用代码)$/i.test(name))
|
|
1177
|
+
?? headers.find((name) => /信用.*代码|credit/i.test(name));
|
|
1178
|
+
return rows.map((row) => {
|
|
1179
|
+
const name = nameField ? String(row?.[nameField] ?? '').trim() : '';
|
|
1180
|
+
const credit = creditField ? String(row?.[creditField] ?? '').trim() : '';
|
|
1181
|
+
return [name, credit].filter(Boolean).join(' | ');
|
|
1182
|
+
}).filter(Boolean).slice(0, 50);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function PromptGenerator(props) {
|
|
1186
|
+
const { sessionId, inputActions, attachImages } = props;
|
|
1187
|
+
const enabled = useCleaningSession(sessionId);
|
|
1188
|
+
const [open, setOpen] = react.useState(false);
|
|
1189
|
+
const [mode, setMode] = react.useState('text');
|
|
1190
|
+
const [rawText, setRawText] = react.useState('');
|
|
1191
|
+
const [entries, setEntries] = react.useState([]);
|
|
1192
|
+
const [entryCount, setEntryCount] = react.useState(0);
|
|
1193
|
+
const [spreadsheetFileName, setSpreadsheetFileName] = react.useState('');
|
|
1194
|
+
const [imageFileName, setImageFileName] = react.useState('');
|
|
1195
|
+
const [imageAttached, setImageAttached] = react.useState(false);
|
|
1196
|
+
const [cleaningKeys, setCleaningKeys] = react.useState(DEFAULT_CLEANING_KEYS);
|
|
1197
|
+
const [enrichmentKeys, setEnrichmentKeys] = react.useState(DEFAULT_ENRICHMENT_KEYS);
|
|
1198
|
+
const [busy, setBusy] = react.useState(false);
|
|
1199
|
+
const [error, setError] = react.useState(null);
|
|
1200
|
+
|
|
1201
|
+
if (!enabled) return null;
|
|
1202
|
+
|
|
1203
|
+
const setSourceMode = (nextMode) => {
|
|
1204
|
+
setMode(nextMode);
|
|
1205
|
+
setError(null);
|
|
1206
|
+
};
|
|
1207
|
+
const toggle = (setter, values, key) => setter(values.includes(key)
|
|
1208
|
+
? values.filter((item) => item !== key)
|
|
1209
|
+
: [...values, key]);
|
|
1210
|
+
const handleFile = async (event) => {
|
|
1211
|
+
const file = event.target?.files?.[0];
|
|
1212
|
+
if (!file || busy) return;
|
|
1213
|
+
setBusy(true);
|
|
1214
|
+
setError(null);
|
|
1215
|
+
try {
|
|
1216
|
+
if (mode === 'image') {
|
|
1217
|
+
if (typeof attachImages !== 'function') throw new Error('当前 DSH 没有可用的图片接入 Bridge。');
|
|
1218
|
+
await attachImages(sessionId, [file]);
|
|
1219
|
+
setImageFileName(file.name);
|
|
1220
|
+
setImageAttached(true);
|
|
1221
|
+
} else {
|
|
1222
|
+
const result = await parseFile(file);
|
|
1223
|
+
if (!result || result.ok === false) throw new Error(result?.message || result?.error || '表格解析失败');
|
|
1224
|
+
const extracted = extractPromptEntries(result);
|
|
1225
|
+
if (!extracted.length) throw new Error('未识别到企业名称或统一社会信用代码列,请检查表头。');
|
|
1226
|
+
setEntries(extracted);
|
|
1227
|
+
setEntryCount(Number.isFinite(result.rowCount) ? result.rowCount : (Array.isArray(result.rows) ? result.rows.length : extracted.length));
|
|
1228
|
+
setSpreadsheetFileName(file.name);
|
|
1229
|
+
requestWorkbenchDataset(result, sessionId);
|
|
1230
|
+
}
|
|
1231
|
+
} catch (fileError) {
|
|
1232
|
+
setError(fileError instanceof Error ? fileError.message : String(fileError));
|
|
1233
|
+
} finally {
|
|
1234
|
+
setBusy(false);
|
|
1235
|
+
if (event.target) event.target.value = '';
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
const generatePrompt = () => {
|
|
1239
|
+
const manualEntries = rawText.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1240
|
+
const selectedEntries = mode === 'text' ? manualEntries : entries;
|
|
1241
|
+
if (mode === 'text' && !selectedEntries.length) {
|
|
1242
|
+
setError('请先录入企业名称或统一社会信用代码,每行一条。');
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
if (mode === 'excel' && !selectedEntries.length) {
|
|
1246
|
+
setError('请先上传并解析 Excel / CSV / JSON 文件。');
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
if (mode === 'image' && !imageAttached) {
|
|
1250
|
+
setError('请先选择并附加一张包含企业名单的图片。');
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (!inputActions || typeof inputActions.setDraft !== 'function') {
|
|
1254
|
+
setError('当前会话输入机尚未就绪,请稍后重试。');
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
inputActions.setDraft(buildTaskPrompt({
|
|
1258
|
+
mode,
|
|
1259
|
+
entries: selectedEntries,
|
|
1260
|
+
entryCount: mode === 'excel' ? entryCount : selectedEntries.length,
|
|
1261
|
+
fileName: mode === 'image' ? imageFileName : spreadsheetFileName,
|
|
1262
|
+
cleaningKeys,
|
|
1263
|
+
enrichmentKeys,
|
|
1264
|
+
}));
|
|
1265
|
+
setError(null);
|
|
1266
|
+
setOpen(false);
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
const choice = (options, values, setter) => options.map(([key, label]) => h('label', {
|
|
1270
|
+
key,
|
|
1271
|
+
className: `dcAgentPromptChoice${values.includes(key) ? ' is-selected' : ''}`,
|
|
1272
|
+
},
|
|
1273
|
+
h('input', {
|
|
1274
|
+
type: 'checkbox',
|
|
1275
|
+
checked: values.includes(key),
|
|
1276
|
+
onChange: () => toggle(setter, values, key),
|
|
1277
|
+
}),
|
|
1278
|
+
label,
|
|
1279
|
+
));
|
|
1280
|
+
|
|
1281
|
+
return h('div', { className: 'dcAgentPromptLayer' },
|
|
1282
|
+
h('button', {
|
|
1283
|
+
type: 'button',
|
|
1284
|
+
className: 'dcAgentPromptTrigger',
|
|
1285
|
+
'aria-label': '打开提示词生成',
|
|
1286
|
+
'aria-expanded': open,
|
|
1287
|
+
onClick: () => setOpen(!open),
|
|
1288
|
+
}, '✨ 提示词生成'),
|
|
1289
|
+
open ? h('section', {
|
|
1290
|
+
className: 'dcAgentPromptPanel',
|
|
1291
|
+
role: 'dialog',
|
|
1292
|
+
'aria-modal': 'false',
|
|
1293
|
+
'aria-label': '数据清洗补全任务生成器',
|
|
1294
|
+
onKeyDown: (event) => {
|
|
1295
|
+
if (event.key === 'Escape') {
|
|
1296
|
+
event.preventDefault();
|
|
1297
|
+
setOpen(false);
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
},
|
|
1301
|
+
h('div', { className: 'dcAgentPromptHead' },
|
|
1302
|
+
h('div', null,
|
|
1303
|
+
h('h3', null, '生成数据清洗补全任务'),
|
|
1304
|
+
h('p', null, '录入主体、选择清洗动作与补全维度,生成后仍可在对话框中人工修改。'),
|
|
1305
|
+
),
|
|
1306
|
+
h('button', { type: 'button', className: 'dcAgentPromptClose', 'aria-label': '关闭提示词生成', onClick: () => setOpen(false) }, '✕'),
|
|
1307
|
+
),
|
|
1308
|
+
h('div', { className: 'dcAgentPromptTabs', role: 'tablist', 'aria-label': '名单录入方式' },
|
|
1309
|
+
[['text', '粘贴名单'], ['excel', '上传 Excel'], ['image', '上传图片']].map(([key, label]) => h('button', {
|
|
1310
|
+
key,
|
|
1311
|
+
type: 'button',
|
|
1312
|
+
role: 'tab',
|
|
1313
|
+
'aria-selected': mode === key,
|
|
1314
|
+
className: `dcAgentPromptTab${mode === key ? ' is-active' : ''}`,
|
|
1315
|
+
onClick: () => setSourceMode(key),
|
|
1316
|
+
}, label)),
|
|
1317
|
+
),
|
|
1318
|
+
mode === 'text' ? h('label', { className: 'dcAgentPromptField' },
|
|
1319
|
+
h('span', null, '企业名称或统一社会信用代码(每行一条)'),
|
|
1320
|
+
h('textarea', {
|
|
1321
|
+
className: 'dcAgentPromptText',
|
|
1322
|
+
value: rawText,
|
|
1323
|
+
placeholder: '企查查科技股份有限公司\n9132…\n某某信息技术(上海)有限公司',
|
|
1324
|
+
onChange: (event) => setRawText(event.target.value),
|
|
1325
|
+
}),
|
|
1326
|
+
) : h('label', { className: 'dcAgentPromptFile' },
|
|
1327
|
+
h('span', null, mode === 'image' ? '选择企业名单图片' : '选择 Excel / CSV / JSON 名单'),
|
|
1328
|
+
h('input', {
|
|
1329
|
+
type: 'file',
|
|
1330
|
+
accept: mode === 'image' ? 'image/png,image/jpeg,image/webp' : '.xlsx,.xls,.csv,.json',
|
|
1331
|
+
disabled: busy,
|
|
1332
|
+
onChange: handleFile,
|
|
1333
|
+
}),
|
|
1334
|
+
(mode === 'excel' ? spreadsheetFileName : imageFileName)
|
|
1335
|
+
? h('b', null, `${mode === 'excel' ? spreadsheetFileName : imageFileName}${mode === 'excel' ? ` · ${entryCount} 条` : ' · 已附加'}`)
|
|
1336
|
+
: null,
|
|
1337
|
+
),
|
|
1338
|
+
h('div', { className: 'dcAgentPromptGroup' },
|
|
1339
|
+
h('b', null, '需要执行哪些清洗?'),
|
|
1340
|
+
h('div', { className: 'dcAgentPromptChoices' }, choice(CLEANING_OPTIONS, cleaningKeys, setCleaningKeys)),
|
|
1341
|
+
),
|
|
1342
|
+
h('div', { className: 'dcAgentPromptGroup' },
|
|
1343
|
+
h('b', null, '需要补全哪些字段 / 维度?'),
|
|
1344
|
+
h('div', { className: 'dcAgentPromptChoices' }, choice(ENRICHMENT_OPTIONS, enrichmentKeys, setEnrichmentKeys)),
|
|
1345
|
+
),
|
|
1346
|
+
h('p', { className: 'dcAgentPromptNote' }, '图片模式只负责把图片附加到当前对话,并生成“优先调用已连接的企查查智能文档解析 MCP”的任务要求;插件不会虚构或硬编码未验证的工具名。企查查调用使用当前客户自己的连接、账号与额度。'),
|
|
1347
|
+
error ? h('p', { className: 'dcAgentPromptError', role: 'alert' }, error) : null,
|
|
1348
|
+
h('div', { className: 'dcAgentPromptActions' },
|
|
1349
|
+
h('button', { type: 'button', className: 'dcAgentPromptAction', onClick: () => setOpen(false) }, '取消'),
|
|
1350
|
+
h('button', { type: 'button', className: 'dcAgentPromptAction is-primary', disabled: busy, onClick: generatePrompt }, busy ? '处理中…' : '生成并回填'),
|
|
1351
|
+
),
|
|
1352
|
+
) : null,
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
728
1356
|
/** 会话头部的可恢复入口:关闭右栏后仍可从当前会话再次打开。 */
|
|
729
1357
|
function WorkbenchHeaderEntry(props) {
|
|
730
1358
|
const { sessionId } = props;
|
|
1359
|
+
const enabled = useCleaningSession(sessionId);
|
|
1360
|
+
if (!enabled) return null;
|
|
731
1361
|
return h('button', {
|
|
732
1362
|
type: 'button',
|
|
733
1363
|
className: 'dcAgentHeaderAction',
|
|
734
|
-
'aria-label': '
|
|
735
|
-
title: '
|
|
1364
|
+
'aria-label': '打开数据清洗补全工作台',
|
|
1365
|
+
title: '打开数据清洗补全工作台',
|
|
736
1366
|
onClick: () => requestWorkbenchOpen(null, sessionId),
|
|
737
1367
|
},
|
|
738
1368
|
typeof IconDataOutline16 === 'function'
|
|
739
1369
|
? h(IconDataOutline16, { size: 16, 'aria-hidden': 'true' })
|
|
740
1370
|
: h('span', { 'aria-hidden': 'true' }, '▦'),
|
|
741
|
-
h('span', null, '
|
|
1371
|
+
h('span', null, '清洗补全工作台'),
|
|
742
1372
|
);
|
|
743
1373
|
}
|
|
744
1374
|
|
|
@@ -901,12 +1531,20 @@ window.__ModuleLoader__.load({
|
|
|
901
1531
|
const detail = event?.detail ?? {};
|
|
902
1532
|
openWorkbench(actions, detail.step ?? null, detail.sessionId ?? null);
|
|
903
1533
|
};
|
|
1534
|
+
const handleDataset = (event) => {
|
|
1535
|
+
event?.preventDefault?.();
|
|
1536
|
+
const detail = event?.detail ?? {};
|
|
1537
|
+
if (detail.result) applyParsed(detail.result, actions);
|
|
1538
|
+
if (detail.sessionId) actions.setActiveSession(detail.sessionId);
|
|
1539
|
+
};
|
|
904
1540
|
if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
|
|
905
1541
|
document.addEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
|
|
1542
|
+
document.addEventListener(WORKBENCH_DATASET_EVENT, handleDataset);
|
|
906
1543
|
}
|
|
907
1544
|
return () => {
|
|
908
1545
|
if (typeof document !== 'undefined' && typeof document.removeEventListener === 'function') {
|
|
909
1546
|
document.removeEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
|
|
1547
|
+
document.removeEventListener(WORKBENCH_DATASET_EVENT, handleDataset);
|
|
910
1548
|
}
|
|
911
1549
|
stopJobsPolling();
|
|
912
1550
|
if (rootWorkbenchActions === actions) rootWorkbenchActions = null;
|
|
@@ -1415,7 +2053,7 @@ window.__ModuleLoader__.load({
|
|
|
1415
2053
|
className: `dcAgentWorkbench${expanded ? ' is-expanded' : ''}`,
|
|
1416
2054
|
role: 'dialog',
|
|
1417
2055
|
'aria-modal': 'false',
|
|
1418
|
-
'aria-label': '
|
|
2056
|
+
'aria-label': '数据清洗补全工作台',
|
|
1419
2057
|
'data-session-id': activeSessionId ?? undefined,
|
|
1420
2058
|
onKeyDown: (event) => {
|
|
1421
2059
|
if (event.key === 'Escape') {
|
|
@@ -1479,7 +2117,9 @@ window.__ModuleLoader__.load({
|
|
|
1479
2117
|
applied: true,
|
|
1480
2118
|
entry: 'sidebar.workspaces:before(portal)',
|
|
1481
2119
|
lifecycleSlot: 'sidebar.footer.action',
|
|
1482
|
-
capabilitySlot: 'conversation.input.
|
|
2120
|
+
capabilitySlot: 'conversation.input.dock',
|
|
2121
|
+
promptSlot: 'conversation.input.overlay',
|
|
2122
|
+
heroTitleBridge: 'exact-text/reversible',
|
|
1483
2123
|
headerSlot: 'conversation.session.header.actions',
|
|
1484
2124
|
overlay: 'shell.overlay',
|
|
1485
2125
|
error: null,
|
|
@@ -1506,12 +2146,20 @@ window.__ModuleLoader__.load({
|
|
|
1506
2146
|
inject: () => ({ startSession: () => startCleaningSession(ctx) }),
|
|
1507
2147
|
}, SidebarEntry));
|
|
1508
2148
|
|
|
1509
|
-
//
|
|
1510
|
-
ctx.slots.inject('conversation.input.
|
|
1511
|
-
name: 'conversation.input.
|
|
2149
|
+
// 独立 dock 行:对齐 Mockup 的五个能力 ICON,CSS 只把本 cell 排到输入框下方。
|
|
2150
|
+
ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
2151
|
+
name: 'conversation.input.dock',
|
|
1512
2152
|
id: 'data-cleaning-agent-capabilities',
|
|
1513
2153
|
order: 110,
|
|
1514
|
-
},
|
|
2154
|
+
}, DataCleaningExperience));
|
|
2155
|
+
|
|
2156
|
+
// 输入框左上角提示词生成器:文本 / Excel / 图片 → 可编辑任务描述。
|
|
2157
|
+
ctx.slots.inject('conversation.input.overlay', () => ctx.slots.register({
|
|
2158
|
+
name: 'conversation.input.overlay',
|
|
2159
|
+
id: 'data-cleaning-agent-prompt-generator',
|
|
2160
|
+
order: 110,
|
|
2161
|
+
inject: () => ({ attachImages: (sessionId, files) => attachPromptImages(ctx, sessionId, files) }),
|
|
2162
|
+
}, PromptGenerator));
|
|
1515
2163
|
|
|
1516
2164
|
// 会话头部恢复入口:用户关闭右栏后可随时重新打开当前任务工作台。
|
|
1517
2165
|
ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({
|
|
@@ -1541,6 +2189,8 @@ window.__ModuleLoader__.load({
|
|
|
1541
2189
|
|
|
1542
2190
|
exports.apply = apply;
|
|
1543
2191
|
exports.inject = inject;
|
|
2192
|
+
// 测试用纯函数,不构成 Host / DSH 稳定 API。
|
|
2193
|
+
exports.__testing = { buildTaskPrompt, extractPromptEntries, markCleaningSession };
|
|
1544
2194
|
return module.exports;
|
|
1545
2195
|
},
|
|
1546
2196
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-data-cleaning-agent",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Clean, complete, and profile enterprise name lists in DeepSeek Harness — a data cleaning & completion agent plugin with local CSV/XLSX/JSON engine and optional Qichacha (QCC) MCP enrichment. Maintained by Qichacha/QCC.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"docs/RELEASE-0.5.0.md",
|
|
31
31
|
"docs/RELEASE-0.5.1.md",
|
|
32
32
|
"docs/RELEASE-0.5.2.md",
|
|
33
|
+
"docs/RELEASE-0.5.3.md",
|
|
33
34
|
"docs/G5-HOST-BRIDGE.md",
|
|
34
35
|
"docs/G5-E2E-RUNBOOK.md"
|
|
35
36
|
],
|