dsh-data-cleaning-agent 0.8.23 → 0.9.1
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 +14 -0
- package/README.en.md +8 -3
- package/README.md +9 -4
- package/docs/COMPATIBILITY.md +24 -2
- package/docs/UI-V1.5.0-ADOPTION.md +43 -0
- package/install.sh +10 -2
- package/lib/better-sidebar-adapter.js +76 -0
- package/lib/client.js +309 -509
- package/lib/install-preflight.js +49 -0
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - `sidebar.footer.action` 仅托管入口生命周期和降级渲染;实际入口 Portal 到
|
|
10
10
|
* `sidebar.workspaces` 前,位于「新会话」与「工作区」之间。
|
|
11
11
|
* - DSH 原生会话保持在中间,能力入口使用 `conversation.input.dock` 并通过稳定
|
|
12
|
-
* `data-composer-seat/card` 将自有菜单 Portal
|
|
12
|
+
* `data-composer-seat/card` 将自有菜单 Portal 到输入框下方;工作台使用 Better Sidebar Session 单例 Tab。
|
|
13
13
|
* - 提示词生成器使用 `conversation.input.overlay`,只对本插件创建的会话生效。
|
|
14
14
|
* Hero 标题没有公开替换槽位,故以可恢复、精确文本匹配的 DOM Bridge 对齐业务首页。
|
|
15
15
|
*
|
|
@@ -77,136 +77,6 @@ window.__ModuleLoader__.load({
|
|
|
77
77
|
'aria-hidden': true, focusable: false }, h('path', { d: STAGE_ICON_PATHS[kind] }));
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
function workbenchWidthBounds(viewport, available = viewport) {
|
|
81
|
-
const mobile = viewport <= 760 || available < 740;
|
|
82
|
-
return { mobile, min: mobile ? viewport : 320, max: mobile ? viewport : Math.max(320, available - 420) };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 自有抽屉的可逆兼容适配;不改 Better Sidebar 或其他插件的面板偏好。
|
|
86
|
-
function installWorkbenchSizing(drawer, { preferredWidth, expanded, onWidthChange }) {
|
|
87
|
-
const doc = drawer.ownerDocument;
|
|
88
|
-
const view = doc.defaultView;
|
|
89
|
-
const handle = drawer.querySelector('.dcAgentResizeHandle');
|
|
90
|
-
if (!handle || !view) return () => {};
|
|
91
|
-
const reservations = new Map();
|
|
92
|
-
const registryKey = Symbol.for('dsh.data-cleaning.sizing-reservations');
|
|
93
|
-
const registry = doc[registryKey] || (doc[registryKey] = new WeakMap());
|
|
94
|
-
let disposed = false;
|
|
95
|
-
let requested = expanded ? 980 : preferredWidth;
|
|
96
|
-
let current = 0;
|
|
97
|
-
let drag = null;
|
|
98
|
-
const boundsForView = () => workbenchWidthBounds(view.innerWidth,
|
|
99
|
-
doc.querySelector('[data-conversation-scroll]')?.getBoundingClientRect().width || view.innerWidth);
|
|
100
|
-
const reserve = () => {
|
|
101
|
-
if (disposed) return;
|
|
102
|
-
const width = boundsForView().mobile ? 0 : drawer.getBoundingClientRect().width;
|
|
103
|
-
for (const el of doc.querySelectorAll('[data-conversation-scroll]')) {
|
|
104
|
-
if (!reservations.has(el)) {
|
|
105
|
-
const previous = registry.get(el) || {
|
|
106
|
-
value: el.style.getPropertyValue('--dc-agent-workbench-reserve'),
|
|
107
|
-
priority: el.style.getPropertyPriority('--dc-agent-workbench-reserve'),
|
|
108
|
-
owners: 0,
|
|
109
|
-
};
|
|
110
|
-
previous.owners += 1;
|
|
111
|
-
registry.set(el, previous);
|
|
112
|
-
reservations.set(el, previous);
|
|
113
|
-
sizeObserver?.observe(el, { box: 'border-box' });
|
|
114
|
-
}
|
|
115
|
-
if (el.style.getPropertyValue('--dc-agent-workbench-reserve') !== `${width}px`) {
|
|
116
|
-
el.style.setProperty('--dc-agent-workbench-reserve', `${width}px`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
const apply = (value) => {
|
|
121
|
-
if (disposed) return;
|
|
122
|
-
const bounds = boundsForView();
|
|
123
|
-
const fallback = view.innerWidth <= 1040 ? Math.min(420, view.innerWidth * .48) : Math.min(460, view.innerWidth * .42);
|
|
124
|
-
current = Math.round(Math.min(bounds.max, Math.max(bounds.min, Number.isFinite(value) ? value : fallback)));
|
|
125
|
-
drawer.dataset.narrow = String(bounds.mobile);
|
|
126
|
-
drawer.style.setProperty('--dc-agent-panel-width', `${current}px`);
|
|
127
|
-
handle.setAttribute('aria-valuemin', String(bounds.min));
|
|
128
|
-
handle.setAttribute('aria-valuemax', String(bounds.max));
|
|
129
|
-
handle.setAttribute('aria-valuenow', String(current));
|
|
130
|
-
handle.setAttribute('aria-valuetext', `${current} 像素`);
|
|
131
|
-
reserve();
|
|
132
|
-
};
|
|
133
|
-
const end = (cancel = false) => {
|
|
134
|
-
if (!drag) return;
|
|
135
|
-
const previous = drag;
|
|
136
|
-
drag = null;
|
|
137
|
-
drawer.classList.remove('is-resizing');
|
|
138
|
-
if (handle.hasPointerCapture?.(previous.id)) handle.releasePointerCapture(previous.id);
|
|
139
|
-
if (cancel) apply(requested);
|
|
140
|
-
else { requested = current; onWidthChange(current); }
|
|
141
|
-
};
|
|
142
|
-
const down = (event) => {
|
|
143
|
-
if (event.button !== 0 || drag || boundsForView().mobile) return;
|
|
144
|
-
event.preventDefault();
|
|
145
|
-
handle.focus();
|
|
146
|
-
drag = { id: event.pointerId, startX: event.clientX, startWidth: current };
|
|
147
|
-
handle.setPointerCapture(event.pointerId);
|
|
148
|
-
drawer.classList.add('is-resizing');
|
|
149
|
-
};
|
|
150
|
-
const move = (event) => {
|
|
151
|
-
if (drag?.id !== event.pointerId) return;
|
|
152
|
-
apply(drag.startWidth + drag.startX - event.clientX);
|
|
153
|
-
};
|
|
154
|
-
const up = (event) => { if (drag?.id === event.pointerId) end(); };
|
|
155
|
-
const cancel = () => end(true);
|
|
156
|
-
const keydown = (event) => {
|
|
157
|
-
if (event.key === 'Escape' && drag) {
|
|
158
|
-
event.preventDefault(); event.stopPropagation(); cancel(); return;
|
|
159
|
-
}
|
|
160
|
-
if (drag || boundsForView().mobile) return;
|
|
161
|
-
const bounds = boundsForView();
|
|
162
|
-
const delta = event.shiftKey ? 48 : 16;
|
|
163
|
-
const value = { ArrowLeft: current + delta, ArrowRight: current - delta, Home: bounds.min, End: bounds.max }[event.key];
|
|
164
|
-
if (value === undefined) return;
|
|
165
|
-
event.preventDefault(); event.stopPropagation();
|
|
166
|
-
requested = value; apply(value); onWidthChange(current);
|
|
167
|
-
};
|
|
168
|
-
const reset = () => { cancel(); requested = null; apply(null); onWidthChange(null); };
|
|
169
|
-
const resize = () => { cancel(); apply(requested); };
|
|
170
|
-
const sizeObserver = typeof view.ResizeObserver === 'function' ? new view.ResizeObserver(resize) : null;
|
|
171
|
-
handle.addEventListener('pointerdown', down);
|
|
172
|
-
handle.addEventListener('pointermove', move);
|
|
173
|
-
handle.addEventListener('pointerup', up);
|
|
174
|
-
handle.addEventListener('pointercancel', cancel);
|
|
175
|
-
handle.addEventListener('lostpointercapture', cancel);
|
|
176
|
-
handle.addEventListener('keydown', keydown);
|
|
177
|
-
handle.addEventListener('dblclick', reset);
|
|
178
|
-
view.addEventListener('resize', resize);
|
|
179
|
-
view.addEventListener('blur', cancel);
|
|
180
|
-
// 会话挂载点可被宿主替换;只对当前存在的滚动区设置自有让位变量。
|
|
181
|
-
const observer = typeof view.MutationObserver === 'function' ? new view.MutationObserver(reserve) : null;
|
|
182
|
-
observer?.observe(doc.body, { childList: true, subtree: true });
|
|
183
|
-
apply(requested);
|
|
184
|
-
return () => {
|
|
185
|
-
disposed = true;
|
|
186
|
-
cancel();
|
|
187
|
-
observer?.disconnect();
|
|
188
|
-
sizeObserver?.disconnect();
|
|
189
|
-
handle.removeEventListener('pointerdown', down);
|
|
190
|
-
handle.removeEventListener('pointermove', move);
|
|
191
|
-
handle.removeEventListener('pointerup', up);
|
|
192
|
-
handle.removeEventListener('pointercancel', cancel);
|
|
193
|
-
handle.removeEventListener('lostpointercapture', cancel);
|
|
194
|
-
handle.removeEventListener('keydown', keydown);
|
|
195
|
-
handle.removeEventListener('dblclick', reset);
|
|
196
|
-
view.removeEventListener('resize', resize);
|
|
197
|
-
view.removeEventListener('blur', cancel);
|
|
198
|
-
drawer.style.removeProperty('--dc-agent-panel-width');
|
|
199
|
-
delete drawer.dataset.narrow;
|
|
200
|
-
for (const [el, previous] of reservations) {
|
|
201
|
-
previous.owners -= 1;
|
|
202
|
-
if (previous.owners > 0) continue;
|
|
203
|
-
registry.delete(el);
|
|
204
|
-
if (previous.value) el.style.setProperty('--dc-agent-workbench-reserve', previous.value, previous.priority);
|
|
205
|
-
else el.style.removeProperty('--dc-agent-workbench-reserve');
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
80
|
// 搜索仅改变可见字段,绝不清除已选字段或改变 Host 的字段目录。
|
|
211
81
|
function visibleCatalogFields(group, query) {
|
|
212
82
|
const needle = String(query || '').trim().toLowerCase();
|
|
@@ -224,7 +94,7 @@ window.__ModuleLoader__.load({
|
|
|
224
94
|
const stylesCss = `
|
|
225
95
|
/* QCC-blue candidate tokens from shared DSH-UX-001; never theme the global shell. */
|
|
226
96
|
:where(.dcAgentTopMount, .dcAgentLauncher, .dcAgentExperience, .dcAgentCapabilityMount,
|
|
227
|
-
.
|
|
97
|
+
.dcAgentWorkbenchContent, .dcAgentPromptBackdrop, .dcAgentPromptPanel, .dcAgentPromptLayer,
|
|
228
98
|
.dcAgentToolCard, [data-dc-agent-hero-row="true"]) {
|
|
229
99
|
--dc-action: light-dark(#0875d1, #82c3ff);
|
|
230
100
|
--dc-hover: light-dark(#0666b7, #acd7ff);
|
|
@@ -283,44 +153,17 @@ window.__ModuleLoader__.load({
|
|
|
283
153
|
width: 36px;
|
|
284
154
|
padding-right: 0;
|
|
285
155
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
display: flex;
|
|
292
|
-
align-items: stretch;
|
|
293
|
-
justify-content: flex-end;
|
|
294
|
-
pointer-events: none;
|
|
295
|
-
background: transparent;
|
|
156
|
+
.dcAgentWorkbenchContent {
|
|
157
|
+
box-sizing: border-box; display: flex; flex-direction: column;
|
|
158
|
+
width: 100%; height: 100%; min-width: 0; min-height: 0; overflow: hidden;
|
|
159
|
+
background: var(--dc-surface); color: var(--dc-text);
|
|
160
|
+
container-type: inline-size;
|
|
296
161
|
}
|
|
297
|
-
|
|
298
|
-
.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
display: flex;
|
|
302
|
-
flex-direction: column;
|
|
303
|
-
width: var(--dc-agent-panel-width, min(460px, 42vw));
|
|
304
|
-
max-width: calc(100vw - 420px);
|
|
305
|
-
min-width: 320px;
|
|
306
|
-
height: 100vh;
|
|
307
|
-
border: 1px solid var(--dc-border);
|
|
308
|
-
border-radius: 12px 0 0 12px;
|
|
309
|
-
background: var(--dc-surface);
|
|
310
|
-
color: var(--dc-text);
|
|
311
|
-
box-shadow: light-dark(-10px 0 32px rgba(33, 55, 88, .12), -12px 0 40px rgba(0, 0, 0, .32));
|
|
312
|
-
overflow: hidden;
|
|
313
|
-
pointer-events: auto;
|
|
162
|
+
@container (max-width: 520px) {
|
|
163
|
+
.dcAgentRulesGrid, .dcAgentMappingRow { grid-template-columns: 1fr; }
|
|
164
|
+
.dcAgentMappingRow > b { display: none; }
|
|
165
|
+
.dcAgentCandidate { grid-template-columns: 1fr; }
|
|
314
166
|
}
|
|
315
|
-
.dcAgentWorkbench.is-expanded { width: var(--dc-agent-panel-width, min(980px, calc(100vw - 420px))); }
|
|
316
|
-
.dcAgentResizeHandle { position: absolute; left: 0; top: 0; bottom: 0; width: 8px; z-index: 5; cursor: col-resize; touch-action: none; outline: none; }
|
|
317
|
-
.dcAgentResizeHandle::after { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: transparent; }
|
|
318
|
-
.dcAgentResizeHandle:hover::after, .dcAgentResizeHandle:focus-visible::after, .is-resizing .dcAgentResizeHandle::after { background: var(--dc-action); }
|
|
319
|
-
.dcAgentWorkbench.is-resizing { user-select: none; }
|
|
320
|
-
.dcAgentWorkbench[data-narrow="true"] { width: 100vw; max-width: none; min-width: 0; border-radius: 0; }
|
|
321
|
-
.dcAgentWorkbench[data-narrow="true"] .dcAgentResizeHandle { display: none; }
|
|
322
|
-
.dcAgentOverlay:has(.dcAgentWorkbench[data-narrow="true"]) { background: rgba(8, 10, 14, 0.22); pointer-events: auto; }
|
|
323
|
-
|
|
324
167
|
.dcAgentCapabilities {
|
|
325
168
|
display: flex;
|
|
326
169
|
justify-content: safe center;
|
|
@@ -333,8 +176,6 @@ window.__ModuleLoader__.load({
|
|
|
333
176
|
padding: 2px 16px 0;
|
|
334
177
|
overflow-x: auto;
|
|
335
178
|
scrollbar-width: none;
|
|
336
|
-
transform: translateX(var(--dc-agent-workbench-shift, 0px));
|
|
337
|
-
transition: transform 160ms ease;
|
|
338
179
|
}
|
|
339
180
|
.dcAgentCapabilities::-webkit-scrollbar { display: none; }
|
|
340
181
|
.dcAgentCapability {
|
|
@@ -610,72 +451,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
610
451
|
color: var(--dc-action);
|
|
611
452
|
}
|
|
612
453
|
|
|
613
|
-
.dcAgentWbHeader {
|
|
614
|
-
display: flex;
|
|
615
|
-
align-items: center;
|
|
616
|
-
gap: 8px;
|
|
617
|
-
padding: 12px 14px;
|
|
618
|
-
border-bottom: 1px solid var(--dc-border);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
.dcAgentWbTitle {
|
|
622
|
-
display: flex;
|
|
623
|
-
align-items: center;
|
|
624
|
-
gap: 10px;
|
|
625
|
-
min-width: 0;
|
|
626
|
-
flex: 1 1 auto;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
.dcAgentWbIcon {
|
|
630
|
-
width: 36px;
|
|
631
|
-
height: 36px;
|
|
632
|
-
display: grid;
|
|
633
|
-
place-items: center;
|
|
634
|
-
flex: 0 0 auto;
|
|
635
|
-
border-radius: 8px;
|
|
636
|
-
font-size: 18px;
|
|
637
|
-
background: var(--dc-selected);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
.dcAgentWbTitle b,
|
|
641
|
-
.dcAgentWbTitle small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
642
|
-
.dcAgentWbTitle > div { min-width: 0; }
|
|
643
|
-
.dcAgentWbTitle b { display: block; font-size: 15px; }
|
|
644
|
-
.dcAgentWbTitle small { display: block; max-width: 190px; color: var(--dc-muted); font-size: 12px; }
|
|
645
|
-
|
|
646
|
-
.dcAgentWbActions {
|
|
647
|
-
display: flex;
|
|
648
|
-
align-items: center;
|
|
649
|
-
gap: 6px;
|
|
650
|
-
flex: 0 0 auto;
|
|
651
|
-
margin-left: auto;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
.dcAgentQccBadge {
|
|
655
|
-
flex: 0 0 auto;
|
|
656
|
-
padding: 4px 10px;
|
|
657
|
-
border: 1px solid var(--dc-border);
|
|
658
|
-
border-radius: 999px;
|
|
659
|
-
color: var(--dc-muted);
|
|
660
|
-
font-size: 12px;
|
|
661
|
-
white-space: nowrap;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
.dcAgentWbClose {
|
|
665
|
-
flex: 0 0 auto;
|
|
666
|
-
width: 30px;
|
|
667
|
-
height: 30px;
|
|
668
|
-
display: grid;
|
|
669
|
-
place-items: center;
|
|
670
|
-
border: 1px solid var(--dc-border);
|
|
671
|
-
border-radius: 8px;
|
|
672
|
-
background: transparent;
|
|
673
|
-
color: inherit;
|
|
674
|
-
font-size: 15px;
|
|
675
|
-
cursor: pointer;
|
|
676
|
-
}
|
|
677
|
-
.dcAgentWbClose:hover, .dcAgentWbClose:focus-visible { background: var(--dc-page); }
|
|
678
|
-
|
|
679
454
|
.dcAgentStepper {
|
|
680
455
|
display: grid;
|
|
681
456
|
grid-template-columns: repeat(var(--dc-stage-count, 5), minmax(0, 1fr));
|
|
@@ -1005,33 +780,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1005
780
|
}
|
|
1006
781
|
.dcAgentHistoryTask { display: block; width: 100%; padding: 10px; text-align: left; }
|
|
1007
782
|
.dcAgentHistoryTask h3 { margin: 0; font-size: 12px; }
|
|
1008
|
-
/*
|
|
1009
|
-
* DSH 的 root.side slot 是覆盖层。桌面端工作台打开时,让带稳定标记的
|
|
1010
|
-
* 会话滚动区主动让出同宽空间,确保消息流和中央 composer 不被遮挡。
|
|
1011
|
-
* 移动端仍保留全屏工作台,不压缩底层会话。
|
|
1012
|
-
*/
|
|
1013
|
-
@media (min-width: 1041px) {
|
|
1014
|
-
body:has(.dcAgentWorkbench) [data-conversation-scroll] {
|
|
1015
|
-
box-sizing: border-box;
|
|
1016
|
-
padding-right: var(--dc-agent-workbench-reserve, min(460px, 42vw));
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
@media (min-width: 761px) and (max-width: 1040px) {
|
|
1020
|
-
body:has(.dcAgentWorkbench) [data-conversation-scroll] {
|
|
1021
|
-
box-sizing: border-box;
|
|
1022
|
-
padding-right: var(--dc-agent-workbench-reserve, min(420px, 48vw));
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
@media (min-width: 761px) {
|
|
1026
|
-
body:has(.dcAgentWorkbench.is-expanded) [data-conversation-scroll] {
|
|
1027
|
-
box-sizing: border-box;
|
|
1028
|
-
padding-right: var(--dc-agent-workbench-reserve, min(980px, calc(100vw - 420px)));
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
783
|
@media (max-width: 760px) {
|
|
1032
|
-
.dcAgentWorkbench, .dcAgentWorkbench.is-expanded { width: 100vw; max-width: none; min-width: 0; border-radius: 0; }
|
|
1033
|
-
.dcAgentOverlay { background: rgba(8, 10, 14, 0.22); pointer-events: auto; }
|
|
1034
|
-
.dcAgentResizeHandle { display: none; }
|
|
1035
784
|
.dcAgentQccBadge, .dcAgentJobsPill { display: none; }
|
|
1036
785
|
.dcAgentCandidate { grid-template-columns: 1fr; }
|
|
1037
786
|
.dcAgentCapabilities { justify-content: flex-start; padding-inline: 12px; }
|
|
@@ -1044,7 +793,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1044
793
|
.dcAgentMappingRow > b { display: none; }
|
|
1045
794
|
}
|
|
1046
795
|
@media (min-width: 761px) and (max-width: 1040px) {
|
|
1047
|
-
.dcAgentWorkbench { width: var(--dc-agent-panel-width, 420px); }
|
|
1048
796
|
.dcAgentQccBadge, .dcAgentJobsPill { display: none; }
|
|
1049
797
|
}
|
|
1050
798
|
@media (max-width: 360px) {
|
|
@@ -1053,8 +801,8 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1053
801
|
.dcAgentStepIcon svg { width: 18px; height: 18px; }
|
|
1054
802
|
}
|
|
1055
803
|
/* Distinct levels: management tabs → compact task steps → working content. */
|
|
1056
|
-
.
|
|
1057
|
-
.
|
|
804
|
+
.dcAgentManagement, .dcAgentStepper { flex: none; }
|
|
805
|
+
.dcAgentWorkbenchContent, .dcAgentPromptPanel { font-size: 14px; line-height: 1.5; }
|
|
1058
806
|
.dcAgentManagement { display: flex; gap: 24px; padding: 0 18px; border-bottom: 1px solid var(--dc-border); }
|
|
1059
807
|
.dcAgentManagement .dcAgentButton { border: 0; border-bottom: 3px solid transparent; border-radius: 0; padding: 10px 0; background: transparent; }
|
|
1060
808
|
.dcAgentManagement .dcAgentButton[aria-pressed="true"] { border-bottom-color: var(--dc-action); color: var(--dc-action); font-weight: 600; }
|
|
@@ -1072,7 +820,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1072
820
|
.dcAgentPromptText, .dcAgentTextarea, .dcAgentField { font-size: 14px; }
|
|
1073
821
|
.dcAgentPromptTrigger { border-radius: 6px; }
|
|
1074
822
|
.dcAgentPromptAction.is-primary:hover:not(:disabled) { background: var(--dc-hover); }
|
|
1075
|
-
:is(.
|
|
823
|
+
:is(.dcAgentWorkbenchContent, .dcAgentPromptPanel, .dcAgentCapabilities, .dcAgentPromptLayer) :is(button, input, select, textarea, summary):focus-visible,
|
|
1076
824
|
.dcAgentLauncher:focus-visible { outline: 2px solid var(--dc-action); outline-offset: 2px; }
|
|
1077
825
|
@media (max-width: 760px) {
|
|
1078
826
|
[data-dc-agent-hero-title="true"] { font-size: 20px; }
|
|
@@ -1335,11 +1083,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1335
1083
|
if (!activeCleaningSessionId || (sessionId && sessionId !== activeCleaningSessionId)) return false;
|
|
1336
1084
|
const previousSessionId = activeCleaningSessionId;
|
|
1337
1085
|
activeCleaningSessionId = null;
|
|
1338
|
-
if (rootWorkbenchActions) {
|
|
1339
|
-
stopJobsPolling();
|
|
1340
|
-
rootWorkbenchActions.close?.();
|
|
1341
|
-
rootWorkbenchActions.setActiveSession?.(null);
|
|
1342
|
-
}
|
|
1343
1086
|
if (typeof window.CustomEvent === 'function' && typeof window.dispatchEvent === 'function') {
|
|
1344
1087
|
window.dispatchEvent(new window.CustomEvent(CLEANING_SESSION_EVENT, {
|
|
1345
1088
|
detail: { sessionId: previousSessionId, active: false },
|
|
@@ -1723,9 +1466,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1723
1466
|
return requestJson(path, body === undefined ? 'GET' : 'POST', body);
|
|
1724
1467
|
}
|
|
1725
1468
|
|
|
1726
|
-
async function waitForQccCommand(commandId, attempts = 180, onStatus) {
|
|
1469
|
+
async function waitForQccCommand(commandId, attempts = 180, onStatus, isActive = () => true) {
|
|
1727
1470
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1471
|
+
if (!isActive()) return null;
|
|
1728
1472
|
const response = await api(`/data-cleaning/api/g5/commands/${encodeURIComponent(commandId)}`);
|
|
1473
|
+
if (!isActive()) return null;
|
|
1729
1474
|
const command = response?.command;
|
|
1730
1475
|
await onStatus?.(command);
|
|
1731
1476
|
if (command?.state === 'completed' || command?.state === 'failed') return command;
|
|
@@ -1752,6 +1497,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1752
1497
|
if (Array.isArray(task.objectives)) actions.setObjectives(task.objectives);
|
|
1753
1498
|
if (Array.isArray(task.mappings)) actions.setMappings(task.mappings);
|
|
1754
1499
|
if (Array.isArray(task.fieldSelection)) actions.setFieldSelection(task.fieldSelection);
|
|
1500
|
+
for (const [key, value] of Object.entries(task.matchRules || {})) actions.setMatchRule(key, value);
|
|
1755
1501
|
return task;
|
|
1756
1502
|
}
|
|
1757
1503
|
|
|
@@ -1894,7 +1640,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1894
1640
|
}
|
|
1895
1641
|
return response.task;
|
|
1896
1642
|
}
|
|
1643
|
+
let commandWatchGeneration = 0;
|
|
1897
1644
|
function watchHostedCommand(actions, sessionId, command) {
|
|
1645
|
+
const generation = commandWatchGeneration;
|
|
1646
|
+
const isActive = () => generation === commandWatchGeneration;
|
|
1898
1647
|
let openedForExecution = false;
|
|
1899
1648
|
const ownerVisible = () => typeof document === 'undefined' || [...document.querySelectorAll('.dcAgentExperience')].some(node => node.dataset.sessionId === sessionId);
|
|
1900
1649
|
void waitForQccCommand(command.commandId, 1800, async (status) => {
|
|
@@ -1905,11 +1654,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1905
1654
|
openWorkbench(actions, workflowTaskBySession.get(String(sessionId))?.stage || 'match', sessionId);
|
|
1906
1655
|
}
|
|
1907
1656
|
if (isCleaningSession(sessionId) && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) actions.setCommandProgress?.({ taskId: command.taskId, ...status.progress });
|
|
1908
|
-
}).then(async (completed) => {
|
|
1657
|
+
}, isActive).then(async (completed) => {
|
|
1658
|
+
if (!completed || !isActive()) return;
|
|
1909
1659
|
await refreshHostedTask(actions, sessionId, command.taskId, completed.run);
|
|
1910
1660
|
if (completed.state === 'failed') throw new Error(completed.error?.message || 'Host 执行失败,请在工作台查看状态。');
|
|
1911
1661
|
}).catch((error) => {
|
|
1912
|
-
if (isCleaningSession(sessionId)) actions.setError(error.message || String(error));
|
|
1662
|
+
if (isActive() && isCleaningSession(sessionId)) actions.setError(error.message || String(error));
|
|
1913
1663
|
});
|
|
1914
1664
|
}
|
|
1915
1665
|
|
|
@@ -1918,8 +1668,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1918
1668
|
return defineStore({
|
|
1919
1669
|
init: () => ({
|
|
1920
1670
|
open: false,
|
|
1921
|
-
expanded: false,
|
|
1922
|
-
preferredWidth: null,
|
|
1923
1671
|
step: 'upload',
|
|
1924
1672
|
busy: false,
|
|
1925
1673
|
error: null,
|
|
@@ -1953,11 +1701,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1953
1701
|
if (sessionId) draft.activeSessionId = sessionId;
|
|
1954
1702
|
},
|
|
1955
1703
|
close: (draft) => { draft.open = false; },
|
|
1956
|
-
toggleExpanded: (draft) => { draft.expanded = !draft.expanded; },
|
|
1957
|
-
setPreferredWidth: (draft, width) => {
|
|
1958
|
-
draft.preferredWidth = Number.isFinite(width) ? width : null;
|
|
1959
|
-
draft.expanded = false;
|
|
1960
|
-
},
|
|
1961
1704
|
setStep: (draft, step) => { draft.step = step; },
|
|
1962
1705
|
setActiveSession: (draft, sessionId) => { draft.activeSessionId = sessionId ?? null; },
|
|
1963
1706
|
setBusy: (draft, busy) => { draft.busy = busy; },
|
|
@@ -2067,11 +1810,20 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2067
1810
|
|
|
2068
1811
|
// rc/alpha 兼容边界:原生 startSession 会复用工作区内的空白会话。
|
|
2069
1812
|
// 只在清洗会话激活时替换此动作,创建普通会话;不改 connectWorkspace 或全局点击。
|
|
2070
|
-
function installNewSessionBridge(ctx) {
|
|
1813
|
+
function installNewSessionBridge(ctx, navigation) {
|
|
1814
|
+
if (!navigation && typeof ctx.inject === 'function') {
|
|
1815
|
+
const legacy = typeof ctx.workspaces?.startSession === 'function'
|
|
1816
|
+
? installNewSessionBridge(ctx, ctx.workspaces) : () => {};
|
|
1817
|
+
const fiber = ctx.inject(['uiWorkspace'], scope => {
|
|
1818
|
+
scope.effect(() => installNewSessionBridge(scope, scope.uiWorkspace), 'data-cleaning-agent: native new session');
|
|
1819
|
+
});
|
|
1820
|
+
return () => { legacy(); fiber?.dispose?.(); };
|
|
1821
|
+
}
|
|
2071
1822
|
const workspaces = ctx.workspaces;
|
|
2072
|
-
|
|
1823
|
+
navigation = navigation || workspaces;
|
|
1824
|
+
const original = navigation?.startSession;
|
|
2073
1825
|
if (typeof original !== 'function' || typeof ctx.sessions?.create !== 'function') return () => {};
|
|
2074
|
-
const ownDescriptor = Object.getOwnPropertyDescriptor(
|
|
1826
|
+
const ownDescriptor = Object.getOwnPropertyDescriptor(navigation, 'startSession');
|
|
2075
1827
|
let pending = null;
|
|
2076
1828
|
let disposed = false;
|
|
2077
1829
|
const wrapped = function (workspaceId) {
|
|
@@ -2094,13 +1846,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2094
1846
|
console.warn('[dc-agent] new session failed; current draft retained:', error instanceof Error ? error.message : String(error));
|
|
2095
1847
|
}).finally(() => { pending = null; });
|
|
2096
1848
|
};
|
|
2097
|
-
try {
|
|
1849
|
+
try { navigation.startSession = wrapped; }
|
|
2098
1850
|
catch (_error) { console.warn('[dc-agent] New Session compatibility bridge unavailable'); return () => {}; }
|
|
2099
1851
|
return () => {
|
|
2100
1852
|
disposed = true;
|
|
2101
|
-
if (
|
|
2102
|
-
if (ownDescriptor) Object.defineProperty(
|
|
2103
|
-
else delete
|
|
1853
|
+
if (navigation.startSession !== wrapped) return;
|
|
1854
|
+
if (ownDescriptor) Object.defineProperty(navigation, 'startSession', ownDescriptor);
|
|
1855
|
+
else delete navigation.startSession;
|
|
2104
1856
|
};
|
|
2105
1857
|
}
|
|
2106
1858
|
|
|
@@ -2212,20 +1964,19 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2212
1964
|
|
|
2213
1965
|
function openWorkbench(actions, step, sessionId) {
|
|
2214
1966
|
actions.openAt(step, sessionId);
|
|
2215
|
-
|
|
1967
|
+
sessionWorkbenchController?.open(sessionId, step);
|
|
2216
1968
|
}
|
|
2217
1969
|
|
|
2218
1970
|
const WORKBENCH_OPEN_EVENT = 'dsh:data-cleaning-workbench-open';
|
|
2219
1971
|
const WORKBENCH_DATASET_EVENT = 'dsh:data-cleaning-workbench-dataset';
|
|
2220
1972
|
const WORKBENCH_DRAFT_EVENT = 'dsh:data-cleaning-workbench-draft';
|
|
2221
1973
|
const WORKBENCH_PREPARE_EVENT = 'dsh:data-cleaning-workbench-prepare';
|
|
2222
|
-
const WORKBENCH_OWNER_LEFT_EVENT = 'dsh:data-cleaning-workbench-owner-left';
|
|
2223
1974
|
|
|
2224
1975
|
// session scope 不能复用 root scope 的 store handle。DSH 当前会分别物化 root/session
|
|
2225
1976
|
// 插槽组件,因此以 document 事件跨 scope 通知;闭包引用仅作为无 DOM 测试降级。
|
|
2226
|
-
let
|
|
1977
|
+
let sessionWorkbenchController = null;
|
|
2227
1978
|
|
|
2228
|
-
/** 从
|
|
1979
|
+
/** 从 composer/header 请求指定 Session 的唯一工作台 Tab。 */
|
|
2229
1980
|
function requestWorkbenchOpen(step, sessionId, task = null) {
|
|
2230
1981
|
if (typeof document !== 'undefined'
|
|
2231
1982
|
&& typeof document.createEvent === 'function'
|
|
@@ -2234,7 +1985,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2234
1985
|
event.initCustomEvent(WORKBENCH_OPEN_EVENT, false, true, { step, sessionId, task });
|
|
2235
1986
|
if (!document.dispatchEvent(event)) return;
|
|
2236
1987
|
}
|
|
2237
|
-
|
|
1988
|
+
sessionWorkbenchController?.open(sessionId, step);
|
|
2238
1989
|
}
|
|
2239
1990
|
|
|
2240
1991
|
function requestPreparedWorkflow(sessionId, config) {
|
|
@@ -2245,7 +1996,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2245
1996
|
});
|
|
2246
1997
|
}
|
|
2247
1998
|
|
|
2248
|
-
/**
|
|
1999
|
+
/** 完整表格交给 Session 业务控制器,不把整表塞进模型上下文。 */
|
|
2249
2000
|
function requestWorkbenchDataset(result, sessionId, source) {
|
|
2250
2001
|
if (typeof document !== 'undefined'
|
|
2251
2002
|
&& typeof document.createEvent === 'function'
|
|
@@ -2254,9 +2005,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2254
2005
|
event.initCustomEvent(WORKBENCH_DATASET_EVENT, false, true, { result, sessionId, source });
|
|
2255
2006
|
if (!document.dispatchEvent(event)) return;
|
|
2256
2007
|
}
|
|
2257
|
-
if (
|
|
2258
|
-
applyParsed(result,
|
|
2259
|
-
|
|
2008
|
+
if (sessionWorkbenchController?.actionsFor(sessionId)) {
|
|
2009
|
+
applyParsed(result, sessionWorkbenchController?.actionsFor(sessionId), `session:${sessionId || 'unassigned'}`, source);
|
|
2010
|
+
sessionWorkbenchController?.actionsFor(sessionId).setActiveSession(sessionId);
|
|
2260
2011
|
}
|
|
2261
2012
|
}
|
|
2262
2013
|
|
|
@@ -2269,11 +2020,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2269
2020
|
event.initCustomEvent(WORKBENCH_DRAFT_EVENT, false, true, { draft, sessionId });
|
|
2270
2021
|
if (!document.dispatchEvent(event)) return;
|
|
2271
2022
|
}
|
|
2272
|
-
if (
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
if (draft.mappings)
|
|
2276
|
-
|
|
2023
|
+
if (sessionWorkbenchController?.actionsFor(sessionId)) {
|
|
2024
|
+
sessionWorkbenchController?.actionsFor(sessionId).setTaskTitle(draft.title);
|
|
2025
|
+
sessionWorkbenchController?.actionsFor(sessionId).setObjectives(draft.objectives);
|
|
2026
|
+
if (draft.mappings) sessionWorkbenchController?.actionsFor(sessionId).setMappings(draft.mappings);
|
|
2027
|
+
sessionWorkbenchController?.actionsFor(sessionId).setFieldSelection(draft.fieldSelection);
|
|
2277
2028
|
}
|
|
2278
2029
|
}
|
|
2279
2030
|
|
|
@@ -2360,7 +2111,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2360
2111
|
'导入企业名单或图片,核验主体、补全字段,并导出可追溯结果。');
|
|
2361
2112
|
}
|
|
2362
2113
|
|
|
2363
|
-
function workflowNavigationIssue(step, task, hasData, profile, qccRun) {
|
|
2114
|
+
function workflowNavigationIssue(step, task, hasData, profile, qccRun, hasLocalResult = false) {
|
|
2364
2115
|
if (['upload', 'history'].includes(step)) return null;
|
|
2365
2116
|
if (step === 'download' && task?.artifacts?.length) return null;
|
|
2366
2117
|
if (!hasData) return '请先在「导入与核验」载入并核对完整名单。';
|
|
@@ -2374,6 +2125,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2374
2125
|
if (step === 'enrich' && requiresQcc && !['matched', 'enriching', 'export_ready', 'completed', 'partial'].includes(task.state)) {
|
|
2375
2126
|
return '请先在「主体匹配」确认企业;多候选需要人工核验。';
|
|
2376
2127
|
}
|
|
2128
|
+
if (step === 'download' && !requiresQcc && hasLocalResult && ['rules_confirmed', 'diagnosed'].includes(task.state)) return null;
|
|
2377
2129
|
if (step === 'download' && !['export_ready', 'completed', 'partial'].includes(task.state) && !['completed', 'partial'].includes(qccRun?.state)) {
|
|
2378
2130
|
return '请先完成清洗或字段补全。原始清单可在「导入与核验」下载。';
|
|
2379
2131
|
}
|
|
@@ -2582,10 +2334,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2582
2334
|
function DataCleaningExperience(props) {
|
|
2583
2335
|
const { sessionId, session: ownerSession } = props;
|
|
2584
2336
|
const enabled = useCleaningSession(sessionId);
|
|
2337
|
+
const [dependencyMessage, setDependencyMessage] = react.useState('');
|
|
2585
2338
|
react.useEffect(() => {
|
|
2586
|
-
if (
|
|
2587
|
-
|
|
2588
|
-
|
|
2339
|
+
const listener = event => { if (event.detail?.sessionId === sessionId) setDependencyMessage(event.detail.message || ''); };
|
|
2340
|
+
document.addEventListener(DEPENDENCY_EVENT, listener);
|
|
2341
|
+
return () => document.removeEventListener(DEPENDENCY_EVENT, listener);
|
|
2342
|
+
}, [sessionId]);
|
|
2589
2343
|
const hero = ownerSession?.composerPhase === 'blank';
|
|
2590
2344
|
const [menuMount, setMenuMount] = react.useState(null);
|
|
2591
2345
|
react.useEffect(() => {
|
|
@@ -2603,6 +2357,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2603
2357
|
className: `dcAgentExperience${hero ? ' is-home' : ''}`,
|
|
2604
2358
|
'data-session-id': sessionId,
|
|
2605
2359
|
},
|
|
2360
|
+
dependencyMessage ? h('p', { className: 'dcAgentError', role: 'alert' }, dependencyMessage) : null,
|
|
2606
2361
|
menuMount ? reactDom.createPortal(h(CapabilityBar, { sessionId }), menuMount) : h(CapabilityBar, { sessionId }),
|
|
2607
2362
|
hero ? h(ProductHome, { sessionId }) : null,
|
|
2608
2363
|
);
|
|
@@ -2950,7 +2705,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2950
2705
|
const hasImage = files.length > 0;
|
|
2951
2706
|
if (!hasImage) return;
|
|
2952
2707
|
const target = event.target;
|
|
2953
|
-
const inComposer = !open && !target?.closest?.('.
|
|
2708
|
+
const inComposer = !open && !target?.closest?.('.dcAgentWorkbenchContent')
|
|
2954
2709
|
&& Boolean(target?.closest?.('textarea, [contenteditable="true"], [role="textbox"]'));
|
|
2955
2710
|
const inSourceStep = open && wizardStep === 1 && Boolean(target?.closest?.('.dcAgentPromptPanel'));
|
|
2956
2711
|
if (!inComposer && !inSourceStep) return;
|
|
@@ -3489,15 +3244,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3489
3244
|
);
|
|
3490
3245
|
}
|
|
3491
3246
|
|
|
3492
|
-
/**
|
|
3493
|
-
function
|
|
3247
|
+
/** Session Tab 业务内容;尺寸、停靠、关闭均由 Better Sidebar 宿主管理。 */
|
|
3248
|
+
function WorkbenchContent(props) {
|
|
3494
3249
|
const { useStore, actions } = props;
|
|
3250
|
+
const visible = props.visible !== false;
|
|
3495
3251
|
const sendSessionCommand = props.sendSessionCommand;
|
|
3496
3252
|
const setSessionDraft = props.setSessionDraft;
|
|
3497
3253
|
const open = useStore((state) => state.open);
|
|
3498
3254
|
const step = useStore((state) => state.step);
|
|
3499
|
-
const expanded = useStore((state) => state.expanded);
|
|
3500
|
-
const preferredWidth = useStore((state) => state.preferredWidth);
|
|
3501
3255
|
const busy = useStore((state) => state.busy);
|
|
3502
3256
|
const error = useStore((state) => state.error);
|
|
3503
3257
|
const input = useStore((state) => state.input);
|
|
@@ -3519,92 +3273,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3519
3273
|
const qccRun = useStore((state) => state.qccRun);
|
|
3520
3274
|
const commandProgress = useStore((state) => state.commandProgress);
|
|
3521
3275
|
const paidConfirmed = useStore((state) => state.paidConfirmed);
|
|
3522
|
-
const jobs = useStore((state) => state.jobs);
|
|
3523
3276
|
const activeSessionId = useStore((state) => state.activeSessionId);
|
|
3524
3277
|
const [fieldQuery, setFieldQuery] = react.useState('');
|
|
3525
3278
|
|
|
3526
3279
|
react.useEffect(() => {
|
|
3527
|
-
if (!open ||
|
|
3528
|
-
const drawer = document.querySelector('.dcAgentWorkbench');
|
|
3529
|
-
if (!drawer) return undefined;
|
|
3530
|
-
return installWorkbenchSizing(drawer, { preferredWidth, expanded, onWidthChange: actions.setPreferredWidth });
|
|
3531
|
-
}, [open, expanded, preferredWidth, actions]);
|
|
3532
|
-
|
|
3533
|
-
react.useEffect(() => {
|
|
3534
|
-
rootWorkbenchActions = actions;
|
|
3535
|
-
const handleOwnerLeft = () => actions.close();
|
|
3536
|
-
const handleOpen = (event) => {
|
|
3537
|
-
event?.preventDefault?.();
|
|
3538
|
-
const detail = event?.detail ?? {};
|
|
3539
|
-
if (detail.task?.id) {
|
|
3540
|
-
workflowTaskBySession.set(String(detail.sessionId || 'unassigned'), detail.task);
|
|
3541
|
-
actions.setWorkflowTask(detail.task);
|
|
3542
|
-
actions.setTaskTitle(detail.task.title);
|
|
3543
|
-
actions.setMappings(detail.task.mappings || []);
|
|
3544
|
-
actions.setObjectives(detail.task.objectives || []);
|
|
3545
|
-
actions.setFieldSelection(detail.task.fieldSelection || []);
|
|
3546
|
-
for (const [key, value] of Object.entries(detail.task.matchRules || {})) actions.setMatchRule(key, value);
|
|
3547
|
-
}
|
|
3548
|
-
openWorkbench(actions, detail.step ?? null, detail.sessionId ?? null);
|
|
3549
|
-
};
|
|
3550
|
-
const handleDataset = (event) => {
|
|
3551
|
-
event?.preventDefault?.();
|
|
3552
|
-
const detail = event?.detail ?? {};
|
|
3553
|
-
if (detail.result) applyParsed(detail.result, actions, `session:${detail.sessionId || 'unassigned'}`, detail.source);
|
|
3554
|
-
if (detail.sessionId) actions.setActiveSession(detail.sessionId);
|
|
3555
|
-
if (detail.result) {
|
|
3556
|
-
void queueWorkflowOperation(detail.sessionId, () => (
|
|
3557
|
-
persistParsedWorkflow(actions, detail.sessionId, detail.result, detail.source)
|
|
3558
|
-
)).catch((eventError) => {
|
|
3559
|
-
actions.setError(eventError instanceof Error ? eventError.message : String(eventError));
|
|
3560
|
-
});
|
|
3561
|
-
}
|
|
3562
|
-
};
|
|
3563
|
-
const handlePrepare = (event) => {
|
|
3564
|
-
event.preventDefault();
|
|
3565
|
-
const { sessionId, config, resolve, reject } = event.detail;
|
|
3566
|
-
queueWorkflowOperation(sessionId, () => stageConfirmedWorkflow(actions, sessionId, config)).then((command) => {
|
|
3567
|
-
watchHostedCommand(actions, sessionId, command);
|
|
3568
|
-
resolve(command);
|
|
3569
|
-
}, reject);
|
|
3570
|
-
};
|
|
3571
|
-
const handleDraft = (event) => {
|
|
3572
|
-
event?.preventDefault?.();
|
|
3573
|
-
const detail = event?.detail ?? {};
|
|
3574
|
-
const draft = detail.draft ?? {};
|
|
3575
|
-
if (detail.sessionId) actions.setActiveSession(detail.sessionId);
|
|
3576
|
-
if (draft.title) actions.setTaskTitle(draft.title);
|
|
3577
|
-
if (draft.objectives) actions.setObjectives(draft.objectives);
|
|
3578
|
-
if (draft.mappings) actions.setMappings(draft.mappings);
|
|
3579
|
-
if (draft.fieldSelection) actions.setFieldSelection(draft.fieldSelection);
|
|
3580
|
-
for (const [key, value] of Object.entries(draft.matchRules || {})) actions.setMatchRule(key, value);
|
|
3581
|
-
void queueWorkflowOperation(detail.sessionId, async () => {
|
|
3582
|
-
const task = await ensureEditableWorkflowTask(actions, detail.sessionId, draft);
|
|
3583
|
-
return updateWorkflowTask(actions, detail.sessionId, task, draft);
|
|
3584
|
-
}).catch((eventError) => actions.setError(eventError instanceof Error ? eventError.message : String(eventError)));
|
|
3585
|
-
};
|
|
3586
|
-
if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
|
|
3587
|
-
document.addEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
|
|
3588
|
-
document.addEventListener(WORKBENCH_OWNER_LEFT_EVENT, handleOwnerLeft);
|
|
3589
|
-
document.addEventListener(WORKBENCH_DATASET_EVENT, handleDataset);
|
|
3590
|
-
document.addEventListener(WORKBENCH_DRAFT_EVENT, handleDraft);
|
|
3591
|
-
document.addEventListener(WORKBENCH_PREPARE_EVENT, handlePrepare);
|
|
3592
|
-
}
|
|
3593
|
-
return () => {
|
|
3594
|
-
if (typeof document !== 'undefined' && typeof document.removeEventListener === 'function') {
|
|
3595
|
-
document.removeEventListener(WORKBENCH_OPEN_EVENT, handleOpen);
|
|
3596
|
-
document.removeEventListener(WORKBENCH_OWNER_LEFT_EVENT, handleOwnerLeft);
|
|
3597
|
-
document.removeEventListener(WORKBENCH_DATASET_EVENT, handleDataset);
|
|
3598
|
-
document.removeEventListener(WORKBENCH_DRAFT_EVENT, handleDraft);
|
|
3599
|
-
document.removeEventListener(WORKBENCH_PREPARE_EVENT, handlePrepare);
|
|
3600
|
-
}
|
|
3601
|
-
stopJobsPolling();
|
|
3602
|
-
if (rootWorkbenchActions === actions) rootWorkbenchActions = null;
|
|
3603
|
-
};
|
|
3604
|
-
}, [actions]);
|
|
3605
|
-
|
|
3606
|
-
react.useEffect(() => {
|
|
3607
|
-
if (!open) return undefined;
|
|
3280
|
+
if (!open || !visible) return undefined;
|
|
3608
3281
|
let disposed = false;
|
|
3609
3282
|
requestJson('/data-cleaning/api/workflow/contract').then((response) => {
|
|
3610
3283
|
if (!disposed) actions.setWorkflowContract(response.contract);
|
|
@@ -3612,19 +3285,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3612
3285
|
loadWorkflowTasks(actions);
|
|
3613
3286
|
if (activeSessionId) {
|
|
3614
3287
|
queueWorkflowOperation(activeSessionId, async () => {
|
|
3615
|
-
const
|
|
3616
|
-
|
|
3288
|
+
const key = String(activeSessionId);
|
|
3289
|
+
const saved = ownedSessionId(key) ? readTaskBindings()[key] : null;
|
|
3290
|
+
const taskId = workflowTaskBySession.get(key)?.id || (/^dcw-[a-zA-Z0-9-]+$/.test(saved || '') ? saved : null);
|
|
3291
|
+
if (taskId) await refreshHostedTask(actions, activeSessionId, taskId, null, false);
|
|
3617
3292
|
}).catch((taskError) => {
|
|
3618
3293
|
if (!disposed) actions.setError(taskError instanceof Error ? taskError.message : String(taskError));
|
|
3619
3294
|
});
|
|
3620
3295
|
}
|
|
3621
3296
|
return () => { disposed = true; };
|
|
3622
|
-
}, [open, activeSessionId, actions]);
|
|
3297
|
+
}, [open, visible, activeSessionId, actions]);
|
|
3623
3298
|
|
|
3624
3299
|
// Reopening during execution follows Host metadata, even when the original
|
|
3625
3300
|
// command watcher was lost on page reload. This performs no QCC calls.
|
|
3626
3301
|
react.useEffect(() => {
|
|
3627
|
-
if (!open || !workflowTask?.id || !['matching', 'enriching'].includes(workflowTask.state)) return undefined;
|
|
3302
|
+
if (!open || !visible || !workflowTask?.id || !['matching', 'enriching'].includes(workflowTask.state)) return undefined;
|
|
3628
3303
|
let disposed = false, timer;
|
|
3629
3304
|
const poll = async () => {
|
|
3630
3305
|
try {
|
|
@@ -3636,51 +3311,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3636
3311
|
};
|
|
3637
3312
|
timer = setTimeout(poll, 2000);
|
|
3638
3313
|
return () => { disposed = true; clearTimeout(timer); };
|
|
3639
|
-
}, [open, workflowTask?.id, workflowTask?.state, activeSessionId, step, actions]);
|
|
3314
|
+
}, [open, visible, workflowTask?.id, workflowTask?.state, activeSessionId, step, actions]);
|
|
3640
3315
|
|
|
3641
|
-
//
|
|
3642
|
-
// 保证 Mockup 中的五个入口在窄桌面视口仍可见、可点击。展开态专注工作台,
|
|
3643
|
-
// 不强行挤压到不足一栏的剩余区域。
|
|
3644
|
-
react.useEffect(() => {
|
|
3645
|
-
if (typeof document === 'undefined' || typeof document.querySelectorAll !== 'function') return undefined;
|
|
3646
|
-
const reset = () => {
|
|
3647
|
-
for (const bar of document.querySelectorAll('.dcAgentCapabilities')) {
|
|
3648
|
-
bar.style?.removeProperty('--dc-agent-workbench-shift');
|
|
3649
|
-
}
|
|
3650
|
-
};
|
|
3651
|
-
if (!open || expanded) {
|
|
3652
|
-
reset();
|
|
3653
|
-
return reset;
|
|
3654
|
-
}
|
|
3655
|
-
const align = () => {
|
|
3656
|
-
const drawer = document.querySelector('.dcAgentWorkbench');
|
|
3657
|
-
if (!drawer) return;
|
|
3658
|
-
const drawerRect = drawer.getBoundingClientRect();
|
|
3659
|
-
for (const bar of document.querySelectorAll('.dcAgentCapabilities')) {
|
|
3660
|
-
const barRect = bar.getBoundingClientRect();
|
|
3661
|
-
const overlap = Math.max(0, barRect.right - drawerRect.left + 8);
|
|
3662
|
-
const leftRoom = Math.max(0, barRect.left - 88);
|
|
3663
|
-
const shift = Math.min(overlap, leftRoom);
|
|
3664
|
-
bar.style?.setProperty('--dc-agent-workbench-shift', `${-shift}px`);
|
|
3665
|
-
}
|
|
3666
|
-
};
|
|
3667
|
-
const view = document.defaultView;
|
|
3668
|
-
const frameId = typeof view?.requestAnimationFrame === 'function'
|
|
3669
|
-
? view.requestAnimationFrame(align)
|
|
3670
|
-
: null;
|
|
3671
|
-
view?.addEventListener?.('resize', align);
|
|
3672
|
-
align();
|
|
3673
|
-
return () => {
|
|
3674
|
-
if (frameId !== null) view?.cancelAnimationFrame?.(frameId);
|
|
3675
|
-
view?.removeEventListener?.('resize', align);
|
|
3676
|
-
reset();
|
|
3677
|
-
};
|
|
3678
|
-
}, [open, expanded, preferredWidth]);
|
|
3679
|
-
|
|
3680
|
-
// DSH 的 useStore 基于 React hooks;所有订阅必须在每次渲染时以相同顺序调用。
|
|
3681
|
-
// 把关闭态 guard 放在全部 useStore 之后,避免首次关闭、随后打开时触发
|
|
3682
|
-
// React #310(Rendered more hooks than during the previous render)。
|
|
3683
|
-
if (!open) return null;
|
|
3316
|
+
// Host 决定 Tab 挂载/可见性;内容不再按私有 open flag 隐藏。
|
|
3684
3317
|
|
|
3685
3318
|
const hasData = dataset !== null && dataset.rowCount > 0;
|
|
3686
3319
|
const requiresQcc = objectives.includes('validate_identity') || objectives.includes('complete_fields');
|
|
@@ -4514,60 +4147,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4514
4147
|
);
|
|
4515
4148
|
}
|
|
4516
4149
|
|
|
4517
|
-
return h('
|
|
4518
|
-
className: '
|
|
4150
|
+
return h('section', {
|
|
4151
|
+
className: 'dcAgentWorkbenchContent',
|
|
4519
4152
|
'data-dsh-plugin': 'data-cleaning-agent',
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
stopJobsPolling();
|
|
4523
|
-
actions.close();
|
|
4524
|
-
}
|
|
4525
|
-
},
|
|
4153
|
+
'aria-label': '数据清洗补全工作台',
|
|
4154
|
+
'data-session-id': activeSessionId ?? undefined,
|
|
4526
4155
|
},
|
|
4527
|
-
h('aside', {
|
|
4528
|
-
className: `dcAgentWorkbench${expanded ? ' is-expanded' : ''}`,
|
|
4529
|
-
role: 'dialog',
|
|
4530
|
-
'aria-modal': 'false',
|
|
4531
|
-
'aria-label': '数据清洗补全工作台',
|
|
4532
|
-
'data-session-id': activeSessionId ?? undefined,
|
|
4533
|
-
id: 'dc-agent-workbench',
|
|
4534
|
-
onKeyDown: (event) => {
|
|
4535
|
-
if (event.key === 'Escape') {
|
|
4536
|
-
event.preventDefault();
|
|
4537
|
-
stopJobsPolling();
|
|
4538
|
-
actions.close();
|
|
4539
|
-
}
|
|
4540
|
-
},
|
|
4541
|
-
},
|
|
4542
|
-
h('div', { className: 'dcAgentResizeHandle', role: 'separator', tabIndex: 0,
|
|
4543
|
-
'aria-label': '调整工作台宽度', 'aria-orientation': 'vertical', 'aria-controls': 'dc-agent-workbench',
|
|
4544
|
-
title: '拖动调整宽度;左右方向键微调,Home/End 最小/最大,双击恢复默认' }),
|
|
4545
|
-
h('header', { className: 'dcAgentWbHeader' },
|
|
4546
|
-
h('div', { className: 'dcAgentWbTitle' },
|
|
4547
|
-
h('span', { className: 'dcAgentWbIcon', 'aria-hidden': 'true' }, h(DatabaseLogo, { size: 22 })),
|
|
4548
|
-
h('div', null,
|
|
4549
|
-
h('b', null, '数据清洗补全'),
|
|
4550
|
-
),
|
|
4551
|
-
),
|
|
4552
|
-
h('div', { className: 'dcAgentWbActions' },
|
|
4553
|
-
h('button', {
|
|
4554
|
-
className: 'dcAgentWbClose',
|
|
4555
|
-
type: 'button',
|
|
4556
|
-
'aria-label': expanded ? '收起工作台' : '展开工作台',
|
|
4557
|
-
onClick: () => actions.toggleExpanded(),
|
|
4558
|
-
}, expanded ? '↘' : '↖'),
|
|
4559
|
-
h('button', {
|
|
4560
|
-
className: 'dcAgentWbClose',
|
|
4561
|
-
type: 'button',
|
|
4562
|
-
title: '关闭工作台(Esc)',
|
|
4563
|
-
'aria-label': '关闭',
|
|
4564
|
-
onClick: () => {
|
|
4565
|
-
stopJobsPolling();
|
|
4566
|
-
actions.close();
|
|
4567
|
-
},
|
|
4568
|
-
}, '✕'),
|
|
4569
|
-
),
|
|
4570
|
-
),
|
|
4571
4156
|
h('nav', { className: 'dcAgentManagement', 'aria-label': '工作台管理' },
|
|
4572
4157
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step !== 'history',
|
|
4573
4158
|
onClick: () => actions.setStep(cachedTask?.stage || 'upload') }, '当前任务'),
|
|
@@ -4611,17 +4196,240 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4611
4196
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'rules', onClick: () => actions.setStep('rules') }, '字段映射与规则'),
|
|
4612
4197
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'profile', onClick: () => actions.setStep('profile') }, '质量体检报告'),
|
|
4613
4198
|
) : null,
|
|
4614
|
-
workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun)
|
|
4199
|
+
workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))
|
|
4615
4200
|
? h('section', { className: 'dcAgentSection', role: 'status' },
|
|
4616
|
-
h('p', null, workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun)),
|
|
4201
|
+
h('p', null, workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))),
|
|
4617
4202
|
h('button', { type: 'button', className: 'dcAgentButton',
|
|
4618
4203
|
onClick: () => actions.setStep(!hasData ? 'upload' : !cachedTask || ['draft', 'uploaded'].includes(cachedTask.state) ? 'rules' : cachedTask.state === 'rules_confirmed' ? 'profile' : step === 'download' ? 'enrich' : 'match') }, '前往前置步骤'))
|
|
4619
4204
|
: pane,
|
|
4620
4205
|
),
|
|
4621
|
-
),
|
|
4622
4206
|
);
|
|
4623
4207
|
}
|
|
4624
4208
|
|
|
4209
|
+
// BEGIN generated Better Sidebar adapter (source: lib/better-sidebar-adapter.js)
|
|
4210
|
+
/** Public Better Sidebar seam. No business task creation, cancellation or geometry. */
|
|
4211
|
+
const CLEANING_TAB_ID = 'dsh-data-cleaning-agent:workbench';
|
|
4212
|
+
const SIDEBAR_DEPENDENCY_MESSAGE = '请安装或升级兼容的 dsh-better-sidebar 并重启 DSH(需要 targetedOpen 和 stateSubscription 能力),然后重新点击流程按钮。现有任务和文件不会删除。';
|
|
4213
|
+
|
|
4214
|
+
function sidebarCompatibility(service) {
|
|
4215
|
+
const methods = ['registerTab', 'openTab', 'isTabEnabled', 'getSnapshot', 'subscribeState'];
|
|
4216
|
+
return Boolean(service && methods.every(key => typeof service[key] === 'function')
|
|
4217
|
+
&& ['targetedOpen', 'stateSubscription'].every(key => service.features?.includes(key)));
|
|
4218
|
+
}
|
|
4219
|
+
|
|
4220
|
+
function contains(node, id) {
|
|
4221
|
+
if (!node) return false;
|
|
4222
|
+
return node.kind === 'leaf' ? node.tabs.some(tab => tab.id === id)
|
|
4223
|
+
: node.children.some(child => contains(child, id));
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
function revealCleaningTab(state, id) {
|
|
4227
|
+
if (state.floats?.some(item => item.tab.id === id)) return state;
|
|
4228
|
+
if (contains(state.bottomSplits, id)) return state.bottomOpen ? state : { ...state, bottomOpen: true };
|
|
4229
|
+
if (contains(state.splits, id)) return state.panelOpen ? state : { ...state, panelOpen: true };
|
|
4230
|
+
return state;
|
|
4231
|
+
}
|
|
4232
|
+
|
|
4233
|
+
/** One adapter per plugin activation. Session navigation survives closing its Tab. */
|
|
4234
|
+
function createCleaningSidebarAdapter(service, { component, icon, onUnavailable = () => {} }) {
|
|
4235
|
+
const targets = new Map();
|
|
4236
|
+
const destinations = new Map();
|
|
4237
|
+
const pending = new Set();
|
|
4238
|
+
let disposed = false;
|
|
4239
|
+
const compatible = sidebarCompatibility(service);
|
|
4240
|
+
const applyPending = sessionId => {
|
|
4241
|
+
if (disposed || service.getSnapshot().sessionId !== sessionId || !pending.has(sessionId)) return;
|
|
4242
|
+
const target = targets.get(sessionId);
|
|
4243
|
+
if (!target) return;
|
|
4244
|
+
pending.delete(sessionId);
|
|
4245
|
+
target.navigate(destinations.get(sessionId));
|
|
4246
|
+
target.store.reduce(state => revealCleaningTab(state, target.tabId));
|
|
4247
|
+
};
|
|
4248
|
+
const unregister = compatible ? service.registerTab({
|
|
4249
|
+
id: CLEANING_TAB_ID, title: '数据清洗补全', icon, order: 30, single: true, component,
|
|
4250
|
+
}) : () => {};
|
|
4251
|
+
const unsubscribe = compatible ? service.subscribeState(() => {
|
|
4252
|
+
const sessionId = service.getSnapshot().sessionId;
|
|
4253
|
+
if (sessionId) applyPending(sessionId);
|
|
4254
|
+
}) : () => {};
|
|
4255
|
+
return {
|
|
4256
|
+
compatible,
|
|
4257
|
+
open(scope, destination = 'upload') {
|
|
4258
|
+
if (disposed) return false;
|
|
4259
|
+
if (!compatible || !service.isTabEnabled(CLEANING_TAB_ID)) {
|
|
4260
|
+
onUnavailable(compatible ? '请在 Better Sidebar 设置中启用「数据清洗补全」Tab。' : SIDEBAR_DEPENDENCY_MESSAGE);
|
|
4261
|
+
return false;
|
|
4262
|
+
}
|
|
4263
|
+
if (!scope?.sessionId) throw new Error('A target Session is required');
|
|
4264
|
+
destinations.set(scope.sessionId, destination);
|
|
4265
|
+
pending.add(scope.sessionId);
|
|
4266
|
+
service.openTab({ type: CLEANING_TAB_ID }, scope);
|
|
4267
|
+
applyPending(scope.sessionId);
|
|
4268
|
+
return true;
|
|
4269
|
+
},
|
|
4270
|
+
attach(scope, target) {
|
|
4271
|
+
if (disposed) return () => {};
|
|
4272
|
+
targets.set(scope.sessionId, target);
|
|
4273
|
+
// Without a pending shortcut, preserve the Session business store's current view.
|
|
4274
|
+
// Closing/reopening a Tab must not rewind an in-progress task to an older shortcut.
|
|
4275
|
+
applyPending(scope.sessionId);
|
|
4276
|
+
return () => { if (targets.get(scope.sessionId) === target) targets.delete(scope.sessionId); };
|
|
4277
|
+
},
|
|
4278
|
+
dispose() {
|
|
4279
|
+
if (disposed) return;
|
|
4280
|
+
disposed = true;
|
|
4281
|
+
unsubscribe(); unregister();
|
|
4282
|
+
targets.clear(); pending.clear(); destinations.clear();
|
|
4283
|
+
},
|
|
4284
|
+
};
|
|
4285
|
+
}
|
|
4286
|
+
|
|
4287
|
+
// END generated Better Sidebar adapter
|
|
4288
|
+
|
|
4289
|
+
// A shared owner across materialized root/Session factories, never a shared UI store.
|
|
4290
|
+
const CONTROLLER_KEY = Symbol.for('dsh.data-cleaning.session-workbench');
|
|
4291
|
+
const DEPENDENCY_EVENT = 'dsh:data-cleaning-sidebar-dependency';
|
|
4292
|
+
function installSessionWorkbench(ctx) {
|
|
4293
|
+
const host = typeof document === 'undefined' ? window : document;
|
|
4294
|
+
if (host[CONTROLLER_KEY]) {
|
|
4295
|
+
const shared = host[CONTROLLER_KEY];
|
|
4296
|
+
shared.references += 1;
|
|
4297
|
+
sessionWorkbenchController = shared.controller;
|
|
4298
|
+
let released = false;
|
|
4299
|
+
return () => { if (!released) { released = true; shared.release(); } };
|
|
4300
|
+
}
|
|
4301
|
+
let disposed = false;
|
|
4302
|
+
const instances = new Map();
|
|
4303
|
+
const spec = createWorkbenchStore();
|
|
4304
|
+
const instanceFor = sessionId => {
|
|
4305
|
+
if (!sessionId) throw new Error('请先打开数据清洗会话。');
|
|
4306
|
+
if (!instances.has(sessionId)) {
|
|
4307
|
+
const instance = spec.create(sessionId);
|
|
4308
|
+
instance.actions.setActiveSession(sessionId);
|
|
4309
|
+
instances.set(sessionId, instance);
|
|
4310
|
+
}
|
|
4311
|
+
return instances.get(sessionId);
|
|
4312
|
+
};
|
|
4313
|
+
let openingSession = null;
|
|
4314
|
+
const notify = message => {
|
|
4315
|
+
if (openingSession) instanceFor(openingSession).actions.setError(message);
|
|
4316
|
+
if (typeof document?.dispatchEvent === 'function') {
|
|
4317
|
+
const event = document.createEvent('CustomEvent');
|
|
4318
|
+
event.initCustomEvent(DEPENDENCY_EVENT, false, false, { sessionId: openingSession, message });
|
|
4319
|
+
document.dispatchEvent(event);
|
|
4320
|
+
}
|
|
4321
|
+
};
|
|
4322
|
+
let adapter;
|
|
4323
|
+
function SessionWorkbenchTab(props) {
|
|
4324
|
+
const instance = instanceFor(props.scope.sessionId);
|
|
4325
|
+
const useStore = pick => react.useSyncExternalStore(instance.subscribe,
|
|
4326
|
+
() => pick(instance.getSnapshot()), () => pick(instance.getSnapshot()));
|
|
4327
|
+
react.useEffect(() => {
|
|
4328
|
+
instance.actions.openAt(null, props.scope.sessionId);
|
|
4329
|
+
return adapter.attach(props.scope, { tabId: props.tab.id, store: props.store,
|
|
4330
|
+
navigate: destination => instance.actions.setStep(destination || instance.getSnapshot().step) });
|
|
4331
|
+
}, [instance, props.scope.sessionId, props.tab.id, props.store]);
|
|
4332
|
+
return h(WorkbenchContent, { useStore, actions: instance.actions, visible: props.visible,
|
|
4333
|
+
sendSessionCommand: (sessionId, prompt) => sendQccAgentCommand(ctx, sessionId, prompt),
|
|
4334
|
+
setSessionDraft: (sessionId, prompt) => setQccAgentDraft(ctx, sessionId, prompt) });
|
|
4335
|
+
}
|
|
4336
|
+
const adapterOptions = { component: SessionWorkbenchTab, icon: size => h(DatabaseLogo, { size }), onUnavailable: notify };
|
|
4337
|
+
adapter = createCleaningSidebarAdapter(null, adapterOptions);
|
|
4338
|
+
const connectProvider = service => {
|
|
4339
|
+
if (disposed) return () => {};
|
|
4340
|
+
const next = createCleaningSidebarAdapter(service, adapterOptions);
|
|
4341
|
+
adapter.dispose();
|
|
4342
|
+
adapter = next;
|
|
4343
|
+
return () => {
|
|
4344
|
+
next.dispose();
|
|
4345
|
+
if (!disposed && adapter === next) adapter = createCleaningSidebarAdapter(null, adapterOptions);
|
|
4346
|
+
};
|
|
4347
|
+
};
|
|
4348
|
+
// Optional child injection grants Cordis service access and follows provider removal.
|
|
4349
|
+
// Core conversation / Host tools remain loaded when Better Sidebar is absent.
|
|
4350
|
+
const providerFiber = typeof ctx.inject === 'function'
|
|
4351
|
+
? ctx.inject(['betterSidebar'], providerContext => {
|
|
4352
|
+
providerContext.effect(() => connectProvider(providerContext.betterSidebar), 'data-cleaning-agent: sidebar provider');
|
|
4353
|
+
})
|
|
4354
|
+
: null;
|
|
4355
|
+
const disconnectFixture = typeof ctx.inject !== 'function' ? connectProvider(ctx.betterSidebar) : () => {};
|
|
4356
|
+
const controller = {
|
|
4357
|
+
storeFor: instanceFor,
|
|
4358
|
+
actionsFor: sessionId => instanceFor(sessionId).actions,
|
|
4359
|
+
open(sessionId, step) {
|
|
4360
|
+
if (disposed || !sessionId) return false;
|
|
4361
|
+
const instance = instanceFor(sessionId);
|
|
4362
|
+
instance.actions.openAt(step, sessionId);
|
|
4363
|
+
const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
|
|
4364
|
+
const summary = sessions?.list?.getSnapshot?.()?.byId?.[sessionId];
|
|
4365
|
+
openingSession = sessionId;
|
|
4366
|
+
try { return adapter.open({ sessionId, ...(summary?.cwd ? { cwd: summary.cwd } : {}) },
|
|
4367
|
+
step || instance.getSnapshot().step); }
|
|
4368
|
+
finally { openingSession = null; }
|
|
4369
|
+
},
|
|
4370
|
+
};
|
|
4371
|
+
sessionWorkbenchController = controller;
|
|
4372
|
+
const listeners = {
|
|
4373
|
+
[WORKBENCH_OPEN_EVENT]: event => {
|
|
4374
|
+
const detail = event.detail || {};
|
|
4375
|
+
const actions = controller.actionsFor(detail.sessionId);
|
|
4376
|
+
if (detail.task?.id) cacheWorkflowTask(actions, detail.sessionId, detail.task);
|
|
4377
|
+
controller.open(detail.sessionId, detail.step);
|
|
4378
|
+
},
|
|
4379
|
+
[WORKBENCH_DATASET_EVENT]: event => {
|
|
4380
|
+
const { sessionId, result, source } = event.detail;
|
|
4381
|
+
const actions = controller.actionsFor(sessionId);
|
|
4382
|
+
if (!result) return;
|
|
4383
|
+
applyParsed(result, actions, `session:${sessionId}`, source);
|
|
4384
|
+
void queueWorkflowOperation(sessionId, () => persistParsedWorkflow(actions, sessionId, result, source))
|
|
4385
|
+
.catch(error => { if (!disposed) actions.setError(error.message || String(error)); });
|
|
4386
|
+
},
|
|
4387
|
+
[WORKBENCH_DRAFT_EVENT]: event => {
|
|
4388
|
+
const { sessionId, draft = {} } = event.detail;
|
|
4389
|
+
const actions = controller.actionsFor(sessionId);
|
|
4390
|
+
if (draft.title) actions.setTaskTitle(draft.title);
|
|
4391
|
+
if (draft.objectives) actions.setObjectives(draft.objectives);
|
|
4392
|
+
if (draft.mappings) actions.setMappings(draft.mappings);
|
|
4393
|
+
if (draft.fieldSelection) actions.setFieldSelection(draft.fieldSelection);
|
|
4394
|
+
for (const [key, value] of Object.entries(draft.matchRules || {})) actions.setMatchRule(key, value);
|
|
4395
|
+
void queueWorkflowOperation(sessionId, async () => {
|
|
4396
|
+
const task = await ensureEditableWorkflowTask(actions, sessionId, draft);
|
|
4397
|
+
return updateWorkflowTask(actions, sessionId, task, draft);
|
|
4398
|
+
}).catch(error => { if (!disposed) actions.setError(error.message || String(error)); });
|
|
4399
|
+
},
|
|
4400
|
+
[WORKBENCH_PREPARE_EVENT]: event => {
|
|
4401
|
+
const { sessionId, config, resolve, reject } = event.detail;
|
|
4402
|
+
const actions = controller.actionsFor(sessionId);
|
|
4403
|
+
queueWorkflowOperation(sessionId, () => stageConfirmedWorkflow(actions, sessionId, config))
|
|
4404
|
+
.then(command => { if (!disposed) watchHostedCommand(actions, sessionId, command); resolve(command); }, reject);
|
|
4405
|
+
},
|
|
4406
|
+
};
|
|
4407
|
+
const bindings = Object.entries(listeners).map(([name, handler]) => {
|
|
4408
|
+
const listener = event => {
|
|
4409
|
+
if (disposed || event.defaultPrevented || !event.detail?.sessionId) return;
|
|
4410
|
+
event.preventDefault();
|
|
4411
|
+
handler(event);
|
|
4412
|
+
};
|
|
4413
|
+
host.addEventListener?.(name, listener);
|
|
4414
|
+
return () => host.removeEventListener?.(name, listener);
|
|
4415
|
+
});
|
|
4416
|
+
const shared = { controller, references: 1, release() {
|
|
4417
|
+
if (--shared.references > 0) return;
|
|
4418
|
+
disposed = true;
|
|
4419
|
+
commandWatchGeneration += 1;
|
|
4420
|
+
for (const remove of bindings) remove();
|
|
4421
|
+
adapter.dispose();
|
|
4422
|
+
disconnectFixture();
|
|
4423
|
+
void providerFiber?.dispose();
|
|
4424
|
+
instances.clear();
|
|
4425
|
+
if (host[CONTROLLER_KEY] === shared) delete host[CONTROLLER_KEY];
|
|
4426
|
+
if (sessionWorkbenchController === controller) sessionWorkbenchController = null;
|
|
4427
|
+
} };
|
|
4428
|
+
host[CONTROLLER_KEY] = shared;
|
|
4429
|
+
let released = false;
|
|
4430
|
+
return () => { if (!released) { released = true; shared.release(); } };
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4625
4433
|
function apply(ctx) {
|
|
4626
4434
|
// eslint-disable-next-line no-console
|
|
4627
4435
|
console.log('[dc-agent] client apply() ran');
|
|
@@ -4633,7 +4441,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4633
4441
|
promptSlot: 'conversation.input.overlay',
|
|
4634
4442
|
heroTitleBridge: 'exact-text/reversible',
|
|
4635
4443
|
headerSlot: 'conversation.session.header.actions',
|
|
4636
|
-
|
|
4444
|
+
workbench: 'better-sidebar:session-singleton',
|
|
4637
4445
|
error: null,
|
|
4638
4446
|
};
|
|
4639
4447
|
window.__DC_MVP__ = state;
|
|
@@ -4642,17 +4450,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4642
4450
|
ctx.effect(() => installUiStyles(), 'data-cleaning-agent: UI styles');
|
|
4643
4451
|
ctx.effect(() => installSessionOwnershipBridge(ctx), 'data-cleaning-agent: session ownership');
|
|
4644
4452
|
|
|
4645
|
-
|
|
4646
|
-
ctx.slots.inject('shell.overlay', () => ctx.slots.register({
|
|
4647
|
-
name: 'shell.overlay',
|
|
4648
|
-
id: 'data-cleaning-agent',
|
|
4649
|
-
order: 200,
|
|
4650
|
-
store: workbenchStore,
|
|
4651
|
-
inject: () => ({
|
|
4652
|
-
sendSessionCommand: (sessionId, prompt) => sendQccAgentCommand(ctx, sessionId, prompt),
|
|
4653
|
-
setSessionDraft: (sessionId, prompt) => setQccAgentDraft(ctx, sessionId, prompt),
|
|
4654
|
-
}),
|
|
4655
|
-
}, WorkbenchDrawer));
|
|
4453
|
+
ctx.effect(() => installSessionWorkbench(ctx), 'data-cleaning-agent: Session workbench');
|
|
4656
4454
|
|
|
4657
4455
|
// 左栏:footer 只托管生命周期和 Portal 降级;实际入口显示在工作区列表前。
|
|
4658
4456
|
ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({
|
|
@@ -4715,7 +4513,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4715
4513
|
exports.inject = inject;
|
|
4716
4514
|
// 测试用纯函数,不构成 Host / DSH 稳定 API。
|
|
4717
4515
|
exports.__testing = {
|
|
4718
|
-
|
|
4516
|
+
createWorkbenchStore,
|
|
4517
|
+
WorkbenchContent,
|
|
4518
|
+
installSessionWorkbench,
|
|
4719
4519
|
DatabaseLogo,
|
|
4720
4520
|
visibleCatalogFields,
|
|
4721
4521
|
installCapabilityMount,
|