dsh-data-cleaning-agent 0.9.12 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.en.md +2 -2
- package/README.md +3 -3
- package/docs/RELEASE-0.9.13.md +9 -0
- package/docs/RELEASE-0.9.14.md +12 -0
- package/docs/RELEASE-0.9.15.md +13 -0
- package/lib/client.js +1097 -208
- package/lib/image-workflow.js +7 -1
- package/lib/qcc-command.js +8 -0
- package/lib/qcc-runs.js +9 -0
- package/lib/web.js +82 -42
- package/lib/workflow-contract.js +125 -7
- package/lib/workflow-execution.js +121 -20
- package/lib/workflow.js +170 -14
- package/package.json +4 -2
- package/docs/RELEASE-0.9.12.md +0 -9
package/lib/client.js
CHANGED
|
@@ -473,6 +473,15 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
473
473
|
}
|
|
474
474
|
.dcAgentStep:last-child { border-right: 0; }
|
|
475
475
|
.dcAgentStep:hover { background: var(--dc-page); }
|
|
476
|
+
.dcAgentStep[data-access="locked"] {
|
|
477
|
+
color: light-dark(#8491a1, #8291a0);
|
|
478
|
+
cursor: not-allowed;
|
|
479
|
+
}
|
|
480
|
+
.dcAgentStep[data-access="locked"]:hover,
|
|
481
|
+
.dcAgentStep[data-access="locked"]:focus-visible {
|
|
482
|
+
background: var(--dc-page);
|
|
483
|
+
color: var(--dc-muted);
|
|
484
|
+
}
|
|
476
485
|
.dcAgentStep.is-active {
|
|
477
486
|
color: var(--dc-action);
|
|
478
487
|
background: var(--dc-selected);
|
|
@@ -488,6 +497,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
488
497
|
background: var(--dc-action);
|
|
489
498
|
}
|
|
490
499
|
.dcAgentStepIcon {
|
|
500
|
+
position: relative;
|
|
491
501
|
display: grid;
|
|
492
502
|
place-items: center;
|
|
493
503
|
width: 30px;
|
|
@@ -497,6 +507,30 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
497
507
|
border-radius: 8px;
|
|
498
508
|
}
|
|
499
509
|
.dcAgentStepIcon svg { display: block; }
|
|
510
|
+
.dcAgentStep[data-access="locked"] .dcAgentStepIcon::before {
|
|
511
|
+
content: '';
|
|
512
|
+
position: absolute;
|
|
513
|
+
right: -4px;
|
|
514
|
+
bottom: -3px;
|
|
515
|
+
box-sizing: border-box;
|
|
516
|
+
width: 11px;
|
|
517
|
+
height: 9px;
|
|
518
|
+
border: 1px solid var(--dc-border);
|
|
519
|
+
border-radius: 2px;
|
|
520
|
+
background: var(--dc-surface);
|
|
521
|
+
}
|
|
522
|
+
.dcAgentStep[data-access="locked"] .dcAgentStepIcon::after {
|
|
523
|
+
content: '';
|
|
524
|
+
position: absolute;
|
|
525
|
+
right: -2px;
|
|
526
|
+
bottom: 5px;
|
|
527
|
+
box-sizing: border-box;
|
|
528
|
+
width: 7px;
|
|
529
|
+
height: 6px;
|
|
530
|
+
border: 1px solid var(--dc-muted);
|
|
531
|
+
border-bottom: 0;
|
|
532
|
+
border-radius: 5px 5px 0 0;
|
|
533
|
+
}
|
|
500
534
|
.dcAgentStepLabel {
|
|
501
535
|
display: block;
|
|
502
536
|
width: 100%;
|
|
@@ -611,6 +645,34 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
611
645
|
color: light-dark(#d2454f, #ff838b);
|
|
612
646
|
font-size: 12px;
|
|
613
647
|
}
|
|
648
|
+
.dcAgentNavigationNotice {
|
|
649
|
+
position: relative;
|
|
650
|
+
flex: none;
|
|
651
|
+
padding: 9px 12px 9px 34px;
|
|
652
|
+
border: 1px solid light-dark(#b8dbef, #315a72);
|
|
653
|
+
border-radius: 6px;
|
|
654
|
+
background: var(--dc-table-head);
|
|
655
|
+
color: var(--dc-muted);
|
|
656
|
+
font-size: 12px;
|
|
657
|
+
line-height: 18px;
|
|
658
|
+
}
|
|
659
|
+
.dcAgentNavigationNotice::before {
|
|
660
|
+
content: 'i';
|
|
661
|
+
position: absolute;
|
|
662
|
+
top: 10px;
|
|
663
|
+
left: 12px;
|
|
664
|
+
box-sizing: border-box;
|
|
665
|
+
width: 14px;
|
|
666
|
+
height: 14px;
|
|
667
|
+
display: grid;
|
|
668
|
+
place-items: center;
|
|
669
|
+
border: 1px solid light-dark(#8fc4e4, #4f829f);
|
|
670
|
+
border-radius: 50%;
|
|
671
|
+
color: var(--dc-action);
|
|
672
|
+
font-size: 9px;
|
|
673
|
+
font-weight: 700;
|
|
674
|
+
line-height: 1;
|
|
675
|
+
}
|
|
614
676
|
|
|
615
677
|
.dcAgentChips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
616
678
|
.dcAgentChip {
|
|
@@ -1133,41 +1195,288 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1133
1195
|
.dcAgentTable th, .dcAgentTable td { height: 44px; box-sizing: border-box; font-size: 13px; }
|
|
1134
1196
|
.dcAgentTable tbody tr:hover { background: var(--dc-selected); }
|
|
1135
1197
|
.dcAgentSection, .dcAgentCard { background: var(--dc-surface); }
|
|
1136
|
-
.
|
|
1198
|
+
.dcAgentTaskContext {
|
|
1199
|
+
flex: none;
|
|
1200
|
+
margin: -18px -18px 0;
|
|
1201
|
+
border-bottom: 1px solid var(--dc-border);
|
|
1202
|
+
background: var(--dc-surface);
|
|
1203
|
+
}
|
|
1204
|
+
.dcAgentTaskStatus {
|
|
1205
|
+
position: relative;
|
|
1206
|
+
padding: 11px 18px 10px;
|
|
1207
|
+
border-bottom: 1px solid var(--dc-border);
|
|
1208
|
+
background: var(--dc-table-head);
|
|
1209
|
+
}
|
|
1210
|
+
.dcAgentTaskStatusRow {
|
|
1211
|
+
min-height: 46px;
|
|
1137
1212
|
display: grid;
|
|
1138
|
-
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
1213
|
+
grid-template-columns: minmax(0, 1fr) auto auto auto;
|
|
1139
1214
|
align-items: center;
|
|
1140
|
-
gap:
|
|
1141
|
-
|
|
1215
|
+
gap: 10px;
|
|
1216
|
+
color: var(--dc-muted);
|
|
1217
|
+
font-size: 12px;
|
|
1142
1218
|
}
|
|
1143
|
-
.
|
|
1144
|
-
|
|
1145
|
-
grid-row: 1;
|
|
1219
|
+
.dcAgentTaskStage,
|
|
1220
|
+
.dcAgentTaskCurrent {
|
|
1146
1221
|
min-width: 0;
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1222
|
+
display: flex;
|
|
1223
|
+
align-items: center;
|
|
1224
|
+
gap: 7px;
|
|
1150
1225
|
}
|
|
1151
|
-
.
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1226
|
+
.dcAgentTaskDoneMark {
|
|
1227
|
+
box-sizing: border-box;
|
|
1228
|
+
width: 17px;
|
|
1229
|
+
height: 17px;
|
|
1230
|
+
display: grid;
|
|
1231
|
+
place-items: center;
|
|
1232
|
+
flex: none;
|
|
1233
|
+
border: 1px solid light-dark(#91cdb2, #3f7861);
|
|
1234
|
+
border-radius: 50%;
|
|
1235
|
+
color: light-dark(#16845b, #5ed29f);
|
|
1236
|
+
font-size: 11px;
|
|
1237
|
+
line-height: 1;
|
|
1238
|
+
}
|
|
1239
|
+
.dcAgentTaskDoneMark[data-complete="false"] {
|
|
1240
|
+
border-color: light-dark(#9bcceb, #477894);
|
|
1241
|
+
background: var(--dc-selected);
|
|
1242
|
+
color: var(--dc-action);
|
|
1243
|
+
}
|
|
1244
|
+
.dcAgentTaskStageCopy { min-width: 0; }
|
|
1245
|
+
.dcAgentTaskStageTitle {
|
|
1246
|
+
display: flex;
|
|
1247
|
+
align-items: center;
|
|
1248
|
+
gap: 7px;
|
|
1249
|
+
min-width: 0;
|
|
1250
|
+
}
|
|
1251
|
+
.dcAgentTaskStageTitle strong {
|
|
1252
|
+
overflow: hidden;
|
|
1253
|
+
color: var(--dc-text);
|
|
1254
|
+
font-size: 13px;
|
|
1255
|
+
font-weight: 650;
|
|
1256
|
+
text-overflow: ellipsis;
|
|
1257
|
+
white-space: nowrap;
|
|
1258
|
+
}
|
|
1259
|
+
.dcAgentTaskState {
|
|
1260
|
+
flex: none;
|
|
1261
|
+
padding: 1px 6px;
|
|
1262
|
+
border: 1px solid light-dark(#b9ddcc, #37634f);
|
|
1156
1263
|
border-radius: 4px;
|
|
1157
|
-
background:
|
|
1264
|
+
background: light-dark(#eaf7f1, #173529);
|
|
1265
|
+
color: light-dark(#16845b, #5ed29f);
|
|
1266
|
+
font-size: 10px;
|
|
1267
|
+
font-weight: 650;
|
|
1268
|
+
white-space: nowrap;
|
|
1269
|
+
}
|
|
1270
|
+
.dcAgentTaskState[data-complete="false"] {
|
|
1271
|
+
border-color: light-dark(#b5d8ef, #426c83);
|
|
1272
|
+
background: var(--dc-selected);
|
|
1273
|
+
color: var(--dc-action);
|
|
1274
|
+
}
|
|
1275
|
+
.dcAgentTaskMeta {
|
|
1276
|
+
display: flex;
|
|
1277
|
+
min-width: 0;
|
|
1278
|
+
margin-top: 2px;
|
|
1279
|
+
overflow: hidden;
|
|
1158
1280
|
color: var(--dc-muted);
|
|
1159
|
-
font-size:
|
|
1160
|
-
|
|
1281
|
+
font-size: 11px;
|
|
1282
|
+
line-height: 16px;
|
|
1161
1283
|
white-space: nowrap;
|
|
1162
1284
|
}
|
|
1163
|
-
.
|
|
1164
|
-
|
|
1165
|
-
|
|
1285
|
+
.dcAgentTaskMeta > span {
|
|
1286
|
+
overflow: hidden;
|
|
1287
|
+
text-overflow: ellipsis;
|
|
1288
|
+
}
|
|
1289
|
+
.dcAgentTaskMeta > span + span::before {
|
|
1290
|
+
content: "·";
|
|
1291
|
+
margin: 0 6px;
|
|
1292
|
+
color: var(--dc-border);
|
|
1293
|
+
}
|
|
1294
|
+
.dcAgentTaskCurrent {
|
|
1295
|
+
padding-left: 10px;
|
|
1296
|
+
border-left: 1px solid var(--dc-border);
|
|
1297
|
+
white-space: nowrap;
|
|
1298
|
+
}
|
|
1299
|
+
.dcAgentTaskCurrent span { color: var(--dc-muted); font-size: 11px; }
|
|
1300
|
+
.dcAgentTaskCurrent strong { color: var(--dc-action); font-size: 12px; }
|
|
1301
|
+
.dcAgentTaskReturn {
|
|
1302
|
+
min-height: 28px;
|
|
1303
|
+
padding: 3px 7px;
|
|
1304
|
+
border: 0;
|
|
1305
|
+
border-radius: 5px;
|
|
1306
|
+
background: transparent;
|
|
1307
|
+
color: var(--dc-action);
|
|
1308
|
+
cursor: pointer;
|
|
1309
|
+
white-space: nowrap;
|
|
1310
|
+
}
|
|
1311
|
+
.dcAgentTaskReturn:hover { background: var(--dc-selected); }
|
|
1312
|
+
.dcAgentTaskDetails { position: relative; }
|
|
1313
|
+
.dcAgentTaskDetails > summary {
|
|
1314
|
+
box-sizing: border-box;
|
|
1315
|
+
width: 28px;
|
|
1316
|
+
height: 28px;
|
|
1317
|
+
display: grid;
|
|
1318
|
+
place-items: center;
|
|
1319
|
+
border: 1px solid transparent;
|
|
1320
|
+
border-radius: 6px;
|
|
1321
|
+
color: var(--dc-muted);
|
|
1322
|
+
cursor: pointer;
|
|
1323
|
+
font-size: 12px;
|
|
1324
|
+
font-weight: 700;
|
|
1325
|
+
list-style: none;
|
|
1326
|
+
}
|
|
1327
|
+
.dcAgentTaskDetails > summary::-webkit-details-marker { display: none; }
|
|
1328
|
+
.dcAgentTaskDetails > summary:hover {
|
|
1329
|
+
border-color: var(--dc-border);
|
|
1330
|
+
background: var(--dc-surface);
|
|
1331
|
+
color: var(--dc-action);
|
|
1332
|
+
}
|
|
1333
|
+
.dcAgentTaskDetailsBody {
|
|
1334
|
+
position: absolute;
|
|
1335
|
+
z-index: 6;
|
|
1336
|
+
top: 32px;
|
|
1337
|
+
right: 0;
|
|
1338
|
+
width: min(310px, calc(100vw - 48px));
|
|
1339
|
+
padding: 12px;
|
|
1340
|
+
border: 1px solid var(--dc-border);
|
|
1341
|
+
border-radius: 8px;
|
|
1342
|
+
background: var(--dc-surface);
|
|
1343
|
+
box-shadow: 0 12px 30px light-dark(rgb(26 74 102 / 10%), rgb(0 0 0 / 34%));
|
|
1344
|
+
color: var(--dc-muted);
|
|
1345
|
+
font-size: 12px;
|
|
1346
|
+
}
|
|
1347
|
+
.dcAgentTaskDetailsBody p { margin: 4px 0; overflow-wrap: anywhere; }
|
|
1348
|
+
.dcAgentTaskSyncNote {
|
|
1349
|
+
margin: 6px 0 0 24px;
|
|
1350
|
+
color: var(--dc-muted);
|
|
1351
|
+
font-size: 11px;
|
|
1352
|
+
}
|
|
1353
|
+
.dcAgentTaskExecution {
|
|
1354
|
+
display: grid;
|
|
1355
|
+
grid-template-columns: auto minmax(100px, 1fr);
|
|
1356
|
+
align-items: center;
|
|
1357
|
+
gap: 8px;
|
|
1358
|
+
margin-top: 6px;
|
|
1359
|
+
color: var(--dc-muted);
|
|
1360
|
+
font-size: 11px;
|
|
1361
|
+
}
|
|
1362
|
+
.dcAgentTaskExecution progress { width: 100%; height: 6px; accent-color: var(--dc-action); }
|
|
1363
|
+
.dcAgentTaskMetrics {
|
|
1364
|
+
display: flex;
|
|
1365
|
+
align-items: center;
|
|
1366
|
+
gap: 16px;
|
|
1367
|
+
margin-top: 7px;
|
|
1368
|
+
padding-top: 8px;
|
|
1369
|
+
border-top: 1px solid var(--dc-border);
|
|
1370
|
+
color: var(--dc-muted);
|
|
1371
|
+
font-size: 11px;
|
|
1372
|
+
}
|
|
1373
|
+
.dcAgentTaskMetricGroup {
|
|
1374
|
+
min-width: 0;
|
|
1375
|
+
display: flex;
|
|
1376
|
+
align-items: center;
|
|
1377
|
+
gap: 10px;
|
|
1378
|
+
}
|
|
1379
|
+
.dcAgentTaskMetricGroup + .dcAgentTaskMetricGroup {
|
|
1380
|
+
flex: 1;
|
|
1381
|
+
padding-left: 16px;
|
|
1382
|
+
border-left: 1px solid var(--dc-border);
|
|
1383
|
+
}
|
|
1384
|
+
.dcAgentTaskMetricLabel { flex: none; color: var(--dc-muted); font-weight: 600; }
|
|
1385
|
+
.dcAgentTaskMetric { color: var(--dc-text); white-space: nowrap; }
|
|
1386
|
+
.dcAgentTaskMetric b { margin-left: 3px; font-size: 12px; font-weight: 650; }
|
|
1387
|
+
.dcAgentTaskMetric.is-good,
|
|
1388
|
+
.dcAgentTaskMetric.is-good b { color: light-dark(#16845b, #5ed29f); }
|
|
1389
|
+
.dcAgentWorkflowGuide { background: var(--dc-surface); }
|
|
1390
|
+
.dcAgentWorkflowGuide > summary {
|
|
1391
|
+
box-sizing: border-box;
|
|
1392
|
+
min-height: 40px;
|
|
1393
|
+
display: flex;
|
|
1394
|
+
align-items: center;
|
|
1395
|
+
gap: 7px;
|
|
1396
|
+
padding: 8px 18px;
|
|
1166
1397
|
color: var(--dc-muted);
|
|
1167
1398
|
font-size: 12px;
|
|
1399
|
+
cursor: pointer;
|
|
1400
|
+
list-style: none;
|
|
1401
|
+
}
|
|
1402
|
+
.dcAgentWorkflowGuide > summary::-webkit-details-marker { display: none; }
|
|
1403
|
+
.dcAgentWorkflowGuide > summary:hover {
|
|
1404
|
+
color: var(--dc-action);
|
|
1405
|
+
background: var(--dc-table-head);
|
|
1406
|
+
}
|
|
1407
|
+
.dcAgentWorkflowGuide > summary::before {
|
|
1408
|
+
content: "?";
|
|
1409
|
+
width: 16px;
|
|
1410
|
+
height: 16px;
|
|
1411
|
+
display: grid;
|
|
1412
|
+
place-items: center;
|
|
1413
|
+
border: 1px solid var(--dc-border);
|
|
1414
|
+
border-radius: 50%;
|
|
1415
|
+
font-size: 10px;
|
|
1416
|
+
}
|
|
1417
|
+
.dcAgentWorkflowGuide > summary::after {
|
|
1418
|
+
content: "›";
|
|
1419
|
+
margin-left: auto;
|
|
1420
|
+
font-size: 17px;
|
|
1421
|
+
transition: transform 120ms ease;
|
|
1422
|
+
}
|
|
1423
|
+
.dcAgentWorkflowGuide[open] > summary::after { transform: rotate(90deg); }
|
|
1424
|
+
.dcAgentWorkflowGuideContent { padding: 0 18px 16px; }
|
|
1425
|
+
.dcAgentWorkflowGuideIntro {
|
|
1426
|
+
margin: 0 0 5px;
|
|
1427
|
+
color: var(--dc-muted);
|
|
1428
|
+
font-size: 12px;
|
|
1429
|
+
line-height: 18px;
|
|
1430
|
+
}
|
|
1431
|
+
.dcAgentWorkflowGuideList {
|
|
1432
|
+
display: grid;
|
|
1433
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1434
|
+
gap: 0 24px;
|
|
1435
|
+
margin-top: 11px;
|
|
1436
|
+
}
|
|
1437
|
+
.dcAgentWorkflowGuideStep {
|
|
1438
|
+
min-width: 0;
|
|
1439
|
+
display: grid;
|
|
1440
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1441
|
+
gap: 9px;
|
|
1442
|
+
padding: 10px 0;
|
|
1443
|
+
border-top: 1px solid var(--dc-border);
|
|
1444
|
+
}
|
|
1445
|
+
.dcAgentWorkflowGuideStep:last-child:nth-child(odd) { grid-column: 1 / -1; }
|
|
1446
|
+
.dcAgentWorkflowGuideIndex {
|
|
1447
|
+
box-sizing: border-box;
|
|
1448
|
+
width: 22px;
|
|
1449
|
+
height: 22px;
|
|
1450
|
+
display: grid;
|
|
1451
|
+
place-items: center;
|
|
1452
|
+
border: 1px solid var(--dc-border);
|
|
1453
|
+
border-radius: 50%;
|
|
1454
|
+
background: var(--dc-page);
|
|
1455
|
+
color: var(--dc-action);
|
|
1456
|
+
font-size: 10px;
|
|
1457
|
+
font-weight: 700;
|
|
1458
|
+
}
|
|
1459
|
+
.dcAgentWorkflowGuideStep strong {
|
|
1460
|
+
display: block;
|
|
1461
|
+
margin-bottom: 2px;
|
|
1462
|
+
color: var(--dc-text);
|
|
1463
|
+
font-size: 12px;
|
|
1464
|
+
}
|
|
1465
|
+
.dcAgentWorkflowGuideStep p {
|
|
1466
|
+
margin: 0;
|
|
1467
|
+
color: var(--dc-muted);
|
|
1468
|
+
font-size: 11px;
|
|
1469
|
+
line-height: 17px;
|
|
1470
|
+
}
|
|
1471
|
+
.dcAgentWorkflowGuideBoundary {
|
|
1472
|
+
margin-top: 8px;
|
|
1473
|
+
padding: 9px 10px;
|
|
1474
|
+
border-left: 2px solid var(--dc-action);
|
|
1475
|
+
background: var(--dc-table-head);
|
|
1476
|
+
color: var(--dc-muted);
|
|
1477
|
+
font-size: 11px;
|
|
1478
|
+
line-height: 17px;
|
|
1168
1479
|
}
|
|
1169
|
-
.dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > :is(p, progress) { grid-column: 1 / -1; }
|
|
1170
|
-
.dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > p { margin: 0; }
|
|
1171
1480
|
.dcAgentWbBody > .dcAgentSection[role="status"]:not([aria-label]) {
|
|
1172
1481
|
min-height: 156px;
|
|
1173
1482
|
display: flex;
|
|
@@ -1222,10 +1531,32 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1222
1531
|
-webkit-box-orient: vertical;
|
|
1223
1532
|
-webkit-line-clamp: 2;
|
|
1224
1533
|
}
|
|
1225
|
-
.
|
|
1226
|
-
.
|
|
1227
|
-
.
|
|
1228
|
-
.
|
|
1534
|
+
.dcAgentTaskStatus { padding-inline: 10px; }
|
|
1535
|
+
.dcAgentTaskStatusRow { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 8px; }
|
|
1536
|
+
.dcAgentTaskStage { grid-column: 1; grid-row: 1; }
|
|
1537
|
+
.dcAgentTaskCurrent {
|
|
1538
|
+
grid-column: 1;
|
|
1539
|
+
grid-row: 2;
|
|
1540
|
+
justify-self: start;
|
|
1541
|
+
margin-left: 24px;
|
|
1542
|
+
padding-left: 0;
|
|
1543
|
+
border-left: 0;
|
|
1544
|
+
}
|
|
1545
|
+
.dcAgentTaskReturn { grid-column: 2; grid-row: 2; }
|
|
1546
|
+
.dcAgentTaskDetails { grid-column: 2; grid-row: 1; justify-self: end; }
|
|
1547
|
+
.dcAgentTaskMetrics { align-items: flex-start; flex-direction: column; gap: 5px; }
|
|
1548
|
+
.dcAgentTaskMetricGroup { width: 100%; gap: 8px; }
|
|
1549
|
+
.dcAgentTaskMetricGroup + .dcAgentTaskMetricGroup {
|
|
1550
|
+
padding-top: 5px;
|
|
1551
|
+
padding-left: 0;
|
|
1552
|
+
border-top: 1px solid var(--dc-border);
|
|
1553
|
+
border-left: 0;
|
|
1554
|
+
}
|
|
1555
|
+
.dcAgentTaskMetricLabel { min-width: 48px; }
|
|
1556
|
+
.dcAgentWorkflowGuide > summary { padding-inline: 10px; }
|
|
1557
|
+
.dcAgentWorkflowGuideContent { padding-inline: 10px; }
|
|
1558
|
+
.dcAgentWorkflowGuideList { grid-template-columns: 1fr; }
|
|
1559
|
+
.dcAgentWorkflowGuideStep:last-child:nth-child(odd) { grid-column: auto; }
|
|
1229
1560
|
.dcAgentDatasetHeader { display: grid; }
|
|
1230
1561
|
.dcAgentDatasetHeader .dcAgentHint { text-align: left; }
|
|
1231
1562
|
.dcAgentDatasetFooter { grid-template-columns: 1fr; }
|
|
@@ -1316,6 +1647,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1316
1647
|
{ key: 'download', label: '结果下载', icon: 'table' },
|
|
1317
1648
|
];
|
|
1318
1649
|
|
|
1650
|
+
const WORKFLOW_GUIDE_STEPS = [
|
|
1651
|
+
['导入与核验', '导入文本、Excel、JSON 或图片名单,并核对完整原始数据。'],
|
|
1652
|
+
['规则与体检', '确认字段映射、匹配规则和补全范围;质量体检在本地完成。'],
|
|
1653
|
+
['主体匹配', '优先使用统一社会信用代码等强标识;名称不明确时进入人工候选核验。'],
|
|
1654
|
+
['字段补全', '只补空值并保留非空原值;外部字段以企查查真实返回为准。'],
|
|
1655
|
+
['结果下载', 'Host 保存结果、异常清单与任务报告,可在任务历史中继续下载。'],
|
|
1656
|
+
];
|
|
1657
|
+
|
|
1319
1658
|
const CAPABILITIES = [
|
|
1320
1659
|
{ key: 'upload', label: '导入名单', icon: IconPaperclipOutline16, fallback: '+' },
|
|
1321
1660
|
{ key: 'profile', label: '质量体检', icon: IconChecklistOutline14, fallback: '✓' },
|
|
@@ -1952,14 +2291,18 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1952
2291
|
if (!task?.originSessionId) return '历史版本未保存来源';
|
|
1953
2292
|
return `工作区:${task.originWorkspaceName || task.originWorkspaceId || '未取得来源工作区'} · 会话:${task.originSessionName || task.originSessionId}`;
|
|
1954
2293
|
}
|
|
1955
|
-
function cacheWorkflowTask(actions, sessionId, task) {
|
|
2294
|
+
function cacheWorkflowTask(actions, sessionId, task, reason = 'update') {
|
|
1956
2295
|
if (!task) return null;
|
|
1957
2296
|
if (!taskBelongsToSession(task, sessionId)) throw new Error('此任务不属于当前会话;请在任务历史中只读查看或打开来源会话。');
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
|
|
2297
|
+
const key = String(sessionId || 'unassigned');
|
|
2298
|
+
const current = workflowTaskBySession.get(key);
|
|
2299
|
+
const explicitSwitch = ['create', 'restore'].includes(reason);
|
|
2300
|
+
if (current && current.id !== task.id && !explicitSwitch) return current;
|
|
2301
|
+
if (current?.id === task.id && Number(current.revision ?? -1) > Number(task.revision ?? -1)) return current;
|
|
2302
|
+
workflowTaskBySession.set(key, task);
|
|
1961
2303
|
bindTask(sessionId, task.id);
|
|
1962
|
-
actions.
|
|
2304
|
+
if (typeof actions.acceptWorkflowTask === 'function') actions.acceptWorkflowTask(task, reason);
|
|
2305
|
+
else actions.setWorkflowTask(task);
|
|
1963
2306
|
if (task.title) actions.setTaskTitle(task.title);
|
|
1964
2307
|
if (Array.isArray(task.objectives)) actions.setObjectives(task.objectives);
|
|
1965
2308
|
if (Array.isArray(task.mappings)) actions.setMappings(task.mappings);
|
|
@@ -1991,7 +2334,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1991
2334
|
}
|
|
1992
2335
|
const response = await pending;
|
|
1993
2336
|
moveRuntime(`session:${key}`, response.task.id);
|
|
1994
|
-
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2337
|
+
return cacheWorkflowTask(actions, sessionId, response.task, 'create');
|
|
1995
2338
|
}
|
|
1996
2339
|
|
|
1997
2340
|
async function ensureWorkflowTask(actions, sessionId, draft = {}, restoreStage = true) {
|
|
@@ -2002,9 +2345,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2002
2345
|
if (ownedSessionId(key) && /^dcw-[a-zA-Z0-9-]+$/.test(saved || '')) {
|
|
2003
2346
|
const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(saved)}`);
|
|
2004
2347
|
if (taskBelongsToSession(response.task, sessionId)) {
|
|
2005
|
-
|
|
2006
|
-
if (restoreStage) actions.setStep(task.stage || 'upload');
|
|
2007
|
-
return task;
|
|
2348
|
+
return cacheWorkflowTask(actions, sessionId, response.task, restoreStage ? 'restore' : 'update');
|
|
2008
2349
|
}
|
|
2009
2350
|
bindTask(sessionId, null);
|
|
2010
2351
|
}
|
|
@@ -2053,7 +2394,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2053
2394
|
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2054
2395
|
}
|
|
2055
2396
|
|
|
2056
|
-
async function workflowAction(actions, sessionId, task, action, input = {}) {
|
|
2397
|
+
async function workflowAction(actions, sessionId, task, action, input = {}, reason = 'update') {
|
|
2057
2398
|
const latest = workflowTaskBySession.get(String(sessionId || 'unassigned')) ?? task;
|
|
2058
2399
|
if (!latest) throw new Error('请先创建数据清洗补全任务。');
|
|
2059
2400
|
if (!taskBelongsToSession(latest, sessionId)) throw new Error('此任务不属于当前会话。');
|
|
@@ -2062,7 +2403,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2062
2403
|
originSessionId: sessionId, originWorkspaceId: latest.originWorkspaceId,
|
|
2063
2404
|
expectedRevision: latest.revision,
|
|
2064
2405
|
});
|
|
2065
|
-
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2406
|
+
return cacheWorkflowTask(actions, sessionId, response.task, reason);
|
|
2066
2407
|
}
|
|
2067
2408
|
|
|
2068
2409
|
async function stageConfirmedWorkflow(actions, sessionId, expected = null) {
|
|
@@ -2104,7 +2445,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2104
2445
|
return response.command;
|
|
2105
2446
|
}
|
|
2106
2447
|
|
|
2107
|
-
async function refreshHostedTask(actions, sessionId, taskId, run,
|
|
2448
|
+
async function refreshHostedTask(actions, sessionId, taskId, run, reason = 'update') {
|
|
2108
2449
|
const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(taskId)}`);
|
|
2109
2450
|
if (!taskBelongsToSession(response.task, sessionId)) {
|
|
2110
2451
|
if (readTaskBindings()[sessionId] === taskId) bindTask(sessionId, null);
|
|
@@ -2113,22 +2454,20 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2113
2454
|
const selectedTask = workflowTaskBySession.get(String(sessionId));
|
|
2114
2455
|
if (selectedTask && selectedTask.id !== taskId) return response.task;
|
|
2115
2456
|
if (selectedTask && selectedTask.revision > response.task.revision) return selectedTask;
|
|
2116
|
-
workflowTaskBySession.set(String(sessionId), response.task);
|
|
2117
|
-
bindTask(sessionId, taskId);
|
|
2118
2457
|
if (isCleaningSession(sessionId)) {
|
|
2119
2458
|
if (!run && response.task.qccRunId && ['review_required', 'partial'].includes(response.task.state)) {
|
|
2120
2459
|
try { run = await requestJson(`/data-cleaning/api/g5/run/${encodeURIComponent(response.task.qccRunId)}`); }
|
|
2121
2460
|
catch { actions.setError('Host 中的候选或重试数据已失效。已有结果仍可下载;重新查询需新建任务,不会自动重复扣量。'); }
|
|
2122
2461
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2462
|
+
const latestSelectedTask = workflowTaskBySession.get(String(sessionId));
|
|
2463
|
+
if (!isCleaningSession(sessionId) || (latestSelectedTask && latestSelectedTask.id !== taskId)) return response.task;
|
|
2464
|
+
cacheWorkflowTask(actions, sessionId, response.task, reason);
|
|
2125
2465
|
if (run) {
|
|
2126
2466
|
actions.setQccRun(run);
|
|
2127
2467
|
const runtime = runtimeFor(taskId);
|
|
2128
2468
|
runtime.resultRows.qcc = run.rows;
|
|
2129
2469
|
if (!runtime.rows.length) { runtime.rows = run.rows; runtime.headers = response.task.source?.headers || run.headers || []; }
|
|
2130
2470
|
}
|
|
2131
|
-
if (navigate) actions.setStep(response.task.stage || 'upload');
|
|
2132
2471
|
}
|
|
2133
2472
|
return response.task;
|
|
2134
2473
|
}
|
|
@@ -2136,14 +2475,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2136
2475
|
function watchHostedCommand(actions, sessionId, command) {
|
|
2137
2476
|
const generation = commandWatchGeneration;
|
|
2138
2477
|
const isActive = () => generation === commandWatchGeneration;
|
|
2139
|
-
let openedForExecution = false;
|
|
2140
|
-
const ownerVisible = () => typeof document === 'undefined' || [...document.querySelectorAll('.dcAgentExperience')].some(node => node.dataset.sessionId === sessionId);
|
|
2141
2478
|
void waitForQccCommand(command.commandId, 1800, async (status) => {
|
|
2142
2479
|
if (!status || status.state === 'prepared') return;
|
|
2143
|
-
await refreshHostedTask(actions, sessionId, command.taskId, null,
|
|
2144
|
-
if (
|
|
2145
|
-
|
|
2146
|
-
|
|
2480
|
+
await refreshHostedTask(actions, sessionId, command.taskId, null, 'update').catch(() => {});
|
|
2481
|
+
if (isActive() && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) {
|
|
2482
|
+
sessionWorkbenchController?.revealSubmitted(sessionId, command.commandId,
|
|
2483
|
+
workflowTaskBySession.get(String(sessionId))?.stage || 'match');
|
|
2147
2484
|
}
|
|
2148
2485
|
if (isCleaningSession(sessionId) && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) actions.setCommandProgress?.({ taskId: command.taskId, ...status.progress });
|
|
2149
2486
|
}, isActive).then(async (completed) => {
|
|
@@ -2205,6 +2542,32 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2205
2542
|
draft.workflowTask = value ?? null;
|
|
2206
2543
|
if (value) draft.workflowTasks = [value, ...(draft.workflowTasks || []).filter(task => task.id !== value.id)];
|
|
2207
2544
|
},
|
|
2545
|
+
acceptWorkflowTask: (draft, value, reason = 'update') => {
|
|
2546
|
+
if (!value) return;
|
|
2547
|
+
const previous = draft.workflowTask;
|
|
2548
|
+
if (previous?.id === value.id && Number(previous.revision ?? -1) > Number(value.revision ?? -1)) return;
|
|
2549
|
+
const previousStage = normalizeViewStage(draft.step);
|
|
2550
|
+
const followedCurrent = previous?.flow?.flowVersion === SUPPORTED_FLOW_VERSION
|
|
2551
|
+
&& resolveStageAccess(previousStage, previous.flow) === 'current';
|
|
2552
|
+
draft.workflowTask = value;
|
|
2553
|
+
draft.workflowTasks = [value, ...(draft.workflowTasks || []).filter(task => task.id !== value.id)];
|
|
2554
|
+
if (value.flow?.flowVersion !== SUPPORTED_FLOW_VERSION) {
|
|
2555
|
+
draft.step = value.stage || 'upload';
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (draft.step === 'history' && reason !== 'restore') return;
|
|
2559
|
+
if (reason === 'import' && previousStage === 'upload'
|
|
2560
|
+
&& resolveStageAccess('upload', value.flow) === 'read'
|
|
2561
|
+
&& value.flow.currentStage === 'rules') {
|
|
2562
|
+
draft.step = 'upload';
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
if (!previous || previous.id !== value.id || ['create', 'restore'].includes(reason) || followedCurrent) {
|
|
2566
|
+
draft.step = value.flow.currentStage;
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
if (resolveStageAccess(previousStage, value.flow) === 'locked') draft.step = value.flow.currentStage;
|
|
2570
|
+
},
|
|
2208
2571
|
setWorkflowTasks: (draft, value) => {
|
|
2209
2572
|
const cached = new Map((draft.workflowTasks || []).map(task => [task.id, task]));
|
|
2210
2573
|
draft.workflowTasks = (Array.isArray(value) ? value : []).map(task =>
|
|
@@ -2438,7 +2801,73 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2438
2801
|
if (!sessionConversation || typeof sessionConversation.send !== 'function') {
|
|
2439
2802
|
throw new Error('当前 DSH 版本没有可用的 Session conversation.send 能力');
|
|
2440
2803
|
}
|
|
2441
|
-
await sessionConversation.send(prompt);
|
|
2804
|
+
const result = await sessionConversation.send(prompt);
|
|
2805
|
+
if (result !== false) sessionWorkbenchController?.revealSubmitted(sessionId, submittedCommandId(prompt));
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
function submittedCommandId(prompt) {
|
|
2809
|
+
return String(prompt || '').match(/\bdc[qi]-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i)?.[0] || null;
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
/** Observe the public admission settlement; drafts and failed sends never reveal a Tab. */
|
|
2813
|
+
function installSubmittedWorkbenchBridge(ctx, controller) {
|
|
2814
|
+
const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
|
|
2815
|
+
let stopSession = () => {};
|
|
2816
|
+
let current = null;
|
|
2817
|
+
let currentFace = null;
|
|
2818
|
+
const connect = () => {
|
|
2819
|
+
const sessionId = sessions?.list?.getSnapshot?.()?.current;
|
|
2820
|
+
const face = sessionId ? sessions?.binding?.(sessionId)?.session : null;
|
|
2821
|
+
if (sessionId === current && face === currentFace) return;
|
|
2822
|
+
stopSession(); stopSession = () => {}; current = sessionId; currentFace = face;
|
|
2823
|
+
if (!sessionId) return;
|
|
2824
|
+
if (typeof face?.beginSubmission === 'function') {
|
|
2825
|
+
const original = face.beginSubmission;
|
|
2826
|
+
const wrapped = function(input) {
|
|
2827
|
+
if (!isCleaningSession(sessionId)) return original.call(this, input);
|
|
2828
|
+
let observed = false;
|
|
2829
|
+
return original.call(this, { ...input, onRetire: settlement => {
|
|
2830
|
+
try { input.onRetire?.(settlement); }
|
|
2831
|
+
finally {
|
|
2832
|
+
if (settlement.reason === 'observed' && !observed) {
|
|
2833
|
+
observed = true;
|
|
2834
|
+
controller.revealSubmitted(sessionId, submittedCommandId(input.text));
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
} });
|
|
2838
|
+
};
|
|
2839
|
+
face.beginSubmission = wrapped;
|
|
2840
|
+
stopSession = () => { if (face.beginSubmission === wrapped) face.beginSubmission = original; };
|
|
2841
|
+
return;
|
|
2842
|
+
}
|
|
2843
|
+
// Older DSH versions publish committed nodes on the Session read face.
|
|
2844
|
+
if (!face?.subscribe || !face?.getSnapshot) return;
|
|
2845
|
+
let previous = face.getSnapshot();
|
|
2846
|
+
let baseline = previous.nodes?.length ?? 0;
|
|
2847
|
+
let highestSeq = Math.max(0, ...(previous.nodes || []).map(node => Number(node.seq) || 0));
|
|
2848
|
+
stopSession = face.subscribe(() => {
|
|
2849
|
+
const snapshot = face.getSnapshot();
|
|
2850
|
+
const nodes = snapshot.nodes || [];
|
|
2851
|
+
const appended = highestSeq ? nodes.filter(node => node.seq > highestSeq) : nodes.slice(baseline);
|
|
2852
|
+
highestSeq = Math.max(highestSeq, ...nodes.map(node => Number(node.seq) || 0));
|
|
2853
|
+
baseline = nodes.length;
|
|
2854
|
+
const restoring = (previous.openState && previous.openState !== 'open') || snapshot.loadingOlder;
|
|
2855
|
+
previous = snapshot;
|
|
2856
|
+
if (restoring) return;
|
|
2857
|
+
if (!isCleaningSession(sessionId)) return;
|
|
2858
|
+
for (const node of appended) {
|
|
2859
|
+
if (node.kind !== 'user' && node.role !== 'user' && node.message?.role !== 'user') continue;
|
|
2860
|
+
const content = node.message?.content ?? node.content ?? node.blocks ?? node.parts;
|
|
2861
|
+
const prompt = node.text || (typeof content === 'string' ? content
|
|
2862
|
+
: Array.isArray(content) ? content.map(part => typeof part === 'string' ? part : part.text || '').join('\n') : '');
|
|
2863
|
+
controller.revealSubmitted(sessionId, submittedCommandId(prompt));
|
|
2864
|
+
}
|
|
2865
|
+
});
|
|
2866
|
+
};
|
|
2867
|
+
connect();
|
|
2868
|
+
const stopList = sessions?.list?.subscribe?.(connect);
|
|
2869
|
+
connect();
|
|
2870
|
+
return () => { stopSession(); stopList?.(); };
|
|
2442
2871
|
}
|
|
2443
2872
|
|
|
2444
2873
|
function setQccAgentDraft(ctx, sessionId, prompt) {
|
|
@@ -2460,8 +2889,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2460
2889
|
}
|
|
2461
2890
|
|
|
2462
2891
|
function openWorkbench(actions, step, sessionId) {
|
|
2463
|
-
|
|
2464
|
-
|
|
2892
|
+
if (sessionWorkbenchController) sessionWorkbenchController.open(sessionId, step);
|
|
2893
|
+
else {
|
|
2894
|
+
const destination = navigateWorkflow(actions, sessionId, step);
|
|
2895
|
+
actions.openAt(destination.stage, sessionId);
|
|
2896
|
+
}
|
|
2465
2897
|
}
|
|
2466
2898
|
|
|
2467
2899
|
const WORKBENCH_OPEN_EVENT = 'dsh:data-cleaning-workbench-open';
|
|
@@ -2613,8 +3045,68 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2613
3045
|
authorization_required: '待连接企查查', cancelled: '已取消', parse_failed: '解析失败',
|
|
2614
3046
|
};
|
|
2615
3047
|
|
|
3048
|
+
const SUPPORTED_FLOW_VERSION = 1;
|
|
3049
|
+
function normalizeViewStage(stage) {
|
|
3050
|
+
return stage === 'profile' ? 'rules' : stage;
|
|
3051
|
+
}
|
|
3052
|
+
function resolveStageAccess(stage, flow) {
|
|
3053
|
+
const normalized = normalizeViewStage(stage);
|
|
3054
|
+
if (flow?.flowVersion !== SUPPORTED_FLOW_VERSION) return 'locked';
|
|
3055
|
+
return flow.stageAccess?.[normalized] || 'locked';
|
|
3056
|
+
}
|
|
3057
|
+
function workflowActionAllowed(task, action) {
|
|
3058
|
+
return task?.flow?.flowVersion === SUPPORTED_FLOW_VERSION
|
|
3059
|
+
&& Array.isArray(task.flow.allowedActions)
|
|
3060
|
+
&& task.flow.allowedActions.includes(action);
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
function workflowDestination(requestedStage, task, currentView = 'upload') {
|
|
3064
|
+
if (requestedStage === 'history') return { stage: 'history', access: 'read' };
|
|
3065
|
+
if (!task) {
|
|
3066
|
+
return requestedStage === 'upload' || !requestedStage
|
|
3067
|
+
? { stage: 'upload', access: 'current' }
|
|
3068
|
+
: { stage: 'upload', access: 'locked' };
|
|
3069
|
+
}
|
|
3070
|
+
if (task.flow?.flowVersion !== SUPPORTED_FLOW_VERSION) {
|
|
3071
|
+
return { stage: task.stage || 'upload', access: 'read', compatibility: true };
|
|
3072
|
+
}
|
|
3073
|
+
const requested = requestedStage || currentView || task.flow.currentStage;
|
|
3074
|
+
const access = resolveStageAccess(requested, task.flow);
|
|
3075
|
+
if (access === 'locked') return { stage: task.flow.currentStage, access };
|
|
3076
|
+
return { stage: requested, access };
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
function workflowStageLabel(stage) {
|
|
3080
|
+
const normalized = normalizeViewStage(stage);
|
|
3081
|
+
return STEPS.find((item) => item.key === normalized)?.label ?? normalized ?? '当前节点';
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
function lockedWorkflowNotice(requestedStage, task, fallbackStage = 'upload') {
|
|
3085
|
+
const currentStage = task?.flow?.currentStage || task?.stage || fallbackStage;
|
|
3086
|
+
return `流程提示:“${workflowStageLabel(requestedStage)}”尚未开放,请先完成“${workflowStageLabel(currentStage)}”。`;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
function navigateWorkflow(actions, sessionId, requestedStage, task) {
|
|
3090
|
+
const current = task ?? workflowTaskBySession.get(String(sessionId || 'unassigned')) ?? null;
|
|
3091
|
+
const destination = workflowDestination(requestedStage, current);
|
|
3092
|
+
actions.setStep(destination.stage);
|
|
3093
|
+
if (destination.compatibility) {
|
|
3094
|
+
actions.setError('页面与 Host 流程版本不一致,当前任务已切换为只读模式;请完整重启并升级 Host。');
|
|
3095
|
+
} else if (destination.access === 'locked') {
|
|
3096
|
+
actions.setError(lockedWorkflowNotice(requestedStage, current, destination.stage));
|
|
3097
|
+
} else {
|
|
3098
|
+
actions.setError(null);
|
|
3099
|
+
}
|
|
3100
|
+
return destination;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
2616
3103
|
function workflowNavigationIssue(step, task, hasData, profile, qccRun, hasLocalResult = false) {
|
|
2617
|
-
if (
|
|
3104
|
+
if (step === 'history') return null;
|
|
3105
|
+
if (task?.flow?.flowVersion === SUPPORTED_FLOW_VERSION) {
|
|
3106
|
+
const access = resolveStageAccess(step, task.flow);
|
|
3107
|
+
return access === 'locked' ? '该步骤尚未开放,请先完成当前流程节点。' : null;
|
|
3108
|
+
}
|
|
3109
|
+
if (step === 'upload') return null;
|
|
2618
3110
|
if (step === 'download' && task?.artifacts?.length) return null;
|
|
2619
3111
|
if (!hasData) return '请先在「导入与核验」载入并核对完整名单。';
|
|
2620
3112
|
if (step === 'rules') return null;
|
|
@@ -2989,7 +3481,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2989
3481
|
if (command.taskId) {
|
|
2990
3482
|
const actions = sessionWorkbenchController?.actionsFor(sessionId);
|
|
2991
3483
|
if (actions) {
|
|
2992
|
-
await refreshHostedTask(actions, sessionId, command.taskId, null,
|
|
3484
|
+
await refreshHostedTask(actions, sessionId, command.taskId, null, 'update');
|
|
2993
3485
|
if (command.state === 'running') requestWorkbenchOpen(command.qccCommand ? 'match' : 'upload', sessionId);
|
|
2994
3486
|
if (command.qccCommand) {
|
|
2995
3487
|
const status = await requestJson(`/data-cleaning/api/g5/commands/${encodeURIComponent(command.qccCommand.commandId)}`);
|
|
@@ -3179,21 +3671,26 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3179
3671
|
removeImage(sessionId, imageAttachment);
|
|
3180
3672
|
setImageAttachment(null);
|
|
3181
3673
|
}
|
|
3182
|
-
if (mode === 'text') {
|
|
3183
|
-
requestWorkbenchDataset(entriesToDataset(config.entries), sessionId, { type: 'text', fileName: '' });
|
|
3184
|
-
} else if (mode === 'excel' && spreadsheetData) {
|
|
3185
|
-
requestWorkbenchDataset(spreadsheetData, sessionId, { type: spreadsheetData.fmt || 'xlsx', fileName: spreadsheetFileName });
|
|
3186
|
-
}
|
|
3187
|
-
requestWorkbenchDraft({
|
|
3188
|
-
title: `企业数据清洗补全任务 · ${config.entryCount || '待解析'} 条`,
|
|
3189
|
-
objectives: cleaningKeys,
|
|
3190
|
-
fieldSelection: enrichmentKeys,
|
|
3191
|
-
matchRules,
|
|
3192
|
-
...(mode === 'excel' ? { mappings: spreadsheetMappings } : {}),
|
|
3193
|
-
source: { type: mode === 'excel' ? 'xlsx' : mode, fileName: config.fileName || '', rowCount: config.entryCount || 0 },
|
|
3194
|
-
}, sessionId);
|
|
3195
3674
|
setBusy(true);
|
|
3196
3675
|
try {
|
|
3676
|
+
if (mode === 'text') {
|
|
3677
|
+
const local = entriesToDataset(config.entries);
|
|
3678
|
+
const verified = await api('/data-cleaning/api/mvp/parse', {
|
|
3679
|
+
filename: 'data.json',
|
|
3680
|
+
content: JSON.stringify(local.rows),
|
|
3681
|
+
});
|
|
3682
|
+
requestWorkbenchDataset({ ...verified, fmt: 'text' }, sessionId, { type: 'text', fileName: '' });
|
|
3683
|
+
} else if (mode === 'excel' && spreadsheetData) {
|
|
3684
|
+
requestWorkbenchDataset(spreadsheetData, sessionId, { type: spreadsheetData.fmt || 'xlsx', fileName: spreadsheetFileName });
|
|
3685
|
+
}
|
|
3686
|
+
requestWorkbenchDraft({
|
|
3687
|
+
title: `企业数据清洗补全任务 · ${config.entryCount || '待解析'} 条`,
|
|
3688
|
+
objectives: cleaningKeys,
|
|
3689
|
+
fieldSelection: enrichmentKeys,
|
|
3690
|
+
matchRules,
|
|
3691
|
+
...(mode === 'excel' ? { mappings: spreadsheetMappings } : {}),
|
|
3692
|
+
source: { type: mode === 'excel' ? 'xlsx' : mode, fileName: config.fileName || '', rowCount: config.entryCount || 0 },
|
|
3693
|
+
}, sessionId);
|
|
3197
3694
|
let prompt = buildTaskPrompt(config);
|
|
3198
3695
|
if (mode !== 'image' || ['prepared', 'completed'].includes(imageCommand?.state)) {
|
|
3199
3696
|
const command = await requestPreparedWorkflow(sessionId, config);
|
|
@@ -3274,12 +3771,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3274
3771
|
const receiveNewTask = (event) => {
|
|
3275
3772
|
if (event.detail?.sessionId !== sessionId || busy || imageCommand?.state === 'running') return;
|
|
3276
3773
|
event.preventDefault();
|
|
3774
|
+
const reused = event.detail?.reuseConfig || null;
|
|
3277
3775
|
discardCurrentImage();
|
|
3278
3776
|
setMode('text'); setRawText(''); setEntries([]); setEntryCount(0); setSpreadsheetFileName('');
|
|
3279
3777
|
setSpreadsheetData(null); setSpreadsheetMappings([]);
|
|
3280
|
-
setCleaningKeys(DEFAULT_CLEANING_KEYS);
|
|
3778
|
+
setCleaningKeys(reused?.objectives || DEFAULT_CLEANING_KEYS);
|
|
3779
|
+
setEnrichmentKeys(reused?.fieldSelection || DEFAULT_ENRICHMENT_KEYS);
|
|
3281
3780
|
setAnchorKeys(['company_name', 'credit_no']);
|
|
3282
|
-
setPromptMatchRules({ normalizeNames: true, preferCreditNo: true, deduplicate: true, manualReviewAmbiguous: true });
|
|
3781
|
+
setPromptMatchRules(reused?.matchRules || { normalizeNames: true, preferCreditNo: true, deduplicate: true, manualReviewAmbiguous: true });
|
|
3283
3782
|
setWizardStep(1); setOpen(true); setError(null);
|
|
3284
3783
|
};
|
|
3285
3784
|
const receiveImage = (event) => {
|
|
@@ -3617,7 +4116,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3617
4116
|
}
|
|
3618
4117
|
|
|
3619
4118
|
/** 上传 pane 的处理:文件 → 文本/base64 → parse。 */
|
|
3620
|
-
async function parseFile(file) {
|
|
4119
|
+
async function parseFile(file, previousChecksum) {
|
|
3621
4120
|
const isXlsx = /\.(xlsx|xls)$/i.test(file && file.name ? file.name : '');
|
|
3622
4121
|
let content;
|
|
3623
4122
|
if (isXlsx) {
|
|
@@ -3629,7 +4128,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3629
4128
|
} else {
|
|
3630
4129
|
content = await file.text();
|
|
3631
4130
|
}
|
|
3632
|
-
return api('/data-cleaning/api/mvp/parse', { filename: file.name, content });
|
|
4131
|
+
return api('/data-cleaning/api/mvp/parse', { filename: file.name, content, previousChecksum });
|
|
3633
4132
|
}
|
|
3634
4133
|
|
|
3635
4134
|
/** 文本/JSON 直通 parse(与 web.js 内联页同构,含 JSON 数组直通)。 */
|
|
@@ -3644,7 +4143,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3644
4143
|
};
|
|
3645
4144
|
}
|
|
3646
4145
|
|
|
3647
|
-
async function parseText(text, actions) {
|
|
4146
|
+
async function parseText(text, actions, previousChecksum) {
|
|
3648
4147
|
const trimmed = (text ?? '').trim();
|
|
3649
4148
|
if (!trimmed) {
|
|
3650
4149
|
actions.setError('请粘贴或上传数据后再解析。');
|
|
@@ -3652,18 +4151,26 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3652
4151
|
}
|
|
3653
4152
|
if (trimmed.startsWith('[')) {
|
|
3654
4153
|
try {
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
4154
|
+
return api('/data-cleaning/api/mvp/parse', {
|
|
4155
|
+
filename: 'data.json',
|
|
4156
|
+
content: trimmed,
|
|
4157
|
+
previousChecksum,
|
|
4158
|
+
});
|
|
3659
4159
|
} catch (error) {
|
|
3660
4160
|
actions.setError(`JSON 解析失败:${error instanceof Error ? error.message : String(error)}`);
|
|
3661
4161
|
}
|
|
3662
4162
|
return;
|
|
3663
4163
|
}
|
|
3664
4164
|
const entityList = plainEntityListDataset(trimmed);
|
|
3665
|
-
if (entityList)
|
|
3666
|
-
|
|
4165
|
+
if (entityList) {
|
|
4166
|
+
const parsed = await api('/data-cleaning/api/mvp/parse', {
|
|
4167
|
+
filename: 'data.json',
|
|
4168
|
+
content: JSON.stringify(entityList.rows),
|
|
4169
|
+
previousChecksum,
|
|
4170
|
+
});
|
|
4171
|
+
return { ...parsed, fmt: 'text' };
|
|
4172
|
+
}
|
|
4173
|
+
return api('/data-cleaning/api/mvp/parse', { filename: 'data.csv', content: trimmed, previousChecksum });
|
|
3667
4174
|
}
|
|
3668
4175
|
|
|
3669
4176
|
/** 解析成功进入 taskId runtime;不把整表写入 Host store。 */
|
|
@@ -3676,6 +4183,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3676
4183
|
type: source.type || result.fmt || 'csv',
|
|
3677
4184
|
fileName: source.fileName || '',
|
|
3678
4185
|
sizeBytes: source.sizeBytes || 0,
|
|
4186
|
+
checksum: result.checksum || source.checksum || '',
|
|
3679
4187
|
};
|
|
3680
4188
|
const mappings = guessMappings(runtime.headers);
|
|
3681
4189
|
const guessedNameField = mappings.find((mapping) => mapping.targetField === 'company_name')?.sourceField
|
|
@@ -3695,6 +4203,28 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3695
4203
|
return runtime;
|
|
3696
4204
|
}
|
|
3697
4205
|
|
|
4206
|
+
function restoreParsedRuntime(result, actions, task, source = {}) {
|
|
4207
|
+
const runtime = runtimeFor(task.id);
|
|
4208
|
+
runtime.rows = Array.isArray(result.rows) ? result.rows : [];
|
|
4209
|
+
runtime.sourceRows = runtime.rows;
|
|
4210
|
+
runtime.headers = Array.isArray(result.headers) ? result.headers : [];
|
|
4211
|
+
runtime.source = {
|
|
4212
|
+
type: source.type || result.fmt || task.source?.type || 'csv',
|
|
4213
|
+
fileName: source.fileName || task.source?.fileName || '',
|
|
4214
|
+
sizeBytes: source.sizeBytes || task.source?.sizeBytes || 0,
|
|
4215
|
+
checksum: result.checksum || '',
|
|
4216
|
+
};
|
|
4217
|
+
actions.setDataset({
|
|
4218
|
+
fmt: result.fmt ?? task.source?.type ?? 'csv',
|
|
4219
|
+
headers: runtime.headers,
|
|
4220
|
+
rowCount: typeof result.rowCount === 'number' ? result.rowCount : runtime.rows.length,
|
|
4221
|
+
preview: Array.isArray(result.preview) ? result.preview : runtime.rows.slice(0, 5),
|
|
4222
|
+
});
|
|
4223
|
+
const primary = (task.mappings || []).find((mapping) => ['company_name', 'credit_no', 'reg_no'].includes(mapping.targetField));
|
|
4224
|
+
if (primary) actions.setNameField(primary.sourceField);
|
|
4225
|
+
return runtime;
|
|
4226
|
+
}
|
|
4227
|
+
|
|
3698
4228
|
async function persistParsedWorkflow(actions, sessionId, result, source = {}, task) {
|
|
3699
4229
|
const importedMappings = guessMappings(result.headers || []);
|
|
3700
4230
|
const importedDraft = { mappings: importedMappings,
|
|
@@ -3708,10 +4238,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3708
4238
|
target.rows = Array.isArray(result.rows) ? result.rows : [];
|
|
3709
4239
|
target.sourceRows = target.rows;
|
|
3710
4240
|
target.headers = Array.isArray(result.headers) ? result.headers : [];
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
4241
|
+
target.source = {
|
|
4242
|
+
type: source.type || result.fmt || 'csv',
|
|
4243
|
+
fileName: source.fileName || '',
|
|
4244
|
+
sizeBytes: source.sizeBytes || 0,
|
|
4245
|
+
checksum: result.checksum || source.checksum || '',
|
|
3715
4246
|
};
|
|
3716
4247
|
if (['draft', 'uploaded', 'parse_failed'].includes(current.state)) {
|
|
3717
4248
|
current = await workflowAction(actions, sessionId, current, 'upload', {
|
|
@@ -3722,8 +4253,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3722
4253
|
columnCount: Array.isArray(result.headers) ? result.headers.length : 0,
|
|
3723
4254
|
headers: Array.isArray(result.headers) ? result.headers : [],
|
|
3724
4255
|
sizeBytes: source.sizeBytes || 0,
|
|
4256
|
+
checksum: result.checksum || source.checksum || '',
|
|
3725
4257
|
},
|
|
3726
|
-
});
|
|
4258
|
+
}, 'import');
|
|
3727
4259
|
}
|
|
3728
4260
|
if (current.state === 'uploaded') current = await updateWorkflowTask(actions, sessionId, current, importedDraft);
|
|
3729
4261
|
return current;
|
|
@@ -3794,6 +4326,103 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3794
4326
|
);
|
|
3795
4327
|
}
|
|
3796
4328
|
|
|
4329
|
+
function WorkflowTaskContext(props) {
|
|
4330
|
+
const stageCompleted = props.stageCompleted;
|
|
4331
|
+
const progressVisible = props.commandProgress?.taskId === props.task.id
|
|
4332
|
+
&& Number.isFinite(props.commandProgress.totalUnique);
|
|
4333
|
+
const completedUnique = Math.min(
|
|
4334
|
+
props.commandProgress?.totalUnique || 0,
|
|
4335
|
+
Math.max(0, props.commandProgress?.completedUnique || 0),
|
|
4336
|
+
);
|
|
4337
|
+
return h('div', { className: 'dcAgentTaskStatus', 'aria-live': 'polite' },
|
|
4338
|
+
h('div', { className: 'dcAgentTaskStatusRow' },
|
|
4339
|
+
h('div', { className: 'dcAgentTaskStage' },
|
|
4340
|
+
h('span', {
|
|
4341
|
+
className: 'dcAgentTaskDoneMark',
|
|
4342
|
+
'data-complete': stageCompleted,
|
|
4343
|
+
'aria-hidden': true,
|
|
4344
|
+
}, stageCompleted ? '✓' : '•'),
|
|
4345
|
+
h('div', { className: 'dcAgentTaskStageCopy' },
|
|
4346
|
+
h('div', { className: 'dcAgentTaskStageTitle' },
|
|
4347
|
+
h('strong', null, props.stageLabel),
|
|
4348
|
+
h('span', {
|
|
4349
|
+
className: 'dcAgentTaskState',
|
|
4350
|
+
'data-complete': stageCompleted,
|
|
4351
|
+
}, props.statusLabel),
|
|
4352
|
+
),
|
|
4353
|
+
props.timeLabel || props.sourceLabel ? h('div', { className: 'dcAgentTaskMeta' },
|
|
4354
|
+
props.timeLabel ? h('span', null,
|
|
4355
|
+
h('time', { dateTime: props.timeValue || undefined }, props.timeLabel)) : null,
|
|
4356
|
+
props.sourceLabel ? h('span', null, props.sourceLabel) : null,
|
|
4357
|
+
) : null,
|
|
4358
|
+
),
|
|
4359
|
+
),
|
|
4360
|
+
props.viewAccess === 'read' ? h('div', { className: 'dcAgentTaskCurrent' },
|
|
4361
|
+
h('span', null, '当前节点'),
|
|
4362
|
+
h('strong', null, props.currentStageLabel),
|
|
4363
|
+
) : null,
|
|
4364
|
+
props.viewAccess === 'read' && props.onReturn ? h('button', {
|
|
4365
|
+
type: 'button',
|
|
4366
|
+
className: 'dcAgentTaskReturn',
|
|
4367
|
+
onClick: props.onReturn,
|
|
4368
|
+
}, '回到当前节点') : null,
|
|
4369
|
+
h('details', { className: 'dcAgentTaskDetails' },
|
|
4370
|
+
h('summary', { 'aria-label': '任务详情', title: '任务详情' }, 'i'),
|
|
4371
|
+
h('div', { className: 'dcAgentTaskDetailsBody' },
|
|
4372
|
+
h('strong', null, `${props.sourceLabel || '当前任务'} · ${props.statusLabel}`),
|
|
4373
|
+
h('p', null, `任务编号:${props.task.id}`),
|
|
4374
|
+
h('p', null, workflowOriginLabel(props.task)),
|
|
4375
|
+
h('p', null, `${props.rowCount} 条企业记录 · ${props.fieldCount} 个补全字段`),
|
|
4376
|
+
),
|
|
4377
|
+
),
|
|
4378
|
+
),
|
|
4379
|
+
['matching', 'enriching'].includes(props.task.state) ? h('p', { className: 'dcAgentTaskSyncNote' },
|
|
4380
|
+
'这是最近同步的任务记录;若原会话已停止,请先查看确认或错误提示,不要重复启动。') : null,
|
|
4381
|
+
progressVisible ? h('div', { className: 'dcAgentTaskExecution' },
|
|
4382
|
+
h('span', null, `已处理主体 ${completedUnique}/${props.commandProgress.totalUnique}`),
|
|
4383
|
+
h('progress', {
|
|
4384
|
+
max: props.commandProgress.totalUnique,
|
|
4385
|
+
value: completedUnique,
|
|
4386
|
+
'aria-label': '主体处理进度',
|
|
4387
|
+
}),
|
|
4388
|
+
) : null,
|
|
4389
|
+
h('div', { className: 'dcAgentTaskMetrics', 'aria-label': '任务摘要' },
|
|
4390
|
+
h('div', { className: 'dcAgentTaskMetricGroup' },
|
|
4391
|
+
h('span', { className: 'dcAgentTaskMetricLabel' }, '任务范围'),
|
|
4392
|
+
h('span', { className: 'dcAgentTaskMetric' }, '企业 ', h('b', null, props.rowCount), ' 家'),
|
|
4393
|
+
h('span', { className: 'dcAgentTaskMetric' }, '字段 ', h('b', null, props.fieldCount), ' 个'),
|
|
4394
|
+
),
|
|
4395
|
+
props.hasResultSummary ? h('div', { className: 'dcAgentTaskMetricGroup' },
|
|
4396
|
+
h('span', { className: 'dcAgentTaskMetricLabel' }, '处理结果'),
|
|
4397
|
+
h('span', { className: 'dcAgentTaskMetric is-good' }, '已补全 ', h('b', null, props.completedMetric)),
|
|
4398
|
+
h('span', { className: 'dcAgentTaskMetric' }, '待核验 ', h('b', null, props.reviewMetric)),
|
|
4399
|
+
h('span', { className: 'dcAgentTaskMetric' }, '失败 ', h('b', null, props.failedMetric)),
|
|
4400
|
+
) : null,
|
|
4401
|
+
),
|
|
4402
|
+
);
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
function WorkflowGuide() {
|
|
4406
|
+
return h('details', { className: 'dcAgentWorkflowGuide' },
|
|
4407
|
+
h('summary', null, '使用说明和任务流程'),
|
|
4408
|
+
h('div', { className: 'dcAgentWorkflowGuideContent' },
|
|
4409
|
+
h('p', { className: 'dcAgentWorkflowGuideIntro' },
|
|
4410
|
+
'导入与核验 → 规则与体检 → 主体匹配 → 字段补全 → 结果下载。菜单只切换视图,不启动调用;按页面主按钮逐步确认。'),
|
|
4411
|
+
h('p', { className: 'dcAgentWorkflowGuideIntro' },
|
|
4412
|
+
'支持文本、Excel、JSON 和图片名单。原值保留、多候选人工核验;企查查调用使用当前用户自己的连接与额度。'),
|
|
4413
|
+
h('div', { className: 'dcAgentWorkflowGuideList' },
|
|
4414
|
+
WORKFLOW_GUIDE_STEPS.map(([title, description], index) => h('div', {
|
|
4415
|
+
key: title,
|
|
4416
|
+
className: 'dcAgentWorkflowGuideStep',
|
|
4417
|
+
},
|
|
4418
|
+
h('span', { className: 'dcAgentWorkflowGuideIndex' }, String(index + 1)),
|
|
4419
|
+
h('div', null, h('strong', null, title), h('p', null, description))))),
|
|
4420
|
+
h('div', { className: 'dcAgentWorkflowGuideBoundary' },
|
|
4421
|
+
'生成任务说明不会查询;发送说明或显式重试时,才使用当前用户连接的企查查账号及额度。'),
|
|
4422
|
+
),
|
|
4423
|
+
);
|
|
4424
|
+
}
|
|
4425
|
+
|
|
3797
4426
|
/** Session Tab 业务内容;尺寸、停靠、关闭均由 Better Sidebar 宿主管理。 */
|
|
3798
4427
|
function WorkbenchContent(props) {
|
|
3799
4428
|
const { useStore, actions } = props;
|
|
@@ -3846,13 +4475,19 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3846
4475
|
requestJson('/data-cleaning/api/workflow/contract').then((response) => {
|
|
3847
4476
|
if (!disposed) actions.setWorkflowContract(response.contract);
|
|
3848
4477
|
}).catch(() => {});
|
|
3849
|
-
loadWorkflowTasks(actions);
|
|
4478
|
+
const tasksPromise = loadWorkflowTasks(actions);
|
|
3850
4479
|
if (activeSessionId) {
|
|
3851
4480
|
queueWorkflowOperation(activeSessionId, async () => {
|
|
3852
4481
|
const key = String(activeSessionId);
|
|
4482
|
+
const tasks = await tasksPromise;
|
|
3853
4483
|
const saved = ownedSessionId(key) ? readTaskBindings()[key] : null;
|
|
3854
|
-
const
|
|
3855
|
-
|
|
4484
|
+
const hosted = tasks
|
|
4485
|
+
.filter((task) => taskBelongsToSession(task, activeSessionId))
|
|
4486
|
+
.sort((left, right) => String(right.createdAt).localeCompare(String(left.createdAt)))[0]?.id;
|
|
4487
|
+
const taskId = workflowTaskBySession.get(key)?.id
|
|
4488
|
+
|| hosted
|
|
4489
|
+
|| (/^dcw-[a-zA-Z0-9-]+$/.test(saved || '') ? saved : null);
|
|
4490
|
+
if (taskId) await refreshHostedTask(actions, activeSessionId, taskId, null, 'update');
|
|
3856
4491
|
}).catch((taskError) => {
|
|
3857
4492
|
if (!disposed) actions.setError(`状态同步异常:${taskError instanceof Error ? taskError.message : String(taskError)}。保留最近任务记录,未重新执行查询。`);
|
|
3858
4493
|
});
|
|
@@ -3867,11 +4502,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3867
4502
|
let disposed = false, timer;
|
|
3868
4503
|
const poll = async () => {
|
|
3869
4504
|
try {
|
|
3870
|
-
const task = await refreshHostedTask(actions, activeSessionId, workflowTask.id, null,
|
|
4505
|
+
const task = await refreshHostedTask(actions, activeSessionId, workflowTask.id, null, 'update');
|
|
3871
4506
|
if (disposed) return;
|
|
3872
4507
|
if (!task) return;
|
|
3873
4508
|
if (['matching', 'enriching'].includes(task.state)) timer = setTimeout(poll, 2000);
|
|
3874
|
-
else if (step === 'match' || step === 'enrich') actions.setStep(task.stage || step);
|
|
3875
4509
|
} catch (error) { if (!disposed) actions.setError(`状态同步异常:${error.message}。保留最近任务记录,未重新执行查询。`); }
|
|
3876
4510
|
};
|
|
3877
4511
|
timer = setTimeout(poll, 2000);
|
|
@@ -3888,7 +4522,29 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3888
4522
|
const stagedSource = runtimeFor(`session:${activeSessionId || 'unassigned'}`, false);
|
|
3889
4523
|
const sourceRows = stagedSource?.sourceRows ?? runtime.sourceRows ?? runtime.rows;
|
|
3890
4524
|
const intake = intakeState(dataset, sourceRows, input, runtime.source || stagedSource?.source || cachedTask?.source);
|
|
3891
|
-
const
|
|
4525
|
+
const normalizedStage = normalizeViewStage(step);
|
|
4526
|
+
const flowCompatible = cachedTask?.flow?.flowVersion === SUPPORTED_FLOW_VERSION;
|
|
4527
|
+
const viewAccess = step === 'history'
|
|
4528
|
+
? 'read'
|
|
4529
|
+
: cachedTask
|
|
4530
|
+
? resolveStageAccess(normalizedStage, cachedTask.flow)
|
|
4531
|
+
: normalizedStage === 'upload' ? 'current' : 'locked';
|
|
4532
|
+
const allowedActions = new Set(flowCompatible ? cachedTask.flow.allowedActions || [] : []);
|
|
4533
|
+
const isCurrentView = viewAccess === 'current';
|
|
4534
|
+
const canAction = (action) => isCurrentView && (
|
|
4535
|
+
cachedTask ? allowedActions.has(action) : action === 'import-data'
|
|
4536
|
+
);
|
|
4537
|
+
const reviewingImportedDataset = normalizedStage === 'upload'
|
|
4538
|
+
&& cachedTask?.state === 'uploaded'
|
|
4539
|
+
&& cachedTask.flow?.currentStage === 'rules'
|
|
4540
|
+
&& viewAccess === 'read';
|
|
4541
|
+
const navigate = (destination) => navigateWorkflow(actions, activeSessionId, destination, cachedTask);
|
|
4542
|
+
const showIntakeEditor = canAction('import-data') && (!dataset || intakeEditorOpen);
|
|
4543
|
+
const runtimeRecoveryNeeded = Boolean(cachedTask?.source?.rowCount) && runtime.rows.length === 0;
|
|
4544
|
+
const runtimeRecoveryAllowed = runtimeRecoveryNeeded
|
|
4545
|
+
&& isCurrentView
|
|
4546
|
+
&& ['uploaded', 'rules_confirmed', 'diagnosed'].includes(cachedTask?.state)
|
|
4547
|
+
&& cachedTask?.source?.type !== 'image';
|
|
3892
4548
|
const lastCsv = runtime.lastCsv;
|
|
3893
4549
|
const fieldByPattern = (pattern) => runtime.headers.find((field) => pattern.test(field)) ?? null;
|
|
3894
4550
|
const phoneField = fieldByPattern(/^(phone|mobile|tel|telephone|联系电话|手机号码|手机号)$/i);
|
|
@@ -3907,7 +4563,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3907
4563
|
};
|
|
3908
4564
|
|
|
3909
4565
|
const handleParse = async () => {
|
|
3910
|
-
if (busy) return;
|
|
4566
|
+
if (busy || !canAction('import-data')) return;
|
|
3911
4567
|
if (!intake.pendingText) {
|
|
3912
4568
|
if (intake.available) actions.setError(null);
|
|
3913
4569
|
return;
|
|
@@ -3915,7 +4571,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3915
4571
|
actions.setBusy(true);
|
|
3916
4572
|
actions.setError(null);
|
|
3917
4573
|
try {
|
|
3918
|
-
const result = await parseText(input, actions);
|
|
4574
|
+
const result = await parseText(input, actions, cachedTask?.source?.checksum);
|
|
3919
4575
|
if (result && result.ok !== false) {
|
|
3920
4576
|
applyParsed(result, actions, runtimeKey, { type: result.fmt || 'csv' });
|
|
3921
4577
|
await persistParsedWorkflow(actions, activeSessionId, result, { type: result.fmt || 'csv' }, cachedTask);
|
|
@@ -3939,7 +4595,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3939
4595
|
return true;
|
|
3940
4596
|
};
|
|
3941
4597
|
const handleFile = async (event) => {
|
|
3942
|
-
if (busy) return;
|
|
4598
|
+
if (busy || !canAction('import-data')) return;
|
|
3943
4599
|
const file = event.target && event.target.files && event.target.files[0];
|
|
3944
4600
|
if (!file) return;
|
|
3945
4601
|
if (String(file.type ?? '').startsWith('image/')) {
|
|
@@ -3950,7 +4606,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3950
4606
|
actions.setBusy(true);
|
|
3951
4607
|
actions.setError(null);
|
|
3952
4608
|
try {
|
|
3953
|
-
const result = await parseFile(file);
|
|
4609
|
+
const result = await parseFile(file, cachedTask?.source?.checksum);
|
|
3954
4610
|
if (result && result.ok !== false) {
|
|
3955
4611
|
applyParsed(result, actions, runtimeKey, { type: result.fmt || 'csv', fileName: file.name, sizeBytes: file.size || 0 });
|
|
3956
4612
|
await persistParsedWorkflow(actions, activeSessionId, result, { type: result.fmt || 'csv', fileName: file.name, sizeBytes: file.size || 0 }, cachedTask);
|
|
@@ -3965,6 +4621,49 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3965
4621
|
}
|
|
3966
4622
|
};
|
|
3967
4623
|
|
|
4624
|
+
const acceptRuntimeRecovery = (result, source) => {
|
|
4625
|
+
if (!cachedTask?.source?.checksum) {
|
|
4626
|
+
throw new Error('该旧任务缺少同源校验信息,不能恢复写操作。请新建任务并复用配置。');
|
|
4627
|
+
}
|
|
4628
|
+
if (!result?.checksum || result.checksum !== cachedTask.source.checksum) {
|
|
4629
|
+
throw new Error('重新载入的数据与原任务不一致,未修改当前任务。请新建任务处理其他名单。');
|
|
4630
|
+
}
|
|
4631
|
+
restoreParsedRuntime(result, actions, cachedTask, source);
|
|
4632
|
+
actions.setInput('');
|
|
4633
|
+
actions.setError(null);
|
|
4634
|
+
};
|
|
4635
|
+
|
|
4636
|
+
const recoverRuntimeFile = async (event) => {
|
|
4637
|
+
if (busy) return;
|
|
4638
|
+
const file = event.target?.files?.[0];
|
|
4639
|
+
if (!file) return;
|
|
4640
|
+
actions.setBusy(true);
|
|
4641
|
+
actions.setError(null);
|
|
4642
|
+
try {
|
|
4643
|
+
const result = await parseFile(file, cachedTask?.source?.checksum);
|
|
4644
|
+
acceptRuntimeRecovery(result, { type: result.fmt, fileName: file.name, sizeBytes: file.size || 0 });
|
|
4645
|
+
} catch (err) {
|
|
4646
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4647
|
+
} finally {
|
|
4648
|
+
event.target.value = '';
|
|
4649
|
+
actions.setBusy(false);
|
|
4650
|
+
}
|
|
4651
|
+
};
|
|
4652
|
+
|
|
4653
|
+
const recoverRuntimeText = async () => {
|
|
4654
|
+
if (busy || !String(input || '').trim()) return;
|
|
4655
|
+
actions.setBusy(true);
|
|
4656
|
+
actions.setError(null);
|
|
4657
|
+
try {
|
|
4658
|
+
const result = await parseText(input, actions, cachedTask?.source?.checksum);
|
|
4659
|
+
acceptRuntimeRecovery(result, { type: result.fmt, fileName: cachedTask?.source?.fileName || '' });
|
|
4660
|
+
} catch (err) {
|
|
4661
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4662
|
+
} finally {
|
|
4663
|
+
actions.setBusy(false);
|
|
4664
|
+
}
|
|
4665
|
+
};
|
|
4666
|
+
|
|
3968
4667
|
const performProfile = async (task) => {
|
|
3969
4668
|
const r = await api('/data-cleaning/api/mvp/profile', {
|
|
3970
4669
|
rows: runtime.rows,
|
|
@@ -3978,7 +4677,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3978
4677
|
};
|
|
3979
4678
|
|
|
3980
4679
|
const runProfile = async () => {
|
|
3981
|
-
if (busy || !runtime.rows.length) {
|
|
4680
|
+
if (busy || !canAction('run-quality') || !runtime.rows.length) {
|
|
3982
4681
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
3983
4682
|
return;
|
|
3984
4683
|
}
|
|
@@ -3986,7 +4685,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3986
4685
|
actions.setError(null);
|
|
3987
4686
|
try {
|
|
3988
4687
|
await performProfile(cachedTask);
|
|
3989
|
-
actions.setStep(requiresQcc ? 'match' : 'enrich');
|
|
3990
4688
|
} catch (err) {
|
|
3991
4689
|
actions.setError(err instanceof Error ? err.message : String(err));
|
|
3992
4690
|
} finally {
|
|
@@ -3995,7 +4693,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3995
4693
|
};
|
|
3996
4694
|
|
|
3997
4695
|
const runClean = async () => {
|
|
3998
|
-
if (busy || !runtime.rows.length) {
|
|
4696
|
+
if (busy || !canAction('run-local-clean') || !runtime.rows.length) {
|
|
3999
4697
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
4000
4698
|
return;
|
|
4001
4699
|
}
|
|
@@ -4023,7 +4721,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4023
4721
|
};
|
|
4024
4722
|
|
|
4025
4723
|
const runComplete = async () => {
|
|
4026
|
-
if (busy || !runtime.rows.length) {
|
|
4724
|
+
if (busy || !canAction('run-local-complete') || !runtime.rows.length) {
|
|
4027
4725
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
4028
4726
|
return;
|
|
4029
4727
|
}
|
|
@@ -4056,48 +4754,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4056
4754
|
runtime.resultRows.qcc = Array.isArray(run.rows) ? run.rows : null;
|
|
4057
4755
|
lastCsv.qcc = { csv: run.csv ?? '', name: run.downloadName ?? 'qcc-enriched.csv' };
|
|
4058
4756
|
if (run.reviewCsv) lastCsv.review = { csv: run.reviewCsv, name: run.reviewDownloadName ?? 'qcc-review.csv' };
|
|
4059
|
-
const summary = run.summary ?? {};
|
|
4060
|
-
const reviewRequired = Number(summary.ambiguous ?? run.reviewQueue?.length ?? 0);
|
|
4061
4757
|
const currentTask = workflowTaskBySession.get(String(activeSessionId || 'unassigned')) ?? cachedTask;
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
actions.setStep(currentTask.stage || 'download');
|
|
4065
|
-
return currentTask;
|
|
4066
|
-
}
|
|
4067
|
-
// partial 表示上一轮仍有失败项。重试成功后可直接重进 enrichment,
|
|
4068
|
-
// 无需伪造一次新的 match 转换;若重试产生候选,再回到人工核验。
|
|
4069
|
-
if (currentTask?.state !== 'partial' || reviewRequired > 0) {
|
|
4070
|
-
current = await workflowAction(actions, activeSessionId, currentTask, 'match', {
|
|
4071
|
-
qccRunId: run.runId,
|
|
4072
|
-
summary: {
|
|
4073
|
-
total: Number(summary.totalRows ?? runtime.rows.length),
|
|
4074
|
-
exact: Number(summary.enriched ?? 0) + Number(summary.fieldReview ?? 0),
|
|
4075
|
-
candidate: reviewRequired,
|
|
4076
|
-
confirmed: 0,
|
|
4077
|
-
unresolved: Number(summary.unresolved ?? summary.missingName ?? 0),
|
|
4078
|
-
failed: Number(summary.failed ?? 0),
|
|
4079
|
-
reviewRequired,
|
|
4080
|
-
},
|
|
4081
|
-
});
|
|
4082
|
-
}
|
|
4083
|
-
if (reviewRequired > 0) {
|
|
4084
|
-
actions.setStep('match');
|
|
4085
|
-
return current;
|
|
4086
|
-
}
|
|
4087
|
-
current = await workflowAction(actions, activeSessionId, current, 'enrich-start', { fieldSelection });
|
|
4088
|
-
current = await workflowAction(actions, activeSessionId, current, 'enrichment', {
|
|
4089
|
-
qccRunId: run.runId,
|
|
4090
|
-
summary: {
|
|
4091
|
-
total: Number(summary.totalRows ?? runtime.rows.length),
|
|
4092
|
-
completed: Number(summary.enriched ?? 0),
|
|
4093
|
-
unchanged: Number(summary.unresolved ?? 0) + Number(summary.missingName ?? 0),
|
|
4094
|
-
failed: Number(summary.failed ?? 0),
|
|
4095
|
-
reviewRequired: Number(summary.fieldReview ?? 0),
|
|
4096
|
-
callsUsed: Array.isArray(run.audit) ? run.audit.length : 0,
|
|
4097
|
-
},
|
|
4098
|
-
});
|
|
4099
|
-
actions.setStep(current.stage === 'download' ? 'download' : 'enrich');
|
|
4100
|
-
return current;
|
|
4758
|
+
if (!currentTask?.id) return null;
|
|
4759
|
+
return refreshHostedTask(actions, activeSessionId, currentTask.id, run, 'update');
|
|
4101
4760
|
};
|
|
4102
4761
|
|
|
4103
4762
|
const executePreparedQccCommand = async (payload) => {
|
|
@@ -4137,7 +4796,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4137
4796
|
};
|
|
4138
4797
|
|
|
4139
4798
|
const runQcc = async () => {
|
|
4140
|
-
if (busy || !cachedTask?.id || !hasData) return;
|
|
4799
|
+
if (busy || !canAction('prepare-qcc-command') || !cachedTask?.id || !hasData) return;
|
|
4141
4800
|
actions.setBusy(true);
|
|
4142
4801
|
actions.setError(null);
|
|
4143
4802
|
try {
|
|
@@ -4159,7 +4818,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4159
4818
|
};
|
|
4160
4819
|
|
|
4161
4820
|
const resolveCandidate = async (item, candidate) => {
|
|
4162
|
-
if (busy || !paidConfirmed || !qccRun) return;
|
|
4821
|
+
if (busy || !canAction('resolve-candidate') || !paidConfirmed || !qccRun) return;
|
|
4163
4822
|
actions.setBusy(true);
|
|
4164
4823
|
actions.setError(null);
|
|
4165
4824
|
try {
|
|
@@ -4180,7 +4839,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4180
4839
|
};
|
|
4181
4840
|
|
|
4182
4841
|
const retryFailures = async () => {
|
|
4183
|
-
if (busy || !paidConfirmed || !qccRun) return;
|
|
4842
|
+
if (busy || !canAction('retry-failed') || !paidConfirmed || !qccRun) return;
|
|
4184
4843
|
const names = [...new Set((qccRun.errors || []).filter((item) => item.error && item.error.retryable).map((item) => item.companyName))];
|
|
4185
4844
|
if (!names.length) return;
|
|
4186
4845
|
actions.setBusy(true);
|
|
@@ -4201,7 +4860,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4201
4860
|
}
|
|
4202
4861
|
};
|
|
4203
4862
|
|
|
4863
|
+
const retryDelivery = async () => {
|
|
4864
|
+
if (busy || !canAction('retry-delivery') || !cachedTask?.id) return;
|
|
4865
|
+
actions.setBusy(true);
|
|
4866
|
+
actions.setError(null);
|
|
4867
|
+
try {
|
|
4868
|
+
await workflowAction(actions, activeSessionId, cachedTask, 'retry-delivery');
|
|
4869
|
+
} catch (err) {
|
|
4870
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4871
|
+
} finally {
|
|
4872
|
+
actions.setBusy(false);
|
|
4873
|
+
}
|
|
4874
|
+
};
|
|
4875
|
+
|
|
4204
4876
|
const saveTaskSettings = async () => {
|
|
4877
|
+
if (!canAction('edit-rules')) return;
|
|
4205
4878
|
actions.setBusy(true);
|
|
4206
4879
|
actions.setError(null);
|
|
4207
4880
|
try {
|
|
@@ -4219,6 +4892,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4219
4892
|
};
|
|
4220
4893
|
|
|
4221
4894
|
const confirmRuleSettings = async () => {
|
|
4895
|
+
if (!canAction('confirm-rules')) return;
|
|
4222
4896
|
if (!hasData) { actions.setError('请先上传并解析数据。'); return; }
|
|
4223
4897
|
const anchorMappings = mappings.filter((mapping) => ['company_name', 'credit_no', 'reg_no'].includes(mapping.targetField));
|
|
4224
4898
|
if (!anchorMappings.length) { actions.setError('请至少映射企业名称、统一社会信用代码或注册号。'); return; }
|
|
@@ -4238,6 +4912,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4238
4912
|
columnCount: dataset.headers?.length || 0,
|
|
4239
4913
|
headers: dataset.headers || [],
|
|
4240
4914
|
sizeBytes: runtime.source?.sizeBytes || 0,
|
|
4915
|
+
checksum: runtime.source?.checksum || '',
|
|
4241
4916
|
},
|
|
4242
4917
|
});
|
|
4243
4918
|
}
|
|
@@ -4252,7 +4927,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4252
4927
|
?? mappings.find((mapping) => mapping.targetField === 'credit_no')
|
|
4253
4928
|
?? mappings.find((mapping) => mapping.targetField === 'reg_no');
|
|
4254
4929
|
if (primary) actions.setNameField(primary.sourceField);
|
|
4255
|
-
actions.setStep('profile');
|
|
4256
4930
|
} catch (ruleError) {
|
|
4257
4931
|
actions.setError(ruleError instanceof Error ? ruleError.message : String(ruleError));
|
|
4258
4932
|
} finally {
|
|
@@ -4269,9 +4943,16 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4269
4943
|
} catch (error) { actions.setError(error.message); return; }
|
|
4270
4944
|
};
|
|
4271
4945
|
|
|
4272
|
-
const startNewWorkflow = () => {
|
|
4946
|
+
const startNewWorkflow = (reuseConfig = false) => {
|
|
4273
4947
|
const event = document.createEvent('CustomEvent');
|
|
4274
|
-
event.initCustomEvent('dsh:data-cleaning-prompt-new', false, true, {
|
|
4948
|
+
event.initCustomEvent('dsh:data-cleaning-prompt-new', false, true, {
|
|
4949
|
+
sessionId: activeSessionId,
|
|
4950
|
+
...(reuseConfig && cachedTask ? { reuseConfig: {
|
|
4951
|
+
objectives: cachedTask.objectives || [],
|
|
4952
|
+
fieldSelection: cachedTask.fieldSelection || [],
|
|
4953
|
+
matchRules: cachedTask.matchRules || {},
|
|
4954
|
+
} } : {}),
|
|
4955
|
+
});
|
|
4275
4956
|
if (document.dispatchEvent(event)) {
|
|
4276
4957
|
actions.setError('当前会话的任务向导尚未就绪或正在解析图片,请稍后重试。');
|
|
4277
4958
|
return;
|
|
@@ -4292,6 +4973,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4292
4973
|
const createArtifactBundle = async () => {
|
|
4293
4974
|
let latest = workflowTaskBySession.get(String(activeSessionId || 'unassigned')) ?? cachedTask;
|
|
4294
4975
|
if (!latest) throw new Error('请先创建并执行数据清洗补全任务。');
|
|
4976
|
+
if (!canAction('create-local-artifacts')) throw new Error('当前流程节点不能生成本地结果。');
|
|
4295
4977
|
if (Array.isArray(latest.artifacts) && latest.artifacts.length) return latest;
|
|
4296
4978
|
const rows = exportRows();
|
|
4297
4979
|
if (!rows?.length) throw new Error('当前页面没有可导出的明细。若任务来自历史记录,请重新上传源文件并继续处理。');
|
|
@@ -4372,6 +5054,37 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4372
5054
|
: cachedTask?.enrichmentSummary?.reviewRequired
|
|
4373
5055
|
?? cachedTask?.matchSummary?.reviewRequired;
|
|
4374
5056
|
const qccFailedCount = qccRun?.summary?.failed ?? cachedTask?.enrichmentSummary?.failed ?? cachedTask?.matchSummary?.failed ?? 0;
|
|
5057
|
+
const viewedStageLabel = workflowStageLabel(normalizedStage);
|
|
5058
|
+
const currentStageLabel = workflowStageLabel(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload');
|
|
5059
|
+
const taskStatusLabel = viewAccess === 'read'
|
|
5060
|
+
? '已完成'
|
|
5061
|
+
: cachedTask?.source?.type === 'image' && cachedTask?.state === 'draft'
|
|
5062
|
+
? '图片已暂存,待识别'
|
|
5063
|
+
: WORKFLOW_STATE_LABELS[cachedTask?.state] || cachedTask?.state || '待开始';
|
|
5064
|
+
const viewedStageCompleted = viewAccess === 'read' || cachedTask?.state === 'completed';
|
|
5065
|
+
const taskTimestamp = cachedTask?.updatedAt || cachedTask?.createdAt;
|
|
5066
|
+
const taskTimeLabel = taskTimestamp && Number.isFinite(new Date(taskTimestamp).getTime())
|
|
5067
|
+
? new Date(taskTimestamp).toLocaleString('zh-CN', {
|
|
5068
|
+
year: 'numeric', month: 'numeric', day: 'numeric',
|
|
5069
|
+
hour: '2-digit', minute: '2-digit', hour12: false,
|
|
5070
|
+
})
|
|
5071
|
+
: '';
|
|
5072
|
+
const taskFileName = cachedTask?.source?.fileName || '';
|
|
5073
|
+
const taskSourceLabel = taskFileName
|
|
5074
|
+
|| (cachedTask?.source?.type === 'image'
|
|
5075
|
+
? '图片名单'
|
|
5076
|
+
: cachedTask?.source
|
|
5077
|
+
? '文本粘贴'
|
|
5078
|
+
: '');
|
|
5079
|
+
const taskRowCount = cachedTask?.source?.rowCount ?? dataset?.rowCount ?? 0;
|
|
5080
|
+
const taskFieldCount = cachedTask?.fieldSelection?.length ?? fieldSelection.length;
|
|
5081
|
+
const hasTaskResultSummary = Boolean(qccRun || cachedTask?.enrichmentSummary || cachedTask?.matchSummary);
|
|
5082
|
+
const taskCompletedCount = Number.isFinite(qccEnrichedCount) ? qccEnrichedCount : 0;
|
|
5083
|
+
const taskReviewCount = Number.isFinite(qccReviewCount) ? qccReviewCount : 0;
|
|
5084
|
+
const taskFailedCount = Number.isFinite(qccFailedCount) ? qccFailedCount : 0;
|
|
5085
|
+
const taskCompletedMetric = `${taskCompletedCount}/${taskRowCount || taskCompletedCount} 条`;
|
|
5086
|
+
const taskReviewMetric = `${taskReviewCount} 条`;
|
|
5087
|
+
const taskFailedMetric = `${taskFailedCount} 条`;
|
|
4375
5088
|
const stat = (label, value, tone) => h('div', { className: 'dcAgentCard' },
|
|
4376
5089
|
h('span', null, label),
|
|
4377
5090
|
h('b', { className: tone ? `is-${tone}` : null }, displayStatValue(value))
|
|
@@ -4408,8 +5121,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4408
5121
|
h('button', {
|
|
4409
5122
|
type: 'button',
|
|
4410
5123
|
className: 'dcAgentButton is-primary',
|
|
4411
|
-
onClick: startNewWorkflow,
|
|
5124
|
+
onClick: () => startNewWorkflow(false),
|
|
4412
5125
|
}, '新建清洗任务'),
|
|
5126
|
+
cachedTask ? h('button', {
|
|
5127
|
+
type: 'button',
|
|
5128
|
+
className: 'dcAgentButton',
|
|
5129
|
+
onClick: () => startNewWorkflow(true),
|
|
5130
|
+
}, '新建任务并复用配置') : null,
|
|
4413
5131
|
),
|
|
4414
5132
|
historyTask ? h('section', { className: 'dcAgentSection', 'aria-label': '历史任务只读详情' },
|
|
4415
5133
|
h('h3', null, workflowDisplayName(historyTask)),
|
|
@@ -4445,8 +5163,48 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4445
5163
|
),
|
|
4446
5164
|
);
|
|
4447
5165
|
} else if (step === 'rules') {
|
|
4448
|
-
|
|
5166
|
+
if (!canAction('edit-rules') && !canAction('confirm-rules')) {
|
|
5167
|
+
pane = h('div', { className: 'dcAgentPane' },
|
|
5168
|
+
h('p', { className: 'dcAgentHint' }, isCurrentView
|
|
5169
|
+
? '规则已经确认,当前只等待生成质量体检。'
|
|
5170
|
+
: '这是已确认规则的只读摘要。'),
|
|
5171
|
+
h('section', { className: 'dcAgentSection' },
|
|
5172
|
+
h('h3', null, taskTitle),
|
|
5173
|
+
h('div', { className: 'dcAgentChips' },
|
|
5174
|
+
mappings.map((mapping) => h('span', { key: mapping.sourceField, className: 'dcAgentChip' },
|
|
5175
|
+
`${mapping.sourceField} → ${resultFieldLabel(mapping.targetField)}`))),
|
|
5176
|
+
h('p', { className: 'dcAgentHint' }, `任务目标 ${objectives.length} 项 · 补全字段 ${fieldSelection.length} 项`),
|
|
5177
|
+
),
|
|
5178
|
+
canAction('run-quality') ? h('button', {
|
|
5179
|
+
type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !runtime.rows.length,
|
|
5180
|
+
onClick: runProfile,
|
|
5181
|
+
}, busy ? '体检中…' : '生成质量体检报告') : null,
|
|
5182
|
+
);
|
|
5183
|
+
} else pane = h('div', { className: 'dcAgentPane' },
|
|
4449
5184
|
h('p', { className: 'dcAgentHint' }, '确认任务名称、字段映射、匹配规则和输出字段。保存草稿不会调用企查查;确认规则后才可进入质量体检。'),
|
|
5185
|
+
canAction('import-data') ? h('section', { className: 'dcAgentSection' },
|
|
5186
|
+
h('div', { className: 'dcAgentRow' },
|
|
5187
|
+
h('h3', null, '当前来源'),
|
|
5188
|
+
!intakeEditorOpen ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5189
|
+
onClick: () => { actions.setError(null); setIntakeEditorOpen(true); } }, '重新导入') : null,
|
|
5190
|
+
),
|
|
5191
|
+
h('p', { className: 'dcAgentHint' }, `${intake.fileName} · ${dataset?.rowCount || 0} 行`),
|
|
5192
|
+
showIntakeEditor ? h('div', { className: 'dcAgentIntakeEditor' },
|
|
5193
|
+
h('div', { className: 'dcAgentIntakeSourceActions' },
|
|
5194
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5195
|
+
onClick: (event) => event.currentTarget.nextElementSibling?.click() }, '选择文件(自动解析)'),
|
|
5196
|
+
h('input', { type: 'file', accept: '.csv,.json,.xlsx,.xls,image/png,image/jpeg,image/webp',
|
|
5197
|
+
style: { display: 'none' }, disabled: busy, 'aria-label': '选择数据文件', onChange: handleFile }),
|
|
5198
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5199
|
+
onClick: () => openImageIntake() }, '图片识别(粘贴 / 选择)'),
|
|
5200
|
+
),
|
|
5201
|
+
h('textarea', { className: 'dcAgentTextarea', value: input, disabled: busy,
|
|
5202
|
+
'aria-label': '粘贴数据', placeholder: '粘贴 CSV、JSON 或逐行企业名单',
|
|
5203
|
+
onInput: (event) => { actions.setInput(event.target.value); actions.setError(null); } }),
|
|
5204
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !intake.pendingText,
|
|
5205
|
+
'aria-label': '解析数据', onClick: handleParse }, busy ? '解析中…' : '解析粘贴内容'),
|
|
5206
|
+
) : null,
|
|
5207
|
+
) : null,
|
|
4450
5208
|
h('label', { className: 'dcAgentFormField' },
|
|
4451
5209
|
h('span', null, '任务名称'),
|
|
4452
5210
|
h('input', { className: 'dcAgentField', value: taskTitle, maxLength: 120, onChange: (event) => actions.setTaskTitle(event.target.value) }),
|
|
@@ -4571,12 +5329,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4571
5329
|
type: 'button',
|
|
4572
5330
|
className: 'dcAgentButton is-primary',
|
|
4573
5331
|
disabled: busy,
|
|
4574
|
-
onClick: () =>
|
|
5332
|
+
onClick: () => navigate(requiresQcc ? 'match' : 'enrich'),
|
|
4575
5333
|
}, requiresQcc ? '下一步:匹配核验' : '下一步:本地清洗补全'),
|
|
4576
5334
|
),
|
|
4577
|
-
) : h('div', { className: 'dcAgentProfileEmpty' },
|
|
5335
|
+
) : canAction('run-quality') ? h('div', { className: 'dcAgentProfileEmpty' },
|
|
4578
5336
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData, 'aria-label': '生成体检报告', onClick: runProfile }, busy ? '体检中…' : '生成体检报告'),
|
|
4579
|
-
),
|
|
5337
|
+
) : h('p', { className: 'dcAgentHint' }, '当前没有可恢复的质量体检明细。'),
|
|
4580
5338
|
),
|
|
4581
5339
|
);
|
|
4582
5340
|
} else if (step === 'match') {
|
|
@@ -4599,19 +5357,20 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4599
5357
|
h('section', { className: 'dcAgentMatchBlock', 'aria-label': '准备任务说明' },
|
|
4600
5358
|
h('div', { className: 'dcAgentMatchBlockHead' },
|
|
4601
5359
|
h('h4', null, '准备任务说明'),
|
|
4602
|
-
h('span', { className: 'dcAgentMatchScope' }, h('b', null,
|
|
5360
|
+
h('span', { className: 'dcAgentMatchScope' }, h('b', null,
|
|
5361
|
+
dataset?.rowCount ?? cachedTask?.source?.rowCount ?? runtime.rows.length), '条记录'),
|
|
4603
5362
|
),
|
|
4604
5363
|
h('p', { className: 'dcAgentHint' }, `已选择 ${fieldSelection.length} 个补全字段。Host 自动检查所需工具并计算去重主体与调用上界。`),
|
|
4605
5364
|
h('p', { className: 'dcAgentHint' }, '回填后可修改;点击发送才开始查询,使用你已连接的企查查 MCP 账号及额度。'),
|
|
4606
5365
|
),
|
|
4607
5366
|
),
|
|
4608
|
-
h('div', { className: 'dcAgentMatchActions' },
|
|
5367
|
+
canAction('prepare-qcc-command') ? h('div', { className: 'dcAgentMatchActions' },
|
|
4609
5368
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData || !cachedTask?.id, onClick: runQcc }, busy ? '检查并生成中…' : '生成可编辑任务说明'),
|
|
4610
|
-
),
|
|
5369
|
+
) : null,
|
|
4611
5370
|
),
|
|
4612
5371
|
qccRun ? h('section', { className: 'dcAgentSection' },
|
|
4613
5372
|
h('h3', null, `任务 ${qccRun.runId} · ${qccRun.state}`),
|
|
4614
|
-
(
|
|
5373
|
+
canAction('resolve-candidate') && (qccRun.reviewQueue || []).length ? h('label', { className: 'dcAgentCheck' },
|
|
4615
5374
|
h('input', { type: 'checkbox', checked: paidConfirmed, 'aria-label': '确认候选或重试的企查查调用', onChange: (event) => actions.setPaidConfirmed(event.target.checked) }),
|
|
4616
5375
|
'确认主体或重试会继续查询,使用当前连接的企查查账号;额度或费用由该账号自行承担。',
|
|
4617
5376
|
) : null,
|
|
@@ -4631,10 +5390,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4631
5390
|
item.candidates.map((candidate) => h('div', { key: candidate.creditNo, className: 'dcAgentCandidate' },
|
|
4632
5391
|
h('div', null, h('b', null, candidate.companyName || '未命名候选'), h('small', null, candidate.creditNo)),
|
|
4633
5392
|
h('small', null, `${candidate.status || '状态未知'} · ${(candidate.legalRep || []).join('、') || '法人未知'}`),
|
|
4634
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: () => resolveCandidate(item, candidate) }, '确认此主体'),
|
|
5393
|
+
canAction('resolve-candidate') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: () => resolveCandidate(item, candidate) }, '确认此主体') : null,
|
|
4635
5394
|
)),
|
|
4636
5395
|
)),
|
|
4637
|
-
(qccRun.errors || []).some((item) => item.error?.retryable) ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: retryFailures }, '重试可恢复失败项') : null,
|
|
4638
5396
|
) : null,
|
|
4639
5397
|
);
|
|
4640
5398
|
} else if (step === 'enrich') {
|
|
@@ -4646,10 +5404,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4646
5404
|
),
|
|
4647
5405
|
h('section', { className: 'dcAgentSection' },
|
|
4648
5406
|
h('h3', null, '本地清洗预处理'),
|
|
4649
|
-
h('div', { className: 'dcAgentRow' },
|
|
4650
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行清洗', onClick: runClean }, busy ? '处理中…' : '执行确定性清洗'),
|
|
4651
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行补全', onClick: runComplete }, busy ? '处理中…' : '本地规则补全'),
|
|
4652
|
-
),
|
|
5407
|
+
canAction('run-local-clean') || canAction('run-local-complete') ? h('div', { className: 'dcAgentRow' },
|
|
5408
|
+
canAction('run-local-clean') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行清洗', onClick: runClean }, busy ? '处理中…' : '执行确定性清洗') : null,
|
|
5409
|
+
canAction('run-local-complete') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行补全', onClick: runComplete }, busy ? '处理中…' : '本地规则补全') : null,
|
|
5410
|
+
) : null,
|
|
4653
5411
|
clean ? h('div', { className: 'dcAgentGrid' },
|
|
4654
5412
|
stat('总数', clean.total), stat('保留', clean.kept, 'good'), stat('剔除', clean.dropped, 'bad'),
|
|
4655
5413
|
stat('缺失关键字段', clean.badMissing, clean.badMissing > 0 ? 'bad' : null),
|
|
@@ -4663,13 +5421,28 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4663
5421
|
stat('QCC 已补全', qccEnrichedCount, qccEnrichedCount > 0 ? 'good' : null),
|
|
4664
5422
|
stat('待核验', qccReviewCount, qccReviewCount > 0 ? 'warn' : null),
|
|
4665
5423
|
),
|
|
4666
|
-
h('div', { className: 'dcAgentRow' }, h('button', {
|
|
5424
|
+
canAction('create-local-artifacts') ? h('div', { className: 'dcAgentRow' }, h('button', {
|
|
5425
|
+
type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !exportRows()?.length,
|
|
5426
|
+
onClick: () => createAndDownload('complete', 'xlsx'),
|
|
5427
|
+
}, '生成结果')) : null,
|
|
4667
5428
|
);
|
|
4668
5429
|
} else if (step === 'download') {
|
|
4669
|
-
const
|
|
5430
|
+
const artifactsByKind = new Map();
|
|
5431
|
+
for (const artifact of cachedTask?.artifacts || []) {
|
|
5432
|
+
if (artifact.format === 'xlsx' && (artifact.kind !== 'review' || artifact.rowCount > 0)) {
|
|
5433
|
+
artifactsByKind.set(artifact.kind, artifact);
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
const availableArtifacts = [...artifactsByKind.values()];
|
|
4670
5437
|
const resultArtifacts = availableArtifacts.filter(artifact => artifact.kind !== 'report');
|
|
4671
5438
|
const reportArtifact = availableArtifacts.find(artifact => artifact.kind === 'report');
|
|
4672
|
-
const canCreateArtifacts = Boolean(exportRows()?.length) &&
|
|
5439
|
+
const canCreateArtifacts = Boolean(exportRows()?.length) && canAction('create-local-artifacts');
|
|
5440
|
+
const retryableErrors = (qccRun?.errors || []).filter((item) => item.error?.retryable);
|
|
5441
|
+
const canRetryFailures = canAction('retry-failed')
|
|
5442
|
+
&& cachedTask?.runtimeCapabilities?.liveRunAvailable
|
|
5443
|
+
&& retryableErrors.length > 0;
|
|
5444
|
+
const canRetryDelivery = canAction('retry-delivery')
|
|
5445
|
+
&& cachedTask?.runtimeCapabilities?.liveRunAvailable;
|
|
4673
5446
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4674
5447
|
h('p', { className: 'dcAgentHint' }, '结果文件由 Host 保存;有失败、未匹配或待核验记录时展示异常清单。任务或插件重启后,可从任务历史继续下载;无需再次调用企查查。'),
|
|
4675
5448
|
h('div', { className: 'dcAgentResultSummary' }, h('div', { className: 'dcAgentGrid' },
|
|
@@ -4706,10 +5479,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4706
5479
|
),
|
|
4707
5480
|
!canCreateArtifacts ? h('p', { className: 'dcAgentHint' }, '请先完成规则确认与清洗补全;历史任务若尚未生成制品,需要重新上传源文件。') : null,
|
|
4708
5481
|
),
|
|
5482
|
+
canRetryFailures ? h('section', { className: 'dcAgentSection' },
|
|
5483
|
+
h('label', { className: 'dcAgentCheck' },
|
|
5484
|
+
h('input', { type: 'checkbox', checked: paidConfirmed, 'aria-label': '确认重试的企查查调用', onChange: (event) => actions.setPaidConfirmed(event.target.checked) }),
|
|
5485
|
+
'重试会继续查询,使用当前连接的企查查账号;额度或费用由该账号自行承担。',
|
|
5486
|
+
),
|
|
5487
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: retryFailures },
|
|
5488
|
+
`重试可恢复失败项 · ${retryableErrors.length}`),
|
|
5489
|
+
) : null,
|
|
5490
|
+
canRetryDelivery ? h('button', {
|
|
5491
|
+
type: 'button', className: 'dcAgentButton', disabled: busy, onClick: retryDelivery,
|
|
5492
|
+
}, '重新生成结果文件') : null,
|
|
4709
5493
|
);
|
|
4710
5494
|
} else {
|
|
4711
5495
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4712
|
-
dataset ? h('section', { className: 'dcAgentSection dcAgentIntakeSummary', 'aria-label': '当前导入数据', role: 'status' },
|
|
5496
|
+
dataset || cachedTask?.source ? h('section', { className: 'dcAgentSection dcAgentIntakeSummary', 'aria-label': '当前导入数据', role: 'status' },
|
|
4713
5497
|
h('div', { className: 'dcAgentIntakeSummaryHead' },
|
|
4714
5498
|
h('div', null,
|
|
4715
5499
|
h('h3', null, intake.available ? `已解析 ${intake.count} 条数据` : '已保存导入摘要'),
|
|
@@ -4717,8 +5501,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4717
5501
|
),
|
|
4718
5502
|
),
|
|
4719
5503
|
h('p', { className: 'dcAgentHint' }, intake.available
|
|
4720
|
-
? '
|
|
4721
|
-
|
|
5504
|
+
? canAction('import-data')
|
|
5505
|
+
? '数据已导入。请核对下方完整名单;需要替换来源时点击“重新导入”。'
|
|
5506
|
+
: '这是已导入来源的只读摘要。更换名单请从任务历史新建任务并复用配置。'
|
|
5507
|
+
: canAction('import-data')
|
|
5508
|
+
? '当前页面没有完整原始数据。请重新导入原文件;历史下载制品仍可从任务历史获取。'
|
|
5509
|
+
: '当前页面只保留 Host 来源摘要;已有制品可从结果下载或任务历史获取。'),
|
|
4722
5510
|
) : null,
|
|
4723
5511
|
showIntakeEditor ? h('section', { id: 'dc-agent-intake-editor', className: 'dcAgentSection dcAgentIntakeEditor', 'aria-label': dataset ? '重新导入数据' : '导入数据' },
|
|
4724
5512
|
h('header', { className: 'dcAgentIntakeEditorHeader' },
|
|
@@ -4776,16 +5564,47 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4776
5564
|
}) : null,
|
|
4777
5565
|
hasData ? h('p', { className: 'dcAgentHint' }, '请核对完整名单。识别清单不代表已完成企查查匹配。核对后进入「规则与体检」,确认规则生成质量体检;再进入「主体匹配」直接生成可编辑任务说明。Host 自动检查工具和调用范围,回填中央对话框后点击发送才开始查询。') : null,
|
|
4778
5566
|
h('div', { className: 'dcAgentUploadActions' },
|
|
4779
|
-
hasData && !intakeEditorOpen ? h('button', {
|
|
5567
|
+
hasData && !intakeEditorOpen && canAction('import-data') ? h('button', {
|
|
4780
5568
|
type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
4781
5569
|
'aria-expanded': false, 'aria-controls': 'dc-agent-intake-editor',
|
|
4782
5570
|
onClick: () => { actions.setError(null); setIntakeEditorOpen(true); },
|
|
4783
5571
|
}, '重新导入') : null,
|
|
4784
|
-
hasData
|
|
5572
|
+
hasData && (canAction('import-data') || reviewingImportedDataset) ? h('button', {
|
|
5573
|
+
type: 'button', className: 'dcAgentButton is-primary',
|
|
5574
|
+
disabled: busy || !intake.available || intake.pendingText,
|
|
5575
|
+
onClick: () => navigate('rules'),
|
|
5576
|
+
}, reviewingImportedDataset ? '确认名单,进入规则与体检' : '已核对清单,下一步:字段映射与规则') : null,
|
|
4785
5577
|
),
|
|
4786
5578
|
);
|
|
4787
5579
|
}
|
|
4788
5580
|
|
|
5581
|
+
const runtimeRecoveryPane = runtimeRecoveryAllowed ? h('div', { className: 'dcAgentPane' },
|
|
5582
|
+
h('section', { className: 'dcAgentSection', role: 'status' },
|
|
5583
|
+
h('h3', null, '重新载入原任务数据'),
|
|
5584
|
+
h('p', { className: 'dcAgentHint' }, cachedTask.source?.checksum
|
|
5585
|
+
? '原始名单未持久化。请选择或粘贴同一份数据;Host 校验一致后只恢复本页运行时数据,不改变任务状态。'
|
|
5586
|
+
: '该旧任务缺少同源校验信息,不能恢复写操作。'),
|
|
5587
|
+
cachedTask.source?.checksum ? h('div', { className: 'dcAgentRow' },
|
|
5588
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5589
|
+
onClick: (event) => event.currentTarget.nextElementSibling?.click() }, '选择原文件'),
|
|
5590
|
+
h('input', { type: 'file', accept: '.csv,.json,.xlsx,.xls', style: { display: 'none' },
|
|
5591
|
+
disabled: busy, 'aria-label': '重新载入原任务文件', onChange: recoverRuntimeFile }),
|
|
5592
|
+
) : null,
|
|
5593
|
+
cachedTask.source?.checksum ? h('textarea', {
|
|
5594
|
+
className: 'dcAgentTextarea', value: input, disabled: busy,
|
|
5595
|
+
placeholder: '也可以粘贴原 CSV、JSON 或逐行企业名单',
|
|
5596
|
+
'aria-label': '重新载入原任务文本',
|
|
5597
|
+
onInput: (event) => { actions.setInput(event.target.value); actions.setError(null); },
|
|
5598
|
+
}) : null,
|
|
5599
|
+
cachedTask.source?.checksum ? h('button', {
|
|
5600
|
+
type: 'button', className: 'dcAgentButton', disabled: busy || !String(input || '').trim(),
|
|
5601
|
+
onClick: recoverRuntimeText,
|
|
5602
|
+
}, '校验并恢复') : null,
|
|
5603
|
+
h('button', { type: 'button', className: 'dcAgentButton', onClick: () => startNewWorkflow(true) },
|
|
5604
|
+
'新建任务并复用配置'),
|
|
5605
|
+
),
|
|
5606
|
+
) : null;
|
|
5607
|
+
|
|
4789
5608
|
return h('section', {
|
|
4790
5609
|
className: 'dcAgentWorkbenchContent',
|
|
4791
5610
|
'data-dsh-plugin': 'data-cleaning-agent',
|
|
@@ -4794,54 +5613,75 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4794
5613
|
},
|
|
4795
5614
|
h('nav', { className: 'dcAgentManagement', 'aria-label': '工作台管理' },
|
|
4796
5615
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step !== 'history',
|
|
4797
|
-
onClick: () =>
|
|
5616
|
+
onClick: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload') }, '当前任务'),
|
|
4798
5617
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'history',
|
|
4799
|
-
onClick: () =>
|
|
5618
|
+
onClick: () => navigate('history') }, '任务历史'),
|
|
4800
5619
|
),
|
|
4801
5620
|
step !== 'history' ? h('nav', { className: 'dcAgentStepper', 'aria-label': '清洗流程',
|
|
4802
5621
|
style: { '--dc-stage-count': STEPS.length } },
|
|
4803
|
-
STEPS.map((st) =>
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
'
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
5622
|
+
STEPS.map((st) => {
|
|
5623
|
+
const access = cachedTask
|
|
5624
|
+
? resolveStageAccess(st.key, cachedTask.flow)
|
|
5625
|
+
: st.key === 'upload' ? 'current' : 'locked';
|
|
5626
|
+
const locked = access === 'locked';
|
|
5627
|
+
const lockHint = locked
|
|
5628
|
+
? `尚未开放,请先完成“${currentStageLabel}”`
|
|
5629
|
+
: st.label;
|
|
5630
|
+
return h('button', {
|
|
5631
|
+
key: st.key,
|
|
5632
|
+
type: 'button',
|
|
5633
|
+
className: `dcAgentStep${(step === 'profile' ? 'rules' : step) === st.key ? ' is-active' : ''}`,
|
|
5634
|
+
'aria-label': st.label,
|
|
5635
|
+
title: lockHint,
|
|
5636
|
+
'aria-current': (step === 'profile' ? 'rules' : step) === st.key ? 'step' : undefined,
|
|
5637
|
+
'aria-disabled': locked ? 'true' : undefined,
|
|
5638
|
+
'aria-description': locked ? lockHint : undefined,
|
|
5639
|
+
'data-access': access,
|
|
5640
|
+
onClick: () => navigate(st.key),
|
|
5641
|
+
}, h('span', { className: 'dcAgentStepIcon' }, h(StageIcon, { kind: st.icon })),
|
|
5642
|
+
h('span', { className: 'dcAgentStepLabel' }, st.label));
|
|
5643
|
+
}),
|
|
4813
5644
|
) : null,
|
|
4814
5645
|
h('div', { className: 'dcAgentWbBody' },
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
5646
|
+
step !== 'history' ? h('section', { className: 'dcAgentTaskContext', 'aria-label': '任务状态与流程说明' },
|
|
5647
|
+
cachedTask ? h(WorkflowTaskContext, {
|
|
5648
|
+
task: cachedTask,
|
|
5649
|
+
stageLabel: viewedStageLabel,
|
|
5650
|
+
viewAccess,
|
|
5651
|
+
currentStageLabel,
|
|
5652
|
+
statusLabel: taskStatusLabel,
|
|
5653
|
+
stageCompleted: viewedStageCompleted,
|
|
5654
|
+
timeLabel: taskTimeLabel,
|
|
5655
|
+
timeValue: taskTimestamp,
|
|
5656
|
+
sourceLabel: taskSourceLabel,
|
|
5657
|
+
rowCount: taskRowCount,
|
|
5658
|
+
fieldCount: taskFieldCount,
|
|
5659
|
+
hasResultSummary: hasTaskResultSummary,
|
|
5660
|
+
completedMetric: taskCompletedMetric,
|
|
5661
|
+
reviewMetric: taskReviewMetric,
|
|
5662
|
+
failedMetric: taskFailedMetric,
|
|
5663
|
+
commandProgress,
|
|
5664
|
+
onReturn: reviewingImportedDataset
|
|
5665
|
+
? null
|
|
5666
|
+
: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload'),
|
|
4825
5667
|
}) : null,
|
|
4826
|
-
h(
|
|
4827
|
-
cachedTask.source?.type === 'image' && cachedTask.state === 'draft' ? '请发送对话中的任务说明开始图片识别,识别后核验企业名单。' : Number.isFinite(qccEnrichedCount) ? `已处理并补全 ${qccEnrichedCount} 条 · 待核验 ${Number.isFinite(qccReviewCount) ? qccReviewCount : '统计中'} 条 · 失败 ${qccFailedCount} 条(按输入行统计;未披露字段保留空白)` : '结果统计中,处理结束后展示补全与待核验数量'),
|
|
4828
|
-
) : null,
|
|
4829
|
-
error ? h('div', { className: 'dcAgentError', role: 'alert' }, error) : null,
|
|
4830
|
-
step !== 'history' ? h('details', { className: 'dcAgentSection' },
|
|
4831
|
-
h('summary', null, '使用说明与任务流程'),
|
|
4832
|
-
h('p', { className: 'dcAgentHint' }, '导入与核验 → 规则与体检 → 主体匹配 → 字段补全 → 结果下载。菜单只切换视图,不启动调用;按页面主按钮逐步确认。'),
|
|
4833
|
-
h('p', { className: 'dcAgentHint' }, '支持文本、Excel、JSON 和图片名单。原值保留、多候选人工核验;企查查调用使用当前用户自己的连接与额度。'),
|
|
5668
|
+
h(WorkflowGuide),
|
|
4834
5669
|
) : null,
|
|
5670
|
+
error ? h('div', {
|
|
5671
|
+
className: error.startsWith('流程提示:') ? 'dcAgentNavigationNotice' : 'dcAgentError',
|
|
5672
|
+
role: error.startsWith('流程提示:') ? 'status' : 'alert',
|
|
5673
|
+
'aria-live': 'polite',
|
|
5674
|
+
}, error) : null,
|
|
4835
5675
|
['rules', 'profile'].includes(step) ? h('nav', { className: 'dcAgentViewTabs', 'aria-label': '规则与体检视图' },
|
|
4836
|
-
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'rules', onClick: () =>
|
|
4837
|
-
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'profile', onClick: () =>
|
|
5676
|
+
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'rules', onClick: () => navigate('rules') }, '字段映射与规则'),
|
|
5677
|
+
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'profile', onClick: () => navigate('profile') }, '质量体检报告'),
|
|
4838
5678
|
) : null,
|
|
4839
|
-
workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))
|
|
5679
|
+
runtimeRecoveryPane || (workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))
|
|
4840
5680
|
? h('section', { className: 'dcAgentSection', role: 'status' },
|
|
4841
5681
|
h('p', null, workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))),
|
|
4842
5682
|
h('button', { type: 'button', className: 'dcAgentButton',
|
|
4843
|
-
onClick: () =>
|
|
4844
|
-
: pane,
|
|
5683
|
+
onClick: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload') }, '返回当前节点'))
|
|
5684
|
+
: pane),
|
|
4845
5685
|
),
|
|
4846
5686
|
);
|
|
4847
5687
|
}
|
|
@@ -4940,6 +5780,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4940
5780
|
}
|
|
4941
5781
|
let disposed = false;
|
|
4942
5782
|
const instances = new Map();
|
|
5783
|
+
const revealedCommands = new Set();
|
|
4943
5784
|
const spec = createWorkbenchStore();
|
|
4944
5785
|
const instanceFor = sessionId => {
|
|
4945
5786
|
if (!sessionId) throw new Error('请先打开数据清洗会话。');
|
|
@@ -4999,7 +5840,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4999
5840
|
react.useEffect(() => {
|
|
5000
5841
|
instance.actions.openAt(null, props.scope.sessionId);
|
|
5001
5842
|
return adapter.attach(props.scope, { tabId: props.tab.id, store: props.store,
|
|
5002
|
-
navigate: destination =>
|
|
5843
|
+
navigate: destination => navigateWorkflow(
|
|
5844
|
+
instance.actions,
|
|
5845
|
+
props.scope.sessionId,
|
|
5846
|
+
destination || instance.getSnapshot().step,
|
|
5847
|
+
instance.getSnapshot().workflowTask,
|
|
5848
|
+
) });
|
|
5003
5849
|
}, [instance, props.scope.sessionId, props.tab.id, props.store]);
|
|
5004
5850
|
return h(WorkbenchContent, { useStore, actions: instance.actions, visible: props.visible,
|
|
5005
5851
|
sendSessionCommand: (sessionId, prompt) => sendQccAgentCommand(ctx, sessionId, prompt),
|
|
@@ -5054,26 +5900,58 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5054
5900
|
sessions.open(task.originSessionId);
|
|
5055
5901
|
// Navigation only: no task binding, command preparation, or draft writes.
|
|
5056
5902
|
},
|
|
5903
|
+
revealSubmitted(sessionId, commandId, step) {
|
|
5904
|
+
if (disposed || !sessionId) return false;
|
|
5905
|
+
const key = commandId && `${sessionId}:${commandId}`;
|
|
5906
|
+
if (key && revealedCommands.has(key)) return false;
|
|
5907
|
+
// Consume the event even if its owner is no longer visible. Polling must
|
|
5908
|
+
// not pull open an old task when the user later returns to its Session.
|
|
5909
|
+
if (key) revealedCommands.add(key);
|
|
5910
|
+
const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
|
|
5911
|
+
if (sessions?.list?.getSnapshot?.()?.current !== sessionId || !isCleaningSession(sessionId)) return false;
|
|
5912
|
+
try { return controller.open(sessionId, step); }
|
|
5913
|
+
catch (error) {
|
|
5914
|
+
instanceFor(sessionId).actions.setError(error.message || String(error));
|
|
5915
|
+
return false;
|
|
5916
|
+
}
|
|
5917
|
+
},
|
|
5057
5918
|
storeFor: instanceFor,
|
|
5058
5919
|
actionsFor: sessionId => instanceFor(sessionId).actions,
|
|
5059
5920
|
open(sessionId, step) {
|
|
5060
5921
|
if (disposed || !sessionId) return false;
|
|
5061
5922
|
const instance = instanceFor(sessionId);
|
|
5062
|
-
|
|
5923
|
+
const destination = workflowDestination(
|
|
5924
|
+
step,
|
|
5925
|
+
workflowTaskBySession.get(String(sessionId)) ?? instance.getSnapshot().workflowTask,
|
|
5926
|
+
instance.getSnapshot().step,
|
|
5927
|
+
);
|
|
5928
|
+
instance.actions.openAt(destination.stage, sessionId);
|
|
5063
5929
|
const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
|
|
5064
5930
|
const summary = sessions?.list?.getSnapshot?.()?.byId?.[sessionId];
|
|
5065
5931
|
openingSession = sessionId;
|
|
5066
|
-
|
|
5067
|
-
|
|
5932
|
+
let opened;
|
|
5933
|
+
try { opened = adapter.open({ sessionId, ...(summary?.cwd ? { cwd: summary.cwd } : {}) },
|
|
5934
|
+
destination.stage); }
|
|
5068
5935
|
finally { openingSession = null; }
|
|
5936
|
+
if (destination.compatibility) {
|
|
5937
|
+
instance.actions.setError('页面与 Host 流程版本不一致,当前任务已切换为只读模式;请完整重启并升级 Host。');
|
|
5938
|
+
} else if (destination.access === 'locked') {
|
|
5939
|
+
instance.actions.setError(lockedWorkflowNotice(
|
|
5940
|
+
step,
|
|
5941
|
+
workflowTaskBySession.get(String(sessionId)) ?? instance.getSnapshot().workflowTask,
|
|
5942
|
+
destination.stage,
|
|
5943
|
+
));
|
|
5944
|
+
}
|
|
5945
|
+
return opened;
|
|
5069
5946
|
},
|
|
5070
5947
|
};
|
|
5071
5948
|
sessionWorkbenchController = controller;
|
|
5949
|
+
const stopSubmittedBridge = installSubmittedWorkbenchBridge(ctx, controller);
|
|
5072
5950
|
const listeners = {
|
|
5073
5951
|
[WORKBENCH_OPEN_EVENT]: event => {
|
|
5074
5952
|
const detail = event.detail || {};
|
|
5075
5953
|
const actions = controller.actionsFor(detail.sessionId);
|
|
5076
|
-
if (detail.task?.id) cacheWorkflowTask(actions, detail.sessionId, detail.task);
|
|
5954
|
+
if (detail.task?.id) cacheWorkflowTask(actions, detail.sessionId, detail.task, 'restore');
|
|
5077
5955
|
controller.open(detail.sessionId, detail.step);
|
|
5078
5956
|
},
|
|
5079
5957
|
[WORKBENCH_DATASET_EVENT]: event => {
|
|
@@ -5116,6 +5994,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5116
5994
|
const shared = { controller, references: 1, release() {
|
|
5117
5995
|
if (--shared.references > 0) return;
|
|
5118
5996
|
disposed = true;
|
|
5997
|
+
stopSubmittedBridge();
|
|
5119
5998
|
commandWatchGeneration += 1;
|
|
5120
5999
|
for (const remove of bindings) remove();
|
|
5121
6000
|
host.removeEventListener?.('click', previewClick, true);
|
|
@@ -5124,6 +6003,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5124
6003
|
disconnectFixture();
|
|
5125
6004
|
void providerFiber?.dispose();
|
|
5126
6005
|
instances.clear();
|
|
6006
|
+
revealedCommands.clear();
|
|
5127
6007
|
if (host[CONTROLLER_KEY] === shared) delete host[CONTROLLER_KEY];
|
|
5128
6008
|
if (sessionWorkbenchController === controller) sessionWorkbenchController = null;
|
|
5129
6009
|
} };
|
|
@@ -5218,10 +6098,17 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5218
6098
|
createWorkbenchStore,
|
|
5219
6099
|
WorkbenchContent,
|
|
5220
6100
|
installSessionWorkbench,
|
|
6101
|
+
sendQccAgentCommand,
|
|
6102
|
+
installSubmittedWorkbenchBridge,
|
|
5221
6103
|
DatabaseLogo,
|
|
5222
6104
|
visibleCatalogFields,
|
|
5223
6105
|
installCapabilityMount,
|
|
5224
6106
|
PromptDialog,
|
|
6107
|
+
normalizeViewStage,
|
|
6108
|
+
resolveStageAccess,
|
|
6109
|
+
workflowDestination,
|
|
6110
|
+
lockedWorkflowNotice,
|
|
6111
|
+
navigateWorkflow,
|
|
5225
6112
|
workflowNavigationIssue,
|
|
5226
6113
|
buildTaskPrompt,
|
|
5227
6114
|
DatasetReview,
|
|
@@ -5235,6 +6122,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5235
6122
|
requestPromptImage,
|
|
5236
6123
|
guessMappings,
|
|
5237
6124
|
intakeState,
|
|
6125
|
+
restoreParsedRuntime,
|
|
5238
6126
|
projectCompletionResult,
|
|
5239
6127
|
mappingRecommendations,
|
|
5240
6128
|
mappedOutputFields,
|
|
@@ -5255,6 +6143,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5255
6143
|
observeHomePhase,
|
|
5256
6144
|
ensureWorkflowTask,
|
|
5257
6145
|
ensureEditableWorkflowTask,
|
|
6146
|
+
refreshHostedTask,
|
|
5258
6147
|
queueWorkflowOperation,
|
|
5259
6148
|
taskBelongsToSession,
|
|
5260
6149
|
workflowOriginLabel,
|