dsh-data-cleaning-agent 0.5.1 → 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 +41 -0
- package/README.en.md +15 -9
- package/README.md +12 -6
- package/docs/COMPATIBILITY.md +24 -2
- package/docs/RELEASE-0.5.1.md +8 -4
- package/docs/RELEASE-0.5.2.md +47 -0
- package/docs/RELEASE-0.5.3.md +59 -0
- package/docs/USER-GUIDE.md +14 -2
- package/lib/client.js +1084 -42
- package/package.json +3 -1
package/lib/client.js
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 通过 `window.__ModuleLoader__.load({id, factory})` 注册为惰性 CJS 工厂,
|
|
5
5
|
* 与 dsh-mcp-connector@0.2.32(生产 profile 中已实测的「MCP连接器」)同构:
|
|
6
|
-
* - `react` / `@deepseek-ai/dsh-client-ui-primitives` 由 web shell 静态模块表提供,
|
|
6
|
+
* - `react` / `react-dom` / `@deepseek-ai/dsh-client-ui-primitives` 由 web shell 静态模块表提供,
|
|
7
7
|
* 第三方 bundle 无需打包 React、无需构建步骤。
|
|
8
8
|
* - `defineStore` 首选 `@deepseek-ai/dsh-client-store`,回退 `@deepseek-ai/dsh-client-runtime/client`。
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
9
|
+
* - `sidebar.footer.action` 仅托管入口生命周期和降级渲染;实际入口 Portal 到
|
|
10
|
+
* `sidebar.workspaces` 前,位于「新会话」与「工作区」之间。
|
|
11
|
+
* - DSH 原生会话保持在中间,能力入口使用 `conversation.input.dock` 并通过稳定
|
|
12
|
+
* `data-slot` 排到输入框下方;右侧非模态工作台使用 `shell.overlay`。
|
|
13
|
+
* - 提示词生成器使用 `conversation.input.overlay`,只对本插件创建的会话生效。
|
|
14
|
+
* Hero 标题没有公开替换槽位,故以可恢复、精确文本匹配的 DOM Bridge 对齐业务首页。
|
|
11
15
|
*
|
|
12
16
|
* 0.5.0 工作台复用本地 `/mvp/*` 与三域 `/phase3/*` 后端,完整覆盖上传映射、
|
|
13
17
|
* 数据体检、匹配核验、补全导出;计费调用仍由 Host Bridge 的确认/幂等/上限门约束。
|
|
@@ -20,7 +24,19 @@ window.__ModuleLoader__.load({
|
|
|
20
24
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
21
25
|
|
|
22
26
|
const react = require('react');
|
|
23
|
-
const
|
|
27
|
+
const reactDom = require('react-dom');
|
|
28
|
+
const primitives = require('@deepseek-ai/dsh-client-ui-primitives');
|
|
29
|
+
const {
|
|
30
|
+
Button,
|
|
31
|
+
IconArchiveOutline20,
|
|
32
|
+
IconChecklistOutline14,
|
|
33
|
+
IconCloseOutline16,
|
|
34
|
+
IconDataOutline16,
|
|
35
|
+
IconDownloadOutline16,
|
|
36
|
+
IconFullscreenOutline16,
|
|
37
|
+
IconPaperclipOutline16,
|
|
38
|
+
IconSearchOutline16,
|
|
39
|
+
} = primitives;
|
|
24
40
|
|
|
25
41
|
let defineStore;
|
|
26
42
|
try {
|
|
@@ -41,20 +57,45 @@ window.__ModuleLoader__.load({
|
|
|
41
57
|
/** 客户端所需服务:与 mcp-connector 对齐(槽位 + 会话/工作区/输入机)。 */
|
|
42
58
|
const inject = ['slots', 'sessions', 'workspaces', 'conversation'];
|
|
43
59
|
|
|
44
|
-
const
|
|
60
|
+
const UI_STYLE_ID = 'dsh-data-cleaning-agent-ui';
|
|
61
|
+
const SIDEBAR_WORKSPACES_SELECTOR = '[data-slot="sidebar.workspaces"]';
|
|
62
|
+
const TOP_MOUNT_SELECTOR = '[data-data-cleaning-top-mount="true"]';
|
|
45
63
|
const stylesCss = `
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
flex-direction: column;
|
|
64
|
+
.dcAgentTopMount {
|
|
65
|
+
flex: none;
|
|
49
66
|
min-width: 0;
|
|
50
67
|
width: 100%;
|
|
51
68
|
}
|
|
52
69
|
|
|
70
|
+
.dcAgentTopEntry {
|
|
71
|
+
box-sizing: border-box;
|
|
72
|
+
width: 100%;
|
|
73
|
+
padding-right: var(--dsh-sidebar-inline-padding, 12px);
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
.dcAgentLauncher {
|
|
54
77
|
flex: none;
|
|
55
78
|
box-sizing: border-box;
|
|
56
79
|
width: 100%;
|
|
80
|
+
height: 42px;
|
|
81
|
+
margin: 0 0 8px;
|
|
82
|
+
padding: 0 10px 0 8px;
|
|
83
|
+
justify-content: flex-start;
|
|
57
84
|
min-width: 0;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
border-radius: 12px;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
}
|
|
89
|
+
.dcAgentLauncher[data-wide="false"] {
|
|
90
|
+
width: 36px;
|
|
91
|
+
height: 36px;
|
|
92
|
+
padding: 0;
|
|
93
|
+
justify-content: center;
|
|
94
|
+
border-radius: 50%;
|
|
95
|
+
}
|
|
96
|
+
.dcAgentTopEntry[data-wide="false"] {
|
|
97
|
+
width: 36px;
|
|
98
|
+
padding-right: 0;
|
|
58
99
|
}
|
|
59
100
|
|
|
60
101
|
.dcAgentOverlay {
|
|
@@ -64,22 +105,293 @@ window.__ModuleLoader__.load({
|
|
|
64
105
|
display: flex;
|
|
65
106
|
align-items: stretch;
|
|
66
107
|
justify-content: flex-end;
|
|
67
|
-
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
background: transparent;
|
|
68
110
|
}
|
|
69
111
|
|
|
70
112
|
.dcAgentWorkbench {
|
|
71
113
|
display: flex;
|
|
72
114
|
flex-direction: column;
|
|
73
|
-
width: min(
|
|
115
|
+
width: min(510px, calc(100vw - 72px));
|
|
74
116
|
height: 100vh;
|
|
75
117
|
border: 1px solid light-dark(#d5dae4, #2a3038);
|
|
76
|
-
border-radius:
|
|
118
|
+
border-radius: 12px 0 0 12px;
|
|
77
119
|
background: light-dark(#ffffff, #161b23);
|
|
78
120
|
color: light-dark(#172033, #edf2fa);
|
|
79
|
-
box-shadow: light-dark(0
|
|
121
|
+
box-shadow: light-dark(-10px 0 32px rgba(33, 55, 88, .12), -12px 0 40px rgba(0, 0, 0, .32));
|
|
80
122
|
overflow: hidden;
|
|
123
|
+
pointer-events: auto;
|
|
124
|
+
}
|
|
125
|
+
.dcAgentWorkbench.is-expanded { width: min(980px, calc(100vw - 72px)); }
|
|
126
|
+
|
|
127
|
+
.dcAgentCapabilities {
|
|
128
|
+
display: flex;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
align-items: center;
|
|
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;
|
|
137
|
+
overflow-x: auto;
|
|
138
|
+
scrollbar-width: none;
|
|
139
|
+
transform: translateX(var(--dc-agent-workbench-shift, 0px));
|
|
140
|
+
transition: transform 160ms ease;
|
|
141
|
+
}
|
|
142
|
+
.dcAgentCapabilities::-webkit-scrollbar { display: none; }
|
|
143
|
+
.dcAgentCapability {
|
|
144
|
+
display: inline-flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 5px;
|
|
147
|
+
flex: 0 0 auto;
|
|
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));
|
|
156
|
+
color: light-dark(#5d687a, #aab5c7);
|
|
157
|
+
font: inherit;
|
|
158
|
+
font-size: 12px;
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
}
|
|
161
|
+
.dcAgentCapability:hover,
|
|
162
|
+
.dcAgentCapability:focus-visible,
|
|
163
|
+
.dcAgentCapability.is-active {
|
|
164
|
+
border-color: light-dark(#c9d9f8, #365681);
|
|
165
|
+
background: light-dark(#edf4ff, #172841);
|
|
166
|
+
color: light-dark(#1556cf, #8cb3ff);
|
|
167
|
+
}
|
|
168
|
+
.dcAgentCapability svg { flex: 0 0 auto; }
|
|
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
|
+
|
|
376
|
+
.dcAgentHeaderAction {
|
|
377
|
+
display: inline-flex;
|
|
378
|
+
align-items: center;
|
|
379
|
+
gap: 6px;
|
|
380
|
+
min-height: 30px;
|
|
381
|
+
padding: 4px 9px;
|
|
382
|
+
border: 1px solid light-dark(#d9dee8, #394352);
|
|
383
|
+
border-radius: 8px;
|
|
384
|
+
background: light-dark(#ffffff, #191f28);
|
|
385
|
+
color: inherit;
|
|
386
|
+
font: inherit;
|
|
387
|
+
font-size: 12px;
|
|
388
|
+
cursor: pointer;
|
|
389
|
+
}
|
|
390
|
+
.dcAgentHeaderAction:hover,
|
|
391
|
+
.dcAgentHeaderAction:focus-visible {
|
|
392
|
+
border-color: light-dark(#2869e6, #6d9eff);
|
|
393
|
+
color: light-dark(#1556cf, #8cb3ff);
|
|
81
394
|
}
|
|
82
|
-
.dcAgentWorkbench.is-expanded { width: calc(100vw - 72px); }
|
|
83
395
|
|
|
84
396
|
.dcAgentWbHeader {
|
|
85
397
|
display: flex;
|
|
@@ -365,17 +677,22 @@ window.__ModuleLoader__.load({
|
|
|
365
677
|
}
|
|
366
678
|
@media (max-width: 760px) {
|
|
367
679
|
.dcAgentWorkbench, .dcAgentWorkbench.is-expanded { width: 100vw; border-radius: 0; }
|
|
680
|
+
.dcAgentOverlay { background: rgba(8, 10, 14, 0.22); pointer-events: auto; }
|
|
368
681
|
.dcAgentStepper { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
369
682
|
.dcAgentQccBadge { display: none; }
|
|
370
683
|
.dcAgentCandidate { grid-template-columns: 1fr; }
|
|
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)); }
|
|
371
688
|
}
|
|
372
689
|
`;
|
|
373
690
|
|
|
374
|
-
/**
|
|
375
|
-
function
|
|
376
|
-
if (document.querySelector(`style[data-plugin="${
|
|
691
|
+
/** 安装入口、会话能力按钮与右侧工作台样式。 */
|
|
692
|
+
function installUiStyles() {
|
|
693
|
+
if (document.querySelector(`style[data-plugin="${UI_STYLE_ID}"]`) !== null) return () => {};
|
|
377
694
|
const style = document.createElement('style');
|
|
378
|
-
style.dataset.plugin =
|
|
695
|
+
style.dataset.plugin = UI_STYLE_ID;
|
|
379
696
|
style.textContent = stylesCss;
|
|
380
697
|
document.head.append(style);
|
|
381
698
|
return () => { style.remove(); };
|
|
@@ -392,6 +709,120 @@ window.__ModuleLoader__.load({
|
|
|
392
709
|
{ key: 'enrich', label: '补全与导出', icon: '⬇️' },
|
|
393
710
|
];
|
|
394
711
|
|
|
712
|
+
const CAPABILITIES = [
|
|
713
|
+
{ key: 'upload', label: '上传清洗', icon: IconPaperclipOutline16, fallback: '+' },
|
|
714
|
+
{ key: 'profile', label: '质量体检', icon: IconChecklistOutline14, fallback: '✓' },
|
|
715
|
+
{ key: 'review', label: '匹配核验', icon: IconSearchOutline16, fallback: '⌕' },
|
|
716
|
+
{ key: 'enrich', label: '字段补全', icon: IconDataOutline16, fallback: '▦' },
|
|
717
|
+
{ key: 'history', label: '任务历史', icon: IconArchiveOutline20, fallback: '◷' },
|
|
718
|
+
];
|
|
719
|
+
|
|
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
|
+
}
|
|
825
|
+
|
|
395
826
|
/** 后端往返:POST JSON,返回解析后的对象。 */
|
|
396
827
|
async function api(path, body) {
|
|
397
828
|
const res = await fetch(path, {
|
|
@@ -422,13 +853,20 @@ window.__ModuleLoader__.load({
|
|
|
422
853
|
qccEstimate: null,
|
|
423
854
|
qccRun: null,
|
|
424
855
|
paidConfirmed: false,
|
|
856
|
+
activeSessionId: null,
|
|
425
857
|
jobs: [], // 后台任务列表(/mvp/jobs 轮询),仅用于状态 pill,不接计费遥测
|
|
426
858
|
}),
|
|
427
859
|
actions: {
|
|
428
860
|
open: (draft) => { draft.open = true; },
|
|
861
|
+
openAt: (draft, step, sessionId) => {
|
|
862
|
+
draft.open = true;
|
|
863
|
+
if (step) draft.step = step;
|
|
864
|
+
if (sessionId) draft.activeSessionId = sessionId;
|
|
865
|
+
},
|
|
429
866
|
close: (draft) => { draft.open = false; },
|
|
430
867
|
toggleExpanded: (draft) => { draft.expanded = !draft.expanded; },
|
|
431
868
|
setStep: (draft, step) => { draft.step = step; },
|
|
869
|
+
setActiveSession: (draft, sessionId) => { draft.activeSessionId = sessionId ?? null; },
|
|
432
870
|
setBusy: (draft, busy) => { draft.busy = busy; },
|
|
433
871
|
setError: (draft, error) => { draft.error = error; },
|
|
434
872
|
setInput: (draft, input) => { draft.input = input; },
|
|
@@ -453,27 +891,485 @@ window.__ModuleLoader__.load({
|
|
|
453
891
|
});
|
|
454
892
|
}
|
|
455
893
|
|
|
456
|
-
/**
|
|
894
|
+
/**
|
|
895
|
+
* DSH 当前没有公开的「新会话与工作区之间」list slot。和 MCP连接器一样,
|
|
896
|
+
* footer slot 只托管生命周期;真实入口只依赖稳定 data-slot Portal 到工作区前。
|
|
897
|
+
*/
|
|
898
|
+
function ensureTopLauncherMount() {
|
|
899
|
+
const workspaceSlot = document.querySelector(SIDEBAR_WORKSPACES_SELECTOR);
|
|
900
|
+
const parent = workspaceSlot && workspaceSlot.parentElement;
|
|
901
|
+
if (!workspaceSlot || !parent) return null;
|
|
902
|
+
let mount = parent.querySelector(TOP_MOUNT_SELECTOR);
|
|
903
|
+
if (!mount) {
|
|
904
|
+
mount = document.createElement('div');
|
|
905
|
+
mount.dataset.dataCleaningTopMount = 'true';
|
|
906
|
+
mount.className = 'dcAgentTopMount';
|
|
907
|
+
}
|
|
908
|
+
// 不与 MCP连接器各自的 MutationObserver 争抢「紧邻工作区」位置:首次挂载后
|
|
909
|
+
// 保持稳定顺序。MCP连接器会占据紧邻工作区的位置,本入口自然位于它上方。
|
|
910
|
+
if (mount.parentElement !== parent) parent.insertBefore(mount, workspaceSlot);
|
|
911
|
+
return mount;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** 使用 DSH 原生工作区、会话和输入机打开中央对话,不构造第二套聊天界面。 */
|
|
915
|
+
async function startCleaningSession(ctx) {
|
|
916
|
+
const workspace = ctx.workspaces.list.getSnapshot();
|
|
917
|
+
const current = ctx.sessions.list.getSnapshot().current;
|
|
918
|
+
const items = Array.isArray(workspace.items) ? workspace.items : [];
|
|
919
|
+
const currentWorkspaceId = current === undefined
|
|
920
|
+
? undefined
|
|
921
|
+
: items.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.includes(current))?.workspaceId;
|
|
922
|
+
const targetWorkspaceId = currentWorkspaceId ?? workspace.recentWorkspaceId ?? items[0]?.workspaceId;
|
|
923
|
+
if (targetWorkspaceId === undefined) {
|
|
924
|
+
throw new Error('请先选择一个工作空间,再打开数据清洗补全智能体');
|
|
925
|
+
}
|
|
926
|
+
// rc.2 暴露 workspaces.connectWorkspace;alpha.2 把同一导航策略迁移到
|
|
927
|
+
// uiWorkspace.connectWorkspace,纯 workspaces controller 不再带该方法。
|
|
928
|
+
// 只在检测到真实方法后调用,避免把 alpha 实验面当成稳定契约。
|
|
929
|
+
const uiWorkspace = typeof ctx.get === 'function' ? ctx.get('uiWorkspace') : ctx.uiWorkspace;
|
|
930
|
+
const connectWorkspace = typeof ctx.workspaces.connectWorkspace === 'function'
|
|
931
|
+
? ctx.workspaces.connectWorkspace.bind(ctx.workspaces)
|
|
932
|
+
: typeof uiWorkspace?.connectWorkspace === 'function'
|
|
933
|
+
? uiWorkspace.connectWorkspace.bind(uiWorkspace)
|
|
934
|
+
: null;
|
|
935
|
+
const sessionId = connectWorkspace
|
|
936
|
+
? await connectWorkspace(targetWorkspaceId)
|
|
937
|
+
: typeof ctx.sessions.create === 'function'
|
|
938
|
+
? await ctx.sessions.create({ workspaceId: targetWorkspaceId })
|
|
939
|
+
: null;
|
|
940
|
+
if (!sessionId) throw new Error('当前 DSH 版本没有可用的工作区会话创建能力');
|
|
941
|
+
const conversation = typeof ctx.get === 'function' ? ctx.get('conversation') : ctx.conversation;
|
|
942
|
+
if (!conversation) throw new Error('DSH 对话服务尚未就绪,请稍后重试');
|
|
943
|
+
markCleaningSession(sessionId);
|
|
944
|
+
conversation.input.shell(sessionId).setDraft(DEFAULT_SESSION_PROMPT);
|
|
945
|
+
ctx.sessions.open(sessionId);
|
|
946
|
+
return sessionId;
|
|
947
|
+
}
|
|
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
|
+
|
|
970
|
+
function capabilityIcon(item, size = 16) {
|
|
971
|
+
return typeof item.icon === 'function'
|
|
972
|
+
? h(item.icon, { size, 'aria-hidden': 'true' })
|
|
973
|
+
: h('span', { 'aria-hidden': 'true' }, item.fallback);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function openWorkbench(actions, step, sessionId) {
|
|
977
|
+
actions.openAt(step, sessionId);
|
|
978
|
+
startJobsPolling(actions);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const WORKBENCH_OPEN_EVENT = 'dsh:data-cleaning-workbench-open';
|
|
982
|
+
const WORKBENCH_DATASET_EVENT = 'dsh:data-cleaning-workbench-dataset';
|
|
983
|
+
|
|
984
|
+
// session scope 不能复用 root scope 的 store handle。DSH 当前会分别物化 root/session
|
|
985
|
+
// 插槽组件,因此以 document 事件跨 scope 通知;闭包引用仅作为无 DOM 测试降级。
|
|
986
|
+
let rootWorkbenchActions = null;
|
|
987
|
+
|
|
988
|
+
/** 从 session scope 的 composer/header 触发 root scope 工作台。 */
|
|
989
|
+
function requestWorkbenchOpen(step, sessionId) {
|
|
990
|
+
if (typeof document !== 'undefined'
|
|
991
|
+
&& typeof document.createEvent === 'function'
|
|
992
|
+
&& typeof document.dispatchEvent === 'function') {
|
|
993
|
+
const event = document.createEvent('CustomEvent');
|
|
994
|
+
event.initCustomEvent(WORKBENCH_OPEN_EVENT, false, true, { step, sessionId });
|
|
995
|
+
if (!document.dispatchEvent(event)) return;
|
|
996
|
+
}
|
|
997
|
+
if (rootWorkbenchActions) openWorkbench(rootWorkbenchActions, step, sessionId);
|
|
998
|
+
}
|
|
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
|
+
|
|
1015
|
+
/** 左栏入口按钮:公开 footer 托管,真实按钮 Portal 到「新会话 / 工作区」之间。 */
|
|
457
1016
|
function SidebarEntry(props) {
|
|
458
|
-
const { wide, useStore, actions } = props;
|
|
459
|
-
|
|
460
|
-
|
|
1017
|
+
const { wide, useStore, actions, startSession } = props;
|
|
1018
|
+
useStore((state) => state.open);
|
|
1019
|
+
const [topMount, setTopMount] = react.useState(null);
|
|
1020
|
+
|
|
1021
|
+
react.useEffect(() => {
|
|
1022
|
+
let disposed = false;
|
|
1023
|
+
const ownedMounts = new Set();
|
|
1024
|
+
const syncMount = () => {
|
|
1025
|
+
if (disposed) return;
|
|
1026
|
+
const mount = ensureTopLauncherMount();
|
|
1027
|
+
if (mount) ownedMounts.add(mount);
|
|
1028
|
+
setTopMount((current) => current === mount ? current : mount);
|
|
1029
|
+
};
|
|
1030
|
+
syncMount();
|
|
1031
|
+
let observer = null;
|
|
1032
|
+
if (typeof window.MutationObserver === 'function' && document.body) {
|
|
1033
|
+
observer = new window.MutationObserver(syncMount);
|
|
1034
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1035
|
+
}
|
|
1036
|
+
return () => {
|
|
1037
|
+
disposed = true;
|
|
1038
|
+
if (observer) observer.disconnect();
|
|
1039
|
+
for (const mount of ownedMounts) mount.remove();
|
|
1040
|
+
};
|
|
1041
|
+
}, []);
|
|
1042
|
+
|
|
1043
|
+
const launcher = react.createElement(Button, {
|
|
461
1044
|
variant: 'ghost',
|
|
462
1045
|
className: 'dcAgentLauncher',
|
|
463
1046
|
'data-wide': wide,
|
|
464
|
-
'aria-label': '
|
|
465
|
-
|
|
466
|
-
'aria-expanded': open,
|
|
467
|
-
onClick: () => {
|
|
1047
|
+
'aria-label': '数据清洗补全',
|
|
1048
|
+
onClick: async () => {
|
|
468
1049
|
try {
|
|
469
|
-
|
|
470
|
-
|
|
1050
|
+
if (typeof startSession === 'function') {
|
|
1051
|
+
const sessionId = await startSession();
|
|
1052
|
+
actions.setActiveSession(sessionId);
|
|
1053
|
+
}
|
|
471
1054
|
} catch (error) {
|
|
1055
|
+
actions.setError(error instanceof Error ? error.message : String(error));
|
|
472
1056
|
console.error('[dc-agent] open failed:', error);
|
|
473
1057
|
}
|
|
474
1058
|
},
|
|
475
|
-
children: wide ? '🧹
|
|
1059
|
+
children: wide ? '🧹 数据清洗补全' : '🧹',
|
|
476
1060
|
});
|
|
1061
|
+
if (!topMount || typeof reactDom.createPortal !== 'function') return launcher;
|
|
1062
|
+
return reactDom.createPortal(h('div', {
|
|
1063
|
+
className: 'dcAgentTopEntry',
|
|
1064
|
+
'data-wide': wide,
|
|
1065
|
+
}, launcher), topMount);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/** Mockup 五能力入口:由 input.dock 承载,并在 CSS 中排到原生 composer 下方。 */
|
|
1069
|
+
function CapabilityBar(props) {
|
|
1070
|
+
const { sessionId } = props;
|
|
1071
|
+
return h('div', { className: 'dcAgentCapabilities', 'aria-label': '数据清洗补全智能体能力' },
|
|
1072
|
+
CAPABILITIES.map((item) => h('button', {
|
|
1073
|
+
key: item.key,
|
|
1074
|
+
type: 'button',
|
|
1075
|
+
className: 'dcAgentCapability',
|
|
1076
|
+
'aria-label': item.label,
|
|
1077
|
+
title: item.label,
|
|
1078
|
+
onClick: () => requestWorkbenchOpen(item.key, sessionId),
|
|
1079
|
+
},
|
|
1080
|
+
capabilityIcon(item),
|
|
1081
|
+
h('span', { className: 'dcAgentCapabilityLabel' }, item.label),
|
|
1082
|
+
)),
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
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
|
+
|
|
1356
|
+
/** 会话头部的可恢复入口:关闭右栏后仍可从当前会话再次打开。 */
|
|
1357
|
+
function WorkbenchHeaderEntry(props) {
|
|
1358
|
+
const { sessionId } = props;
|
|
1359
|
+
const enabled = useCleaningSession(sessionId);
|
|
1360
|
+
if (!enabled) return null;
|
|
1361
|
+
return h('button', {
|
|
1362
|
+
type: 'button',
|
|
1363
|
+
className: 'dcAgentHeaderAction',
|
|
1364
|
+
'aria-label': '打开数据清洗补全工作台',
|
|
1365
|
+
title: '打开数据清洗补全工作台',
|
|
1366
|
+
onClick: () => requestWorkbenchOpen(null, sessionId),
|
|
1367
|
+
},
|
|
1368
|
+
typeof IconDataOutline16 === 'function'
|
|
1369
|
+
? h(IconDataOutline16, { size: 16, 'aria-hidden': 'true' })
|
|
1370
|
+
: h('span', { 'aria-hidden': 'true' }, '▦'),
|
|
1371
|
+
h('span', null, '清洗补全工作台'),
|
|
1372
|
+
);
|
|
477
1373
|
}
|
|
478
1374
|
|
|
479
1375
|
/** M3 · 三工具 tool.call.toolview 卡片元数据(wire 名 → 展示文案)。 */
|
|
@@ -606,8 +1502,8 @@ window.__ModuleLoader__.load({
|
|
|
606
1502
|
});
|
|
607
1503
|
}
|
|
608
1504
|
|
|
609
|
-
/**
|
|
610
|
-
function
|
|
1505
|
+
/** 右侧非模态工作台:中央区域始终保留 DSH 原生会话。 */
|
|
1506
|
+
function WorkbenchDrawer(props) {
|
|
611
1507
|
const { useStore, actions } = props;
|
|
612
1508
|
const open = useStore((state) => state.open);
|
|
613
1509
|
const step = useStore((state) => state.step);
|
|
@@ -626,6 +1522,73 @@ window.__ModuleLoader__.load({
|
|
|
626
1522
|
const qccRun = useStore((state) => state.qccRun);
|
|
627
1523
|
const paidConfirmed = useStore((state) => state.paidConfirmed);
|
|
628
1524
|
const jobs = useStore((state) => state.jobs);
|
|
1525
|
+
const activeSessionId = useStore((state) => state.activeSessionId);
|
|
1526
|
+
|
|
1527
|
+
react.useEffect(() => {
|
|
1528
|
+
rootWorkbenchActions = actions;
|
|
1529
|
+
const handleOpen = (event) => {
|
|
1530
|
+
event?.preventDefault?.();
|
|
1531
|
+
const detail = event?.detail ?? {};
|
|
1532
|
+
openWorkbench(actions, detail.step ?? null, detail.sessionId ?? null);
|
|
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
|
+
};
|
|
1540
|
+
if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
|
|
1541
|
+
document.addEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
|
|
1542
|
+
document.addEventListener(WORKBENCH_DATASET_EVENT, handleDataset);
|
|
1543
|
+
}
|
|
1544
|
+
return () => {
|
|
1545
|
+
if (typeof document !== 'undefined' && typeof document.removeEventListener === 'function') {
|
|
1546
|
+
document.removeEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
|
|
1547
|
+
document.removeEventListener(WORKBENCH_DATASET_EVENT, handleDataset);
|
|
1548
|
+
}
|
|
1549
|
+
stopJobsPolling();
|
|
1550
|
+
if (rootWorkbenchActions === actions) rootWorkbenchActions = null;
|
|
1551
|
+
};
|
|
1552
|
+
}, [actions]);
|
|
1553
|
+
|
|
1554
|
+
// 非模态右栏会覆盖 Host 的中央画布;按实际重叠量把 composer 能力栏左移,
|
|
1555
|
+
// 保证 Mockup 中的五个入口在窄桌面视口仍可见、可点击。展开态专注工作台,
|
|
1556
|
+
// 不强行挤压到不足一栏的剩余区域。
|
|
1557
|
+
react.useEffect(() => {
|
|
1558
|
+
if (typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') return undefined;
|
|
1559
|
+
const reset = () => {
|
|
1560
|
+
for (const bar of document.querySelectorAll('.dcAgentCapabilities')) {
|
|
1561
|
+
bar.style?.removeProperty('--dc-agent-workbench-shift');
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
if (!open || expanded) {
|
|
1565
|
+
reset();
|
|
1566
|
+
return reset;
|
|
1567
|
+
}
|
|
1568
|
+
const align = () => {
|
|
1569
|
+
const drawer = document.querySelector('.dcAgentWorkbench');
|
|
1570
|
+
if (!drawer) return;
|
|
1571
|
+
const drawerRect = drawer.getBoundingClientRect();
|
|
1572
|
+
for (const bar of document.querySelectorAll('.dcAgentCapabilities')) {
|
|
1573
|
+
const barRect = bar.getBoundingClientRect();
|
|
1574
|
+
const overlap = Math.max(0, barRect.right - drawerRect.left + 8);
|
|
1575
|
+
const leftRoom = Math.max(0, barRect.left - 88);
|
|
1576
|
+
const shift = Math.min(overlap, leftRoom);
|
|
1577
|
+
bar.style?.setProperty('--dc-agent-workbench-shift', `${-shift}px`);
|
|
1578
|
+
}
|
|
1579
|
+
};
|
|
1580
|
+
const view = document.defaultView;
|
|
1581
|
+
const frameId = typeof view?.requestAnimationFrame === 'function'
|
|
1582
|
+
? view.requestAnimationFrame(align)
|
|
1583
|
+
: null;
|
|
1584
|
+
view?.addEventListener?.('resize', align);
|
|
1585
|
+
align();
|
|
1586
|
+
return () => {
|
|
1587
|
+
if (frameId !== null) view?.cancelAnimationFrame?.(frameId);
|
|
1588
|
+
view?.removeEventListener?.('resize', align);
|
|
1589
|
+
reset();
|
|
1590
|
+
};
|
|
1591
|
+
}, [open, expanded]);
|
|
629
1592
|
|
|
630
1593
|
// DSH 的 useStore 基于 React hooks;所有订阅必须在每次渲染时以相同顺序调用。
|
|
631
1594
|
// 把关闭态 guard 放在全部 useStore 之后,避免首次关闭、随后打开时触发
|
|
@@ -885,7 +1848,37 @@ window.__ModuleLoader__.load({
|
|
|
885
1848
|
);
|
|
886
1849
|
|
|
887
1850
|
let pane;
|
|
888
|
-
if (step === '
|
|
1851
|
+
if (step === 'history') {
|
|
1852
|
+
pane = h('div', { className: 'dcAgentPane' },
|
|
1853
|
+
h('p', { className: 'dcAgentHint' }, '查看当前 Host 的数据清洗后台任务。任务数据仅在本机保存,不进入模型上下文。'),
|
|
1854
|
+
h('div', { className: 'dcAgentRow' },
|
|
1855
|
+
h('button', {
|
|
1856
|
+
type: 'button',
|
|
1857
|
+
className: 'dcAgentButton',
|
|
1858
|
+
disabled: busy,
|
|
1859
|
+
'aria-label': '刷新任务历史',
|
|
1860
|
+
onClick: () => pollJobsOnce(actions),
|
|
1861
|
+
}, '刷新任务'),
|
|
1862
|
+
h('button', {
|
|
1863
|
+
type: 'button',
|
|
1864
|
+
className: 'dcAgentButton is-primary',
|
|
1865
|
+
onClick: () => actions.setStep('upload'),
|
|
1866
|
+
}, '新建清洗任务'),
|
|
1867
|
+
),
|
|
1868
|
+
jobs.length ? h('div', { className: 'dcAgentPane', 'aria-label': '任务历史列表' },
|
|
1869
|
+
jobs.map((job) => h('section', { key: job.id, className: 'dcAgentSection' },
|
|
1870
|
+
h('h3', null, job.name || job.id || '数据清洗任务'),
|
|
1871
|
+
h('div', { className: 'dcAgentRow' },
|
|
1872
|
+
h('span', { className: 'dcAgentJobsPill', 'data-state': job.state ?? 'idle' }, JOB_STATE_LABEL[job.state] ?? job.state ?? '未知'),
|
|
1873
|
+
h('span', { className: 'dcAgentHint' }, job.createdAt ? new Date(job.createdAt).toLocaleString() : ''),
|
|
1874
|
+
),
|
|
1875
|
+
)),
|
|
1876
|
+
) : h('section', { className: 'dcAgentSection' },
|
|
1877
|
+
h('h3', null, '暂无后台任务'),
|
|
1878
|
+
h('p', { className: 'dcAgentHint' }, '上传企业名单并开始处理后,任务状态会显示在这里。'),
|
|
1879
|
+
),
|
|
1880
|
+
);
|
|
1881
|
+
} else if (step === 'profile') {
|
|
889
1882
|
pane = h('div', { className: 'dcAgentPane' },
|
|
890
1883
|
h('p', { className: 'dcAgentHint' }, '基于已解析数据生成本地质量画像(缺失率、去重值、金额分布)。本步骤不发起企查查调用。'),
|
|
891
1884
|
profile ? h('div', null,
|
|
@@ -1048,20 +2041,34 @@ window.__ModuleLoader__.load({
|
|
|
1048
2041
|
|
|
1049
2042
|
return h('div', {
|
|
1050
2043
|
className: 'dcAgentOverlay',
|
|
1051
|
-
|
|
1052
|
-
'aria-modal': 'true',
|
|
1053
|
-
'aria-label': '数据清洗',
|
|
2044
|
+
'data-dsh-plugin': 'data-cleaning-agent',
|
|
1054
2045
|
onClick: (event) => {
|
|
1055
|
-
if (event.target === event.currentTarget)
|
|
2046
|
+
if (event.target === event.currentTarget) {
|
|
2047
|
+
stopJobsPolling();
|
|
2048
|
+
actions.close();
|
|
2049
|
+
}
|
|
1056
2050
|
},
|
|
1057
2051
|
},
|
|
1058
|
-
h('
|
|
2052
|
+
h('aside', {
|
|
2053
|
+
className: `dcAgentWorkbench${expanded ? ' is-expanded' : ''}`,
|
|
2054
|
+
role: 'dialog',
|
|
2055
|
+
'aria-modal': 'false',
|
|
2056
|
+
'aria-label': '数据清洗补全工作台',
|
|
2057
|
+
'data-session-id': activeSessionId ?? undefined,
|
|
2058
|
+
onKeyDown: (event) => {
|
|
2059
|
+
if (event.key === 'Escape') {
|
|
2060
|
+
event.preventDefault();
|
|
2061
|
+
stopJobsPolling();
|
|
2062
|
+
actions.close();
|
|
2063
|
+
}
|
|
2064
|
+
},
|
|
2065
|
+
},
|
|
1059
2066
|
h('header', { className: 'dcAgentWbHeader' },
|
|
1060
2067
|
h('div', { className: 'dcAgentWbTitle' },
|
|
1061
2068
|
h('span', { className: 'dcAgentWbIcon', 'aria-hidden': 'true' }, '🧹'),
|
|
1062
2069
|
h('div', null,
|
|
1063
2070
|
h('b', null, '数据清洗补全'),
|
|
1064
|
-
h('small', null, '本地确定性清洗 ·
|
|
2071
|
+
h('small', null, '本地确定性清洗 · 可选企查查 MCP 补全'),
|
|
1065
2072
|
),
|
|
1066
2073
|
),
|
|
1067
2074
|
h('span', {
|
|
@@ -1106,28 +2113,61 @@ window.__ModuleLoader__.load({
|
|
|
1106
2113
|
function apply(ctx) {
|
|
1107
2114
|
// eslint-disable-next-line no-console
|
|
1108
2115
|
console.log('[dc-agent] client apply() ran');
|
|
1109
|
-
const state = {
|
|
2116
|
+
const state = {
|
|
2117
|
+
applied: true,
|
|
2118
|
+
entry: 'sidebar.workspaces:before(portal)',
|
|
2119
|
+
lifecycleSlot: 'sidebar.footer.action',
|
|
2120
|
+
capabilitySlot: 'conversation.input.dock',
|
|
2121
|
+
promptSlot: 'conversation.input.overlay',
|
|
2122
|
+
heroTitleBridge: 'exact-text/reversible',
|
|
2123
|
+
headerSlot: 'conversation.session.header.actions',
|
|
2124
|
+
overlay: 'shell.overlay',
|
|
2125
|
+
error: null,
|
|
2126
|
+
};
|
|
1110
2127
|
window.__DC_MVP__ = state;
|
|
1111
2128
|
try {
|
|
1112
2129
|
const workbenchStore = createWorkbenchStore();
|
|
1113
|
-
ctx.effect(() =>
|
|
2130
|
+
ctx.effect(() => installUiStyles(), 'data-cleaning-agent: UI styles');
|
|
1114
2131
|
|
|
1115
|
-
//
|
|
2132
|
+
// 右侧工作台:additive overlay,不替换 DSH 单占位 details 面板。
|
|
1116
2133
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
1117
2134
|
name: 'shell.overlay',
|
|
1118
2135
|
id: 'data-cleaning-agent',
|
|
1119
2136
|
order: 200,
|
|
1120
2137
|
store: workbenchStore,
|
|
1121
|
-
},
|
|
2138
|
+
}, WorkbenchDrawer));
|
|
1122
2139
|
|
|
1123
|
-
//
|
|
2140
|
+
// 左栏:footer 只托管生命周期和 Portal 降级;实际入口显示在工作区列表前。
|
|
1124
2141
|
ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({
|
|
1125
2142
|
name: 'sidebar.footer.action',
|
|
1126
2143
|
id: 'data-cleaning-agent',
|
|
1127
2144
|
order: 10,
|
|
1128
2145
|
store: workbenchStore,
|
|
2146
|
+
inject: () => ({ startSession: () => startCleaningSession(ctx) }),
|
|
1129
2147
|
}, SidebarEntry));
|
|
1130
2148
|
|
|
2149
|
+
// 独立 dock 行:对齐 Mockup 的五个能力 ICON,CSS 只把本 cell 排到输入框下方。
|
|
2150
|
+
ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
2151
|
+
name: 'conversation.input.dock',
|
|
2152
|
+
id: 'data-cleaning-agent-capabilities',
|
|
2153
|
+
order: 110,
|
|
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));
|
|
2163
|
+
|
|
2164
|
+
// 会话头部恢复入口:用户关闭右栏后可随时重新打开当前任务工作台。
|
|
2165
|
+
ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({
|
|
2166
|
+
name: 'conversation.session.header.actions',
|
|
2167
|
+
id: 'data-cleaning-agent-workbench',
|
|
2168
|
+
order: 110,
|
|
2169
|
+
}, WorkbenchHeaderEntry));
|
|
2170
|
+
|
|
1131
2171
|
// M3:三个工具的 tool.call.toolview 富化卡片(keyed by wire name,替代裸 JSON 摘要)。
|
|
1132
2172
|
// 说明:分三条独立 inject(而非 generator)——测试 shim 对 inject 回调仅执行一次并 push 其返回值。
|
|
1133
2173
|
const toolviewKeys = ['data_clean_rows', 'data_complete_rows', 'data_profile'];
|
|
@@ -1149,6 +2189,8 @@ window.__ModuleLoader__.load({
|
|
|
1149
2189
|
|
|
1150
2190
|
exports.apply = apply;
|
|
1151
2191
|
exports.inject = inject;
|
|
2192
|
+
// 测试用纯函数,不构成 Host / DSH 稳定 API。
|
|
2193
|
+
exports.__testing = { buildTaskPrompt, extractPromptEntries, markCleaningSession };
|
|
1152
2194
|
return module.exports;
|
|
1153
2195
|
},
|
|
1154
2196
|
});
|