dsh-data-cleaning-agent 0.9.13 → 0.9.16
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 +22 -0
- package/README.en.md +4 -2
- package/README.md +5 -3
- package/docs/COMPATIBILITY.md +7 -0
- package/docs/RELEASE-0.9.14.md +12 -0
- package/docs/RELEASE-0.9.15.md +13 -0
- package/docs/UNIFIED-HOME-1.5.4-ADOPTION.md +22 -1
- package/lib/client.js +1321 -298
- package/lib/image-workflow.js +7 -1
- package/lib/qcc-command.js +8 -0
- package/lib/qcc-runs.js +9 -0
- package/lib/skill-enrich.js +1 -0
- package/lib/skill.js +1 -0
- package/lib/web.js +98 -42
- package/lib/workflow-contract.js +125 -7
- package/lib/workflow-execution.js +121 -20
- package/lib/workflow.js +170 -14
- package/package.json +3 -1
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;
|
|
1283
|
+
white-space: nowrap;
|
|
1284
|
+
}
|
|
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;
|
|
1161
1309
|
white-space: nowrap;
|
|
1162
1310
|
}
|
|
1163
|
-
.
|
|
1164
|
-
|
|
1165
|
-
|
|
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;
|
|
1166
1321
|
color: var(--dc-muted);
|
|
1322
|
+
cursor: pointer;
|
|
1167
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;
|
|
1397
|
+
color: var(--dc-muted);
|
|
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; }
|
|
@@ -1274,19 +1605,104 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1274
1605
|
return () => { style.remove(); };
|
|
1275
1606
|
}
|
|
1276
1607
|
|
|
1277
|
-
// 原始数据不进入 Host storageDomain
|
|
1608
|
+
// 原始数据不进入 Host storageDomain 或模型上下文;仅在当前浏览器页签内按 taskId 隔离。
|
|
1609
|
+
// sessionStorage 只用于刷新恢复,关闭页签后不承诺保留;写入失败时降级为同源重新载入。
|
|
1610
|
+
const RUNTIME_SESSION_CACHE_KEY = 'dsh.data-cleaning-agent.runtime-tasks.v1';
|
|
1611
|
+
const RUNTIME_SESSION_CACHE_LIMIT = 20;
|
|
1612
|
+
const RUNTIME_SESSION_CACHE_MAX_ROWS = 5_000;
|
|
1613
|
+
const RUNTIME_SESSION_CACHE_MAX_CHARS = 2 * 1024 * 1024;
|
|
1278
1614
|
const runtimeTasks = new Map();
|
|
1279
1615
|
const workflowTaskBySession = new Map();
|
|
1280
1616
|
const workflowTaskCreationBySession = new Map();
|
|
1281
1617
|
const workflowOperationBySession = new Map();
|
|
1282
1618
|
|
|
1283
|
-
function
|
|
1619
|
+
function readRuntimeSessionCache() {
|
|
1620
|
+
try {
|
|
1621
|
+
const value = JSON.parse(window.sessionStorage?.getItem(RUNTIME_SESSION_CACHE_KEY) || '{}');
|
|
1622
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
1623
|
+
} catch { return {}; }
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function runtimeFromSession(taskId, expectedChecksum = '') {
|
|
1627
|
+
const key = String(taskId || 'unassigned');
|
|
1628
|
+
const value = readRuntimeSessionCache()[key];
|
|
1629
|
+
const rows = Array.isArray(value?.rows)
|
|
1630
|
+
? value.rows.filter((row) => row && typeof row === 'object' && !Array.isArray(row))
|
|
1631
|
+
: [];
|
|
1632
|
+
const checksum = String(value?.source?.checksum || '');
|
|
1633
|
+
if (!rows.length || !expectedChecksum || checksum !== expectedChecksum) return null;
|
|
1634
|
+
return {
|
|
1635
|
+
rows,
|
|
1636
|
+
sourceRows: rows,
|
|
1637
|
+
headers: Array.isArray(value.headers) ? value.headers.map(String) : [],
|
|
1638
|
+
source: value.source && typeof value.source === 'object' ? value.source : null,
|
|
1639
|
+
restoredFromSession: true,
|
|
1640
|
+
lastCsv: { clean: null, complete: null, qcc: null, review: null },
|
|
1641
|
+
resultRows: { clean: null, complete: null, qcc: null },
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
function persistRuntimeSession(taskId, runtime, removeTaskId = null) {
|
|
1646
|
+
const rows = Array.isArray(runtime?.sourceRows) && runtime.sourceRows.length
|
|
1647
|
+
? runtime.sourceRows
|
|
1648
|
+
: Array.isArray(runtime?.rows) ? runtime.rows : [];
|
|
1649
|
+
if (!rows.length || rows.length > RUNTIME_SESSION_CACHE_MAX_ROWS
|
|
1650
|
+
|| typeof window.sessionStorage?.setItem !== 'function') return false;
|
|
1651
|
+
try {
|
|
1652
|
+
const key = String(taskId || 'unassigned');
|
|
1653
|
+
const cache = readRuntimeSessionCache();
|
|
1654
|
+
if (removeTaskId !== null) delete cache[String(removeTaskId || 'unassigned')];
|
|
1655
|
+
delete cache[key];
|
|
1656
|
+
cache[key] = {
|
|
1657
|
+
rows,
|
|
1658
|
+
headers: Array.isArray(runtime.headers) ? runtime.headers : [],
|
|
1659
|
+
source: runtime.source && typeof runtime.source === 'object' ? runtime.source : null,
|
|
1660
|
+
};
|
|
1661
|
+
const entries = Object.entries(cache).slice(-RUNTIME_SESSION_CACHE_LIMIT);
|
|
1662
|
+
while (entries.length) {
|
|
1663
|
+
const serialized = JSON.stringify(Object.fromEntries(entries));
|
|
1664
|
+
if (serialized.length <= RUNTIME_SESSION_CACHE_MAX_CHARS) {
|
|
1665
|
+
window.sessionStorage?.setItem(RUNTIME_SESSION_CACHE_KEY, serialized);
|
|
1666
|
+
return true;
|
|
1667
|
+
}
|
|
1668
|
+
entries.shift();
|
|
1669
|
+
}
|
|
1670
|
+
return false;
|
|
1671
|
+
} catch { return false; }
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
function removeRuntimeSession(taskId) {
|
|
1675
|
+
try {
|
|
1676
|
+
const key = String(taskId || 'unassigned');
|
|
1677
|
+
const cache = readRuntimeSessionCache();
|
|
1678
|
+
if (!(key in cache)) return;
|
|
1679
|
+
delete cache[key];
|
|
1680
|
+
window.sessionStorage?.setItem(RUNTIME_SESSION_CACHE_KEY, JSON.stringify(cache));
|
|
1681
|
+
} catch {}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function removeRuntime(taskId) {
|
|
1685
|
+
runtimeTasks.delete(String(taskId || 'unassigned'));
|
|
1686
|
+
removeRuntimeSession(taskId);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
function runtimeFor(taskId, create = true, expectedChecksum = '') {
|
|
1284
1690
|
const key = String(taskId || 'unassigned');
|
|
1691
|
+
const current = runtimeTasks.get(key);
|
|
1692
|
+
if (current && expectedChecksum && current.source?.checksum !== expectedChecksum) {
|
|
1693
|
+
runtimeTasks.delete(key);
|
|
1694
|
+
removeRuntimeSession(key);
|
|
1695
|
+
}
|
|
1696
|
+
if (!runtimeTasks.has(key)) {
|
|
1697
|
+
const restored = runtimeFromSession(key, expectedChecksum);
|
|
1698
|
+
if (restored) runtimeTasks.set(key, restored);
|
|
1699
|
+
}
|
|
1285
1700
|
if (!runtimeTasks.has(key) && create) {
|
|
1286
1701
|
runtimeTasks.set(key, {
|
|
1287
1702
|
rows: [],
|
|
1288
1703
|
headers: [],
|
|
1289
1704
|
source: null,
|
|
1705
|
+
restoredFromSession: false,
|
|
1290
1706
|
lastCsv: { clean: null, complete: null, qcc: null, review: null },
|
|
1291
1707
|
resultRows: { clean: null, complete: null, qcc: null },
|
|
1292
1708
|
});
|
|
@@ -1302,6 +1718,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1302
1718
|
target.sourceRows = from.sourceRows;
|
|
1303
1719
|
target.headers = from.headers;
|
|
1304
1720
|
target.source = from.source;
|
|
1721
|
+
target.restoredFromSession = from.restoredFromSession;
|
|
1305
1722
|
target.lastCsv = from.lastCsv;
|
|
1306
1723
|
target.resultRows = from.resultRows;
|
|
1307
1724
|
runtimeTasks.delete(String(fromId || 'unassigned'));
|
|
@@ -1316,6 +1733,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1316
1733
|
{ key: 'download', label: '结果下载', icon: 'table' },
|
|
1317
1734
|
];
|
|
1318
1735
|
|
|
1736
|
+
const WORKFLOW_GUIDE_STEPS = [
|
|
1737
|
+
['导入与核验', '导入文本、Excel、JSON 或图片名单,并核对完整原始数据。'],
|
|
1738
|
+
['规则与体检', '确认字段映射、匹配规则和补全范围;质量体检在本地完成。'],
|
|
1739
|
+
['主体匹配', '优先使用统一社会信用代码等强标识;名称不明确时进入人工候选核验。'],
|
|
1740
|
+
['字段补全', '只补空值并保留非空原值;外部字段以企查查真实返回为准。'],
|
|
1741
|
+
['结果下载', 'Host 保存结果、异常清单与任务报告,可在任务历史中继续下载。'],
|
|
1742
|
+
];
|
|
1743
|
+
|
|
1319
1744
|
const CAPABILITIES = [
|
|
1320
1745
|
{ key: 'upload', label: '导入名单', icon: IconPaperclipOutline16, fallback: '+' },
|
|
1321
1746
|
{ key: 'profile', label: '质量体检', icon: IconChecklistOutline14, fallback: '✓' },
|
|
@@ -1442,7 +1867,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1442
1867
|
];
|
|
1443
1868
|
const DEFAULT_CLEANING_KEYS = ['clean_name', 'deduplicate', 'validate_identity', 'complete_fields'];
|
|
1444
1869
|
const DEFAULT_ENRICHMENT_KEYS = ['credit_no', 'legal_rep', 'reg_capital', 'establish_date', 'reg_status'];
|
|
1445
|
-
const DEFAULT_SESSION_PROMPT = '
|
|
1870
|
+
const DEFAULT_SESSION_PROMPT = '请帮我清洗并补全企业名单。请粘贴企业名称,或上传 Excel / 名单图片,并说明需要补全的字段;也可点击左上角「提示词生成」设置匹配与输出。例如:补全【企业名单】的统一社会信用代码、法定代表人和联系方式,结果导出为 Excel。';
|
|
1871
|
+
const INITIAL_DRAFT_TEMPLATE = Object.freeze({ id: 'dsh-initial-draft/data-cleaning/1', version: 1,
|
|
1872
|
+
fingerprint: 'sha256:2b84b86f669e64e970381e4c7ab8547c77d5e10ee7d5441543c893b9f1272e2b', text: DEFAULT_SESSION_PROMPT });
|
|
1873
|
+
const INITIAL_DRAFT_LEDGER = Symbol.for('dsh.data-cleaning.initial-draft.v1');
|
|
1874
|
+
const compositionState = document[Symbol.for('dsh.data-cleaning.composition.v1')] ||= { active: false };
|
|
1875
|
+
let initialDraftEpoch = 0;
|
|
1446
1876
|
const CLEANING_SESSION_STORAGE_KEYS = [
|
|
1447
1877
|
'dsh.data-cleaning-agent.active-session.v2',
|
|
1448
1878
|
'dsh.data-cleaning-agent.sessions.v1',
|
|
@@ -1488,28 +1918,17 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1488
1918
|
}
|
|
1489
1919
|
|
|
1490
1920
|
function isKnownCleaningDraft(value) {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
const wizardDraft = value.startsWith('请执行一项企业名单数据清洗补全任务。')
|
|
1494
|
-
&& value.includes('企查查连接、套餐额度和费用均由当前用户自己的账号承担。')
|
|
1495
|
-
&& value.includes('提供结果和待复核清单的导出。');
|
|
1496
|
-
const executionDraft = value.startsWith('请执行已在「数据清洗补全工作台」确认的企业数据任务。')
|
|
1497
|
-
&& value.includes('安全任务凭证:dcq-')
|
|
1498
|
-
&& value.includes('data_cleaning_qcc_run');
|
|
1499
|
-
const imageDraft = value.startsWith('请识别我刚刚在向导中安全暂存的企业名单图片')
|
|
1500
|
-
&& value.includes('安全图片凭证:dci-')
|
|
1501
|
-
&& value.includes('data_cleaning_extract_image_companies');
|
|
1502
|
-
return wizardDraft || executionDraft || imageDraft;
|
|
1921
|
+
// Exact text is the fingerprint: no trimming, substring or fuzzy recognition.
|
|
1922
|
+
return value === INITIAL_DRAFT_TEMPLATE.text;
|
|
1503
1923
|
}
|
|
1504
1924
|
|
|
1505
|
-
function
|
|
1506
|
-
if (
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
return true;
|
|
1925
|
+
function wizardListEntries(value) {
|
|
1926
|
+
if (isKnownCleaningDraft(value)) return [];
|
|
1927
|
+
return String(value || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
function invalidListInstruction(values) {
|
|
1931
|
+
return values.some((value) => /【[^】]*】|^(?:请|例如[::]|输入来源[::]|结果导出|需要补全)/u.test(value));
|
|
1513
1932
|
}
|
|
1514
1933
|
|
|
1515
1934
|
function deactivateCleaningSession(sessionId = activeCleaningSessionId) {
|
|
@@ -1550,43 +1969,19 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1550
1969
|
*/
|
|
1551
1970
|
function installSessionOwnershipBridge(ctx) {
|
|
1552
1971
|
const releaseNewSessionBridge = installNewSessionBridge(ctx);
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
const cleared = clearCleaningDraft(ctx, currentSessionId, true);
|
|
1559
|
-
if (cleared && staleDraftObserver) {
|
|
1560
|
-
staleDraftObserver.disconnect();
|
|
1561
|
-
staleDraftObserver = null;
|
|
1562
|
-
}
|
|
1563
|
-
return cleared;
|
|
1564
|
-
};
|
|
1972
|
+
// Observe composition only; never read/write another product's draft.
|
|
1973
|
+
const compositionStart = () => { compositionState.active = true; };
|
|
1974
|
+
const compositionEnd = () => { compositionState.active = false; };
|
|
1975
|
+
document.addEventListener?.('compositionstart', compositionStart, true);
|
|
1976
|
+
document.addEventListener?.('compositionend', compositionEnd, true);
|
|
1565
1977
|
const leaveSubsystem = () => {
|
|
1566
1978
|
const sessionId = activeCleaningSessionId;
|
|
1567
|
-
if (sessionId && !ownedSessionId(sessionId)) clearCleaningDraft(ctx, sessionId, true);
|
|
1568
1979
|
deactivateCleaningSession(sessionId);
|
|
1569
1980
|
};
|
|
1570
1981
|
// Only our independently-created native session namespace may regain ownership.
|
|
1571
1982
|
const initialSession = ctx.sessions?.list?.getSnapshot?.()?.current;
|
|
1572
1983
|
if (ownedSessionId(initialSession)) markCleaningSession(initialSession);
|
|
1573
|
-
|
|
1574
|
-
// DSH 会在插件 apply() 之后异步恢复 composer draft。在首屏稳定期内
|
|
1575
|
-
// 监听 DOM 变化并仅清理完全匹配本插件默认文案,或同时命中
|
|
1576
|
-
// 提示词向导固定首尾与费用声明签名的草稿;不触碰用户自写内容。
|
|
1577
|
-
if (typeof MutationObserver === 'function' && document.documentElement) {
|
|
1578
|
-
staleDraftObserver = new MutationObserver(clearStaleDefaultDraft);
|
|
1579
|
-
staleDraftObserver.observe(document.documentElement, {
|
|
1580
|
-
childList: true,
|
|
1581
|
-
characterData: true,
|
|
1582
|
-
subtree: true,
|
|
1583
|
-
});
|
|
1584
|
-
staleDraftObserverTimer = setTimeout(() => {
|
|
1585
|
-
staleDraftObserver?.disconnect();
|
|
1586
|
-
staleDraftObserver = null;
|
|
1587
|
-
staleDraftObserverTimer = null;
|
|
1588
|
-
}, 10_000);
|
|
1589
|
-
}
|
|
1984
|
+
// Restore ownership only. Refresh/remount/navigation must never seed or clear drafts.
|
|
1590
1985
|
const unsubscribe = ctx.sessions?.list?.subscribe?.((snapshot) => {
|
|
1591
1986
|
// Store subscriptions may signal without a payload; read authoritative state.
|
|
1592
1987
|
const selected = ctx.sessions?.list?.getSnapshot?.() ?? snapshot;
|
|
@@ -1597,14 +1992,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1597
1992
|
}
|
|
1598
1993
|
if (!activeCleaningSessionId && ownedSessionId(nextSessionId)) {
|
|
1599
1994
|
markCleaningSession(nextSessionId);
|
|
1600
|
-
} else if (!activeCleaningSessionId) {
|
|
1601
|
-
clearStaleDefaultDraft();
|
|
1602
1995
|
}
|
|
1603
1996
|
});
|
|
1604
1997
|
return () => {
|
|
1998
|
+
initialDraftEpoch++;
|
|
1605
1999
|
releaseNewSessionBridge();
|
|
1606
|
-
|
|
1607
|
-
|
|
2000
|
+
document.removeEventListener?.('compositionstart', compositionStart, true);
|
|
2001
|
+
document.removeEventListener?.('compositionend', compositionEnd, true);
|
|
1608
2002
|
if (typeof unsubscribe === 'function') unsubscribe();
|
|
1609
2003
|
};
|
|
1610
2004
|
}
|
|
@@ -1952,14 +2346,18 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1952
2346
|
if (!task?.originSessionId) return '历史版本未保存来源';
|
|
1953
2347
|
return `工作区:${task.originWorkspaceName || task.originWorkspaceId || '未取得来源工作区'} · 会话:${task.originSessionName || task.originSessionId}`;
|
|
1954
2348
|
}
|
|
1955
|
-
function cacheWorkflowTask(actions, sessionId, task) {
|
|
2349
|
+
function cacheWorkflowTask(actions, sessionId, task, reason = 'update') {
|
|
1956
2350
|
if (!task) return null;
|
|
1957
2351
|
if (!taskBelongsToSession(task, sessionId)) throw new Error('此任务不属于当前会话;请在任务历史中只读查看或打开来源会话。');
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
|
|
2352
|
+
const key = String(sessionId || 'unassigned');
|
|
2353
|
+
const current = workflowTaskBySession.get(key);
|
|
2354
|
+
const explicitSwitch = ['create', 'restore'].includes(reason);
|
|
2355
|
+
if (current && current.id !== task.id && !explicitSwitch) return current;
|
|
2356
|
+
if (current?.id === task.id && Number(current.revision ?? -1) > Number(task.revision ?? -1)) return current;
|
|
2357
|
+
workflowTaskBySession.set(key, task);
|
|
1961
2358
|
bindTask(sessionId, task.id);
|
|
1962
|
-
actions.
|
|
2359
|
+
if (typeof actions.acceptWorkflowTask === 'function') actions.acceptWorkflowTask(task, reason);
|
|
2360
|
+
else actions.setWorkflowTask(task);
|
|
1963
2361
|
if (task.title) actions.setTaskTitle(task.title);
|
|
1964
2362
|
if (Array.isArray(task.objectives)) actions.setObjectives(task.objectives);
|
|
1965
2363
|
if (Array.isArray(task.mappings)) actions.setMappings(task.mappings);
|
|
@@ -1991,7 +2389,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1991
2389
|
}
|
|
1992
2390
|
const response = await pending;
|
|
1993
2391
|
moveRuntime(`session:${key}`, response.task.id);
|
|
1994
|
-
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2392
|
+
return cacheWorkflowTask(actions, sessionId, response.task, 'create');
|
|
1995
2393
|
}
|
|
1996
2394
|
|
|
1997
2395
|
async function ensureWorkflowTask(actions, sessionId, draft = {}, restoreStage = true) {
|
|
@@ -2002,9 +2400,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2002
2400
|
if (ownedSessionId(key) && /^dcw-[a-zA-Z0-9-]+$/.test(saved || '')) {
|
|
2003
2401
|
const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(saved)}`);
|
|
2004
2402
|
if (taskBelongsToSession(response.task, sessionId)) {
|
|
2005
|
-
|
|
2006
|
-
if (restoreStage) actions.setStep(task.stage || 'upload');
|
|
2007
|
-
return task;
|
|
2403
|
+
return cacheWorkflowTask(actions, sessionId, response.task, restoreStage ? 'restore' : 'update');
|
|
2008
2404
|
}
|
|
2009
2405
|
bindTask(sessionId, null);
|
|
2010
2406
|
}
|
|
@@ -2053,7 +2449,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2053
2449
|
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2054
2450
|
}
|
|
2055
2451
|
|
|
2056
|
-
async function workflowAction(actions, sessionId, task, action, input = {}) {
|
|
2452
|
+
async function workflowAction(actions, sessionId, task, action, input = {}, reason = 'update') {
|
|
2057
2453
|
const latest = workflowTaskBySession.get(String(sessionId || 'unassigned')) ?? task;
|
|
2058
2454
|
if (!latest) throw new Error('请先创建数据清洗补全任务。');
|
|
2059
2455
|
if (!taskBelongsToSession(latest, sessionId)) throw new Error('此任务不属于当前会话。');
|
|
@@ -2062,7 +2458,33 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2062
2458
|
originSessionId: sessionId, originWorkspaceId: latest.originWorkspaceId,
|
|
2063
2459
|
expectedRevision: latest.revision,
|
|
2064
2460
|
});
|
|
2065
|
-
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2461
|
+
return cacheWorkflowTask(actions, sessionId, response.task, reason);
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
async function verifyRestoredRuntime(actions, sessionId, task, runtime) {
|
|
2465
|
+
if (!runtime?.restoredFromSession) return runtime;
|
|
2466
|
+
if (!task?.id || !task.source?.checksum || !runtime.sourceRows?.length) {
|
|
2467
|
+
if (task?.id) removeRuntime(task.id);
|
|
2468
|
+
actions.setDataset?.(null);
|
|
2469
|
+
throw new Error('刷新缓存缺少同源校验信息,请重新载入原文件或原文本。');
|
|
2470
|
+
}
|
|
2471
|
+
const response = await requestJson(
|
|
2472
|
+
`/data-cleaning/api/workflow/tasks/${encodeURIComponent(task.id)}/verify-source`,
|
|
2473
|
+
'POST',
|
|
2474
|
+
{
|
|
2475
|
+
originSessionId: sessionId,
|
|
2476
|
+
originWorkspaceId: task.originWorkspaceId,
|
|
2477
|
+
headers: runtime.headers,
|
|
2478
|
+
rows: runtime.sourceRows,
|
|
2479
|
+
},
|
|
2480
|
+
);
|
|
2481
|
+
if (response.verified !== true) {
|
|
2482
|
+
removeRuntime(task.id);
|
|
2483
|
+
actions.setDataset?.(null);
|
|
2484
|
+
throw new Error('刷新缓存与原任务不一致,已停止本次操作。请重新载入原文件或新建任务。');
|
|
2485
|
+
}
|
|
2486
|
+
runtime.restoredFromSession = false;
|
|
2487
|
+
return runtime;
|
|
2066
2488
|
}
|
|
2067
2489
|
|
|
2068
2490
|
async function stageConfirmedWorkflow(actions, sessionId, expected = null) {
|
|
@@ -2086,9 +2508,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2086
2508
|
throw new Error('工作台保存的范围与向导不一致,请重试确认;未生成执行凭证。');
|
|
2087
2509
|
}
|
|
2088
2510
|
}
|
|
2089
|
-
const runtime = runtimeFor(task.id, false);
|
|
2511
|
+
const runtime = runtimeFor(task.id, false, task.source?.checksum || '');
|
|
2090
2512
|
if (!task.fieldSelection?.length) throw new Error('当前未选择企查查补全字段。请在规则页选择补全字段,或使用工作台本地清洗与结果导出;不会发起企查查调用。');
|
|
2091
2513
|
if (!runtime?.rows?.length) throw new Error('Host 名单尚未载入,请先解析数据。');
|
|
2514
|
+
await verifyRestoredRuntime(actions, sessionId, task, runtime);
|
|
2092
2515
|
if (task.state === 'uploaded') task = await workflowAction(actions, sessionId, task, 'rules', {
|
|
2093
2516
|
objectives: task.objectives, fieldSelection: task.fieldSelection, mappings: task.mappings, matchRules: task.matchRules,
|
|
2094
2517
|
});
|
|
@@ -2104,7 +2527,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2104
2527
|
return response.command;
|
|
2105
2528
|
}
|
|
2106
2529
|
|
|
2107
|
-
async function refreshHostedTask(actions, sessionId, taskId, run,
|
|
2530
|
+
async function refreshHostedTask(actions, sessionId, taskId, run, reason = 'update') {
|
|
2108
2531
|
const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(taskId)}`);
|
|
2109
2532
|
if (!taskBelongsToSession(response.task, sessionId)) {
|
|
2110
2533
|
if (readTaskBindings()[sessionId] === taskId) bindTask(sessionId, null);
|
|
@@ -2113,22 +2536,20 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2113
2536
|
const selectedTask = workflowTaskBySession.get(String(sessionId));
|
|
2114
2537
|
if (selectedTask && selectedTask.id !== taskId) return response.task;
|
|
2115
2538
|
if (selectedTask && selectedTask.revision > response.task.revision) return selectedTask;
|
|
2116
|
-
workflowTaskBySession.set(String(sessionId), response.task);
|
|
2117
|
-
bindTask(sessionId, taskId);
|
|
2118
2539
|
if (isCleaningSession(sessionId)) {
|
|
2119
2540
|
if (!run && response.task.qccRunId && ['review_required', 'partial'].includes(response.task.state)) {
|
|
2120
2541
|
try { run = await requestJson(`/data-cleaning/api/g5/run/${encodeURIComponent(response.task.qccRunId)}`); }
|
|
2121
2542
|
catch { actions.setError('Host 中的候选或重试数据已失效。已有结果仍可下载;重新查询需新建任务,不会自动重复扣量。'); }
|
|
2122
2543
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2544
|
+
const latestSelectedTask = workflowTaskBySession.get(String(sessionId));
|
|
2545
|
+
if (!isCleaningSession(sessionId) || (latestSelectedTask && latestSelectedTask.id !== taskId)) return response.task;
|
|
2546
|
+
cacheWorkflowTask(actions, sessionId, response.task, reason);
|
|
2125
2547
|
if (run) {
|
|
2126
2548
|
actions.setQccRun(run);
|
|
2127
2549
|
const runtime = runtimeFor(taskId);
|
|
2128
2550
|
runtime.resultRows.qcc = run.rows;
|
|
2129
2551
|
if (!runtime.rows.length) { runtime.rows = run.rows; runtime.headers = response.task.source?.headers || run.headers || []; }
|
|
2130
2552
|
}
|
|
2131
|
-
if (navigate) actions.setStep(response.task.stage || 'upload');
|
|
2132
2553
|
}
|
|
2133
2554
|
return response.task;
|
|
2134
2555
|
}
|
|
@@ -2138,7 +2559,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2138
2559
|
const isActive = () => generation === commandWatchGeneration;
|
|
2139
2560
|
void waitForQccCommand(command.commandId, 1800, async (status) => {
|
|
2140
2561
|
if (!status || status.state === 'prepared') return;
|
|
2141
|
-
await refreshHostedTask(actions, sessionId, command.taskId, null,
|
|
2562
|
+
await refreshHostedTask(actions, sessionId, command.taskId, null, 'update').catch(() => {});
|
|
2142
2563
|
if (isActive() && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) {
|
|
2143
2564
|
sessionWorkbenchController?.revealSubmitted(sessionId, command.commandId,
|
|
2144
2565
|
workflowTaskBySession.get(String(sessionId))?.stage || 'match');
|
|
@@ -2203,6 +2624,32 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2203
2624
|
draft.workflowTask = value ?? null;
|
|
2204
2625
|
if (value) draft.workflowTasks = [value, ...(draft.workflowTasks || []).filter(task => task.id !== value.id)];
|
|
2205
2626
|
},
|
|
2627
|
+
acceptWorkflowTask: (draft, value, reason = 'update') => {
|
|
2628
|
+
if (!value) return;
|
|
2629
|
+
const previous = draft.workflowTask;
|
|
2630
|
+
if (previous?.id === value.id && Number(previous.revision ?? -1) > Number(value.revision ?? -1)) return;
|
|
2631
|
+
const previousStage = normalizeViewStage(draft.step);
|
|
2632
|
+
const followedCurrent = previous?.flow?.flowVersion === SUPPORTED_FLOW_VERSION
|
|
2633
|
+
&& resolveStageAccess(previousStage, previous.flow) === 'current';
|
|
2634
|
+
draft.workflowTask = value;
|
|
2635
|
+
draft.workflowTasks = [value, ...(draft.workflowTasks || []).filter(task => task.id !== value.id)];
|
|
2636
|
+
if (value.flow?.flowVersion !== SUPPORTED_FLOW_VERSION) {
|
|
2637
|
+
draft.step = value.stage || 'upload';
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
if (draft.step === 'history' && reason !== 'restore') return;
|
|
2641
|
+
if (reason === 'import' && previousStage === 'upload'
|
|
2642
|
+
&& resolveStageAccess('upload', value.flow) === 'read'
|
|
2643
|
+
&& value.flow.currentStage === 'rules') {
|
|
2644
|
+
draft.step = 'upload';
|
|
2645
|
+
return;
|
|
2646
|
+
}
|
|
2647
|
+
if (!previous || previous.id !== value.id || ['create', 'restore'].includes(reason) || followedCurrent) {
|
|
2648
|
+
draft.step = value.flow.currentStage;
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
if (resolveStageAccess(previousStage, value.flow) === 'locked') draft.step = value.flow.currentStage;
|
|
2652
|
+
},
|
|
2206
2653
|
setWorkflowTasks: (draft, value) => {
|
|
2207
2654
|
const cached = new Map((draft.workflowTasks || []).map(task => [task.id, task]));
|
|
2208
2655
|
draft.workflowTasks = (Array.isArray(value) ? value : []).map(task =>
|
|
@@ -2360,13 +2807,45 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2360
2807
|
return `${CLEANING_SESSION_ID_PREFIX}${globalThis.crypto.randomUUID()}`;
|
|
2361
2808
|
}
|
|
2362
2809
|
|
|
2810
|
+
function emptyInitialInput(snapshot) {
|
|
2811
|
+
// Missing Host capabilities fail closed. draftRev also detects type-then-clear.
|
|
2812
|
+
return snapshot?.phase === 'plain' && snapshot.draft === ''
|
|
2813
|
+
&& snapshot.draftRev === 0 && Array.isArray(snapshot.imageIds) && snapshot.imageIds.length === 0
|
|
2814
|
+
&& Array.isArray(snapshot.occurrences) && snapshot.occurrences.length === 0;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
async function initializeCleaningDraft(ctx, sessionId, entry) {
|
|
2818
|
+
const ledger = document[INITIAL_DRAFT_LEDGER] ||= new Map();
|
|
2819
|
+
if (!ownedSessionId(sessionId) || entry?.createdId !== sessionId || ledger.has(sessionId)) return false;
|
|
2820
|
+
// Reserve before yielding: no duplicate initialization, even on plugin remount.
|
|
2821
|
+
ledger.set(sessionId, { templateId: INITIAL_DRAFT_TEMPLATE.id, state: 'skipped' });
|
|
2822
|
+
const conversation = typeof ctx.get === 'function' ? ctx.get('conversation') : ctx.conversation;
|
|
2823
|
+
const shell = conversation?.input?.shell?.(sessionId);
|
|
2824
|
+
const eligible = () => !compositionState.active && !entry.interrupted()
|
|
2825
|
+
&& ctx.sessions.list.getSnapshot().current === entry.previousSession
|
|
2826
|
+
&& typeof shell?.setDraft === 'function' && typeof shell.state?.subscribe === 'function'
|
|
2827
|
+
&& emptyInitialInput(shell.snapshot);
|
|
2828
|
+
if (!eligible()) return false;
|
|
2829
|
+
const revision = shell.snapshot.draftRev;
|
|
2830
|
+
let changed = false;
|
|
2831
|
+
const release = shell.state?.subscribe?.(() => { if (!emptyInitialInput(shell.snapshot)) changed = true; });
|
|
2832
|
+
try {
|
|
2833
|
+
await Promise.resolve();
|
|
2834
|
+
// Authoritative second snapshot, not captured render props. No await between CAS and write.
|
|
2835
|
+
if (changed || !eligible() || shell.snapshot.draftRev !== revision) return false;
|
|
2836
|
+
shell.setDraft(INITIAL_DRAFT_TEMPLATE.text);
|
|
2837
|
+
const accepted = shell.snapshot?.draft === INITIAL_DRAFT_TEMPLATE.text;
|
|
2838
|
+
ledger.set(sessionId, { templateId: INITIAL_DRAFT_TEMPLATE.id, state: accepted ? 'initialized' : 'skipped' });
|
|
2839
|
+
return accepted;
|
|
2840
|
+
} finally { if (typeof release === 'function') release(); }
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2363
2843
|
/** 使用 DSH 原生工作区、会话和输入机打开中央对话,不构造第二套聊天界面。 */
|
|
2364
2844
|
async function startCleaningSession(ctx) {
|
|
2365
2845
|
const workspace = ctx.workspaces.list.getSnapshot();
|
|
2366
2846
|
const current = ctx.sessions.list.getSnapshot().current;
|
|
2367
2847
|
if (ownedSessionId(current)) {
|
|
2368
2848
|
markCleaningSession(current);
|
|
2369
|
-
requestWorkbenchOpen(workflowTaskBySession.get(String(current))?.stage || 'upload', current);
|
|
2370
2849
|
return current;
|
|
2371
2850
|
}
|
|
2372
2851
|
const items = Array.isArray(workspace.items) ? workspace.items : [];
|
|
@@ -2387,16 +2866,34 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2387
2866
|
throw new Error('当前 DSH 版本没有可用的独立会话创建能力');
|
|
2388
2867
|
}
|
|
2389
2868
|
const requestedId = createCleaningSessionId();
|
|
2390
|
-
const
|
|
2391
|
-
|
|
2392
|
-
|
|
2869
|
+
const entryEpoch = initialDraftEpoch;
|
|
2870
|
+
let interrupted = compositionState.active;
|
|
2871
|
+
const interrupt = () => { interrupted = true; };
|
|
2872
|
+
const events = ['beforeinput', 'input', 'paste', 'drop', 'compositionstart'];
|
|
2873
|
+
for (const event of events) document.addEventListener?.(event, interrupt, true);
|
|
2874
|
+
const stopNavigation = ctx.sessions.list.subscribe?.(() => {
|
|
2875
|
+
if (ctx.sessions.list.getSnapshot().current !== current) interrupt();
|
|
2876
|
+
});
|
|
2877
|
+
let sessionId;
|
|
2878
|
+
try {
|
|
2879
|
+
sessionId = await ctx.sessions.create({ workspaceId: targetWorkspace.workspaceId, sessionId: requestedId });
|
|
2880
|
+
if (sessionId !== requestedId) {
|
|
2881
|
+
throw new Error('当前 DSH 版本不支持独立会话创建,请升级 DSH 后再打开数据清洗补全智能体');
|
|
2882
|
+
}
|
|
2883
|
+
await initializeCleaningDraft(ctx, sessionId, {
|
|
2884
|
+
createdId: requestedId, previousSession: current,
|
|
2885
|
+
interrupted: () => interrupted || entryEpoch !== initialDraftEpoch,
|
|
2886
|
+
});
|
|
2887
|
+
// A late create must not bring the user back from another Session/product.
|
|
2888
|
+
if (!interrupted && entryEpoch === initialDraftEpoch && ctx.sessions.list.getSnapshot().current === current) {
|
|
2889
|
+
ctx.sessions.open(sessionId);
|
|
2890
|
+
markCleaningSession(sessionId);
|
|
2891
|
+
}
|
|
2892
|
+
return sessionId;
|
|
2893
|
+
} finally {
|
|
2894
|
+
if (typeof stopNavigation === 'function') stopNavigation();
|
|
2895
|
+
for (const event of events) document.removeEventListener?.(event, interrupt, true);
|
|
2393
2896
|
}
|
|
2394
|
-
const conversation = typeof ctx.get === 'function' ? ctx.get('conversation') : ctx.conversation;
|
|
2395
|
-
if (!conversation) throw new Error('DSH 对话服务尚未就绪,请稍后重试');
|
|
2396
|
-
conversation.input.shell(sessionId).setDraft(DEFAULT_SESSION_PROMPT);
|
|
2397
|
-
ctx.sessions.open(sessionId);
|
|
2398
|
-
markCleaningSession(sessionId);
|
|
2399
|
-
return sessionId;
|
|
2400
2897
|
}
|
|
2401
2898
|
|
|
2402
2899
|
/**
|
|
@@ -2524,8 +3021,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2524
3021
|
}
|
|
2525
3022
|
|
|
2526
3023
|
function openWorkbench(actions, step, sessionId) {
|
|
2527
|
-
|
|
2528
|
-
|
|
3024
|
+
if (sessionWorkbenchController) sessionWorkbenchController.open(sessionId, step);
|
|
3025
|
+
else {
|
|
3026
|
+
const destination = navigateWorkflow(actions, sessionId, step);
|
|
3027
|
+
actions.openAt(destination.stage, sessionId);
|
|
3028
|
+
}
|
|
2529
3029
|
}
|
|
2530
3030
|
|
|
2531
3031
|
const WORKBENCH_OPEN_EVENT = 'dsh:data-cleaning-workbench-open';
|
|
@@ -2677,8 +3177,68 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2677
3177
|
authorization_required: '待连接企查查', cancelled: '已取消', parse_failed: '解析失败',
|
|
2678
3178
|
};
|
|
2679
3179
|
|
|
3180
|
+
const SUPPORTED_FLOW_VERSION = 1;
|
|
3181
|
+
function normalizeViewStage(stage) {
|
|
3182
|
+
return stage === 'profile' ? 'rules' : stage;
|
|
3183
|
+
}
|
|
3184
|
+
function resolveStageAccess(stage, flow) {
|
|
3185
|
+
const normalized = normalizeViewStage(stage);
|
|
3186
|
+
if (flow?.flowVersion !== SUPPORTED_FLOW_VERSION) return 'locked';
|
|
3187
|
+
return flow.stageAccess?.[normalized] || 'locked';
|
|
3188
|
+
}
|
|
3189
|
+
function workflowActionAllowed(task, action) {
|
|
3190
|
+
return task?.flow?.flowVersion === SUPPORTED_FLOW_VERSION
|
|
3191
|
+
&& Array.isArray(task.flow.allowedActions)
|
|
3192
|
+
&& task.flow.allowedActions.includes(action);
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
function workflowDestination(requestedStage, task, currentView = 'upload') {
|
|
3196
|
+
if (requestedStage === 'history') return { stage: 'history', access: 'read' };
|
|
3197
|
+
if (!task) {
|
|
3198
|
+
return requestedStage === 'upload' || !requestedStage
|
|
3199
|
+
? { stage: 'upload', access: 'current' }
|
|
3200
|
+
: { stage: 'upload', access: 'locked' };
|
|
3201
|
+
}
|
|
3202
|
+
if (task.flow?.flowVersion !== SUPPORTED_FLOW_VERSION) {
|
|
3203
|
+
return { stage: task.stage || 'upload', access: 'read', compatibility: true };
|
|
3204
|
+
}
|
|
3205
|
+
const requested = requestedStage || currentView || task.flow.currentStage;
|
|
3206
|
+
const access = resolveStageAccess(requested, task.flow);
|
|
3207
|
+
if (access === 'locked') return { stage: task.flow.currentStage, access };
|
|
3208
|
+
return { stage: requested, access };
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
function workflowStageLabel(stage) {
|
|
3212
|
+
const normalized = normalizeViewStage(stage);
|
|
3213
|
+
return STEPS.find((item) => item.key === normalized)?.label ?? normalized ?? '当前节点';
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
function lockedWorkflowNotice(requestedStage, task, fallbackStage = 'upload') {
|
|
3217
|
+
const currentStage = task?.flow?.currentStage || task?.stage || fallbackStage;
|
|
3218
|
+
return `流程提示:“${workflowStageLabel(requestedStage)}”尚未开放,请先完成“${workflowStageLabel(currentStage)}”。`;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
function navigateWorkflow(actions, sessionId, requestedStage, task) {
|
|
3222
|
+
const current = task ?? workflowTaskBySession.get(String(sessionId || 'unassigned')) ?? null;
|
|
3223
|
+
const destination = workflowDestination(requestedStage, current);
|
|
3224
|
+
actions.setStep(destination.stage);
|
|
3225
|
+
if (destination.compatibility) {
|
|
3226
|
+
actions.setError('页面与 Host 流程版本不一致,当前任务已切换为只读模式;请完整重启并升级 Host。');
|
|
3227
|
+
} else if (destination.access === 'locked') {
|
|
3228
|
+
actions.setError(lockedWorkflowNotice(requestedStage, current, destination.stage));
|
|
3229
|
+
} else {
|
|
3230
|
+
actions.setError(null);
|
|
3231
|
+
}
|
|
3232
|
+
return destination;
|
|
3233
|
+
}
|
|
3234
|
+
|
|
2680
3235
|
function workflowNavigationIssue(step, task, hasData, profile, qccRun, hasLocalResult = false) {
|
|
2681
|
-
if (
|
|
3236
|
+
if (step === 'history') return null;
|
|
3237
|
+
if (task?.flow?.flowVersion === SUPPORTED_FLOW_VERSION) {
|
|
3238
|
+
const access = resolveStageAccess(step, task.flow);
|
|
3239
|
+
return access === 'locked' ? '该步骤尚未开放,请先完成当前流程节点。' : null;
|
|
3240
|
+
}
|
|
3241
|
+
if (step === 'upload') return null;
|
|
2682
3242
|
if (step === 'download' && task?.artifacts?.length) return null;
|
|
2683
3243
|
if (!hasData) return '请先在「导入与核验」载入并核对完整名单。';
|
|
2684
3244
|
if (step === 'rules') return null;
|
|
@@ -2957,6 +3517,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2957
3517
|
return rows.map((row) => {
|
|
2958
3518
|
const name = nameField ? String(row?.[nameField] ?? '').trim() : '';
|
|
2959
3519
|
const credit = creditField ? String(row?.[creditField] ?? '').trim() : '';
|
|
3520
|
+
if (isKnownCleaningDraft(name)) return '';
|
|
3521
|
+
if (invalidListInstruction([name, credit])) {
|
|
3522
|
+
throw new Error('名单包含占位符、示例或任务说明,请提供实际企业名称或信用代码后再继续。');
|
|
3523
|
+
}
|
|
2960
3524
|
return [name, credit].filter(Boolean).join(' | ');
|
|
2961
3525
|
}).filter(Boolean).slice(0, 50);
|
|
2962
3526
|
}
|
|
@@ -3037,6 +3601,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3037
3601
|
});
|
|
3038
3602
|
const [busy, setBusy] = react.useState(false);
|
|
3039
3603
|
const [error, setError] = react.useState(null);
|
|
3604
|
+
const [draftPolicy, setDraftPolicy] = react.useState('cancel');
|
|
3605
|
+
const lastWizardDraft = react.useRef(null);
|
|
3606
|
+
|
|
3607
|
+
react.useEffect(() => { setDraftPolicy('cancel'); lastWizardDraft.current = null; }, [sessionId]);
|
|
3040
3608
|
|
|
3041
3609
|
react.useEffect(() => {
|
|
3042
3610
|
const commandId = imageCommand?.commandId;
|
|
@@ -3053,7 +3621,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3053
3621
|
if (command.taskId) {
|
|
3054
3622
|
const actions = sessionWorkbenchController?.actionsFor(sessionId);
|
|
3055
3623
|
if (actions) {
|
|
3056
|
-
await refreshHostedTask(actions, sessionId, command.taskId, null,
|
|
3624
|
+
await refreshHostedTask(actions, sessionId, command.taskId, null, 'update');
|
|
3057
3625
|
if (command.state === 'running') requestWorkbenchOpen(command.qccCommand ? 'match' : 'upload', sessionId);
|
|
3058
3626
|
if (command.qccCommand) {
|
|
3059
3627
|
const status = await requestJson(`/data-cleaning/api/g5/commands/${encodeURIComponent(command.qccCommand.commandId)}`);
|
|
@@ -3197,11 +3765,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3197
3765
|
}
|
|
3198
3766
|
};
|
|
3199
3767
|
const selectedEntries = () => mode === 'text'
|
|
3200
|
-
? rawText
|
|
3768
|
+
? wizardListEntries(rawText)
|
|
3201
3769
|
: entries;
|
|
3202
3770
|
const validateSource = () => {
|
|
3203
3771
|
const values = selectedEntries();
|
|
3204
3772
|
if (mode === 'text' && !values.length) return '请先录入企业名称或统一社会信用代码,每行一条。';
|
|
3773
|
+
if (mode === 'text' && invalidListInstruction(values)) return '请提供实际企业名单,每行一个名称或信用代码;请勿将【企业名单】、示例或任务说明作为企业。';
|
|
3205
3774
|
if (mode === 'text' && values.some(isImagePathEntry)) return '检测到图片文件路径,不是企业名单。请切换「上传图片识别」,重新选择或粘贴图片。';
|
|
3206
3775
|
if (mode === 'excel' && !values.length) return '请先上传并解析 Excel / CSV / JSON 文件。';
|
|
3207
3776
|
if (mode === 'image' && !imageCommand) return '请先选择、粘贴或拖入一张包含企业名单的图片。';
|
|
@@ -3233,40 +3802,64 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3233
3802
|
return;
|
|
3234
3803
|
}
|
|
3235
3804
|
const inputSnapshot = getInputSnapshot?.(sessionId);
|
|
3236
|
-
if (
|
|
3805
|
+
if (!inputSnapshot || inputSnapshot.phase !== 'plain' || compositionState.active) {
|
|
3237
3806
|
setError('当前对话正在提交,请等待完成后再回填。图片与字段选择已保留。');
|
|
3238
3807
|
return;
|
|
3239
3808
|
}
|
|
3809
|
+
const unchangedWizard = lastWizardDraft.current?.sessionId === sessionId
|
|
3810
|
+
&& lastWizardDraft.current.draft === inputSnapshot.draft;
|
|
3811
|
+
const userDraft = inputSnapshot.draft && !isKnownCleaningDraft(inputSnapshot.draft) && !unchangedWizard;
|
|
3812
|
+
if (userDraft && draftPolicy === 'cancel') {
|
|
3813
|
+
setError('已保留当前草稿。请选择“替换”或“追加”后回填,或关闭向导取消。'); return;
|
|
3814
|
+
}
|
|
3815
|
+
const prefix = unchangedWizard ? lastWizardDraft.current.prefix
|
|
3816
|
+
: userDraft && draftPolicy === 'append' ? inputSnapshot.draft + '\n\n' : '';
|
|
3240
3817
|
const config = promptConfig();
|
|
3241
3818
|
if (mode === 'image' && imageCommand?.state === 'prepared' && imageAttachment && typeof removeImage === 'function') {
|
|
3242
3819
|
// qcc-document-mcp 只读取 Host 暂存文件;发送前释放原生附件,文本模型不会收到图片。
|
|
3243
3820
|
removeImage(sessionId, imageAttachment);
|
|
3244
3821
|
setImageAttachment(null);
|
|
3245
3822
|
}
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
} else if (mode === 'excel' && spreadsheetData) {
|
|
3249
|
-
requestWorkbenchDataset(spreadsheetData, sessionId, { type: spreadsheetData.fmt || 'xlsx', fileName: spreadsheetFileName });
|
|
3250
|
-
}
|
|
3251
|
-
requestWorkbenchDraft({
|
|
3252
|
-
title: `企业数据清洗补全任务 · ${config.entryCount || '待解析'} 条`,
|
|
3253
|
-
objectives: cleaningKeys,
|
|
3254
|
-
fieldSelection: enrichmentKeys,
|
|
3255
|
-
matchRules,
|
|
3256
|
-
...(mode === 'excel' ? { mappings: spreadsheetMappings } : {}),
|
|
3257
|
-
source: { type: mode === 'excel' ? 'xlsx' : mode, fileName: config.fileName || '', rowCount: config.entryCount || 0 },
|
|
3258
|
-
}, sessionId);
|
|
3823
|
+
const attachmentSnapshot = JSON.stringify({imageIds:getInputSnapshot?.(sessionId)?.imageIds,
|
|
3824
|
+
occurrences:getInputSnapshot?.(sessionId)?.occurrences});
|
|
3259
3825
|
setBusy(true);
|
|
3260
3826
|
try {
|
|
3827
|
+
if (mode === 'text') {
|
|
3828
|
+
const local = entriesToDataset(config.entries);
|
|
3829
|
+
const verified = await api('/data-cleaning/api/mvp/parse', {
|
|
3830
|
+
filename: 'data.json',
|
|
3831
|
+
content: JSON.stringify(local.rows),
|
|
3832
|
+
});
|
|
3833
|
+
requestWorkbenchDataset({ ...verified, fmt: 'text' }, sessionId, { type: 'text', fileName: '' });
|
|
3834
|
+
} else if (mode === 'excel' && spreadsheetData) {
|
|
3835
|
+
requestWorkbenchDataset(spreadsheetData, sessionId, { type: spreadsheetData.fmt || 'xlsx', fileName: spreadsheetFileName });
|
|
3836
|
+
}
|
|
3837
|
+
requestWorkbenchDraft({
|
|
3838
|
+
title: `企业数据清洗补全任务 · ${config.entryCount || '待解析'} 条`,
|
|
3839
|
+
objectives: cleaningKeys,
|
|
3840
|
+
fieldSelection: enrichmentKeys,
|
|
3841
|
+
matchRules,
|
|
3842
|
+
...(mode === 'excel' ? { mappings: spreadsheetMappings } : {}),
|
|
3843
|
+
source: { type: mode === 'excel' ? 'xlsx' : mode, fileName: config.fileName || '', rowCount: config.entryCount || 0 },
|
|
3844
|
+
}, sessionId);
|
|
3261
3845
|
let prompt = buildTaskPrompt(config);
|
|
3262
3846
|
if (mode !== 'image' || ['prepared', 'completed'].includes(imageCommand?.state)) {
|
|
3263
3847
|
const command = await requestPreparedWorkflow(sessionId, config);
|
|
3264
3848
|
prompt = mode === 'image' && imageCommand?.state === 'prepared' ? command.prompt : command.prompt + '\n\n' + buildTaskPrompt(config);
|
|
3265
3849
|
if (mode === 'image' && imageCommand?.state === 'prepared') setImageCommand(command);
|
|
3266
3850
|
}
|
|
3851
|
+
const latest = getInputSnapshot?.(sessionId);
|
|
3852
|
+
if (!isCleaningSession(sessionId) || compositionState.active || latest?.phase !== 'plain'
|
|
3853
|
+
|| latest.draft !== inputSnapshot.draft || latest.draftRev !== inputSnapshot.draftRev
|
|
3854
|
+
|| JSON.stringify({imageIds:latest.imageIds,occurrences:latest.occurrences}) !== attachmentSnapshot) {
|
|
3855
|
+
throw new Error('草稿或会话已变化,未覆盖您的内容;请确认后再次回填。');
|
|
3856
|
+
}
|
|
3857
|
+
prompt = prefix + prompt;
|
|
3267
3858
|
inputActions.setDraft(prompt);
|
|
3268
3859
|
const updatedInput = getInputSnapshot?.(sessionId);
|
|
3269
3860
|
if (updatedInput && updatedInput.draft !== prompt) throw new Error('当前对话尚未接收任务说明,请稍后重试回填。');
|
|
3861
|
+
lastWizardDraft.current = {sessionId,draft:prompt,prefix};
|
|
3862
|
+
setDraftPolicy('cancel');
|
|
3270
3863
|
} catch (draftError) {
|
|
3271
3864
|
setError(draftError instanceof Error ? draftError.message : String(draftError));
|
|
3272
3865
|
return;
|
|
@@ -3338,12 +3931,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3338
3931
|
const receiveNewTask = (event) => {
|
|
3339
3932
|
if (event.detail?.sessionId !== sessionId || busy || imageCommand?.state === 'running') return;
|
|
3340
3933
|
event.preventDefault();
|
|
3934
|
+
const reused = event.detail?.reuseConfig || null;
|
|
3341
3935
|
discardCurrentImage();
|
|
3342
3936
|
setMode('text'); setRawText(''); setEntries([]); setEntryCount(0); setSpreadsheetFileName('');
|
|
3343
3937
|
setSpreadsheetData(null); setSpreadsheetMappings([]);
|
|
3344
|
-
setCleaningKeys(DEFAULT_CLEANING_KEYS);
|
|
3938
|
+
setCleaningKeys(reused?.objectives || DEFAULT_CLEANING_KEYS);
|
|
3939
|
+
setEnrichmentKeys(reused?.fieldSelection || DEFAULT_ENRICHMENT_KEYS);
|
|
3345
3940
|
setAnchorKeys(['company_name', 'credit_no']);
|
|
3346
|
-
setPromptMatchRules({ normalizeNames: true, preferCreditNo: true, deduplicate: true, manualReviewAmbiguous: true });
|
|
3941
|
+
setPromptMatchRules(reused?.matchRules || { normalizeNames: true, preferCreditNo: true, deduplicate: true, manualReviewAmbiguous: true });
|
|
3347
3942
|
setWizardStep(1); setOpen(true); setError(null);
|
|
3348
3943
|
};
|
|
3349
3944
|
const receiveImage = (event) => {
|
|
@@ -3555,6 +4150,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3555
4150
|
wizardStep === 4 ? h('div', { className: 'dcAgentWizardPane' },
|
|
3556
4151
|
h('h4', null, '确认任务描述'),
|
|
3557
4152
|
h('p', null, '回填后仍可在对话框继续编辑;回填本身不会调用企查查 MCP。'),
|
|
4153
|
+
h('label', null, '已有草稿时的回填方式 ',
|
|
4154
|
+
h('select', { 'aria-label':'已有草稿时的回填方式', value:draftPolicy, onChange:event=>setDraftPolicy(event.target.value) },
|
|
4155
|
+
h('option', {value:'cancel'}, '保留草稿(取消回填)'),
|
|
4156
|
+
h('option', {value:'replace'}, '替换已有草稿'),
|
|
4157
|
+
h('option', {value:'append'}, '追加到已有草稿'))),
|
|
3558
4158
|
h('pre', { className: 'dcAgentPromptPreview' }, buildTaskPrompt(promptConfig())),
|
|
3559
4159
|
) : null,
|
|
3560
4160
|
h('p', { className: 'dcAgentPromptNote' }, '向导只生成任务描述并保存任务设置。只有你在任务中明确确认后,才会使用当前客户自己的企查查 MCP 连接、账号和额度。'),
|
|
@@ -3681,7 +4281,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3681
4281
|
}
|
|
3682
4282
|
|
|
3683
4283
|
/** 上传 pane 的处理:文件 → 文本/base64 → parse。 */
|
|
3684
|
-
async function parseFile(file) {
|
|
4284
|
+
async function parseFile(file, previousChecksum) {
|
|
3685
4285
|
const isXlsx = /\.(xlsx|xls)$/i.test(file && file.name ? file.name : '');
|
|
3686
4286
|
let content;
|
|
3687
4287
|
if (isXlsx) {
|
|
@@ -3693,7 +4293,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3693
4293
|
} else {
|
|
3694
4294
|
content = await file.text();
|
|
3695
4295
|
}
|
|
3696
|
-
return api('/data-cleaning/api/mvp/parse', { filename: file.name, content });
|
|
4296
|
+
return api('/data-cleaning/api/mvp/parse', { filename: file.name, content, previousChecksum });
|
|
3697
4297
|
}
|
|
3698
4298
|
|
|
3699
4299
|
/** 文本/JSON 直通 parse(与 web.js 内联页同构,含 JSON 数组直通)。 */
|
|
@@ -3708,7 +4308,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3708
4308
|
};
|
|
3709
4309
|
}
|
|
3710
4310
|
|
|
3711
|
-
async function parseText(text, actions) {
|
|
4311
|
+
async function parseText(text, actions, previousChecksum) {
|
|
3712
4312
|
const trimmed = (text ?? '').trim();
|
|
3713
4313
|
if (!trimmed) {
|
|
3714
4314
|
actions.setError('请粘贴或上传数据后再解析。');
|
|
@@ -3716,18 +4316,26 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3716
4316
|
}
|
|
3717
4317
|
if (trimmed.startsWith('[')) {
|
|
3718
4318
|
try {
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
4319
|
+
return api('/data-cleaning/api/mvp/parse', {
|
|
4320
|
+
filename: 'data.json',
|
|
4321
|
+
content: trimmed,
|
|
4322
|
+
previousChecksum,
|
|
4323
|
+
});
|
|
3723
4324
|
} catch (error) {
|
|
3724
4325
|
actions.setError(`JSON 解析失败:${error instanceof Error ? error.message : String(error)}`);
|
|
3725
4326
|
}
|
|
3726
4327
|
return;
|
|
3727
4328
|
}
|
|
3728
4329
|
const entityList = plainEntityListDataset(trimmed);
|
|
3729
|
-
if (entityList)
|
|
3730
|
-
|
|
4330
|
+
if (entityList) {
|
|
4331
|
+
const parsed = await api('/data-cleaning/api/mvp/parse', {
|
|
4332
|
+
filename: 'data.json',
|
|
4333
|
+
content: JSON.stringify(entityList.rows),
|
|
4334
|
+
previousChecksum,
|
|
4335
|
+
});
|
|
4336
|
+
return { ...parsed, fmt: 'text' };
|
|
4337
|
+
}
|
|
4338
|
+
return api('/data-cleaning/api/mvp/parse', { filename: 'data.csv', content: trimmed, previousChecksum });
|
|
3731
4339
|
}
|
|
3732
4340
|
|
|
3733
4341
|
/** 解析成功进入 taskId runtime;不把整表写入 Host store。 */
|
|
@@ -3740,7 +4348,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3740
4348
|
type: source.type || result.fmt || 'csv',
|
|
3741
4349
|
fileName: source.fileName || '',
|
|
3742
4350
|
sizeBytes: source.sizeBytes || 0,
|
|
4351
|
+
checksum: result.checksum || source.checksum || '',
|
|
3743
4352
|
};
|
|
4353
|
+
runtime.restoredFromSession = false;
|
|
3744
4354
|
const mappings = guessMappings(runtime.headers);
|
|
3745
4355
|
const guessedNameField = mappings.find((mapping) => mapping.targetField === 'company_name')?.sourceField
|
|
3746
4356
|
?? mappings.find((mapping) => mapping.targetField === 'credit_no')?.sourceField
|
|
@@ -3759,6 +4369,30 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3759
4369
|
return runtime;
|
|
3760
4370
|
}
|
|
3761
4371
|
|
|
4372
|
+
function restoreParsedRuntime(result, actions, task, source = {}) {
|
|
4373
|
+
const runtime = runtimeFor(task.id);
|
|
4374
|
+
runtime.rows = Array.isArray(result.rows) ? result.rows : [];
|
|
4375
|
+
runtime.sourceRows = runtime.rows;
|
|
4376
|
+
runtime.headers = Array.isArray(result.headers) ? result.headers : [];
|
|
4377
|
+
runtime.source = {
|
|
4378
|
+
type: source.type || result.fmt || task.source?.type || 'csv',
|
|
4379
|
+
fileName: source.fileName || task.source?.fileName || '',
|
|
4380
|
+
sizeBytes: source.sizeBytes || task.source?.sizeBytes || 0,
|
|
4381
|
+
checksum: result.checksum || '',
|
|
4382
|
+
};
|
|
4383
|
+
runtime.restoredFromSession = false;
|
|
4384
|
+
actions.setDataset({
|
|
4385
|
+
fmt: result.fmt ?? task.source?.type ?? 'csv',
|
|
4386
|
+
headers: runtime.headers,
|
|
4387
|
+
rowCount: typeof result.rowCount === 'number' ? result.rowCount : runtime.rows.length,
|
|
4388
|
+
preview: Array.isArray(result.preview) ? result.preview : runtime.rows.slice(0, 5),
|
|
4389
|
+
});
|
|
4390
|
+
const primary = (task.mappings || []).find((mapping) => ['company_name', 'credit_no', 'reg_no'].includes(mapping.targetField));
|
|
4391
|
+
if (primary) actions.setNameField(primary.sourceField);
|
|
4392
|
+
persistRuntimeSession(task.id, runtime);
|
|
4393
|
+
return runtime;
|
|
4394
|
+
}
|
|
4395
|
+
|
|
3762
4396
|
async function persistParsedWorkflow(actions, sessionId, result, source = {}, task) {
|
|
3763
4397
|
const importedMappings = guessMappings(result.headers || []);
|
|
3764
4398
|
const importedDraft = { mappings: importedMappings,
|
|
@@ -3772,11 +4406,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3772
4406
|
target.rows = Array.isArray(result.rows) ? result.rows : [];
|
|
3773
4407
|
target.sourceRows = target.rows;
|
|
3774
4408
|
target.headers = Array.isArray(result.headers) ? result.headers : [];
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
4409
|
+
target.source = {
|
|
4410
|
+
type: source.type || result.fmt || 'csv',
|
|
4411
|
+
fileName: source.fileName || '',
|
|
4412
|
+
sizeBytes: source.sizeBytes || 0,
|
|
4413
|
+
checksum: result.checksum || source.checksum || '',
|
|
3779
4414
|
};
|
|
4415
|
+
target.restoredFromSession = false;
|
|
3780
4416
|
if (['draft', 'uploaded', 'parse_failed'].includes(current.state)) {
|
|
3781
4417
|
current = await workflowAction(actions, sessionId, current, 'upload', {
|
|
3782
4418
|
source: {
|
|
@@ -3786,10 +4422,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3786
4422
|
columnCount: Array.isArray(result.headers) ? result.headers.length : 0,
|
|
3787
4423
|
headers: Array.isArray(result.headers) ? result.headers : [],
|
|
3788
4424
|
sizeBytes: source.sizeBytes || 0,
|
|
4425
|
+
checksum: result.checksum || source.checksum || '',
|
|
3789
4426
|
},
|
|
3790
|
-
});
|
|
4427
|
+
}, 'import');
|
|
3791
4428
|
}
|
|
3792
4429
|
if (current.state === 'uploaded') current = await updateWorkflowTask(actions, sessionId, current, importedDraft);
|
|
4430
|
+
persistRuntimeSession(current.id, target, stagingKey);
|
|
3793
4431
|
return current;
|
|
3794
4432
|
}
|
|
3795
4433
|
|
|
@@ -3858,6 +4496,103 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3858
4496
|
);
|
|
3859
4497
|
}
|
|
3860
4498
|
|
|
4499
|
+
function WorkflowTaskContext(props) {
|
|
4500
|
+
const stageCompleted = props.stageCompleted;
|
|
4501
|
+
const progressVisible = props.commandProgress?.taskId === props.task.id
|
|
4502
|
+
&& Number.isFinite(props.commandProgress.totalUnique);
|
|
4503
|
+
const completedUnique = Math.min(
|
|
4504
|
+
props.commandProgress?.totalUnique || 0,
|
|
4505
|
+
Math.max(0, props.commandProgress?.completedUnique || 0),
|
|
4506
|
+
);
|
|
4507
|
+
return h('div', { className: 'dcAgentTaskStatus', 'aria-live': 'polite' },
|
|
4508
|
+
h('div', { className: 'dcAgentTaskStatusRow' },
|
|
4509
|
+
h('div', { className: 'dcAgentTaskStage' },
|
|
4510
|
+
h('span', {
|
|
4511
|
+
className: 'dcAgentTaskDoneMark',
|
|
4512
|
+
'data-complete': stageCompleted,
|
|
4513
|
+
'aria-hidden': true,
|
|
4514
|
+
}, stageCompleted ? '✓' : '•'),
|
|
4515
|
+
h('div', { className: 'dcAgentTaskStageCopy' },
|
|
4516
|
+
h('div', { className: 'dcAgentTaskStageTitle' },
|
|
4517
|
+
h('strong', null, props.stageLabel),
|
|
4518
|
+
h('span', {
|
|
4519
|
+
className: 'dcAgentTaskState',
|
|
4520
|
+
'data-complete': stageCompleted,
|
|
4521
|
+
}, props.statusLabel),
|
|
4522
|
+
),
|
|
4523
|
+
props.timeLabel || props.sourceLabel ? h('div', { className: 'dcAgentTaskMeta' },
|
|
4524
|
+
props.timeLabel ? h('span', null,
|
|
4525
|
+
h('time', { dateTime: props.timeValue || undefined }, props.timeLabel)) : null,
|
|
4526
|
+
props.sourceLabel ? h('span', null, props.sourceLabel) : null,
|
|
4527
|
+
) : null,
|
|
4528
|
+
),
|
|
4529
|
+
),
|
|
4530
|
+
props.viewAccess === 'read' ? h('div', { className: 'dcAgentTaskCurrent' },
|
|
4531
|
+
h('span', null, '当前节点'),
|
|
4532
|
+
h('strong', null, props.currentStageLabel),
|
|
4533
|
+
) : null,
|
|
4534
|
+
props.viewAccess === 'read' && props.onReturn ? h('button', {
|
|
4535
|
+
type: 'button',
|
|
4536
|
+
className: 'dcAgentTaskReturn',
|
|
4537
|
+
onClick: props.onReturn,
|
|
4538
|
+
}, '回到当前节点') : null,
|
|
4539
|
+
h('details', { className: 'dcAgentTaskDetails' },
|
|
4540
|
+
h('summary', { 'aria-label': '任务详情', title: '任务详情' }, 'i'),
|
|
4541
|
+
h('div', { className: 'dcAgentTaskDetailsBody' },
|
|
4542
|
+
h('strong', null, `${props.sourceLabel || '当前任务'} · ${props.statusLabel}`),
|
|
4543
|
+
h('p', null, `任务编号:${props.task.id}`),
|
|
4544
|
+
h('p', null, workflowOriginLabel(props.task)),
|
|
4545
|
+
h('p', null, `${props.rowCount} 条企业记录 · ${props.fieldCount} 个补全字段`),
|
|
4546
|
+
),
|
|
4547
|
+
),
|
|
4548
|
+
),
|
|
4549
|
+
['matching', 'enriching'].includes(props.task.state) ? h('p', { className: 'dcAgentTaskSyncNote' },
|
|
4550
|
+
'这是最近同步的任务记录;若原会话已停止,请先查看确认或错误提示,不要重复启动。') : null,
|
|
4551
|
+
progressVisible ? h('div', { className: 'dcAgentTaskExecution' },
|
|
4552
|
+
h('span', null, `已处理主体 ${completedUnique}/${props.commandProgress.totalUnique}`),
|
|
4553
|
+
h('progress', {
|
|
4554
|
+
max: props.commandProgress.totalUnique,
|
|
4555
|
+
value: completedUnique,
|
|
4556
|
+
'aria-label': '主体处理进度',
|
|
4557
|
+
}),
|
|
4558
|
+
) : null,
|
|
4559
|
+
h('div', { className: 'dcAgentTaskMetrics', 'aria-label': '任务摘要' },
|
|
4560
|
+
h('div', { className: 'dcAgentTaskMetricGroup' },
|
|
4561
|
+
h('span', { className: 'dcAgentTaskMetricLabel' }, '任务范围'),
|
|
4562
|
+
h('span', { className: 'dcAgentTaskMetric' }, '企业 ', h('b', null, props.rowCount), ' 家'),
|
|
4563
|
+
h('span', { className: 'dcAgentTaskMetric' }, '字段 ', h('b', null, props.fieldCount), ' 个'),
|
|
4564
|
+
),
|
|
4565
|
+
props.hasResultSummary ? h('div', { className: 'dcAgentTaskMetricGroup' },
|
|
4566
|
+
h('span', { className: 'dcAgentTaskMetricLabel' }, '处理结果'),
|
|
4567
|
+
h('span', { className: 'dcAgentTaskMetric is-good' }, '已补全 ', h('b', null, props.completedMetric)),
|
|
4568
|
+
h('span', { className: 'dcAgentTaskMetric' }, '待核验 ', h('b', null, props.reviewMetric)),
|
|
4569
|
+
h('span', { className: 'dcAgentTaskMetric' }, '失败 ', h('b', null, props.failedMetric)),
|
|
4570
|
+
) : null,
|
|
4571
|
+
),
|
|
4572
|
+
);
|
|
4573
|
+
}
|
|
4574
|
+
|
|
4575
|
+
function WorkflowGuide() {
|
|
4576
|
+
return h('details', { className: 'dcAgentWorkflowGuide' },
|
|
4577
|
+
h('summary', null, '使用说明和任务流程'),
|
|
4578
|
+
h('div', { className: 'dcAgentWorkflowGuideContent' },
|
|
4579
|
+
h('p', { className: 'dcAgentWorkflowGuideIntro' },
|
|
4580
|
+
'导入与核验 → 规则与体检 → 主体匹配 → 字段补全 → 结果下载。菜单只切换视图,不启动调用;按页面主按钮逐步确认。'),
|
|
4581
|
+
h('p', { className: 'dcAgentWorkflowGuideIntro' },
|
|
4582
|
+
'支持文本、Excel、JSON 和图片名单。原值保留、多候选人工核验;企查查调用使用当前用户自己的连接与额度。'),
|
|
4583
|
+
h('div', { className: 'dcAgentWorkflowGuideList' },
|
|
4584
|
+
WORKFLOW_GUIDE_STEPS.map(([title, description], index) => h('div', {
|
|
4585
|
+
key: title,
|
|
4586
|
+
className: 'dcAgentWorkflowGuideStep',
|
|
4587
|
+
},
|
|
4588
|
+
h('span', { className: 'dcAgentWorkflowGuideIndex' }, String(index + 1)),
|
|
4589
|
+
h('div', null, h('strong', null, title), h('p', null, description))))),
|
|
4590
|
+
h('div', { className: 'dcAgentWorkflowGuideBoundary' },
|
|
4591
|
+
'生成任务说明不会查询;发送说明或显式重试时,才使用当前用户连接的企查查账号及额度。'),
|
|
4592
|
+
),
|
|
4593
|
+
);
|
|
4594
|
+
}
|
|
4595
|
+
|
|
3861
4596
|
/** Session Tab 业务内容;尺寸、停靠、关闭均由 Better Sidebar 宿主管理。 */
|
|
3862
4597
|
function WorkbenchContent(props) {
|
|
3863
4598
|
const { useStore, actions } = props;
|
|
@@ -3878,7 +4613,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3878
4613
|
const objectives = useStore((state) => state.objectives);
|
|
3879
4614
|
const fieldSelection = useStore((state) => state.fieldSelection);
|
|
3880
4615
|
const matchRules = useStore((state) => state.matchRules);
|
|
3881
|
-
const
|
|
4616
|
+
const storedDataset = useStore((state) => state.dataset);
|
|
3882
4617
|
const profile = useStore((state) => state.profile);
|
|
3883
4618
|
const clean = useStore((state) => state.clean);
|
|
3884
4619
|
const complete = useStore((state) => state.complete);
|
|
@@ -3890,11 +4625,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3890
4625
|
const paidConfirmed = useStore((state) => state.paidConfirmed);
|
|
3891
4626
|
const activeSessionId = useStore((state) => state.activeSessionId);
|
|
3892
4627
|
const [fieldQuery, setFieldQuery] = react.useState('');
|
|
3893
|
-
const [intakeEditorOpen, setIntakeEditorOpen] = react.useState(() =>
|
|
4628
|
+
const [intakeEditorOpen, setIntakeEditorOpen] = react.useState(() => storedDataset == null);
|
|
3894
4629
|
|
|
3895
4630
|
react.useEffect(() => {
|
|
3896
|
-
setIntakeEditorOpen(
|
|
3897
|
-
}, [
|
|
4631
|
+
setIntakeEditorOpen(storedDataset == null);
|
|
4632
|
+
}, [storedDataset]);
|
|
3898
4633
|
|
|
3899
4634
|
react.useEffect(() => {
|
|
3900
4635
|
if (!open || !visible) return;
|
|
@@ -3910,13 +4645,19 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3910
4645
|
requestJson('/data-cleaning/api/workflow/contract').then((response) => {
|
|
3911
4646
|
if (!disposed) actions.setWorkflowContract(response.contract);
|
|
3912
4647
|
}).catch(() => {});
|
|
3913
|
-
loadWorkflowTasks(actions);
|
|
4648
|
+
const tasksPromise = loadWorkflowTasks(actions);
|
|
3914
4649
|
if (activeSessionId) {
|
|
3915
4650
|
queueWorkflowOperation(activeSessionId, async () => {
|
|
3916
4651
|
const key = String(activeSessionId);
|
|
4652
|
+
const tasks = await tasksPromise;
|
|
3917
4653
|
const saved = ownedSessionId(key) ? readTaskBindings()[key] : null;
|
|
3918
|
-
const
|
|
3919
|
-
|
|
4654
|
+
const hosted = tasks
|
|
4655
|
+
.filter((task) => taskBelongsToSession(task, activeSessionId))
|
|
4656
|
+
.sort((left, right) => String(right.createdAt).localeCompare(String(left.createdAt)))[0]?.id;
|
|
4657
|
+
const taskId = workflowTaskBySession.get(key)?.id
|
|
4658
|
+
|| hosted
|
|
4659
|
+
|| (/^dcw-[a-zA-Z0-9-]+$/.test(saved || '') ? saved : null);
|
|
4660
|
+
if (taskId) await refreshHostedTask(actions, activeSessionId, taskId, null, 'update');
|
|
3920
4661
|
}).catch((taskError) => {
|
|
3921
4662
|
if (!disposed) actions.setError(`状态同步异常:${taskError instanceof Error ? taskError.message : String(taskError)}。保留最近任务记录,未重新执行查询。`);
|
|
3922
4663
|
});
|
|
@@ -3931,11 +4672,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3931
4672
|
let disposed = false, timer;
|
|
3932
4673
|
const poll = async () => {
|
|
3933
4674
|
try {
|
|
3934
|
-
const task = await refreshHostedTask(actions, activeSessionId, workflowTask.id, null,
|
|
4675
|
+
const task = await refreshHostedTask(actions, activeSessionId, workflowTask.id, null, 'update');
|
|
3935
4676
|
if (disposed) return;
|
|
3936
4677
|
if (!task) return;
|
|
3937
4678
|
if (['matching', 'enriching'].includes(task.state)) timer = setTimeout(poll, 2000);
|
|
3938
|
-
else if (step === 'match' || step === 'enrich') actions.setStep(task.stage || step);
|
|
3939
4679
|
} catch (error) { if (!disposed) actions.setError(`状态同步异常:${error.message}。保留最近任务记录,未重新执行查询。`); }
|
|
3940
4680
|
};
|
|
3941
4681
|
timer = setTimeout(poll, 2000);
|
|
@@ -3944,15 +4684,43 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3944
4684
|
|
|
3945
4685
|
// Host 决定 Tab 挂载/可见性;内容不再按私有 open flag 隐藏。
|
|
3946
4686
|
|
|
3947
|
-
const hasData = dataset !== null && dataset.rowCount > 0;
|
|
3948
|
-
const requiresQcc = objectives.includes('validate_identity') || objectives.includes('complete_fields');
|
|
3949
4687
|
const cachedTask = workflowTaskBySession.get(String(activeSessionId || 'unassigned')) ?? workflowTask;
|
|
3950
4688
|
const runtimeKey = cachedTask?.id ?? `session:${activeSessionId || 'unassigned'}`;
|
|
3951
|
-
const runtime = runtimeFor(runtimeKey);
|
|
4689
|
+
const runtime = runtimeFor(runtimeKey, true, cachedTask?.source?.checksum || '');
|
|
3952
4690
|
const stagedSource = runtimeFor(`session:${activeSessionId || 'unassigned'}`, false);
|
|
3953
4691
|
const sourceRows = stagedSource?.sourceRows ?? runtime.sourceRows ?? runtime.rows;
|
|
4692
|
+
const dataset = storedDataset ?? (sourceRows.length ? {
|
|
4693
|
+
fmt: runtime.source?.type || cachedTask?.source?.type || 'csv',
|
|
4694
|
+
headers: runtime.headers.length ? runtime.headers : cachedTask?.source?.headers || [],
|
|
4695
|
+
rowCount: cachedTask?.source?.rowCount ?? sourceRows.length,
|
|
4696
|
+
preview: sourceRows.slice(0, 5),
|
|
4697
|
+
} : null);
|
|
4698
|
+
const hasData = dataset !== null && dataset.rowCount > 0;
|
|
4699
|
+
const requiresQcc = objectives.includes('validate_identity') || objectives.includes('complete_fields');
|
|
3954
4700
|
const intake = intakeState(dataset, sourceRows, input, runtime.source || stagedSource?.source || cachedTask?.source);
|
|
3955
|
-
const
|
|
4701
|
+
const normalizedStage = normalizeViewStage(step);
|
|
4702
|
+
const flowCompatible = cachedTask?.flow?.flowVersion === SUPPORTED_FLOW_VERSION;
|
|
4703
|
+
const viewAccess = step === 'history'
|
|
4704
|
+
? 'read'
|
|
4705
|
+
: cachedTask
|
|
4706
|
+
? resolveStageAccess(normalizedStage, cachedTask.flow)
|
|
4707
|
+
: normalizedStage === 'upload' ? 'current' : 'locked';
|
|
4708
|
+
const allowedActions = new Set(flowCompatible ? cachedTask.flow.allowedActions || [] : []);
|
|
4709
|
+
const isCurrentView = viewAccess === 'current';
|
|
4710
|
+
const canAction = (action) => isCurrentView && (
|
|
4711
|
+
cachedTask ? allowedActions.has(action) : action === 'import-data'
|
|
4712
|
+
);
|
|
4713
|
+
const reviewingImportedDataset = normalizedStage === 'upload'
|
|
4714
|
+
&& cachedTask?.state === 'uploaded'
|
|
4715
|
+
&& cachedTask.flow?.currentStage === 'rules'
|
|
4716
|
+
&& viewAccess === 'read';
|
|
4717
|
+
const navigate = (destination) => navigateWorkflow(actions, activeSessionId, destination, cachedTask);
|
|
4718
|
+
const showIntakeEditor = canAction('import-data') && (!dataset || intakeEditorOpen);
|
|
4719
|
+
const runtimeRecoveryNeeded = Boolean(cachedTask?.source?.rowCount) && runtime.rows.length === 0;
|
|
4720
|
+
const runtimeRecoveryAllowed = runtimeRecoveryNeeded
|
|
4721
|
+
&& ((isCurrentView && ['uploaded', 'rules_confirmed', 'diagnosed'].includes(cachedTask?.state))
|
|
4722
|
+
|| (normalizedStage === 'upload' && viewAccess === 'read'))
|
|
4723
|
+
&& cachedTask?.source?.type !== 'image';
|
|
3956
4724
|
const lastCsv = runtime.lastCsv;
|
|
3957
4725
|
const fieldByPattern = (pattern) => runtime.headers.find((field) => pattern.test(field)) ?? null;
|
|
3958
4726
|
const phoneField = fieldByPattern(/^(phone|mobile|tel|telephone|联系电话|手机号码|手机号)$/i);
|
|
@@ -3971,7 +4739,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3971
4739
|
};
|
|
3972
4740
|
|
|
3973
4741
|
const handleParse = async () => {
|
|
3974
|
-
if (busy) return;
|
|
4742
|
+
if (busy || !canAction('import-data')) return;
|
|
3975
4743
|
if (!intake.pendingText) {
|
|
3976
4744
|
if (intake.available) actions.setError(null);
|
|
3977
4745
|
return;
|
|
@@ -3979,7 +4747,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3979
4747
|
actions.setBusy(true);
|
|
3980
4748
|
actions.setError(null);
|
|
3981
4749
|
try {
|
|
3982
|
-
const result = await parseText(input, actions);
|
|
4750
|
+
const result = await parseText(input, actions, cachedTask?.source?.checksum);
|
|
3983
4751
|
if (result && result.ok !== false) {
|
|
3984
4752
|
applyParsed(result, actions, runtimeKey, { type: result.fmt || 'csv' });
|
|
3985
4753
|
await persistParsedWorkflow(actions, activeSessionId, result, { type: result.fmt || 'csv' }, cachedTask);
|
|
@@ -4003,7 +4771,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4003
4771
|
return true;
|
|
4004
4772
|
};
|
|
4005
4773
|
const handleFile = async (event) => {
|
|
4006
|
-
if (busy) return;
|
|
4774
|
+
if (busy || !canAction('import-data')) return;
|
|
4007
4775
|
const file = event.target && event.target.files && event.target.files[0];
|
|
4008
4776
|
if (!file) return;
|
|
4009
4777
|
if (String(file.type ?? '').startsWith('image/')) {
|
|
@@ -4014,7 +4782,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4014
4782
|
actions.setBusy(true);
|
|
4015
4783
|
actions.setError(null);
|
|
4016
4784
|
try {
|
|
4017
|
-
const result = await parseFile(file);
|
|
4785
|
+
const result = await parseFile(file, cachedTask?.source?.checksum);
|
|
4018
4786
|
if (result && result.ok !== false) {
|
|
4019
4787
|
applyParsed(result, actions, runtimeKey, { type: result.fmt || 'csv', fileName: file.name, sizeBytes: file.size || 0 });
|
|
4020
4788
|
await persistParsedWorkflow(actions, activeSessionId, result, { type: result.fmt || 'csv', fileName: file.name, sizeBytes: file.size || 0 }, cachedTask);
|
|
@@ -4029,7 +4797,51 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4029
4797
|
}
|
|
4030
4798
|
};
|
|
4031
4799
|
|
|
4800
|
+
const acceptRuntimeRecovery = (result, source) => {
|
|
4801
|
+
if (!cachedTask?.source?.checksum) {
|
|
4802
|
+
throw new Error('该旧任务缺少同源校验信息,不能恢复写操作。请新建任务并复用配置。');
|
|
4803
|
+
}
|
|
4804
|
+
if (!result?.checksum || result.checksum !== cachedTask.source.checksum) {
|
|
4805
|
+
throw new Error('重新载入的数据与原任务不一致,未修改当前任务。请新建任务处理其他名单。');
|
|
4806
|
+
}
|
|
4807
|
+
restoreParsedRuntime(result, actions, cachedTask, source);
|
|
4808
|
+
actions.setInput('');
|
|
4809
|
+
actions.setError(null);
|
|
4810
|
+
};
|
|
4811
|
+
|
|
4812
|
+
const recoverRuntimeFile = async (event) => {
|
|
4813
|
+
if (busy) return;
|
|
4814
|
+
const file = event.target?.files?.[0];
|
|
4815
|
+
if (!file) return;
|
|
4816
|
+
actions.setBusy(true);
|
|
4817
|
+
actions.setError(null);
|
|
4818
|
+
try {
|
|
4819
|
+
const result = await parseFile(file, cachedTask?.source?.checksum);
|
|
4820
|
+
acceptRuntimeRecovery(result, { type: result.fmt, fileName: file.name, sizeBytes: file.size || 0 });
|
|
4821
|
+
} catch (err) {
|
|
4822
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4823
|
+
} finally {
|
|
4824
|
+
event.target.value = '';
|
|
4825
|
+
actions.setBusy(false);
|
|
4826
|
+
}
|
|
4827
|
+
};
|
|
4828
|
+
|
|
4829
|
+
const recoverRuntimeText = async () => {
|
|
4830
|
+
if (busy || !String(input || '').trim()) return;
|
|
4831
|
+
actions.setBusy(true);
|
|
4832
|
+
actions.setError(null);
|
|
4833
|
+
try {
|
|
4834
|
+
const result = await parseText(input, actions, cachedTask?.source?.checksum);
|
|
4835
|
+
acceptRuntimeRecovery(result, { type: result.fmt, fileName: cachedTask?.source?.fileName || '' });
|
|
4836
|
+
} catch (err) {
|
|
4837
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4838
|
+
} finally {
|
|
4839
|
+
actions.setBusy(false);
|
|
4840
|
+
}
|
|
4841
|
+
};
|
|
4842
|
+
|
|
4032
4843
|
const performProfile = async (task) => {
|
|
4844
|
+
await verifyRestoredRuntime(actions, activeSessionId, task, runtime);
|
|
4033
4845
|
const r = await api('/data-cleaning/api/mvp/profile', {
|
|
4034
4846
|
rows: runtime.rows,
|
|
4035
4847
|
headers: runtime.headers,
|
|
@@ -4042,7 +4854,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4042
4854
|
};
|
|
4043
4855
|
|
|
4044
4856
|
const runProfile = async () => {
|
|
4045
|
-
if (busy || !runtime.rows.length) {
|
|
4857
|
+
if (busy || !canAction('run-quality') || !runtime.rows.length) {
|
|
4046
4858
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
4047
4859
|
return;
|
|
4048
4860
|
}
|
|
@@ -4050,7 +4862,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4050
4862
|
actions.setError(null);
|
|
4051
4863
|
try {
|
|
4052
4864
|
await performProfile(cachedTask);
|
|
4053
|
-
actions.setStep(requiresQcc ? 'match' : 'enrich');
|
|
4054
4865
|
} catch (err) {
|
|
4055
4866
|
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4056
4867
|
} finally {
|
|
@@ -4059,13 +4870,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4059
4870
|
};
|
|
4060
4871
|
|
|
4061
4872
|
const runClean = async () => {
|
|
4062
|
-
if (busy || !runtime.rows.length) {
|
|
4873
|
+
if (busy || !canAction('run-local-clean') || !runtime.rows.length) {
|
|
4063
4874
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
4064
4875
|
return;
|
|
4065
4876
|
}
|
|
4066
4877
|
actions.setBusy(true);
|
|
4067
4878
|
actions.setError(null);
|
|
4068
4879
|
try {
|
|
4880
|
+
await verifyRestoredRuntime(actions, activeSessionId, cachedTask, runtime);
|
|
4069
4881
|
const r = await api('/data-cleaning/api/mvp/clean', {
|
|
4070
4882
|
rows: runtime.rows,
|
|
4071
4883
|
headers: runtime.headers,
|
|
@@ -4087,13 +4899,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4087
4899
|
};
|
|
4088
4900
|
|
|
4089
4901
|
const runComplete = async () => {
|
|
4090
|
-
if (busy || !runtime.rows.length) {
|
|
4902
|
+
if (busy || !canAction('run-local-complete') || !runtime.rows.length) {
|
|
4091
4903
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
4092
4904
|
return;
|
|
4093
4905
|
}
|
|
4094
4906
|
actions.setBusy(true);
|
|
4095
4907
|
actions.setError(null);
|
|
4096
4908
|
try {
|
|
4909
|
+
await verifyRestoredRuntime(actions, activeSessionId, cachedTask, runtime);
|
|
4097
4910
|
const r = await api('/data-cleaning/api/mvp/complete', {
|
|
4098
4911
|
rows: runtime.rows,
|
|
4099
4912
|
headers: runtime.headers,
|
|
@@ -4120,48 +4933,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4120
4933
|
runtime.resultRows.qcc = Array.isArray(run.rows) ? run.rows : null;
|
|
4121
4934
|
lastCsv.qcc = { csv: run.csv ?? '', name: run.downloadName ?? 'qcc-enriched.csv' };
|
|
4122
4935
|
if (run.reviewCsv) lastCsv.review = { csv: run.reviewCsv, name: run.reviewDownloadName ?? 'qcc-review.csv' };
|
|
4123
|
-
const summary = run.summary ?? {};
|
|
4124
|
-
const reviewRequired = Number(summary.ambiguous ?? run.reviewQueue?.length ?? 0);
|
|
4125
4936
|
const currentTask = workflowTaskBySession.get(String(activeSessionId || 'unassigned')) ?? cachedTask;
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
actions.setStep(currentTask.stage || 'download');
|
|
4129
|
-
return currentTask;
|
|
4130
|
-
}
|
|
4131
|
-
// partial 表示上一轮仍有失败项。重试成功后可直接重进 enrichment,
|
|
4132
|
-
// 无需伪造一次新的 match 转换;若重试产生候选,再回到人工核验。
|
|
4133
|
-
if (currentTask?.state !== 'partial' || reviewRequired > 0) {
|
|
4134
|
-
current = await workflowAction(actions, activeSessionId, currentTask, 'match', {
|
|
4135
|
-
qccRunId: run.runId,
|
|
4136
|
-
summary: {
|
|
4137
|
-
total: Number(summary.totalRows ?? runtime.rows.length),
|
|
4138
|
-
exact: Number(summary.enriched ?? 0) + Number(summary.fieldReview ?? 0),
|
|
4139
|
-
candidate: reviewRequired,
|
|
4140
|
-
confirmed: 0,
|
|
4141
|
-
unresolved: Number(summary.unresolved ?? summary.missingName ?? 0),
|
|
4142
|
-
failed: Number(summary.failed ?? 0),
|
|
4143
|
-
reviewRequired,
|
|
4144
|
-
},
|
|
4145
|
-
});
|
|
4146
|
-
}
|
|
4147
|
-
if (reviewRequired > 0) {
|
|
4148
|
-
actions.setStep('match');
|
|
4149
|
-
return current;
|
|
4150
|
-
}
|
|
4151
|
-
current = await workflowAction(actions, activeSessionId, current, 'enrich-start', { fieldSelection });
|
|
4152
|
-
current = await workflowAction(actions, activeSessionId, current, 'enrichment', {
|
|
4153
|
-
qccRunId: run.runId,
|
|
4154
|
-
summary: {
|
|
4155
|
-
total: Number(summary.totalRows ?? runtime.rows.length),
|
|
4156
|
-
completed: Number(summary.enriched ?? 0),
|
|
4157
|
-
unchanged: Number(summary.unresolved ?? 0) + Number(summary.missingName ?? 0),
|
|
4158
|
-
failed: Number(summary.failed ?? 0),
|
|
4159
|
-
reviewRequired: Number(summary.fieldReview ?? 0),
|
|
4160
|
-
callsUsed: Array.isArray(run.audit) ? run.audit.length : 0,
|
|
4161
|
-
},
|
|
4162
|
-
});
|
|
4163
|
-
actions.setStep(current.stage === 'download' ? 'download' : 'enrich');
|
|
4164
|
-
return current;
|
|
4937
|
+
if (!currentTask?.id) return null;
|
|
4938
|
+
return refreshHostedTask(actions, activeSessionId, currentTask.id, run, 'update');
|
|
4165
4939
|
};
|
|
4166
4940
|
|
|
4167
4941
|
const executePreparedQccCommand = async (payload) => {
|
|
@@ -4201,7 +4975,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4201
4975
|
};
|
|
4202
4976
|
|
|
4203
4977
|
const runQcc = async () => {
|
|
4204
|
-
if (busy || !cachedTask?.id || !hasData) return;
|
|
4978
|
+
if (busy || !canAction('prepare-qcc-command') || !cachedTask?.id || !hasData) return;
|
|
4205
4979
|
actions.setBusy(true);
|
|
4206
4980
|
actions.setError(null);
|
|
4207
4981
|
try {
|
|
@@ -4223,7 +4997,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4223
4997
|
};
|
|
4224
4998
|
|
|
4225
4999
|
const resolveCandidate = async (item, candidate) => {
|
|
4226
|
-
if (busy || !paidConfirmed || !qccRun) return;
|
|
5000
|
+
if (busy || !canAction('resolve-candidate') || !paidConfirmed || !qccRun) return;
|
|
4227
5001
|
actions.setBusy(true);
|
|
4228
5002
|
actions.setError(null);
|
|
4229
5003
|
try {
|
|
@@ -4244,7 +5018,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4244
5018
|
};
|
|
4245
5019
|
|
|
4246
5020
|
const retryFailures = async () => {
|
|
4247
|
-
if (busy || !paidConfirmed || !qccRun) return;
|
|
5021
|
+
if (busy || !canAction('retry-failed') || !paidConfirmed || !qccRun) return;
|
|
4248
5022
|
const names = [...new Set((qccRun.errors || []).filter((item) => item.error && item.error.retryable).map((item) => item.companyName))];
|
|
4249
5023
|
if (!names.length) return;
|
|
4250
5024
|
actions.setBusy(true);
|
|
@@ -4265,7 +5039,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4265
5039
|
}
|
|
4266
5040
|
};
|
|
4267
5041
|
|
|
5042
|
+
const retryDelivery = async () => {
|
|
5043
|
+
if (busy || !canAction('retry-delivery') || !cachedTask?.id) return;
|
|
5044
|
+
actions.setBusy(true);
|
|
5045
|
+
actions.setError(null);
|
|
5046
|
+
try {
|
|
5047
|
+
await workflowAction(actions, activeSessionId, cachedTask, 'retry-delivery');
|
|
5048
|
+
} catch (err) {
|
|
5049
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
5050
|
+
} finally {
|
|
5051
|
+
actions.setBusy(false);
|
|
5052
|
+
}
|
|
5053
|
+
};
|
|
5054
|
+
|
|
4268
5055
|
const saveTaskSettings = async () => {
|
|
5056
|
+
if (!canAction('edit-rules')) return;
|
|
4269
5057
|
actions.setBusy(true);
|
|
4270
5058
|
actions.setError(null);
|
|
4271
5059
|
try {
|
|
@@ -4283,6 +5071,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4283
5071
|
};
|
|
4284
5072
|
|
|
4285
5073
|
const confirmRuleSettings = async () => {
|
|
5074
|
+
if (!canAction('confirm-rules')) return;
|
|
4286
5075
|
if (!hasData) { actions.setError('请先上传并解析数据。'); return; }
|
|
4287
5076
|
const anchorMappings = mappings.filter((mapping) => ['company_name', 'credit_no', 'reg_no'].includes(mapping.targetField));
|
|
4288
5077
|
if (!anchorMappings.length) { actions.setError('请至少映射企业名称、统一社会信用代码或注册号。'); return; }
|
|
@@ -4290,6 +5079,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4290
5079
|
actions.setBusy(true);
|
|
4291
5080
|
actions.setError(null);
|
|
4292
5081
|
try {
|
|
5082
|
+
await verifyRestoredRuntime(actions, activeSessionId, cachedTask, runtime);
|
|
4293
5083
|
let current = await ensureWorkflowTask(actions, activeSessionId, {
|
|
4294
5084
|
title: taskTitle, objectives, fieldSelection, mappings, matchRules,
|
|
4295
5085
|
});
|
|
@@ -4302,6 +5092,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4302
5092
|
columnCount: dataset.headers?.length || 0,
|
|
4303
5093
|
headers: dataset.headers || [],
|
|
4304
5094
|
sizeBytes: runtime.source?.sizeBytes || 0,
|
|
5095
|
+
checksum: runtime.source?.checksum || '',
|
|
4305
5096
|
},
|
|
4306
5097
|
});
|
|
4307
5098
|
}
|
|
@@ -4316,7 +5107,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4316
5107
|
?? mappings.find((mapping) => mapping.targetField === 'credit_no')
|
|
4317
5108
|
?? mappings.find((mapping) => mapping.targetField === 'reg_no');
|
|
4318
5109
|
if (primary) actions.setNameField(primary.sourceField);
|
|
4319
|
-
actions.setStep('profile');
|
|
4320
5110
|
} catch (ruleError) {
|
|
4321
5111
|
actions.setError(ruleError instanceof Error ? ruleError.message : String(ruleError));
|
|
4322
5112
|
} finally {
|
|
@@ -4333,16 +5123,23 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4333
5123
|
} catch (error) { actions.setError(error.message); return; }
|
|
4334
5124
|
};
|
|
4335
5125
|
|
|
4336
|
-
const startNewWorkflow = () => {
|
|
5126
|
+
const startNewWorkflow = (reuseConfig = false) => {
|
|
4337
5127
|
const event = document.createEvent('CustomEvent');
|
|
4338
|
-
event.initCustomEvent('dsh:data-cleaning-prompt-new', false, true, {
|
|
5128
|
+
event.initCustomEvent('dsh:data-cleaning-prompt-new', false, true, {
|
|
5129
|
+
sessionId: activeSessionId,
|
|
5130
|
+
...(reuseConfig && cachedTask ? { reuseConfig: {
|
|
5131
|
+
objectives: cachedTask.objectives || [],
|
|
5132
|
+
fieldSelection: cachedTask.fieldSelection || [],
|
|
5133
|
+
matchRules: cachedTask.matchRules || {},
|
|
5134
|
+
} } : {}),
|
|
5135
|
+
});
|
|
4339
5136
|
if (document.dispatchEvent(event)) {
|
|
4340
5137
|
actions.setError('当前会话的任务向导尚未就绪或正在解析图片,请稍后重试。');
|
|
4341
5138
|
return;
|
|
4342
5139
|
}
|
|
4343
|
-
if (cachedTask?.id)
|
|
4344
|
-
|
|
4345
|
-
|
|
5140
|
+
if (cachedTask?.id) removeRuntime(cachedTask.id);
|
|
5141
|
+
removeRuntime(`session:${activeSessionId || 'unassigned'}`);
|
|
5142
|
+
removeRuntime('unassigned');
|
|
4346
5143
|
workflowTaskBySession.delete(String(activeSessionId || 'unassigned'));
|
|
4347
5144
|
actions.resetWorkflow();
|
|
4348
5145
|
actions.close();
|
|
@@ -4356,9 +5153,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4356
5153
|
const createArtifactBundle = async () => {
|
|
4357
5154
|
let latest = workflowTaskBySession.get(String(activeSessionId || 'unassigned')) ?? cachedTask;
|
|
4358
5155
|
if (!latest) throw new Error('请先创建并执行数据清洗补全任务。');
|
|
5156
|
+
if (!canAction('create-local-artifacts')) throw new Error('当前流程节点不能生成本地结果。');
|
|
4359
5157
|
if (Array.isArray(latest.artifacts) && latest.artifacts.length) return latest;
|
|
4360
5158
|
const rows = exportRows();
|
|
4361
5159
|
if (!rows?.length) throw new Error('当前页面没有可导出的明细。若任务来自历史记录,请重新上传源文件并继续处理。');
|
|
5160
|
+
await verifyRestoredRuntime(actions, activeSessionId, latest, runtime);
|
|
4362
5161
|
const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(latest.id)}/artifacts`, 'POST', {
|
|
4363
5162
|
originSessionId: activeSessionId, originWorkspaceId: latest.originWorkspaceId,
|
|
4364
5163
|
expectedRevision: latest.revision,
|
|
@@ -4436,6 +5235,37 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4436
5235
|
: cachedTask?.enrichmentSummary?.reviewRequired
|
|
4437
5236
|
?? cachedTask?.matchSummary?.reviewRequired;
|
|
4438
5237
|
const qccFailedCount = qccRun?.summary?.failed ?? cachedTask?.enrichmentSummary?.failed ?? cachedTask?.matchSummary?.failed ?? 0;
|
|
5238
|
+
const viewedStageLabel = workflowStageLabel(normalizedStage);
|
|
5239
|
+
const currentStageLabel = workflowStageLabel(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload');
|
|
5240
|
+
const taskStatusLabel = viewAccess === 'read'
|
|
5241
|
+
? '已完成'
|
|
5242
|
+
: cachedTask?.source?.type === 'image' && cachedTask?.state === 'draft'
|
|
5243
|
+
? '图片已暂存,待识别'
|
|
5244
|
+
: WORKFLOW_STATE_LABELS[cachedTask?.state] || cachedTask?.state || '待开始';
|
|
5245
|
+
const viewedStageCompleted = viewAccess === 'read' || cachedTask?.state === 'completed';
|
|
5246
|
+
const taskTimestamp = cachedTask?.updatedAt || cachedTask?.createdAt;
|
|
5247
|
+
const taskTimeLabel = taskTimestamp && Number.isFinite(new Date(taskTimestamp).getTime())
|
|
5248
|
+
? new Date(taskTimestamp).toLocaleString('zh-CN', {
|
|
5249
|
+
year: 'numeric', month: 'numeric', day: 'numeric',
|
|
5250
|
+
hour: '2-digit', minute: '2-digit', hour12: false,
|
|
5251
|
+
})
|
|
5252
|
+
: '';
|
|
5253
|
+
const taskFileName = cachedTask?.source?.fileName || '';
|
|
5254
|
+
const taskSourceLabel = taskFileName
|
|
5255
|
+
|| (cachedTask?.source?.type === 'image'
|
|
5256
|
+
? '图片名单'
|
|
5257
|
+
: cachedTask?.source
|
|
5258
|
+
? '文本粘贴'
|
|
5259
|
+
: '');
|
|
5260
|
+
const taskRowCount = cachedTask?.source?.rowCount ?? dataset?.rowCount ?? 0;
|
|
5261
|
+
const taskFieldCount = cachedTask?.fieldSelection?.length ?? fieldSelection.length;
|
|
5262
|
+
const hasTaskResultSummary = Boolean(qccRun || cachedTask?.enrichmentSummary || cachedTask?.matchSummary);
|
|
5263
|
+
const taskCompletedCount = Number.isFinite(qccEnrichedCount) ? qccEnrichedCount : 0;
|
|
5264
|
+
const taskReviewCount = Number.isFinite(qccReviewCount) ? qccReviewCount : 0;
|
|
5265
|
+
const taskFailedCount = Number.isFinite(qccFailedCount) ? qccFailedCount : 0;
|
|
5266
|
+
const taskCompletedMetric = `${taskCompletedCount}/${taskRowCount || taskCompletedCount} 条`;
|
|
5267
|
+
const taskReviewMetric = `${taskReviewCount} 条`;
|
|
5268
|
+
const taskFailedMetric = `${taskFailedCount} 条`;
|
|
4439
5269
|
const stat = (label, value, tone) => h('div', { className: 'dcAgentCard' },
|
|
4440
5270
|
h('span', null, label),
|
|
4441
5271
|
h('b', { className: tone ? `is-${tone}` : null }, displayStatValue(value))
|
|
@@ -4472,8 +5302,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4472
5302
|
h('button', {
|
|
4473
5303
|
type: 'button',
|
|
4474
5304
|
className: 'dcAgentButton is-primary',
|
|
4475
|
-
onClick: startNewWorkflow,
|
|
5305
|
+
onClick: () => startNewWorkflow(false),
|
|
4476
5306
|
}, '新建清洗任务'),
|
|
5307
|
+
cachedTask ? h('button', {
|
|
5308
|
+
type: 'button',
|
|
5309
|
+
className: 'dcAgentButton',
|
|
5310
|
+
onClick: () => startNewWorkflow(true),
|
|
5311
|
+
}, '新建任务并复用配置') : null,
|
|
4477
5312
|
),
|
|
4478
5313
|
historyTask ? h('section', { className: 'dcAgentSection', 'aria-label': '历史任务只读详情' },
|
|
4479
5314
|
h('h3', null, workflowDisplayName(historyTask)),
|
|
@@ -4509,8 +5344,48 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4509
5344
|
),
|
|
4510
5345
|
);
|
|
4511
5346
|
} else if (step === 'rules') {
|
|
4512
|
-
|
|
5347
|
+
if (!canAction('edit-rules') && !canAction('confirm-rules')) {
|
|
5348
|
+
pane = h('div', { className: 'dcAgentPane' },
|
|
5349
|
+
h('p', { className: 'dcAgentHint' }, isCurrentView
|
|
5350
|
+
? '规则已经确认,当前只等待生成质量体检。'
|
|
5351
|
+
: '这是已确认规则的只读摘要。'),
|
|
5352
|
+
h('section', { className: 'dcAgentSection' },
|
|
5353
|
+
h('h3', null, taskTitle),
|
|
5354
|
+
h('div', { className: 'dcAgentChips' },
|
|
5355
|
+
mappings.map((mapping) => h('span', { key: mapping.sourceField, className: 'dcAgentChip' },
|
|
5356
|
+
`${mapping.sourceField} → ${resultFieldLabel(mapping.targetField)}`))),
|
|
5357
|
+
h('p', { className: 'dcAgentHint' }, `任务目标 ${objectives.length} 项 · 补全字段 ${fieldSelection.length} 项`),
|
|
5358
|
+
),
|
|
5359
|
+
canAction('run-quality') ? h('button', {
|
|
5360
|
+
type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !runtime.rows.length,
|
|
5361
|
+
onClick: runProfile,
|
|
5362
|
+
}, busy ? '体检中…' : '生成质量体检报告') : null,
|
|
5363
|
+
);
|
|
5364
|
+
} else pane = h('div', { className: 'dcAgentPane' },
|
|
4513
5365
|
h('p', { className: 'dcAgentHint' }, '确认任务名称、字段映射、匹配规则和输出字段。保存草稿不会调用企查查;确认规则后才可进入质量体检。'),
|
|
5366
|
+
canAction('import-data') ? h('section', { className: 'dcAgentSection' },
|
|
5367
|
+
h('div', { className: 'dcAgentRow' },
|
|
5368
|
+
h('h3', null, '当前来源'),
|
|
5369
|
+
!intakeEditorOpen ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5370
|
+
onClick: () => { actions.setError(null); setIntakeEditorOpen(true); } }, '重新导入') : null,
|
|
5371
|
+
),
|
|
5372
|
+
h('p', { className: 'dcAgentHint' }, `${intake.fileName} · ${dataset?.rowCount || 0} 行`),
|
|
5373
|
+
showIntakeEditor ? h('div', { className: 'dcAgentIntakeEditor' },
|
|
5374
|
+
h('div', { className: 'dcAgentIntakeSourceActions' },
|
|
5375
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5376
|
+
onClick: (event) => event.currentTarget.nextElementSibling?.click() }, '选择文件(自动解析)'),
|
|
5377
|
+
h('input', { type: 'file', accept: '.csv,.json,.xlsx,.xls,image/png,image/jpeg,image/webp',
|
|
5378
|
+
style: { display: 'none' }, disabled: busy, 'aria-label': '选择数据文件', onChange: handleFile }),
|
|
5379
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5380
|
+
onClick: () => openImageIntake() }, '图片识别(粘贴 / 选择)'),
|
|
5381
|
+
),
|
|
5382
|
+
h('textarea', { className: 'dcAgentTextarea', value: input, disabled: busy,
|
|
5383
|
+
'aria-label': '粘贴数据', placeholder: '粘贴 CSV、JSON 或逐行企业名单',
|
|
5384
|
+
onInput: (event) => { actions.setInput(event.target.value); actions.setError(null); } }),
|
|
5385
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !intake.pendingText,
|
|
5386
|
+
'aria-label': '解析数据', onClick: handleParse }, busy ? '解析中…' : '解析粘贴内容'),
|
|
5387
|
+
) : null,
|
|
5388
|
+
) : null,
|
|
4514
5389
|
h('label', { className: 'dcAgentFormField' },
|
|
4515
5390
|
h('span', null, '任务名称'),
|
|
4516
5391
|
h('input', { className: 'dcAgentField', value: taskTitle, maxLength: 120, onChange: (event) => actions.setTaskTitle(event.target.value) }),
|
|
@@ -4593,22 +5468,30 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4593
5468
|
),
|
|
4594
5469
|
);
|
|
4595
5470
|
} else if (step === 'profile') {
|
|
5471
|
+
const persistedQuality = cachedTask?.qualitySummary;
|
|
4596
5472
|
pane = h('div', { className: 'dcAgentPane dcAgentProfilePane' },
|
|
4597
5473
|
h('section', { className: 'dcAgentSection dcAgentProfileSurface', 'aria-label': '质量体检报告' },
|
|
4598
5474
|
h('header', { className: 'dcAgentProfileHeader' },
|
|
4599
5475
|
h('h3', null, '质量体检报告'),
|
|
4600
5476
|
h('p', { className: 'dcAgentHint' }, '基于已解析数据生成本地质量画像(缺失率、去重值、金额分布)。本步骤不发起企查查调用。'),
|
|
4601
5477
|
),
|
|
4602
|
-
profile ? h('div', { className: 'dcAgentProfileReport' },
|
|
5478
|
+
profile || persistedQuality ? h('div', { className: 'dcAgentProfileReport' },
|
|
5479
|
+
!profile ? h('p', { className: 'dcAgentHint', role: 'status' }, '以下为 Host 保存的体检摘要;字段级明细未持久化。') : null,
|
|
4603
5480
|
h('div', { className: 'dcAgentGrid dcAgentProfileMetrics', 'aria-label': '体检摘要' },
|
|
4604
|
-
stat('行数', profile
|
|
4605
|
-
stat('列数', profile
|
|
4606
|
-
profile
|
|
4607
|
-
profile
|
|
4608
|
-
profile
|
|
4609
|
-
profile
|
|
5481
|
+
stat('行数', profile?.rowCount ?? persistedQuality?.total ?? cachedTask?.source?.rowCount ?? '—'),
|
|
5482
|
+
stat('列数', profile?.columnCount ?? cachedTask?.source?.columnCount ?? '—'),
|
|
5483
|
+
profile?.amountStats ? stat('金额 最小', profile.amountStats.min) : null,
|
|
5484
|
+
profile?.amountStats ? stat('金额 最大', profile.amountStats.max) : null,
|
|
5485
|
+
profile?.amountStats ? stat('金额 总和', profile.amountStats.sum) : null,
|
|
5486
|
+
profile?.amountStats ? stat('金额 均值', typeof profile.amountStats.mean === 'number' ? profile.amountStats.mean.toFixed(2) : profile.amountStats.mean) : null,
|
|
5487
|
+
!profile && persistedQuality ? stat('有效主体', persistedQuality.valid, persistedQuality.valid > 0 ? 'good' : null) : null,
|
|
5488
|
+
!profile && persistedQuality ? stat('缺少主体标识', persistedQuality.missingAnchor, persistedQuality.missingAnchor > 0 ? 'bad' : null) : null,
|
|
5489
|
+
!profile && persistedQuality ? stat('重复主体', persistedQuality.duplicates, persistedQuality.duplicates > 0 ? 'warn' : null) : null,
|
|
5490
|
+
!profile && persistedQuality ? stat('信用代码异常', persistedQuality.invalidCreditNo, persistedQuality.invalidCreditNo > 0 ? 'warn' : null) : null,
|
|
5491
|
+
!profile && persistedQuality ? stat('联系电话异常', persistedQuality.invalidPhone, persistedQuality.invalidPhone > 0 ? 'warn' : null) : null,
|
|
5492
|
+
!profile && persistedQuality ? stat('空白字段', persistedQuality.emptyFields, persistedQuality.emptyFields > 0 ? 'warn' : null) : null,
|
|
4610
5493
|
),
|
|
4611
|
-
Array.isArray(profile
|
|
5494
|
+
Array.isArray(profile?.columns) && profile.columns.length ? h('section', { className: 'dcAgentProfileTableSection', 'aria-label': '字段质量明细' },
|
|
4612
5495
|
h('div', { className: 'dcAgentProfileTableHead' },
|
|
4613
5496
|
h('h4', null, '字段质量明细'),
|
|
4614
5497
|
h('span', { className: 'dcAgentHint' }, `共 ${profile.columns.length} 列`),
|
|
@@ -4635,12 +5518,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4635
5518
|
type: 'button',
|
|
4636
5519
|
className: 'dcAgentButton is-primary',
|
|
4637
5520
|
disabled: busy,
|
|
4638
|
-
onClick: () =>
|
|
5521
|
+
onClick: () => navigate(requiresQcc ? 'match' : 'enrich'),
|
|
4639
5522
|
}, requiresQcc ? '下一步:匹配核验' : '下一步:本地清洗补全'),
|
|
4640
5523
|
),
|
|
4641
|
-
) : h('div', { className: 'dcAgentProfileEmpty' },
|
|
5524
|
+
) : canAction('run-quality') ? h('div', { className: 'dcAgentProfileEmpty' },
|
|
4642
5525
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData, 'aria-label': '生成体检报告', onClick: runProfile }, busy ? '体检中…' : '生成体检报告'),
|
|
4643
|
-
),
|
|
5526
|
+
) : h('p', { className: 'dcAgentHint' }, '当前没有可恢复的质量体检明细。'),
|
|
4644
5527
|
),
|
|
4645
5528
|
);
|
|
4646
5529
|
} else if (step === 'match') {
|
|
@@ -4663,19 +5546,20 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4663
5546
|
h('section', { className: 'dcAgentMatchBlock', 'aria-label': '准备任务说明' },
|
|
4664
5547
|
h('div', { className: 'dcAgentMatchBlockHead' },
|
|
4665
5548
|
h('h4', null, '准备任务说明'),
|
|
4666
|
-
h('span', { className: 'dcAgentMatchScope' }, h('b', null,
|
|
5549
|
+
h('span', { className: 'dcAgentMatchScope' }, h('b', null,
|
|
5550
|
+
dataset?.rowCount ?? cachedTask?.source?.rowCount ?? runtime.rows.length), '条记录'),
|
|
4667
5551
|
),
|
|
4668
5552
|
h('p', { className: 'dcAgentHint' }, `已选择 ${fieldSelection.length} 个补全字段。Host 自动检查所需工具并计算去重主体与调用上界。`),
|
|
4669
5553
|
h('p', { className: 'dcAgentHint' }, '回填后可修改;点击发送才开始查询,使用你已连接的企查查 MCP 账号及额度。'),
|
|
4670
5554
|
),
|
|
4671
5555
|
),
|
|
4672
|
-
h('div', { className: 'dcAgentMatchActions' },
|
|
5556
|
+
canAction('prepare-qcc-command') ? h('div', { className: 'dcAgentMatchActions' },
|
|
4673
5557
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData || !cachedTask?.id, onClick: runQcc }, busy ? '检查并生成中…' : '生成可编辑任务说明'),
|
|
4674
|
-
),
|
|
5558
|
+
) : null,
|
|
4675
5559
|
),
|
|
4676
5560
|
qccRun ? h('section', { className: 'dcAgentSection' },
|
|
4677
5561
|
h('h3', null, `任务 ${qccRun.runId} · ${qccRun.state}`),
|
|
4678
|
-
(
|
|
5562
|
+
canAction('resolve-candidate') && (qccRun.reviewQueue || []).length ? h('label', { className: 'dcAgentCheck' },
|
|
4679
5563
|
h('input', { type: 'checkbox', checked: paidConfirmed, 'aria-label': '确认候选或重试的企查查调用', onChange: (event) => actions.setPaidConfirmed(event.target.checked) }),
|
|
4680
5564
|
'确认主体或重试会继续查询,使用当前连接的企查查账号;额度或费用由该账号自行承担。',
|
|
4681
5565
|
) : null,
|
|
@@ -4695,13 +5579,25 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4695
5579
|
item.candidates.map((candidate) => h('div', { key: candidate.creditNo, className: 'dcAgentCandidate' },
|
|
4696
5580
|
h('div', null, h('b', null, candidate.companyName || '未命名候选'), h('small', null, candidate.creditNo)),
|
|
4697
5581
|
h('small', null, `${candidate.status || '状态未知'} · ${(candidate.legalRep || []).join('、') || '法人未知'}`),
|
|
4698
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: () => resolveCandidate(item, candidate) }, '确认此主体'),
|
|
5582
|
+
canAction('resolve-candidate') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: () => resolveCandidate(item, candidate) }, '确认此主体') : null,
|
|
4699
5583
|
)),
|
|
4700
5584
|
)),
|
|
4701
|
-
|
|
5585
|
+
) : cachedTask?.matchSummary ? h('section', { className: 'dcAgentSection', 'aria-label': '主体匹配结果摘要' },
|
|
5586
|
+
h('h3', null, '主体匹配结果'),
|
|
5587
|
+
h('p', { className: 'dcAgentHint', role: 'status' }, '以下为 Host 保存的任务摘要;逐行结果和候选详情未持久化。'),
|
|
5588
|
+
h('div', { className: 'dcAgentGrid' },
|
|
5589
|
+
stat('总数', cachedTask.matchSummary.total),
|
|
5590
|
+
stat('精确匹配', (cachedTask.matchSummary.exact ?? 0) + (cachedTask.matchSummary.confirmed ?? 0), 'good'),
|
|
5591
|
+
stat('候选记录', cachedTask.matchSummary.candidate, cachedTask.matchSummary.candidate > 0 ? 'warn' : null),
|
|
5592
|
+
stat('待核验', cachedTask.matchSummary.reviewRequired, cachedTask.matchSummary.reviewRequired > 0 ? 'warn' : null),
|
|
5593
|
+
stat('未匹配', cachedTask.matchSummary.unresolved, cachedTask.matchSummary.unresolved > 0 ? 'warn' : null),
|
|
5594
|
+
stat('失败', cachedTask.matchSummary.failed, cachedTask.matchSummary.failed > 0 ? 'bad' : null),
|
|
5595
|
+
),
|
|
4702
5596
|
) : null,
|
|
4703
5597
|
);
|
|
4704
5598
|
} else if (step === 'enrich') {
|
|
5599
|
+
const persistedEnrichment = cachedTask?.enrichmentSummary;
|
|
5600
|
+
const hasRuntimeEnrichment = Boolean(clean || complete || qccRun);
|
|
4705
5601
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4706
5602
|
h('p', { className: 'dcAgentHint' }, '执行本地确定性清洗并核对本任务所选补全字段。外部字段以当前用户企查查 MCP 的真实返回为准,缺失值不会被编造。'),
|
|
4707
5603
|
h('section', { className: 'dcAgentSection' },
|
|
@@ -4710,30 +5606,55 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4710
5606
|
),
|
|
4711
5607
|
h('section', { className: 'dcAgentSection' },
|
|
4712
5608
|
h('h3', null, '本地清洗预处理'),
|
|
4713
|
-
h('div', { className: 'dcAgentRow' },
|
|
4714
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行清洗', onClick: runClean }, busy ? '处理中…' : '执行确定性清洗'),
|
|
4715
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行补全', onClick: runComplete }, busy ? '处理中…' : '本地规则补全'),
|
|
4716
|
-
),
|
|
5609
|
+
canAction('run-local-clean') || canAction('run-local-complete') ? h('div', { className: 'dcAgentRow' },
|
|
5610
|
+
canAction('run-local-clean') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行清洗', onClick: runClean }, busy ? '处理中…' : '执行确定性清洗') : null,
|
|
5611
|
+
canAction('run-local-complete') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行补全', onClick: runComplete }, busy ? '处理中…' : '本地规则补全') : null,
|
|
5612
|
+
) : null,
|
|
4717
5613
|
clean ? h('div', { className: 'dcAgentGrid' },
|
|
4718
5614
|
stat('总数', clean.total), stat('保留', clean.kept, 'good'), stat('剔除', clean.dropped, 'bad'),
|
|
4719
5615
|
stat('缺失关键字段', clean.badMissing, clean.badMissing > 0 ? 'bad' : null),
|
|
4720
5616
|
stat('重复', clean.badDuplicate, clean.badDuplicate > 0 ? 'warn' : null),
|
|
4721
5617
|
) : null,
|
|
4722
5618
|
),
|
|
4723
|
-
h('
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
5619
|
+
persistedEnrichment && !hasRuntimeEnrichment ? h('section', { className: 'dcAgentSection', 'aria-label': '字段补全结果摘要' },
|
|
5620
|
+
h('h3', null, '字段补全结果'),
|
|
5621
|
+
h('p', { className: 'dcAgentHint', role: 'status' }, '以下为 Host 保存的任务摘要;逐行补全结果请从结果下载查看。'),
|
|
5622
|
+
h('div', { className: 'dcAgentGrid' },
|
|
5623
|
+
stat('总数', persistedEnrichment.total),
|
|
5624
|
+
stat('已补全', persistedEnrichment.completed, persistedEnrichment.completed > 0 ? 'good' : null),
|
|
5625
|
+
stat('原值保留', persistedEnrichment.unchanged),
|
|
5626
|
+
stat('待核验', persistedEnrichment.reviewRequired, persistedEnrichment.reviewRequired > 0 ? 'warn' : null),
|
|
5627
|
+
stat('失败', persistedEnrichment.failed, persistedEnrichment.failed > 0 ? 'bad' : null),
|
|
5628
|
+
),
|
|
5629
|
+
) : h('div', { className: 'dcAgentGrid' },
|
|
5630
|
+
stat('输入行数', dataset ? dataset.rowCount : '—'),
|
|
5631
|
+
stat('清洗保留', clean ? clean.kept : '—', clean && clean.kept > 0 ? 'good' : null),
|
|
5632
|
+
stat('本地补全', complete ? complete.completed : '—', complete && complete.completed > 0 ? 'good' : null),
|
|
5633
|
+
stat('QCC 已补全', qccEnrichedCount, qccEnrichedCount > 0 ? 'good' : null),
|
|
5634
|
+
stat('待核验', qccReviewCount, qccReviewCount > 0 ? 'warn' : null),
|
|
5635
|
+
),
|
|
5636
|
+
canAction('create-local-artifacts') ? h('div', { className: 'dcAgentRow' }, h('button', {
|
|
5637
|
+
type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !exportRows()?.length,
|
|
5638
|
+
onClick: () => createAndDownload('complete', 'xlsx'),
|
|
5639
|
+
}, '生成结果')) : null,
|
|
4731
5640
|
);
|
|
4732
5641
|
} else if (step === 'download') {
|
|
4733
|
-
const
|
|
5642
|
+
const artifactsByKind = new Map();
|
|
5643
|
+
for (const artifact of cachedTask?.artifacts || []) {
|
|
5644
|
+
if (artifact.format === 'xlsx' && (artifact.kind !== 'review' || artifact.rowCount > 0)) {
|
|
5645
|
+
artifactsByKind.set(artifact.kind, artifact);
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
const availableArtifacts = [...artifactsByKind.values()];
|
|
4734
5649
|
const resultArtifacts = availableArtifacts.filter(artifact => artifact.kind !== 'report');
|
|
4735
5650
|
const reportArtifact = availableArtifacts.find(artifact => artifact.kind === 'report');
|
|
4736
|
-
const canCreateArtifacts = Boolean(exportRows()?.length) &&
|
|
5651
|
+
const canCreateArtifacts = Boolean(exportRows()?.length) && canAction('create-local-artifacts');
|
|
5652
|
+
const retryableErrors = (qccRun?.errors || []).filter((item) => item.error?.retryable);
|
|
5653
|
+
const canRetryFailures = canAction('retry-failed')
|
|
5654
|
+
&& cachedTask?.runtimeCapabilities?.liveRunAvailable
|
|
5655
|
+
&& retryableErrors.length > 0;
|
|
5656
|
+
const canRetryDelivery = canAction('retry-delivery')
|
|
5657
|
+
&& cachedTask?.runtimeCapabilities?.liveRunAvailable;
|
|
4737
5658
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4738
5659
|
h('p', { className: 'dcAgentHint' }, '结果文件由 Host 保存;有失败、未匹配或待核验记录时展示异常清单。任务或插件重启后,可从任务历史继续下载;无需再次调用企查查。'),
|
|
4739
5660
|
h('div', { className: 'dcAgentResultSummary' }, h('div', { className: 'dcAgentGrid' },
|
|
@@ -4770,10 +5691,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4770
5691
|
),
|
|
4771
5692
|
!canCreateArtifacts ? h('p', { className: 'dcAgentHint' }, '请先完成规则确认与清洗补全;历史任务若尚未生成制品,需要重新上传源文件。') : null,
|
|
4772
5693
|
),
|
|
5694
|
+
canRetryFailures ? h('section', { className: 'dcAgentSection' },
|
|
5695
|
+
h('label', { className: 'dcAgentCheck' },
|
|
5696
|
+
h('input', { type: 'checkbox', checked: paidConfirmed, 'aria-label': '确认重试的企查查调用', onChange: (event) => actions.setPaidConfirmed(event.target.checked) }),
|
|
5697
|
+
'重试会继续查询,使用当前连接的企查查账号;额度或费用由该账号自行承担。',
|
|
5698
|
+
),
|
|
5699
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: retryFailures },
|
|
5700
|
+
`重试可恢复失败项 · ${retryableErrors.length}`),
|
|
5701
|
+
) : null,
|
|
5702
|
+
canRetryDelivery ? h('button', {
|
|
5703
|
+
type: 'button', className: 'dcAgentButton', disabled: busy, onClick: retryDelivery,
|
|
5704
|
+
}, '重新生成结果文件') : null,
|
|
4773
5705
|
);
|
|
4774
5706
|
} else {
|
|
4775
5707
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4776
|
-
dataset ? h('section', { className: 'dcAgentSection dcAgentIntakeSummary', 'aria-label': '当前导入数据', role: 'status' },
|
|
5708
|
+
dataset || cachedTask?.source ? h('section', { className: 'dcAgentSection dcAgentIntakeSummary', 'aria-label': '当前导入数据', role: 'status' },
|
|
4777
5709
|
h('div', { className: 'dcAgentIntakeSummaryHead' },
|
|
4778
5710
|
h('div', null,
|
|
4779
5711
|
h('h3', null, intake.available ? `已解析 ${intake.count} 条数据` : '已保存导入摘要'),
|
|
@@ -4781,8 +5713,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4781
5713
|
),
|
|
4782
5714
|
),
|
|
4783
5715
|
h('p', { className: 'dcAgentHint' }, intake.available
|
|
4784
|
-
? '
|
|
4785
|
-
|
|
5716
|
+
? canAction('import-data')
|
|
5717
|
+
? '数据已导入。请核对下方完整名单;需要替换来源时点击“重新导入”。'
|
|
5718
|
+
: '这是已导入来源的只读摘要。更换名单请从任务历史新建任务并复用配置。'
|
|
5719
|
+
: canAction('import-data')
|
|
5720
|
+
? '当前页面没有完整原始数据。请重新导入原文件;历史下载制品仍可从任务历史获取。'
|
|
5721
|
+
: '当前页面只保留 Host 来源摘要;已有制品可从结果下载或任务历史获取。'),
|
|
4786
5722
|
) : null,
|
|
4787
5723
|
showIntakeEditor ? h('section', { id: 'dc-agent-intake-editor', className: 'dcAgentSection dcAgentIntakeEditor', 'aria-label': dataset ? '重新导入数据' : '导入数据' },
|
|
4788
5724
|
h('header', { className: 'dcAgentIntakeEditorHeader' },
|
|
@@ -4840,16 +5776,47 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4840
5776
|
}) : null,
|
|
4841
5777
|
hasData ? h('p', { className: 'dcAgentHint' }, '请核对完整名单。识别清单不代表已完成企查查匹配。核对后进入「规则与体检」,确认规则生成质量体检;再进入「主体匹配」直接生成可编辑任务说明。Host 自动检查工具和调用范围,回填中央对话框后点击发送才开始查询。') : null,
|
|
4842
5778
|
h('div', { className: 'dcAgentUploadActions' },
|
|
4843
|
-
hasData && !intakeEditorOpen ? h('button', {
|
|
5779
|
+
hasData && !intakeEditorOpen && canAction('import-data') ? h('button', {
|
|
4844
5780
|
type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
4845
5781
|
'aria-expanded': false, 'aria-controls': 'dc-agent-intake-editor',
|
|
4846
5782
|
onClick: () => { actions.setError(null); setIntakeEditorOpen(true); },
|
|
4847
5783
|
}, '重新导入') : null,
|
|
4848
|
-
hasData
|
|
5784
|
+
hasData && (canAction('import-data') || reviewingImportedDataset) ? h('button', {
|
|
5785
|
+
type: 'button', className: 'dcAgentButton is-primary',
|
|
5786
|
+
disabled: busy || !intake.available || intake.pendingText,
|
|
5787
|
+
onClick: () => navigate('rules'),
|
|
5788
|
+
}, reviewingImportedDataset ? '确认名单,进入规则与体检' : '已核对清单,下一步:字段映射与规则') : null,
|
|
4849
5789
|
),
|
|
4850
5790
|
);
|
|
4851
5791
|
}
|
|
4852
5792
|
|
|
5793
|
+
const runtimeRecoveryPane = runtimeRecoveryAllowed ? h('div', { className: 'dcAgentPane' },
|
|
5794
|
+
h('section', { className: 'dcAgentSection', role: 'status' },
|
|
5795
|
+
h('h3', null, '重新载入原任务数据'),
|
|
5796
|
+
h('p', { className: 'dcAgentHint' }, cachedTask.source?.checksum
|
|
5797
|
+
? '原始名单未持久化。请选择或粘贴同一份数据;Host 校验一致后只恢复本页运行时数据,不改变任务状态。'
|
|
5798
|
+
: '该旧任务缺少同源校验信息,不能恢复写操作。'),
|
|
5799
|
+
cachedTask.source?.checksum ? h('div', { className: 'dcAgentRow' },
|
|
5800
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5801
|
+
onClick: (event) => event.currentTarget.nextElementSibling?.click() }, '选择原文件'),
|
|
5802
|
+
h('input', { type: 'file', accept: '.csv,.json,.xlsx,.xls', style: { display: 'none' },
|
|
5803
|
+
disabled: busy, 'aria-label': '重新载入原任务文件', onChange: recoverRuntimeFile }),
|
|
5804
|
+
) : null,
|
|
5805
|
+
cachedTask.source?.checksum ? h('textarea', {
|
|
5806
|
+
className: 'dcAgentTextarea', value: input, disabled: busy,
|
|
5807
|
+
placeholder: '也可以粘贴原 CSV、JSON 或逐行企业名单',
|
|
5808
|
+
'aria-label': '重新载入原任务文本',
|
|
5809
|
+
onInput: (event) => { actions.setInput(event.target.value); actions.setError(null); },
|
|
5810
|
+
}) : null,
|
|
5811
|
+
cachedTask.source?.checksum ? h('button', {
|
|
5812
|
+
type: 'button', className: 'dcAgentButton', disabled: busy || !String(input || '').trim(),
|
|
5813
|
+
onClick: recoverRuntimeText,
|
|
5814
|
+
}, '校验并恢复') : null,
|
|
5815
|
+
h('button', { type: 'button', className: 'dcAgentButton', onClick: () => startNewWorkflow(true) },
|
|
5816
|
+
'新建任务并复用配置'),
|
|
5817
|
+
),
|
|
5818
|
+
) : null;
|
|
5819
|
+
|
|
4853
5820
|
return h('section', {
|
|
4854
5821
|
className: 'dcAgentWorkbenchContent',
|
|
4855
5822
|
'data-dsh-plugin': 'data-cleaning-agent',
|
|
@@ -4858,54 +5825,75 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4858
5825
|
},
|
|
4859
5826
|
h('nav', { className: 'dcAgentManagement', 'aria-label': '工作台管理' },
|
|
4860
5827
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step !== 'history',
|
|
4861
|
-
onClick: () =>
|
|
5828
|
+
onClick: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload') }, '当前任务'),
|
|
4862
5829
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'history',
|
|
4863
|
-
onClick: () =>
|
|
5830
|
+
onClick: () => navigate('history') }, '任务历史'),
|
|
4864
5831
|
),
|
|
4865
5832
|
step !== 'history' ? h('nav', { className: 'dcAgentStepper', 'aria-label': '清洗流程',
|
|
4866
5833
|
style: { '--dc-stage-count': STEPS.length } },
|
|
4867
|
-
STEPS.map((st) =>
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
'
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
5834
|
+
STEPS.map((st) => {
|
|
5835
|
+
const access = cachedTask
|
|
5836
|
+
? resolveStageAccess(st.key, cachedTask.flow)
|
|
5837
|
+
: st.key === 'upload' ? 'current' : 'locked';
|
|
5838
|
+
const locked = access === 'locked';
|
|
5839
|
+
const lockHint = locked
|
|
5840
|
+
? `尚未开放,请先完成“${currentStageLabel}”`
|
|
5841
|
+
: st.label;
|
|
5842
|
+
return h('button', {
|
|
5843
|
+
key: st.key,
|
|
5844
|
+
type: 'button',
|
|
5845
|
+
className: `dcAgentStep${(step === 'profile' ? 'rules' : step) === st.key ? ' is-active' : ''}`,
|
|
5846
|
+
'aria-label': st.label,
|
|
5847
|
+
title: lockHint,
|
|
5848
|
+
'aria-current': (step === 'profile' ? 'rules' : step) === st.key ? 'step' : undefined,
|
|
5849
|
+
'aria-disabled': locked ? 'true' : undefined,
|
|
5850
|
+
'aria-description': locked ? lockHint : undefined,
|
|
5851
|
+
'data-access': access,
|
|
5852
|
+
onClick: () => navigate(st.key),
|
|
5853
|
+
}, h('span', { className: 'dcAgentStepIcon' }, h(StageIcon, { kind: st.icon })),
|
|
5854
|
+
h('span', { className: 'dcAgentStepLabel' }, st.label));
|
|
5855
|
+
}),
|
|
4877
5856
|
) : null,
|
|
4878
5857
|
h('div', { className: 'dcAgentWbBody' },
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
5858
|
+
step !== 'history' ? h('section', { className: 'dcAgentTaskContext', 'aria-label': '任务状态与流程说明' },
|
|
5859
|
+
cachedTask ? h(WorkflowTaskContext, {
|
|
5860
|
+
task: cachedTask,
|
|
5861
|
+
stageLabel: viewedStageLabel,
|
|
5862
|
+
viewAccess,
|
|
5863
|
+
currentStageLabel,
|
|
5864
|
+
statusLabel: taskStatusLabel,
|
|
5865
|
+
stageCompleted: viewedStageCompleted,
|
|
5866
|
+
timeLabel: taskTimeLabel,
|
|
5867
|
+
timeValue: taskTimestamp,
|
|
5868
|
+
sourceLabel: taskSourceLabel,
|
|
5869
|
+
rowCount: taskRowCount,
|
|
5870
|
+
fieldCount: taskFieldCount,
|
|
5871
|
+
hasResultSummary: hasTaskResultSummary,
|
|
5872
|
+
completedMetric: taskCompletedMetric,
|
|
5873
|
+
reviewMetric: taskReviewMetric,
|
|
5874
|
+
failedMetric: taskFailedMetric,
|
|
5875
|
+
commandProgress,
|
|
5876
|
+
onReturn: reviewingImportedDataset
|
|
5877
|
+
? null
|
|
5878
|
+
: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload'),
|
|
4889
5879
|
}) : null,
|
|
4890
|
-
h(
|
|
4891
|
-
cachedTask.source?.type === 'image' && cachedTask.state === 'draft' ? '请发送对话中的任务说明开始图片识别,识别后核验企业名单。' : Number.isFinite(qccEnrichedCount) ? `已处理并补全 ${qccEnrichedCount} 条 · 待核验 ${Number.isFinite(qccReviewCount) ? qccReviewCount : '统计中'} 条 · 失败 ${qccFailedCount} 条(按输入行统计;未披露字段保留空白)` : '结果统计中,处理结束后展示补全与待核验数量'),
|
|
4892
|
-
) : null,
|
|
4893
|
-
error ? h('div', { className: 'dcAgentError', role: 'alert' }, error) : null,
|
|
4894
|
-
step !== 'history' ? h('details', { className: 'dcAgentSection' },
|
|
4895
|
-
h('summary', null, '使用说明与任务流程'),
|
|
4896
|
-
h('p', { className: 'dcAgentHint' }, '导入与核验 → 规则与体检 → 主体匹配 → 字段补全 → 结果下载。菜单只切换视图,不启动调用;按页面主按钮逐步确认。'),
|
|
4897
|
-
h('p', { className: 'dcAgentHint' }, '支持文本、Excel、JSON 和图片名单。原值保留、多候选人工核验;企查查调用使用当前用户自己的连接与额度。'),
|
|
5880
|
+
h(WorkflowGuide),
|
|
4898
5881
|
) : null,
|
|
5882
|
+
error ? h('div', {
|
|
5883
|
+
className: error.startsWith('流程提示:') ? 'dcAgentNavigationNotice' : 'dcAgentError',
|
|
5884
|
+
role: error.startsWith('流程提示:') ? 'status' : 'alert',
|
|
5885
|
+
'aria-live': 'polite',
|
|
5886
|
+
}, error) : null,
|
|
4899
5887
|
['rules', 'profile'].includes(step) ? h('nav', { className: 'dcAgentViewTabs', 'aria-label': '规则与体检视图' },
|
|
4900
|
-
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'rules', onClick: () =>
|
|
4901
|
-
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'profile', onClick: () =>
|
|
5888
|
+
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'rules', onClick: () => navigate('rules') }, '字段映射与规则'),
|
|
5889
|
+
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'profile', onClick: () => navigate('profile') }, '质量体检报告'),
|
|
4902
5890
|
) : null,
|
|
4903
|
-
workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))
|
|
5891
|
+
runtimeRecoveryPane || (workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))
|
|
4904
5892
|
? h('section', { className: 'dcAgentSection', role: 'status' },
|
|
4905
5893
|
h('p', null, workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))),
|
|
4906
5894
|
h('button', { type: 'button', className: 'dcAgentButton',
|
|
4907
|
-
onClick: () =>
|
|
4908
|
-
: pane,
|
|
5895
|
+
onClick: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload') }, '返回当前节点'))
|
|
5896
|
+
: pane),
|
|
4909
5897
|
),
|
|
4910
5898
|
);
|
|
4911
5899
|
}
|
|
@@ -5064,7 +6052,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5064
6052
|
react.useEffect(() => {
|
|
5065
6053
|
instance.actions.openAt(null, props.scope.sessionId);
|
|
5066
6054
|
return adapter.attach(props.scope, { tabId: props.tab.id, store: props.store,
|
|
5067
|
-
navigate: destination =>
|
|
6055
|
+
navigate: destination => navigateWorkflow(
|
|
6056
|
+
instance.actions,
|
|
6057
|
+
props.scope.sessionId,
|
|
6058
|
+
destination || instance.getSnapshot().step,
|
|
6059
|
+
instance.getSnapshot().workflowTask,
|
|
6060
|
+
) });
|
|
5068
6061
|
}, [instance, props.scope.sessionId, props.tab.id, props.store]);
|
|
5069
6062
|
return h(WorkbenchContent, { useStore, actions: instance.actions, visible: props.visible,
|
|
5070
6063
|
sendSessionCommand: (sessionId, prompt) => sendQccAgentCommand(ctx, sessionId, prompt),
|
|
@@ -5139,13 +6132,29 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5139
6132
|
open(sessionId, step) {
|
|
5140
6133
|
if (disposed || !sessionId) return false;
|
|
5141
6134
|
const instance = instanceFor(sessionId);
|
|
5142
|
-
|
|
6135
|
+
const destination = workflowDestination(
|
|
6136
|
+
step,
|
|
6137
|
+
workflowTaskBySession.get(String(sessionId)) ?? instance.getSnapshot().workflowTask,
|
|
6138
|
+
instance.getSnapshot().step,
|
|
6139
|
+
);
|
|
6140
|
+
instance.actions.openAt(destination.stage, sessionId);
|
|
5143
6141
|
const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
|
|
5144
6142
|
const summary = sessions?.list?.getSnapshot?.()?.byId?.[sessionId];
|
|
5145
6143
|
openingSession = sessionId;
|
|
5146
|
-
|
|
5147
|
-
|
|
6144
|
+
let opened;
|
|
6145
|
+
try { opened = adapter.open({ sessionId, ...(summary?.cwd ? { cwd: summary.cwd } : {}) },
|
|
6146
|
+
destination.stage); }
|
|
5148
6147
|
finally { openingSession = null; }
|
|
6148
|
+
if (destination.compatibility) {
|
|
6149
|
+
instance.actions.setError('页面与 Host 流程版本不一致,当前任务已切换为只读模式;请完整重启并升级 Host。');
|
|
6150
|
+
} else if (destination.access === 'locked') {
|
|
6151
|
+
instance.actions.setError(lockedWorkflowNotice(
|
|
6152
|
+
step,
|
|
6153
|
+
workflowTaskBySession.get(String(sessionId)) ?? instance.getSnapshot().workflowTask,
|
|
6154
|
+
destination.stage,
|
|
6155
|
+
));
|
|
6156
|
+
}
|
|
6157
|
+
return opened;
|
|
5149
6158
|
},
|
|
5150
6159
|
};
|
|
5151
6160
|
sessionWorkbenchController = controller;
|
|
@@ -5154,7 +6163,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5154
6163
|
[WORKBENCH_OPEN_EVENT]: event => {
|
|
5155
6164
|
const detail = event.detail || {};
|
|
5156
6165
|
const actions = controller.actionsFor(detail.sessionId);
|
|
5157
|
-
if (detail.task?.id) cacheWorkflowTask(actions, detail.sessionId, detail.task);
|
|
6166
|
+
if (detail.task?.id) cacheWorkflowTask(actions, detail.sessionId, detail.task, 'restore');
|
|
5158
6167
|
controller.open(detail.sessionId, detail.step);
|
|
5159
6168
|
},
|
|
5160
6169
|
[WORKBENCH_DATASET_EVENT]: event => {
|
|
@@ -5307,12 +6316,16 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5307
6316
|
visibleCatalogFields,
|
|
5308
6317
|
installCapabilityMount,
|
|
5309
6318
|
PromptDialog,
|
|
6319
|
+
normalizeViewStage,
|
|
6320
|
+
resolveStageAccess,
|
|
6321
|
+
workflowDestination,
|
|
6322
|
+
lockedWorkflowNotice,
|
|
6323
|
+
navigateWorkflow,
|
|
5310
6324
|
workflowNavigationIssue,
|
|
5311
6325
|
buildTaskPrompt,
|
|
5312
6326
|
DatasetReview,
|
|
5313
6327
|
reviewColumns,
|
|
5314
6328
|
reviewCsv,
|
|
5315
|
-
clearCleaningDraft,
|
|
5316
6329
|
entriesToDataset,
|
|
5317
6330
|
extractPromptEntries,
|
|
5318
6331
|
imageFilesFromTransfer,
|
|
@@ -5320,6 +6333,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5320
6333
|
requestPromptImage,
|
|
5321
6334
|
guessMappings,
|
|
5322
6335
|
intakeState,
|
|
6336
|
+
restoreParsedRuntime,
|
|
6337
|
+
runtimeFor,
|
|
6338
|
+
verifyRestoredRuntime,
|
|
5323
6339
|
projectCompletionResult,
|
|
5324
6340
|
mappingRecommendations,
|
|
5325
6341
|
mappedOutputFields,
|
|
@@ -5332,6 +6348,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5332
6348
|
installNewSessionBridge,
|
|
5333
6349
|
isCleaningSession,
|
|
5334
6350
|
isKnownCleaningDraft,
|
|
6351
|
+
wizardListEntries,
|
|
6352
|
+
invalidListInstruction,
|
|
6353
|
+
INITIAL_DRAFT_TEMPLATE,
|
|
6354
|
+
initializeCleaningDraft,
|
|
6355
|
+
emptyInitialInput,
|
|
6356
|
+
startCleaningSession,
|
|
5335
6357
|
markCleaningSession,
|
|
5336
6358
|
qualitySummaryFor,
|
|
5337
6359
|
plainEntityListDataset,
|
|
@@ -5340,6 +6362,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5340
6362
|
observeHomePhase,
|
|
5341
6363
|
ensureWorkflowTask,
|
|
5342
6364
|
ensureEditableWorkflowTask,
|
|
6365
|
+
refreshHostedTask,
|
|
5343
6366
|
queueWorkflowOperation,
|
|
5344
6367
|
taskBelongsToSession,
|
|
5345
6368
|
workflowOriginLabel,
|