dsh-data-cleaning-agent 0.9.13 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.en.md +2 -2
- package/README.md +3 -3
- package/docs/RELEASE-0.9.14.md +12 -0
- package/docs/RELEASE-0.9.15.md +13 -0
- package/lib/client.js +1006 -202
- package/lib/image-workflow.js +7 -1
- package/lib/qcc-command.js +8 -0
- package/lib/qcc-runs.js +9 -0
- package/lib/web.js +82 -42
- package/lib/workflow-contract.js +125 -7
- package/lib/workflow-execution.js +121 -20
- package/lib/workflow.js +170 -14
- package/package.json +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);
|
|
1161
1297
|
white-space: nowrap;
|
|
1162
1298
|
}
|
|
1163
|
-
.
|
|
1164
|
-
|
|
1165
|
-
|
|
1299
|
+
.dcAgentTaskCurrent span { color: var(--dc-muted); font-size: 11px; }
|
|
1300
|
+
.dcAgentTaskCurrent strong { color: var(--dc-action); font-size: 12px; }
|
|
1301
|
+
.dcAgentTaskReturn {
|
|
1302
|
+
min-height: 28px;
|
|
1303
|
+
padding: 3px 7px;
|
|
1304
|
+
border: 0;
|
|
1305
|
+
border-radius: 5px;
|
|
1306
|
+
background: transparent;
|
|
1307
|
+
color: var(--dc-action);
|
|
1308
|
+
cursor: pointer;
|
|
1309
|
+
white-space: nowrap;
|
|
1310
|
+
}
|
|
1311
|
+
.dcAgentTaskReturn:hover { background: var(--dc-selected); }
|
|
1312
|
+
.dcAgentTaskDetails { position: relative; }
|
|
1313
|
+
.dcAgentTaskDetails > summary {
|
|
1314
|
+
box-sizing: border-box;
|
|
1315
|
+
width: 28px;
|
|
1316
|
+
height: 28px;
|
|
1317
|
+
display: grid;
|
|
1318
|
+
place-items: center;
|
|
1319
|
+
border: 1px solid transparent;
|
|
1320
|
+
border-radius: 6px;
|
|
1321
|
+
color: var(--dc-muted);
|
|
1322
|
+
cursor: pointer;
|
|
1323
|
+
font-size: 12px;
|
|
1324
|
+
font-weight: 700;
|
|
1325
|
+
list-style: none;
|
|
1326
|
+
}
|
|
1327
|
+
.dcAgentTaskDetails > summary::-webkit-details-marker { display: none; }
|
|
1328
|
+
.dcAgentTaskDetails > summary:hover {
|
|
1329
|
+
border-color: var(--dc-border);
|
|
1330
|
+
background: var(--dc-surface);
|
|
1331
|
+
color: var(--dc-action);
|
|
1332
|
+
}
|
|
1333
|
+
.dcAgentTaskDetailsBody {
|
|
1334
|
+
position: absolute;
|
|
1335
|
+
z-index: 6;
|
|
1336
|
+
top: 32px;
|
|
1337
|
+
right: 0;
|
|
1338
|
+
width: min(310px, calc(100vw - 48px));
|
|
1339
|
+
padding: 12px;
|
|
1340
|
+
border: 1px solid var(--dc-border);
|
|
1341
|
+
border-radius: 8px;
|
|
1342
|
+
background: var(--dc-surface);
|
|
1343
|
+
box-shadow: 0 12px 30px light-dark(rgb(26 74 102 / 10%), rgb(0 0 0 / 34%));
|
|
1344
|
+
color: var(--dc-muted);
|
|
1345
|
+
font-size: 12px;
|
|
1346
|
+
}
|
|
1347
|
+
.dcAgentTaskDetailsBody p { margin: 4px 0; overflow-wrap: anywhere; }
|
|
1348
|
+
.dcAgentTaskSyncNote {
|
|
1349
|
+
margin: 6px 0 0 24px;
|
|
1350
|
+
color: var(--dc-muted);
|
|
1351
|
+
font-size: 11px;
|
|
1352
|
+
}
|
|
1353
|
+
.dcAgentTaskExecution {
|
|
1354
|
+
display: grid;
|
|
1355
|
+
grid-template-columns: auto minmax(100px, 1fr);
|
|
1356
|
+
align-items: center;
|
|
1357
|
+
gap: 8px;
|
|
1358
|
+
margin-top: 6px;
|
|
1359
|
+
color: var(--dc-muted);
|
|
1360
|
+
font-size: 11px;
|
|
1361
|
+
}
|
|
1362
|
+
.dcAgentTaskExecution progress { width: 100%; height: 6px; accent-color: var(--dc-action); }
|
|
1363
|
+
.dcAgentTaskMetrics {
|
|
1364
|
+
display: flex;
|
|
1365
|
+
align-items: center;
|
|
1366
|
+
gap: 16px;
|
|
1367
|
+
margin-top: 7px;
|
|
1368
|
+
padding-top: 8px;
|
|
1369
|
+
border-top: 1px solid var(--dc-border);
|
|
1370
|
+
color: var(--dc-muted);
|
|
1371
|
+
font-size: 11px;
|
|
1372
|
+
}
|
|
1373
|
+
.dcAgentTaskMetricGroup {
|
|
1374
|
+
min-width: 0;
|
|
1375
|
+
display: flex;
|
|
1376
|
+
align-items: center;
|
|
1377
|
+
gap: 10px;
|
|
1378
|
+
}
|
|
1379
|
+
.dcAgentTaskMetricGroup + .dcAgentTaskMetricGroup {
|
|
1380
|
+
flex: 1;
|
|
1381
|
+
padding-left: 16px;
|
|
1382
|
+
border-left: 1px solid var(--dc-border);
|
|
1383
|
+
}
|
|
1384
|
+
.dcAgentTaskMetricLabel { flex: none; color: var(--dc-muted); font-weight: 600; }
|
|
1385
|
+
.dcAgentTaskMetric { color: var(--dc-text); white-space: nowrap; }
|
|
1386
|
+
.dcAgentTaskMetric b { margin-left: 3px; font-size: 12px; font-weight: 650; }
|
|
1387
|
+
.dcAgentTaskMetric.is-good,
|
|
1388
|
+
.dcAgentTaskMetric.is-good b { color: light-dark(#16845b, #5ed29f); }
|
|
1389
|
+
.dcAgentWorkflowGuide { background: var(--dc-surface); }
|
|
1390
|
+
.dcAgentWorkflowGuide > summary {
|
|
1391
|
+
box-sizing: border-box;
|
|
1392
|
+
min-height: 40px;
|
|
1393
|
+
display: flex;
|
|
1394
|
+
align-items: center;
|
|
1395
|
+
gap: 7px;
|
|
1396
|
+
padding: 8px 18px;
|
|
1166
1397
|
color: var(--dc-muted);
|
|
1167
1398
|
font-size: 12px;
|
|
1399
|
+
cursor: pointer;
|
|
1400
|
+
list-style: none;
|
|
1401
|
+
}
|
|
1402
|
+
.dcAgentWorkflowGuide > summary::-webkit-details-marker { display: none; }
|
|
1403
|
+
.dcAgentWorkflowGuide > summary:hover {
|
|
1404
|
+
color: var(--dc-action);
|
|
1405
|
+
background: var(--dc-table-head);
|
|
1406
|
+
}
|
|
1407
|
+
.dcAgentWorkflowGuide > summary::before {
|
|
1408
|
+
content: "?";
|
|
1409
|
+
width: 16px;
|
|
1410
|
+
height: 16px;
|
|
1411
|
+
display: grid;
|
|
1412
|
+
place-items: center;
|
|
1413
|
+
border: 1px solid var(--dc-border);
|
|
1414
|
+
border-radius: 50%;
|
|
1415
|
+
font-size: 10px;
|
|
1416
|
+
}
|
|
1417
|
+
.dcAgentWorkflowGuide > summary::after {
|
|
1418
|
+
content: "›";
|
|
1419
|
+
margin-left: auto;
|
|
1420
|
+
font-size: 17px;
|
|
1421
|
+
transition: transform 120ms ease;
|
|
1422
|
+
}
|
|
1423
|
+
.dcAgentWorkflowGuide[open] > summary::after { transform: rotate(90deg); }
|
|
1424
|
+
.dcAgentWorkflowGuideContent { padding: 0 18px 16px; }
|
|
1425
|
+
.dcAgentWorkflowGuideIntro {
|
|
1426
|
+
margin: 0 0 5px;
|
|
1427
|
+
color: var(--dc-muted);
|
|
1428
|
+
font-size: 12px;
|
|
1429
|
+
line-height: 18px;
|
|
1430
|
+
}
|
|
1431
|
+
.dcAgentWorkflowGuideList {
|
|
1432
|
+
display: grid;
|
|
1433
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1434
|
+
gap: 0 24px;
|
|
1435
|
+
margin-top: 11px;
|
|
1436
|
+
}
|
|
1437
|
+
.dcAgentWorkflowGuideStep {
|
|
1438
|
+
min-width: 0;
|
|
1439
|
+
display: grid;
|
|
1440
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1441
|
+
gap: 9px;
|
|
1442
|
+
padding: 10px 0;
|
|
1443
|
+
border-top: 1px solid var(--dc-border);
|
|
1444
|
+
}
|
|
1445
|
+
.dcAgentWorkflowGuideStep:last-child:nth-child(odd) { grid-column: 1 / -1; }
|
|
1446
|
+
.dcAgentWorkflowGuideIndex {
|
|
1447
|
+
box-sizing: border-box;
|
|
1448
|
+
width: 22px;
|
|
1449
|
+
height: 22px;
|
|
1450
|
+
display: grid;
|
|
1451
|
+
place-items: center;
|
|
1452
|
+
border: 1px solid var(--dc-border);
|
|
1453
|
+
border-radius: 50%;
|
|
1454
|
+
background: var(--dc-page);
|
|
1455
|
+
color: var(--dc-action);
|
|
1456
|
+
font-size: 10px;
|
|
1457
|
+
font-weight: 700;
|
|
1458
|
+
}
|
|
1459
|
+
.dcAgentWorkflowGuideStep strong {
|
|
1460
|
+
display: block;
|
|
1461
|
+
margin-bottom: 2px;
|
|
1462
|
+
color: var(--dc-text);
|
|
1463
|
+
font-size: 12px;
|
|
1464
|
+
}
|
|
1465
|
+
.dcAgentWorkflowGuideStep p {
|
|
1466
|
+
margin: 0;
|
|
1467
|
+
color: var(--dc-muted);
|
|
1468
|
+
font-size: 11px;
|
|
1469
|
+
line-height: 17px;
|
|
1470
|
+
}
|
|
1471
|
+
.dcAgentWorkflowGuideBoundary {
|
|
1472
|
+
margin-top: 8px;
|
|
1473
|
+
padding: 9px 10px;
|
|
1474
|
+
border-left: 2px solid var(--dc-action);
|
|
1475
|
+
background: var(--dc-table-head);
|
|
1476
|
+
color: var(--dc-muted);
|
|
1477
|
+
font-size: 11px;
|
|
1478
|
+
line-height: 17px;
|
|
1168
1479
|
}
|
|
1169
|
-
.dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > :is(p, progress) { grid-column: 1 / -1; }
|
|
1170
|
-
.dcAgentWbBody > .dcAgentSection[aria-label="当前任务进展"] > p { margin: 0; }
|
|
1171
1480
|
.dcAgentWbBody > .dcAgentSection[role="status"]:not([aria-label]) {
|
|
1172
1481
|
min-height: 156px;
|
|
1173
1482
|
display: flex;
|
|
@@ -1222,10 +1531,32 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1222
1531
|
-webkit-box-orient: vertical;
|
|
1223
1532
|
-webkit-line-clamp: 2;
|
|
1224
1533
|
}
|
|
1225
|
-
.
|
|
1226
|
-
.
|
|
1227
|
-
.
|
|
1228
|
-
.
|
|
1534
|
+
.dcAgentTaskStatus { padding-inline: 10px; }
|
|
1535
|
+
.dcAgentTaskStatusRow { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 8px; }
|
|
1536
|
+
.dcAgentTaskStage { grid-column: 1; grid-row: 1; }
|
|
1537
|
+
.dcAgentTaskCurrent {
|
|
1538
|
+
grid-column: 1;
|
|
1539
|
+
grid-row: 2;
|
|
1540
|
+
justify-self: start;
|
|
1541
|
+
margin-left: 24px;
|
|
1542
|
+
padding-left: 0;
|
|
1543
|
+
border-left: 0;
|
|
1544
|
+
}
|
|
1545
|
+
.dcAgentTaskReturn { grid-column: 2; grid-row: 2; }
|
|
1546
|
+
.dcAgentTaskDetails { grid-column: 2; grid-row: 1; justify-self: end; }
|
|
1547
|
+
.dcAgentTaskMetrics { align-items: flex-start; flex-direction: column; gap: 5px; }
|
|
1548
|
+
.dcAgentTaskMetricGroup { width: 100%; gap: 8px; }
|
|
1549
|
+
.dcAgentTaskMetricGroup + .dcAgentTaskMetricGroup {
|
|
1550
|
+
padding-top: 5px;
|
|
1551
|
+
padding-left: 0;
|
|
1552
|
+
border-top: 1px solid var(--dc-border);
|
|
1553
|
+
border-left: 0;
|
|
1554
|
+
}
|
|
1555
|
+
.dcAgentTaskMetricLabel { min-width: 48px; }
|
|
1556
|
+
.dcAgentWorkflowGuide > summary { padding-inline: 10px; }
|
|
1557
|
+
.dcAgentWorkflowGuideContent { padding-inline: 10px; }
|
|
1558
|
+
.dcAgentWorkflowGuideList { grid-template-columns: 1fr; }
|
|
1559
|
+
.dcAgentWorkflowGuideStep:last-child:nth-child(odd) { grid-column: auto; }
|
|
1229
1560
|
.dcAgentDatasetHeader { display: grid; }
|
|
1230
1561
|
.dcAgentDatasetHeader .dcAgentHint { text-align: left; }
|
|
1231
1562
|
.dcAgentDatasetFooter { grid-template-columns: 1fr; }
|
|
@@ -1316,6 +1647,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1316
1647
|
{ key: 'download', label: '结果下载', icon: 'table' },
|
|
1317
1648
|
];
|
|
1318
1649
|
|
|
1650
|
+
const WORKFLOW_GUIDE_STEPS = [
|
|
1651
|
+
['导入与核验', '导入文本、Excel、JSON 或图片名单,并核对完整原始数据。'],
|
|
1652
|
+
['规则与体检', '确认字段映射、匹配规则和补全范围;质量体检在本地完成。'],
|
|
1653
|
+
['主体匹配', '优先使用统一社会信用代码等强标识;名称不明确时进入人工候选核验。'],
|
|
1654
|
+
['字段补全', '只补空值并保留非空原值;外部字段以企查查真实返回为准。'],
|
|
1655
|
+
['结果下载', 'Host 保存结果、异常清单与任务报告,可在任务历史中继续下载。'],
|
|
1656
|
+
];
|
|
1657
|
+
|
|
1319
1658
|
const CAPABILITIES = [
|
|
1320
1659
|
{ key: 'upload', label: '导入名单', icon: IconPaperclipOutline16, fallback: '+' },
|
|
1321
1660
|
{ key: 'profile', label: '质量体检', icon: IconChecklistOutline14, fallback: '✓' },
|
|
@@ -1952,14 +2291,18 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1952
2291
|
if (!task?.originSessionId) return '历史版本未保存来源';
|
|
1953
2292
|
return `工作区:${task.originWorkspaceName || task.originWorkspaceId || '未取得来源工作区'} · 会话:${task.originSessionName || task.originSessionId}`;
|
|
1954
2293
|
}
|
|
1955
|
-
function cacheWorkflowTask(actions, sessionId, task) {
|
|
2294
|
+
function cacheWorkflowTask(actions, sessionId, task, reason = 'update') {
|
|
1956
2295
|
if (!task) return null;
|
|
1957
2296
|
if (!taskBelongsToSession(task, sessionId)) throw new Error('此任务不属于当前会话;请在任务历史中只读查看或打开来源会话。');
|
|
1958
|
-
const
|
|
1959
|
-
|
|
1960
|
-
|
|
2297
|
+
const key = String(sessionId || 'unassigned');
|
|
2298
|
+
const current = workflowTaskBySession.get(key);
|
|
2299
|
+
const explicitSwitch = ['create', 'restore'].includes(reason);
|
|
2300
|
+
if (current && current.id !== task.id && !explicitSwitch) return current;
|
|
2301
|
+
if (current?.id === task.id && Number(current.revision ?? -1) > Number(task.revision ?? -1)) return current;
|
|
2302
|
+
workflowTaskBySession.set(key, task);
|
|
1961
2303
|
bindTask(sessionId, task.id);
|
|
1962
|
-
actions.
|
|
2304
|
+
if (typeof actions.acceptWorkflowTask === 'function') actions.acceptWorkflowTask(task, reason);
|
|
2305
|
+
else actions.setWorkflowTask(task);
|
|
1963
2306
|
if (task.title) actions.setTaskTitle(task.title);
|
|
1964
2307
|
if (Array.isArray(task.objectives)) actions.setObjectives(task.objectives);
|
|
1965
2308
|
if (Array.isArray(task.mappings)) actions.setMappings(task.mappings);
|
|
@@ -1991,7 +2334,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
1991
2334
|
}
|
|
1992
2335
|
const response = await pending;
|
|
1993
2336
|
moveRuntime(`session:${key}`, response.task.id);
|
|
1994
|
-
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2337
|
+
return cacheWorkflowTask(actions, sessionId, response.task, 'create');
|
|
1995
2338
|
}
|
|
1996
2339
|
|
|
1997
2340
|
async function ensureWorkflowTask(actions, sessionId, draft = {}, restoreStage = true) {
|
|
@@ -2002,9 +2345,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2002
2345
|
if (ownedSessionId(key) && /^dcw-[a-zA-Z0-9-]+$/.test(saved || '')) {
|
|
2003
2346
|
const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(saved)}`);
|
|
2004
2347
|
if (taskBelongsToSession(response.task, sessionId)) {
|
|
2005
|
-
|
|
2006
|
-
if (restoreStage) actions.setStep(task.stage || 'upload');
|
|
2007
|
-
return task;
|
|
2348
|
+
return cacheWorkflowTask(actions, sessionId, response.task, restoreStage ? 'restore' : 'update');
|
|
2008
2349
|
}
|
|
2009
2350
|
bindTask(sessionId, null);
|
|
2010
2351
|
}
|
|
@@ -2053,7 +2394,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2053
2394
|
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2054
2395
|
}
|
|
2055
2396
|
|
|
2056
|
-
async function workflowAction(actions, sessionId, task, action, input = {}) {
|
|
2397
|
+
async function workflowAction(actions, sessionId, task, action, input = {}, reason = 'update') {
|
|
2057
2398
|
const latest = workflowTaskBySession.get(String(sessionId || 'unassigned')) ?? task;
|
|
2058
2399
|
if (!latest) throw new Error('请先创建数据清洗补全任务。');
|
|
2059
2400
|
if (!taskBelongsToSession(latest, sessionId)) throw new Error('此任务不属于当前会话。');
|
|
@@ -2062,7 +2403,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2062
2403
|
originSessionId: sessionId, originWorkspaceId: latest.originWorkspaceId,
|
|
2063
2404
|
expectedRevision: latest.revision,
|
|
2064
2405
|
});
|
|
2065
|
-
return cacheWorkflowTask(actions, sessionId, response.task);
|
|
2406
|
+
return cacheWorkflowTask(actions, sessionId, response.task, reason);
|
|
2066
2407
|
}
|
|
2067
2408
|
|
|
2068
2409
|
async function stageConfirmedWorkflow(actions, sessionId, expected = null) {
|
|
@@ -2104,7 +2445,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2104
2445
|
return response.command;
|
|
2105
2446
|
}
|
|
2106
2447
|
|
|
2107
|
-
async function refreshHostedTask(actions, sessionId, taskId, run,
|
|
2448
|
+
async function refreshHostedTask(actions, sessionId, taskId, run, reason = 'update') {
|
|
2108
2449
|
const response = await requestJson(`/data-cleaning/api/workflow/tasks/${encodeURIComponent(taskId)}`);
|
|
2109
2450
|
if (!taskBelongsToSession(response.task, sessionId)) {
|
|
2110
2451
|
if (readTaskBindings()[sessionId] === taskId) bindTask(sessionId, null);
|
|
@@ -2113,22 +2454,20 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2113
2454
|
const selectedTask = workflowTaskBySession.get(String(sessionId));
|
|
2114
2455
|
if (selectedTask && selectedTask.id !== taskId) return response.task;
|
|
2115
2456
|
if (selectedTask && selectedTask.revision > response.task.revision) return selectedTask;
|
|
2116
|
-
workflowTaskBySession.set(String(sessionId), response.task);
|
|
2117
|
-
bindTask(sessionId, taskId);
|
|
2118
2457
|
if (isCleaningSession(sessionId)) {
|
|
2119
2458
|
if (!run && response.task.qccRunId && ['review_required', 'partial'].includes(response.task.state)) {
|
|
2120
2459
|
try { run = await requestJson(`/data-cleaning/api/g5/run/${encodeURIComponent(response.task.qccRunId)}`); }
|
|
2121
2460
|
catch { actions.setError('Host 中的候选或重试数据已失效。已有结果仍可下载;重新查询需新建任务,不会自动重复扣量。'); }
|
|
2122
2461
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2462
|
+
const latestSelectedTask = workflowTaskBySession.get(String(sessionId));
|
|
2463
|
+
if (!isCleaningSession(sessionId) || (latestSelectedTask && latestSelectedTask.id !== taskId)) return response.task;
|
|
2464
|
+
cacheWorkflowTask(actions, sessionId, response.task, reason);
|
|
2125
2465
|
if (run) {
|
|
2126
2466
|
actions.setQccRun(run);
|
|
2127
2467
|
const runtime = runtimeFor(taskId);
|
|
2128
2468
|
runtime.resultRows.qcc = run.rows;
|
|
2129
2469
|
if (!runtime.rows.length) { runtime.rows = run.rows; runtime.headers = response.task.source?.headers || run.headers || []; }
|
|
2130
2470
|
}
|
|
2131
|
-
if (navigate) actions.setStep(response.task.stage || 'upload');
|
|
2132
2471
|
}
|
|
2133
2472
|
return response.task;
|
|
2134
2473
|
}
|
|
@@ -2138,7 +2477,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2138
2477
|
const isActive = () => generation === commandWatchGeneration;
|
|
2139
2478
|
void waitForQccCommand(command.commandId, 1800, async (status) => {
|
|
2140
2479
|
if (!status || status.state === 'prepared') return;
|
|
2141
|
-
await refreshHostedTask(actions, sessionId, command.taskId, null,
|
|
2480
|
+
await refreshHostedTask(actions, sessionId, command.taskId, null, 'update').catch(() => {});
|
|
2142
2481
|
if (isActive() && workflowTaskBySession.get(String(sessionId))?.id === command.taskId) {
|
|
2143
2482
|
sessionWorkbenchController?.revealSubmitted(sessionId, command.commandId,
|
|
2144
2483
|
workflowTaskBySession.get(String(sessionId))?.stage || 'match');
|
|
@@ -2203,6 +2542,32 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2203
2542
|
draft.workflowTask = value ?? null;
|
|
2204
2543
|
if (value) draft.workflowTasks = [value, ...(draft.workflowTasks || []).filter(task => task.id !== value.id)];
|
|
2205
2544
|
},
|
|
2545
|
+
acceptWorkflowTask: (draft, value, reason = 'update') => {
|
|
2546
|
+
if (!value) return;
|
|
2547
|
+
const previous = draft.workflowTask;
|
|
2548
|
+
if (previous?.id === value.id && Number(previous.revision ?? -1) > Number(value.revision ?? -1)) return;
|
|
2549
|
+
const previousStage = normalizeViewStage(draft.step);
|
|
2550
|
+
const followedCurrent = previous?.flow?.flowVersion === SUPPORTED_FLOW_VERSION
|
|
2551
|
+
&& resolveStageAccess(previousStage, previous.flow) === 'current';
|
|
2552
|
+
draft.workflowTask = value;
|
|
2553
|
+
draft.workflowTasks = [value, ...(draft.workflowTasks || []).filter(task => task.id !== value.id)];
|
|
2554
|
+
if (value.flow?.flowVersion !== SUPPORTED_FLOW_VERSION) {
|
|
2555
|
+
draft.step = value.stage || 'upload';
|
|
2556
|
+
return;
|
|
2557
|
+
}
|
|
2558
|
+
if (draft.step === 'history' && reason !== 'restore') return;
|
|
2559
|
+
if (reason === 'import' && previousStage === 'upload'
|
|
2560
|
+
&& resolveStageAccess('upload', value.flow) === 'read'
|
|
2561
|
+
&& value.flow.currentStage === 'rules') {
|
|
2562
|
+
draft.step = 'upload';
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
if (!previous || previous.id !== value.id || ['create', 'restore'].includes(reason) || followedCurrent) {
|
|
2566
|
+
draft.step = value.flow.currentStage;
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
if (resolveStageAccess(previousStage, value.flow) === 'locked') draft.step = value.flow.currentStage;
|
|
2570
|
+
},
|
|
2206
2571
|
setWorkflowTasks: (draft, value) => {
|
|
2207
2572
|
const cached = new Map((draft.workflowTasks || []).map(task => [task.id, task]));
|
|
2208
2573
|
draft.workflowTasks = (Array.isArray(value) ? value : []).map(task =>
|
|
@@ -2524,8 +2889,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2524
2889
|
}
|
|
2525
2890
|
|
|
2526
2891
|
function openWorkbench(actions, step, sessionId) {
|
|
2527
|
-
|
|
2528
|
-
|
|
2892
|
+
if (sessionWorkbenchController) sessionWorkbenchController.open(sessionId, step);
|
|
2893
|
+
else {
|
|
2894
|
+
const destination = navigateWorkflow(actions, sessionId, step);
|
|
2895
|
+
actions.openAt(destination.stage, sessionId);
|
|
2896
|
+
}
|
|
2529
2897
|
}
|
|
2530
2898
|
|
|
2531
2899
|
const WORKBENCH_OPEN_EVENT = 'dsh:data-cleaning-workbench-open';
|
|
@@ -2677,8 +3045,68 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
2677
3045
|
authorization_required: '待连接企查查', cancelled: '已取消', parse_failed: '解析失败',
|
|
2678
3046
|
};
|
|
2679
3047
|
|
|
3048
|
+
const SUPPORTED_FLOW_VERSION = 1;
|
|
3049
|
+
function normalizeViewStage(stage) {
|
|
3050
|
+
return stage === 'profile' ? 'rules' : stage;
|
|
3051
|
+
}
|
|
3052
|
+
function resolveStageAccess(stage, flow) {
|
|
3053
|
+
const normalized = normalizeViewStage(stage);
|
|
3054
|
+
if (flow?.flowVersion !== SUPPORTED_FLOW_VERSION) return 'locked';
|
|
3055
|
+
return flow.stageAccess?.[normalized] || 'locked';
|
|
3056
|
+
}
|
|
3057
|
+
function workflowActionAllowed(task, action) {
|
|
3058
|
+
return task?.flow?.flowVersion === SUPPORTED_FLOW_VERSION
|
|
3059
|
+
&& Array.isArray(task.flow.allowedActions)
|
|
3060
|
+
&& task.flow.allowedActions.includes(action);
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
function workflowDestination(requestedStage, task, currentView = 'upload') {
|
|
3064
|
+
if (requestedStage === 'history') return { stage: 'history', access: 'read' };
|
|
3065
|
+
if (!task) {
|
|
3066
|
+
return requestedStage === 'upload' || !requestedStage
|
|
3067
|
+
? { stage: 'upload', access: 'current' }
|
|
3068
|
+
: { stage: 'upload', access: 'locked' };
|
|
3069
|
+
}
|
|
3070
|
+
if (task.flow?.flowVersion !== SUPPORTED_FLOW_VERSION) {
|
|
3071
|
+
return { stage: task.stage || 'upload', access: 'read', compatibility: true };
|
|
3072
|
+
}
|
|
3073
|
+
const requested = requestedStage || currentView || task.flow.currentStage;
|
|
3074
|
+
const access = resolveStageAccess(requested, task.flow);
|
|
3075
|
+
if (access === 'locked') return { stage: task.flow.currentStage, access };
|
|
3076
|
+
return { stage: requested, access };
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
function workflowStageLabel(stage) {
|
|
3080
|
+
const normalized = normalizeViewStage(stage);
|
|
3081
|
+
return STEPS.find((item) => item.key === normalized)?.label ?? normalized ?? '当前节点';
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
function lockedWorkflowNotice(requestedStage, task, fallbackStage = 'upload') {
|
|
3085
|
+
const currentStage = task?.flow?.currentStage || task?.stage || fallbackStage;
|
|
3086
|
+
return `流程提示:“${workflowStageLabel(requestedStage)}”尚未开放,请先完成“${workflowStageLabel(currentStage)}”。`;
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
function navigateWorkflow(actions, sessionId, requestedStage, task) {
|
|
3090
|
+
const current = task ?? workflowTaskBySession.get(String(sessionId || 'unassigned')) ?? null;
|
|
3091
|
+
const destination = workflowDestination(requestedStage, current);
|
|
3092
|
+
actions.setStep(destination.stage);
|
|
3093
|
+
if (destination.compatibility) {
|
|
3094
|
+
actions.setError('页面与 Host 流程版本不一致,当前任务已切换为只读模式;请完整重启并升级 Host。');
|
|
3095
|
+
} else if (destination.access === 'locked') {
|
|
3096
|
+
actions.setError(lockedWorkflowNotice(requestedStage, current, destination.stage));
|
|
3097
|
+
} else {
|
|
3098
|
+
actions.setError(null);
|
|
3099
|
+
}
|
|
3100
|
+
return destination;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
2680
3103
|
function workflowNavigationIssue(step, task, hasData, profile, qccRun, hasLocalResult = false) {
|
|
2681
|
-
if (
|
|
3104
|
+
if (step === 'history') return null;
|
|
3105
|
+
if (task?.flow?.flowVersion === SUPPORTED_FLOW_VERSION) {
|
|
3106
|
+
const access = resolveStageAccess(step, task.flow);
|
|
3107
|
+
return access === 'locked' ? '该步骤尚未开放,请先完成当前流程节点。' : null;
|
|
3108
|
+
}
|
|
3109
|
+
if (step === 'upload') return null;
|
|
2682
3110
|
if (step === 'download' && task?.artifacts?.length) return null;
|
|
2683
3111
|
if (!hasData) return '请先在「导入与核验」载入并核对完整名单。';
|
|
2684
3112
|
if (step === 'rules') return null;
|
|
@@ -3053,7 +3481,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3053
3481
|
if (command.taskId) {
|
|
3054
3482
|
const actions = sessionWorkbenchController?.actionsFor(sessionId);
|
|
3055
3483
|
if (actions) {
|
|
3056
|
-
await refreshHostedTask(actions, sessionId, command.taskId, null,
|
|
3484
|
+
await refreshHostedTask(actions, sessionId, command.taskId, null, 'update');
|
|
3057
3485
|
if (command.state === 'running') requestWorkbenchOpen(command.qccCommand ? 'match' : 'upload', sessionId);
|
|
3058
3486
|
if (command.qccCommand) {
|
|
3059
3487
|
const status = await requestJson(`/data-cleaning/api/g5/commands/${encodeURIComponent(command.qccCommand.commandId)}`);
|
|
@@ -3243,21 +3671,26 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3243
3671
|
removeImage(sessionId, imageAttachment);
|
|
3244
3672
|
setImageAttachment(null);
|
|
3245
3673
|
}
|
|
3246
|
-
if (mode === 'text') {
|
|
3247
|
-
requestWorkbenchDataset(entriesToDataset(config.entries), sessionId, { type: 'text', fileName: '' });
|
|
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);
|
|
3259
3674
|
setBusy(true);
|
|
3260
3675
|
try {
|
|
3676
|
+
if (mode === 'text') {
|
|
3677
|
+
const local = entriesToDataset(config.entries);
|
|
3678
|
+
const verified = await api('/data-cleaning/api/mvp/parse', {
|
|
3679
|
+
filename: 'data.json',
|
|
3680
|
+
content: JSON.stringify(local.rows),
|
|
3681
|
+
});
|
|
3682
|
+
requestWorkbenchDataset({ ...verified, fmt: 'text' }, sessionId, { type: 'text', fileName: '' });
|
|
3683
|
+
} else if (mode === 'excel' && spreadsheetData) {
|
|
3684
|
+
requestWorkbenchDataset(spreadsheetData, sessionId, { type: spreadsheetData.fmt || 'xlsx', fileName: spreadsheetFileName });
|
|
3685
|
+
}
|
|
3686
|
+
requestWorkbenchDraft({
|
|
3687
|
+
title: `企业数据清洗补全任务 · ${config.entryCount || '待解析'} 条`,
|
|
3688
|
+
objectives: cleaningKeys,
|
|
3689
|
+
fieldSelection: enrichmentKeys,
|
|
3690
|
+
matchRules,
|
|
3691
|
+
...(mode === 'excel' ? { mappings: spreadsheetMappings } : {}),
|
|
3692
|
+
source: { type: mode === 'excel' ? 'xlsx' : mode, fileName: config.fileName || '', rowCount: config.entryCount || 0 },
|
|
3693
|
+
}, sessionId);
|
|
3261
3694
|
let prompt = buildTaskPrompt(config);
|
|
3262
3695
|
if (mode !== 'image' || ['prepared', 'completed'].includes(imageCommand?.state)) {
|
|
3263
3696
|
const command = await requestPreparedWorkflow(sessionId, config);
|
|
@@ -3338,12 +3771,14 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3338
3771
|
const receiveNewTask = (event) => {
|
|
3339
3772
|
if (event.detail?.sessionId !== sessionId || busy || imageCommand?.state === 'running') return;
|
|
3340
3773
|
event.preventDefault();
|
|
3774
|
+
const reused = event.detail?.reuseConfig || null;
|
|
3341
3775
|
discardCurrentImage();
|
|
3342
3776
|
setMode('text'); setRawText(''); setEntries([]); setEntryCount(0); setSpreadsheetFileName('');
|
|
3343
3777
|
setSpreadsheetData(null); setSpreadsheetMappings([]);
|
|
3344
|
-
setCleaningKeys(DEFAULT_CLEANING_KEYS);
|
|
3778
|
+
setCleaningKeys(reused?.objectives || DEFAULT_CLEANING_KEYS);
|
|
3779
|
+
setEnrichmentKeys(reused?.fieldSelection || DEFAULT_ENRICHMENT_KEYS);
|
|
3345
3780
|
setAnchorKeys(['company_name', 'credit_no']);
|
|
3346
|
-
setPromptMatchRules({ normalizeNames: true, preferCreditNo: true, deduplicate: true, manualReviewAmbiguous: true });
|
|
3781
|
+
setPromptMatchRules(reused?.matchRules || { normalizeNames: true, preferCreditNo: true, deduplicate: true, manualReviewAmbiguous: true });
|
|
3347
3782
|
setWizardStep(1); setOpen(true); setError(null);
|
|
3348
3783
|
};
|
|
3349
3784
|
const receiveImage = (event) => {
|
|
@@ -3681,7 +4116,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3681
4116
|
}
|
|
3682
4117
|
|
|
3683
4118
|
/** 上传 pane 的处理:文件 → 文本/base64 → parse。 */
|
|
3684
|
-
async function parseFile(file) {
|
|
4119
|
+
async function parseFile(file, previousChecksum) {
|
|
3685
4120
|
const isXlsx = /\.(xlsx|xls)$/i.test(file && file.name ? file.name : '');
|
|
3686
4121
|
let content;
|
|
3687
4122
|
if (isXlsx) {
|
|
@@ -3693,7 +4128,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3693
4128
|
} else {
|
|
3694
4129
|
content = await file.text();
|
|
3695
4130
|
}
|
|
3696
|
-
return api('/data-cleaning/api/mvp/parse', { filename: file.name, content });
|
|
4131
|
+
return api('/data-cleaning/api/mvp/parse', { filename: file.name, content, previousChecksum });
|
|
3697
4132
|
}
|
|
3698
4133
|
|
|
3699
4134
|
/** 文本/JSON 直通 parse(与 web.js 内联页同构,含 JSON 数组直通)。 */
|
|
@@ -3708,7 +4143,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3708
4143
|
};
|
|
3709
4144
|
}
|
|
3710
4145
|
|
|
3711
|
-
async function parseText(text, actions) {
|
|
4146
|
+
async function parseText(text, actions, previousChecksum) {
|
|
3712
4147
|
const trimmed = (text ?? '').trim();
|
|
3713
4148
|
if (!trimmed) {
|
|
3714
4149
|
actions.setError('请粘贴或上传数据后再解析。');
|
|
@@ -3716,18 +4151,26 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3716
4151
|
}
|
|
3717
4152
|
if (trimmed.startsWith('[')) {
|
|
3718
4153
|
try {
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
4154
|
+
return api('/data-cleaning/api/mvp/parse', {
|
|
4155
|
+
filename: 'data.json',
|
|
4156
|
+
content: trimmed,
|
|
4157
|
+
previousChecksum,
|
|
4158
|
+
});
|
|
3723
4159
|
} catch (error) {
|
|
3724
4160
|
actions.setError(`JSON 解析失败:${error instanceof Error ? error.message : String(error)}`);
|
|
3725
4161
|
}
|
|
3726
4162
|
return;
|
|
3727
4163
|
}
|
|
3728
4164
|
const entityList = plainEntityListDataset(trimmed);
|
|
3729
|
-
if (entityList)
|
|
3730
|
-
|
|
4165
|
+
if (entityList) {
|
|
4166
|
+
const parsed = await api('/data-cleaning/api/mvp/parse', {
|
|
4167
|
+
filename: 'data.json',
|
|
4168
|
+
content: JSON.stringify(entityList.rows),
|
|
4169
|
+
previousChecksum,
|
|
4170
|
+
});
|
|
4171
|
+
return { ...parsed, fmt: 'text' };
|
|
4172
|
+
}
|
|
4173
|
+
return api('/data-cleaning/api/mvp/parse', { filename: 'data.csv', content: trimmed, previousChecksum });
|
|
3731
4174
|
}
|
|
3732
4175
|
|
|
3733
4176
|
/** 解析成功进入 taskId runtime;不把整表写入 Host store。 */
|
|
@@ -3740,6 +4183,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3740
4183
|
type: source.type || result.fmt || 'csv',
|
|
3741
4184
|
fileName: source.fileName || '',
|
|
3742
4185
|
sizeBytes: source.sizeBytes || 0,
|
|
4186
|
+
checksum: result.checksum || source.checksum || '',
|
|
3743
4187
|
};
|
|
3744
4188
|
const mappings = guessMappings(runtime.headers);
|
|
3745
4189
|
const guessedNameField = mappings.find((mapping) => mapping.targetField === 'company_name')?.sourceField
|
|
@@ -3759,6 +4203,28 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3759
4203
|
return runtime;
|
|
3760
4204
|
}
|
|
3761
4205
|
|
|
4206
|
+
function restoreParsedRuntime(result, actions, task, source = {}) {
|
|
4207
|
+
const runtime = runtimeFor(task.id);
|
|
4208
|
+
runtime.rows = Array.isArray(result.rows) ? result.rows : [];
|
|
4209
|
+
runtime.sourceRows = runtime.rows;
|
|
4210
|
+
runtime.headers = Array.isArray(result.headers) ? result.headers : [];
|
|
4211
|
+
runtime.source = {
|
|
4212
|
+
type: source.type || result.fmt || task.source?.type || 'csv',
|
|
4213
|
+
fileName: source.fileName || task.source?.fileName || '',
|
|
4214
|
+
sizeBytes: source.sizeBytes || task.source?.sizeBytes || 0,
|
|
4215
|
+
checksum: result.checksum || '',
|
|
4216
|
+
};
|
|
4217
|
+
actions.setDataset({
|
|
4218
|
+
fmt: result.fmt ?? task.source?.type ?? 'csv',
|
|
4219
|
+
headers: runtime.headers,
|
|
4220
|
+
rowCount: typeof result.rowCount === 'number' ? result.rowCount : runtime.rows.length,
|
|
4221
|
+
preview: Array.isArray(result.preview) ? result.preview : runtime.rows.slice(0, 5),
|
|
4222
|
+
});
|
|
4223
|
+
const primary = (task.mappings || []).find((mapping) => ['company_name', 'credit_no', 'reg_no'].includes(mapping.targetField));
|
|
4224
|
+
if (primary) actions.setNameField(primary.sourceField);
|
|
4225
|
+
return runtime;
|
|
4226
|
+
}
|
|
4227
|
+
|
|
3762
4228
|
async function persistParsedWorkflow(actions, sessionId, result, source = {}, task) {
|
|
3763
4229
|
const importedMappings = guessMappings(result.headers || []);
|
|
3764
4230
|
const importedDraft = { mappings: importedMappings,
|
|
@@ -3772,10 +4238,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3772
4238
|
target.rows = Array.isArray(result.rows) ? result.rows : [];
|
|
3773
4239
|
target.sourceRows = target.rows;
|
|
3774
4240
|
target.headers = Array.isArray(result.headers) ? result.headers : [];
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
4241
|
+
target.source = {
|
|
4242
|
+
type: source.type || result.fmt || 'csv',
|
|
4243
|
+
fileName: source.fileName || '',
|
|
4244
|
+
sizeBytes: source.sizeBytes || 0,
|
|
4245
|
+
checksum: result.checksum || source.checksum || '',
|
|
3779
4246
|
};
|
|
3780
4247
|
if (['draft', 'uploaded', 'parse_failed'].includes(current.state)) {
|
|
3781
4248
|
current = await workflowAction(actions, sessionId, current, 'upload', {
|
|
@@ -3786,8 +4253,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3786
4253
|
columnCount: Array.isArray(result.headers) ? result.headers.length : 0,
|
|
3787
4254
|
headers: Array.isArray(result.headers) ? result.headers : [],
|
|
3788
4255
|
sizeBytes: source.sizeBytes || 0,
|
|
4256
|
+
checksum: result.checksum || source.checksum || '',
|
|
3789
4257
|
},
|
|
3790
|
-
});
|
|
4258
|
+
}, 'import');
|
|
3791
4259
|
}
|
|
3792
4260
|
if (current.state === 'uploaded') current = await updateWorkflowTask(actions, sessionId, current, importedDraft);
|
|
3793
4261
|
return current;
|
|
@@ -3858,6 +4326,103 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3858
4326
|
);
|
|
3859
4327
|
}
|
|
3860
4328
|
|
|
4329
|
+
function WorkflowTaskContext(props) {
|
|
4330
|
+
const stageCompleted = props.stageCompleted;
|
|
4331
|
+
const progressVisible = props.commandProgress?.taskId === props.task.id
|
|
4332
|
+
&& Number.isFinite(props.commandProgress.totalUnique);
|
|
4333
|
+
const completedUnique = Math.min(
|
|
4334
|
+
props.commandProgress?.totalUnique || 0,
|
|
4335
|
+
Math.max(0, props.commandProgress?.completedUnique || 0),
|
|
4336
|
+
);
|
|
4337
|
+
return h('div', { className: 'dcAgentTaskStatus', 'aria-live': 'polite' },
|
|
4338
|
+
h('div', { className: 'dcAgentTaskStatusRow' },
|
|
4339
|
+
h('div', { className: 'dcAgentTaskStage' },
|
|
4340
|
+
h('span', {
|
|
4341
|
+
className: 'dcAgentTaskDoneMark',
|
|
4342
|
+
'data-complete': stageCompleted,
|
|
4343
|
+
'aria-hidden': true,
|
|
4344
|
+
}, stageCompleted ? '✓' : '•'),
|
|
4345
|
+
h('div', { className: 'dcAgentTaskStageCopy' },
|
|
4346
|
+
h('div', { className: 'dcAgentTaskStageTitle' },
|
|
4347
|
+
h('strong', null, props.stageLabel),
|
|
4348
|
+
h('span', {
|
|
4349
|
+
className: 'dcAgentTaskState',
|
|
4350
|
+
'data-complete': stageCompleted,
|
|
4351
|
+
}, props.statusLabel),
|
|
4352
|
+
),
|
|
4353
|
+
props.timeLabel || props.sourceLabel ? h('div', { className: 'dcAgentTaskMeta' },
|
|
4354
|
+
props.timeLabel ? h('span', null,
|
|
4355
|
+
h('time', { dateTime: props.timeValue || undefined }, props.timeLabel)) : null,
|
|
4356
|
+
props.sourceLabel ? h('span', null, props.sourceLabel) : null,
|
|
4357
|
+
) : null,
|
|
4358
|
+
),
|
|
4359
|
+
),
|
|
4360
|
+
props.viewAccess === 'read' ? h('div', { className: 'dcAgentTaskCurrent' },
|
|
4361
|
+
h('span', null, '当前节点'),
|
|
4362
|
+
h('strong', null, props.currentStageLabel),
|
|
4363
|
+
) : null,
|
|
4364
|
+
props.viewAccess === 'read' && props.onReturn ? h('button', {
|
|
4365
|
+
type: 'button',
|
|
4366
|
+
className: 'dcAgentTaskReturn',
|
|
4367
|
+
onClick: props.onReturn,
|
|
4368
|
+
}, '回到当前节点') : null,
|
|
4369
|
+
h('details', { className: 'dcAgentTaskDetails' },
|
|
4370
|
+
h('summary', { 'aria-label': '任务详情', title: '任务详情' }, 'i'),
|
|
4371
|
+
h('div', { className: 'dcAgentTaskDetailsBody' },
|
|
4372
|
+
h('strong', null, `${props.sourceLabel || '当前任务'} · ${props.statusLabel}`),
|
|
4373
|
+
h('p', null, `任务编号:${props.task.id}`),
|
|
4374
|
+
h('p', null, workflowOriginLabel(props.task)),
|
|
4375
|
+
h('p', null, `${props.rowCount} 条企业记录 · ${props.fieldCount} 个补全字段`),
|
|
4376
|
+
),
|
|
4377
|
+
),
|
|
4378
|
+
),
|
|
4379
|
+
['matching', 'enriching'].includes(props.task.state) ? h('p', { className: 'dcAgentTaskSyncNote' },
|
|
4380
|
+
'这是最近同步的任务记录;若原会话已停止,请先查看确认或错误提示,不要重复启动。') : null,
|
|
4381
|
+
progressVisible ? h('div', { className: 'dcAgentTaskExecution' },
|
|
4382
|
+
h('span', null, `已处理主体 ${completedUnique}/${props.commandProgress.totalUnique}`),
|
|
4383
|
+
h('progress', {
|
|
4384
|
+
max: props.commandProgress.totalUnique,
|
|
4385
|
+
value: completedUnique,
|
|
4386
|
+
'aria-label': '主体处理进度',
|
|
4387
|
+
}),
|
|
4388
|
+
) : null,
|
|
4389
|
+
h('div', { className: 'dcAgentTaskMetrics', 'aria-label': '任务摘要' },
|
|
4390
|
+
h('div', { className: 'dcAgentTaskMetricGroup' },
|
|
4391
|
+
h('span', { className: 'dcAgentTaskMetricLabel' }, '任务范围'),
|
|
4392
|
+
h('span', { className: 'dcAgentTaskMetric' }, '企业 ', h('b', null, props.rowCount), ' 家'),
|
|
4393
|
+
h('span', { className: 'dcAgentTaskMetric' }, '字段 ', h('b', null, props.fieldCount), ' 个'),
|
|
4394
|
+
),
|
|
4395
|
+
props.hasResultSummary ? h('div', { className: 'dcAgentTaskMetricGroup' },
|
|
4396
|
+
h('span', { className: 'dcAgentTaskMetricLabel' }, '处理结果'),
|
|
4397
|
+
h('span', { className: 'dcAgentTaskMetric is-good' }, '已补全 ', h('b', null, props.completedMetric)),
|
|
4398
|
+
h('span', { className: 'dcAgentTaskMetric' }, '待核验 ', h('b', null, props.reviewMetric)),
|
|
4399
|
+
h('span', { className: 'dcAgentTaskMetric' }, '失败 ', h('b', null, props.failedMetric)),
|
|
4400
|
+
) : null,
|
|
4401
|
+
),
|
|
4402
|
+
);
|
|
4403
|
+
}
|
|
4404
|
+
|
|
4405
|
+
function WorkflowGuide() {
|
|
4406
|
+
return h('details', { className: 'dcAgentWorkflowGuide' },
|
|
4407
|
+
h('summary', null, '使用说明和任务流程'),
|
|
4408
|
+
h('div', { className: 'dcAgentWorkflowGuideContent' },
|
|
4409
|
+
h('p', { className: 'dcAgentWorkflowGuideIntro' },
|
|
4410
|
+
'导入与核验 → 规则与体检 → 主体匹配 → 字段补全 → 结果下载。菜单只切换视图,不启动调用;按页面主按钮逐步确认。'),
|
|
4411
|
+
h('p', { className: 'dcAgentWorkflowGuideIntro' },
|
|
4412
|
+
'支持文本、Excel、JSON 和图片名单。原值保留、多候选人工核验;企查查调用使用当前用户自己的连接与额度。'),
|
|
4413
|
+
h('div', { className: 'dcAgentWorkflowGuideList' },
|
|
4414
|
+
WORKFLOW_GUIDE_STEPS.map(([title, description], index) => h('div', {
|
|
4415
|
+
key: title,
|
|
4416
|
+
className: 'dcAgentWorkflowGuideStep',
|
|
4417
|
+
},
|
|
4418
|
+
h('span', { className: 'dcAgentWorkflowGuideIndex' }, String(index + 1)),
|
|
4419
|
+
h('div', null, h('strong', null, title), h('p', null, description))))),
|
|
4420
|
+
h('div', { className: 'dcAgentWorkflowGuideBoundary' },
|
|
4421
|
+
'生成任务说明不会查询;发送说明或显式重试时,才使用当前用户连接的企查查账号及额度。'),
|
|
4422
|
+
),
|
|
4423
|
+
);
|
|
4424
|
+
}
|
|
4425
|
+
|
|
3861
4426
|
/** Session Tab 业务内容;尺寸、停靠、关闭均由 Better Sidebar 宿主管理。 */
|
|
3862
4427
|
function WorkbenchContent(props) {
|
|
3863
4428
|
const { useStore, actions } = props;
|
|
@@ -3910,13 +4475,19 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3910
4475
|
requestJson('/data-cleaning/api/workflow/contract').then((response) => {
|
|
3911
4476
|
if (!disposed) actions.setWorkflowContract(response.contract);
|
|
3912
4477
|
}).catch(() => {});
|
|
3913
|
-
loadWorkflowTasks(actions);
|
|
4478
|
+
const tasksPromise = loadWorkflowTasks(actions);
|
|
3914
4479
|
if (activeSessionId) {
|
|
3915
4480
|
queueWorkflowOperation(activeSessionId, async () => {
|
|
3916
4481
|
const key = String(activeSessionId);
|
|
4482
|
+
const tasks = await tasksPromise;
|
|
3917
4483
|
const saved = ownedSessionId(key) ? readTaskBindings()[key] : null;
|
|
3918
|
-
const
|
|
3919
|
-
|
|
4484
|
+
const hosted = tasks
|
|
4485
|
+
.filter((task) => taskBelongsToSession(task, activeSessionId))
|
|
4486
|
+
.sort((left, right) => String(right.createdAt).localeCompare(String(left.createdAt)))[0]?.id;
|
|
4487
|
+
const taskId = workflowTaskBySession.get(key)?.id
|
|
4488
|
+
|| hosted
|
|
4489
|
+
|| (/^dcw-[a-zA-Z0-9-]+$/.test(saved || '') ? saved : null);
|
|
4490
|
+
if (taskId) await refreshHostedTask(actions, activeSessionId, taskId, null, 'update');
|
|
3920
4491
|
}).catch((taskError) => {
|
|
3921
4492
|
if (!disposed) actions.setError(`状态同步异常:${taskError instanceof Error ? taskError.message : String(taskError)}。保留最近任务记录,未重新执行查询。`);
|
|
3922
4493
|
});
|
|
@@ -3931,11 +4502,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3931
4502
|
let disposed = false, timer;
|
|
3932
4503
|
const poll = async () => {
|
|
3933
4504
|
try {
|
|
3934
|
-
const task = await refreshHostedTask(actions, activeSessionId, workflowTask.id, null,
|
|
4505
|
+
const task = await refreshHostedTask(actions, activeSessionId, workflowTask.id, null, 'update');
|
|
3935
4506
|
if (disposed) return;
|
|
3936
4507
|
if (!task) return;
|
|
3937
4508
|
if (['matching', 'enriching'].includes(task.state)) timer = setTimeout(poll, 2000);
|
|
3938
|
-
else if (step === 'match' || step === 'enrich') actions.setStep(task.stage || step);
|
|
3939
4509
|
} catch (error) { if (!disposed) actions.setError(`状态同步异常:${error.message}。保留最近任务记录,未重新执行查询。`); }
|
|
3940
4510
|
};
|
|
3941
4511
|
timer = setTimeout(poll, 2000);
|
|
@@ -3952,7 +4522,29 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3952
4522
|
const stagedSource = runtimeFor(`session:${activeSessionId || 'unassigned'}`, false);
|
|
3953
4523
|
const sourceRows = stagedSource?.sourceRows ?? runtime.sourceRows ?? runtime.rows;
|
|
3954
4524
|
const intake = intakeState(dataset, sourceRows, input, runtime.source || stagedSource?.source || cachedTask?.source);
|
|
3955
|
-
const
|
|
4525
|
+
const normalizedStage = normalizeViewStage(step);
|
|
4526
|
+
const flowCompatible = cachedTask?.flow?.flowVersion === SUPPORTED_FLOW_VERSION;
|
|
4527
|
+
const viewAccess = step === 'history'
|
|
4528
|
+
? 'read'
|
|
4529
|
+
: cachedTask
|
|
4530
|
+
? resolveStageAccess(normalizedStage, cachedTask.flow)
|
|
4531
|
+
: normalizedStage === 'upload' ? 'current' : 'locked';
|
|
4532
|
+
const allowedActions = new Set(flowCompatible ? cachedTask.flow.allowedActions || [] : []);
|
|
4533
|
+
const isCurrentView = viewAccess === 'current';
|
|
4534
|
+
const canAction = (action) => isCurrentView && (
|
|
4535
|
+
cachedTask ? allowedActions.has(action) : action === 'import-data'
|
|
4536
|
+
);
|
|
4537
|
+
const reviewingImportedDataset = normalizedStage === 'upload'
|
|
4538
|
+
&& cachedTask?.state === 'uploaded'
|
|
4539
|
+
&& cachedTask.flow?.currentStage === 'rules'
|
|
4540
|
+
&& viewAccess === 'read';
|
|
4541
|
+
const navigate = (destination) => navigateWorkflow(actions, activeSessionId, destination, cachedTask);
|
|
4542
|
+
const showIntakeEditor = canAction('import-data') && (!dataset || intakeEditorOpen);
|
|
4543
|
+
const runtimeRecoveryNeeded = Boolean(cachedTask?.source?.rowCount) && runtime.rows.length === 0;
|
|
4544
|
+
const runtimeRecoveryAllowed = runtimeRecoveryNeeded
|
|
4545
|
+
&& isCurrentView
|
|
4546
|
+
&& ['uploaded', 'rules_confirmed', 'diagnosed'].includes(cachedTask?.state)
|
|
4547
|
+
&& cachedTask?.source?.type !== 'image';
|
|
3956
4548
|
const lastCsv = runtime.lastCsv;
|
|
3957
4549
|
const fieldByPattern = (pattern) => runtime.headers.find((field) => pattern.test(field)) ?? null;
|
|
3958
4550
|
const phoneField = fieldByPattern(/^(phone|mobile|tel|telephone|联系电话|手机号码|手机号)$/i);
|
|
@@ -3971,7 +4563,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3971
4563
|
};
|
|
3972
4564
|
|
|
3973
4565
|
const handleParse = async () => {
|
|
3974
|
-
if (busy) return;
|
|
4566
|
+
if (busy || !canAction('import-data')) return;
|
|
3975
4567
|
if (!intake.pendingText) {
|
|
3976
4568
|
if (intake.available) actions.setError(null);
|
|
3977
4569
|
return;
|
|
@@ -3979,7 +4571,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
3979
4571
|
actions.setBusy(true);
|
|
3980
4572
|
actions.setError(null);
|
|
3981
4573
|
try {
|
|
3982
|
-
const result = await parseText(input, actions);
|
|
4574
|
+
const result = await parseText(input, actions, cachedTask?.source?.checksum);
|
|
3983
4575
|
if (result && result.ok !== false) {
|
|
3984
4576
|
applyParsed(result, actions, runtimeKey, { type: result.fmt || 'csv' });
|
|
3985
4577
|
await persistParsedWorkflow(actions, activeSessionId, result, { type: result.fmt || 'csv' }, cachedTask);
|
|
@@ -4003,7 +4595,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4003
4595
|
return true;
|
|
4004
4596
|
};
|
|
4005
4597
|
const handleFile = async (event) => {
|
|
4006
|
-
if (busy) return;
|
|
4598
|
+
if (busy || !canAction('import-data')) return;
|
|
4007
4599
|
const file = event.target && event.target.files && event.target.files[0];
|
|
4008
4600
|
if (!file) return;
|
|
4009
4601
|
if (String(file.type ?? '').startsWith('image/')) {
|
|
@@ -4014,7 +4606,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4014
4606
|
actions.setBusy(true);
|
|
4015
4607
|
actions.setError(null);
|
|
4016
4608
|
try {
|
|
4017
|
-
const result = await parseFile(file);
|
|
4609
|
+
const result = await parseFile(file, cachedTask?.source?.checksum);
|
|
4018
4610
|
if (result && result.ok !== false) {
|
|
4019
4611
|
applyParsed(result, actions, runtimeKey, { type: result.fmt || 'csv', fileName: file.name, sizeBytes: file.size || 0 });
|
|
4020
4612
|
await persistParsedWorkflow(actions, activeSessionId, result, { type: result.fmt || 'csv', fileName: file.name, sizeBytes: file.size || 0 }, cachedTask);
|
|
@@ -4029,6 +4621,49 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4029
4621
|
}
|
|
4030
4622
|
};
|
|
4031
4623
|
|
|
4624
|
+
const acceptRuntimeRecovery = (result, source) => {
|
|
4625
|
+
if (!cachedTask?.source?.checksum) {
|
|
4626
|
+
throw new Error('该旧任务缺少同源校验信息,不能恢复写操作。请新建任务并复用配置。');
|
|
4627
|
+
}
|
|
4628
|
+
if (!result?.checksum || result.checksum !== cachedTask.source.checksum) {
|
|
4629
|
+
throw new Error('重新载入的数据与原任务不一致,未修改当前任务。请新建任务处理其他名单。');
|
|
4630
|
+
}
|
|
4631
|
+
restoreParsedRuntime(result, actions, cachedTask, source);
|
|
4632
|
+
actions.setInput('');
|
|
4633
|
+
actions.setError(null);
|
|
4634
|
+
};
|
|
4635
|
+
|
|
4636
|
+
const recoverRuntimeFile = async (event) => {
|
|
4637
|
+
if (busy) return;
|
|
4638
|
+
const file = event.target?.files?.[0];
|
|
4639
|
+
if (!file) return;
|
|
4640
|
+
actions.setBusy(true);
|
|
4641
|
+
actions.setError(null);
|
|
4642
|
+
try {
|
|
4643
|
+
const result = await parseFile(file, cachedTask?.source?.checksum);
|
|
4644
|
+
acceptRuntimeRecovery(result, { type: result.fmt, fileName: file.name, sizeBytes: file.size || 0 });
|
|
4645
|
+
} catch (err) {
|
|
4646
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4647
|
+
} finally {
|
|
4648
|
+
event.target.value = '';
|
|
4649
|
+
actions.setBusy(false);
|
|
4650
|
+
}
|
|
4651
|
+
};
|
|
4652
|
+
|
|
4653
|
+
const recoverRuntimeText = async () => {
|
|
4654
|
+
if (busy || !String(input || '').trim()) return;
|
|
4655
|
+
actions.setBusy(true);
|
|
4656
|
+
actions.setError(null);
|
|
4657
|
+
try {
|
|
4658
|
+
const result = await parseText(input, actions, cachedTask?.source?.checksum);
|
|
4659
|
+
acceptRuntimeRecovery(result, { type: result.fmt, fileName: cachedTask?.source?.fileName || '' });
|
|
4660
|
+
} catch (err) {
|
|
4661
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4662
|
+
} finally {
|
|
4663
|
+
actions.setBusy(false);
|
|
4664
|
+
}
|
|
4665
|
+
};
|
|
4666
|
+
|
|
4032
4667
|
const performProfile = async (task) => {
|
|
4033
4668
|
const r = await api('/data-cleaning/api/mvp/profile', {
|
|
4034
4669
|
rows: runtime.rows,
|
|
@@ -4042,7 +4677,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4042
4677
|
};
|
|
4043
4678
|
|
|
4044
4679
|
const runProfile = async () => {
|
|
4045
|
-
if (busy || !runtime.rows.length) {
|
|
4680
|
+
if (busy || !canAction('run-quality') || !runtime.rows.length) {
|
|
4046
4681
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
4047
4682
|
return;
|
|
4048
4683
|
}
|
|
@@ -4050,7 +4685,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4050
4685
|
actions.setError(null);
|
|
4051
4686
|
try {
|
|
4052
4687
|
await performProfile(cachedTask);
|
|
4053
|
-
actions.setStep(requiresQcc ? 'match' : 'enrich');
|
|
4054
4688
|
} catch (err) {
|
|
4055
4689
|
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4056
4690
|
} finally {
|
|
@@ -4059,7 +4693,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4059
4693
|
};
|
|
4060
4694
|
|
|
4061
4695
|
const runClean = async () => {
|
|
4062
|
-
if (busy || !runtime.rows.length) {
|
|
4696
|
+
if (busy || !canAction('run-local-clean') || !runtime.rows.length) {
|
|
4063
4697
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
4064
4698
|
return;
|
|
4065
4699
|
}
|
|
@@ -4087,7 +4721,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4087
4721
|
};
|
|
4088
4722
|
|
|
4089
4723
|
const runComplete = async () => {
|
|
4090
|
-
if (busy || !runtime.rows.length) {
|
|
4724
|
+
if (busy || !canAction('run-local-complete') || !runtime.rows.length) {
|
|
4091
4725
|
if (!runtime.rows.length) actions.setError('请先上传并解析数据。');
|
|
4092
4726
|
return;
|
|
4093
4727
|
}
|
|
@@ -4120,48 +4754,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4120
4754
|
runtime.resultRows.qcc = Array.isArray(run.rows) ? run.rows : null;
|
|
4121
4755
|
lastCsv.qcc = { csv: run.csv ?? '', name: run.downloadName ?? 'qcc-enriched.csv' };
|
|
4122
4756
|
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
4757
|
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;
|
|
4758
|
+
if (!currentTask?.id) return null;
|
|
4759
|
+
return refreshHostedTask(actions, activeSessionId, currentTask.id, run, 'update');
|
|
4165
4760
|
};
|
|
4166
4761
|
|
|
4167
4762
|
const executePreparedQccCommand = async (payload) => {
|
|
@@ -4201,7 +4796,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4201
4796
|
};
|
|
4202
4797
|
|
|
4203
4798
|
const runQcc = async () => {
|
|
4204
|
-
if (busy || !cachedTask?.id || !hasData) return;
|
|
4799
|
+
if (busy || !canAction('prepare-qcc-command') || !cachedTask?.id || !hasData) return;
|
|
4205
4800
|
actions.setBusy(true);
|
|
4206
4801
|
actions.setError(null);
|
|
4207
4802
|
try {
|
|
@@ -4223,7 +4818,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4223
4818
|
};
|
|
4224
4819
|
|
|
4225
4820
|
const resolveCandidate = async (item, candidate) => {
|
|
4226
|
-
if (busy || !paidConfirmed || !qccRun) return;
|
|
4821
|
+
if (busy || !canAction('resolve-candidate') || !paidConfirmed || !qccRun) return;
|
|
4227
4822
|
actions.setBusy(true);
|
|
4228
4823
|
actions.setError(null);
|
|
4229
4824
|
try {
|
|
@@ -4244,7 +4839,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4244
4839
|
};
|
|
4245
4840
|
|
|
4246
4841
|
const retryFailures = async () => {
|
|
4247
|
-
if (busy || !paidConfirmed || !qccRun) return;
|
|
4842
|
+
if (busy || !canAction('retry-failed') || !paidConfirmed || !qccRun) return;
|
|
4248
4843
|
const names = [...new Set((qccRun.errors || []).filter((item) => item.error && item.error.retryable).map((item) => item.companyName))];
|
|
4249
4844
|
if (!names.length) return;
|
|
4250
4845
|
actions.setBusy(true);
|
|
@@ -4265,7 +4860,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4265
4860
|
}
|
|
4266
4861
|
};
|
|
4267
4862
|
|
|
4863
|
+
const retryDelivery = async () => {
|
|
4864
|
+
if (busy || !canAction('retry-delivery') || !cachedTask?.id) return;
|
|
4865
|
+
actions.setBusy(true);
|
|
4866
|
+
actions.setError(null);
|
|
4867
|
+
try {
|
|
4868
|
+
await workflowAction(actions, activeSessionId, cachedTask, 'retry-delivery');
|
|
4869
|
+
} catch (err) {
|
|
4870
|
+
actions.setError(err instanceof Error ? err.message : String(err));
|
|
4871
|
+
} finally {
|
|
4872
|
+
actions.setBusy(false);
|
|
4873
|
+
}
|
|
4874
|
+
};
|
|
4875
|
+
|
|
4268
4876
|
const saveTaskSettings = async () => {
|
|
4877
|
+
if (!canAction('edit-rules')) return;
|
|
4269
4878
|
actions.setBusy(true);
|
|
4270
4879
|
actions.setError(null);
|
|
4271
4880
|
try {
|
|
@@ -4283,6 +4892,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4283
4892
|
};
|
|
4284
4893
|
|
|
4285
4894
|
const confirmRuleSettings = async () => {
|
|
4895
|
+
if (!canAction('confirm-rules')) return;
|
|
4286
4896
|
if (!hasData) { actions.setError('请先上传并解析数据。'); return; }
|
|
4287
4897
|
const anchorMappings = mappings.filter((mapping) => ['company_name', 'credit_no', 'reg_no'].includes(mapping.targetField));
|
|
4288
4898
|
if (!anchorMappings.length) { actions.setError('请至少映射企业名称、统一社会信用代码或注册号。'); return; }
|
|
@@ -4302,6 +4912,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4302
4912
|
columnCount: dataset.headers?.length || 0,
|
|
4303
4913
|
headers: dataset.headers || [],
|
|
4304
4914
|
sizeBytes: runtime.source?.sizeBytes || 0,
|
|
4915
|
+
checksum: runtime.source?.checksum || '',
|
|
4305
4916
|
},
|
|
4306
4917
|
});
|
|
4307
4918
|
}
|
|
@@ -4316,7 +4927,6 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4316
4927
|
?? mappings.find((mapping) => mapping.targetField === 'credit_no')
|
|
4317
4928
|
?? mappings.find((mapping) => mapping.targetField === 'reg_no');
|
|
4318
4929
|
if (primary) actions.setNameField(primary.sourceField);
|
|
4319
|
-
actions.setStep('profile');
|
|
4320
4930
|
} catch (ruleError) {
|
|
4321
4931
|
actions.setError(ruleError instanceof Error ? ruleError.message : String(ruleError));
|
|
4322
4932
|
} finally {
|
|
@@ -4333,9 +4943,16 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4333
4943
|
} catch (error) { actions.setError(error.message); return; }
|
|
4334
4944
|
};
|
|
4335
4945
|
|
|
4336
|
-
const startNewWorkflow = () => {
|
|
4946
|
+
const startNewWorkflow = (reuseConfig = false) => {
|
|
4337
4947
|
const event = document.createEvent('CustomEvent');
|
|
4338
|
-
event.initCustomEvent('dsh:data-cleaning-prompt-new', false, true, {
|
|
4948
|
+
event.initCustomEvent('dsh:data-cleaning-prompt-new', false, true, {
|
|
4949
|
+
sessionId: activeSessionId,
|
|
4950
|
+
...(reuseConfig && cachedTask ? { reuseConfig: {
|
|
4951
|
+
objectives: cachedTask.objectives || [],
|
|
4952
|
+
fieldSelection: cachedTask.fieldSelection || [],
|
|
4953
|
+
matchRules: cachedTask.matchRules || {},
|
|
4954
|
+
} } : {}),
|
|
4955
|
+
});
|
|
4339
4956
|
if (document.dispatchEvent(event)) {
|
|
4340
4957
|
actions.setError('当前会话的任务向导尚未就绪或正在解析图片,请稍后重试。');
|
|
4341
4958
|
return;
|
|
@@ -4356,6 +4973,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4356
4973
|
const createArtifactBundle = async () => {
|
|
4357
4974
|
let latest = workflowTaskBySession.get(String(activeSessionId || 'unassigned')) ?? cachedTask;
|
|
4358
4975
|
if (!latest) throw new Error('请先创建并执行数据清洗补全任务。');
|
|
4976
|
+
if (!canAction('create-local-artifacts')) throw new Error('当前流程节点不能生成本地结果。');
|
|
4359
4977
|
if (Array.isArray(latest.artifacts) && latest.artifacts.length) return latest;
|
|
4360
4978
|
const rows = exportRows();
|
|
4361
4979
|
if (!rows?.length) throw new Error('当前页面没有可导出的明细。若任务来自历史记录,请重新上传源文件并继续处理。');
|
|
@@ -4436,6 +5054,37 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4436
5054
|
: cachedTask?.enrichmentSummary?.reviewRequired
|
|
4437
5055
|
?? cachedTask?.matchSummary?.reviewRequired;
|
|
4438
5056
|
const qccFailedCount = qccRun?.summary?.failed ?? cachedTask?.enrichmentSummary?.failed ?? cachedTask?.matchSummary?.failed ?? 0;
|
|
5057
|
+
const viewedStageLabel = workflowStageLabel(normalizedStage);
|
|
5058
|
+
const currentStageLabel = workflowStageLabel(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload');
|
|
5059
|
+
const taskStatusLabel = viewAccess === 'read'
|
|
5060
|
+
? '已完成'
|
|
5061
|
+
: cachedTask?.source?.type === 'image' && cachedTask?.state === 'draft'
|
|
5062
|
+
? '图片已暂存,待识别'
|
|
5063
|
+
: WORKFLOW_STATE_LABELS[cachedTask?.state] || cachedTask?.state || '待开始';
|
|
5064
|
+
const viewedStageCompleted = viewAccess === 'read' || cachedTask?.state === 'completed';
|
|
5065
|
+
const taskTimestamp = cachedTask?.updatedAt || cachedTask?.createdAt;
|
|
5066
|
+
const taskTimeLabel = taskTimestamp && Number.isFinite(new Date(taskTimestamp).getTime())
|
|
5067
|
+
? new Date(taskTimestamp).toLocaleString('zh-CN', {
|
|
5068
|
+
year: 'numeric', month: 'numeric', day: 'numeric',
|
|
5069
|
+
hour: '2-digit', minute: '2-digit', hour12: false,
|
|
5070
|
+
})
|
|
5071
|
+
: '';
|
|
5072
|
+
const taskFileName = cachedTask?.source?.fileName || '';
|
|
5073
|
+
const taskSourceLabel = taskFileName
|
|
5074
|
+
|| (cachedTask?.source?.type === 'image'
|
|
5075
|
+
? '图片名单'
|
|
5076
|
+
: cachedTask?.source
|
|
5077
|
+
? '文本粘贴'
|
|
5078
|
+
: '');
|
|
5079
|
+
const taskRowCount = cachedTask?.source?.rowCount ?? dataset?.rowCount ?? 0;
|
|
5080
|
+
const taskFieldCount = cachedTask?.fieldSelection?.length ?? fieldSelection.length;
|
|
5081
|
+
const hasTaskResultSummary = Boolean(qccRun || cachedTask?.enrichmentSummary || cachedTask?.matchSummary);
|
|
5082
|
+
const taskCompletedCount = Number.isFinite(qccEnrichedCount) ? qccEnrichedCount : 0;
|
|
5083
|
+
const taskReviewCount = Number.isFinite(qccReviewCount) ? qccReviewCount : 0;
|
|
5084
|
+
const taskFailedCount = Number.isFinite(qccFailedCount) ? qccFailedCount : 0;
|
|
5085
|
+
const taskCompletedMetric = `${taskCompletedCount}/${taskRowCount || taskCompletedCount} 条`;
|
|
5086
|
+
const taskReviewMetric = `${taskReviewCount} 条`;
|
|
5087
|
+
const taskFailedMetric = `${taskFailedCount} 条`;
|
|
4439
5088
|
const stat = (label, value, tone) => h('div', { className: 'dcAgentCard' },
|
|
4440
5089
|
h('span', null, label),
|
|
4441
5090
|
h('b', { className: tone ? `is-${tone}` : null }, displayStatValue(value))
|
|
@@ -4472,8 +5121,13 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4472
5121
|
h('button', {
|
|
4473
5122
|
type: 'button',
|
|
4474
5123
|
className: 'dcAgentButton is-primary',
|
|
4475
|
-
onClick: startNewWorkflow,
|
|
5124
|
+
onClick: () => startNewWorkflow(false),
|
|
4476
5125
|
}, '新建清洗任务'),
|
|
5126
|
+
cachedTask ? h('button', {
|
|
5127
|
+
type: 'button',
|
|
5128
|
+
className: 'dcAgentButton',
|
|
5129
|
+
onClick: () => startNewWorkflow(true),
|
|
5130
|
+
}, '新建任务并复用配置') : null,
|
|
4477
5131
|
),
|
|
4478
5132
|
historyTask ? h('section', { className: 'dcAgentSection', 'aria-label': '历史任务只读详情' },
|
|
4479
5133
|
h('h3', null, workflowDisplayName(historyTask)),
|
|
@@ -4509,8 +5163,48 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4509
5163
|
),
|
|
4510
5164
|
);
|
|
4511
5165
|
} else if (step === 'rules') {
|
|
4512
|
-
|
|
5166
|
+
if (!canAction('edit-rules') && !canAction('confirm-rules')) {
|
|
5167
|
+
pane = h('div', { className: 'dcAgentPane' },
|
|
5168
|
+
h('p', { className: 'dcAgentHint' }, isCurrentView
|
|
5169
|
+
? '规则已经确认,当前只等待生成质量体检。'
|
|
5170
|
+
: '这是已确认规则的只读摘要。'),
|
|
5171
|
+
h('section', { className: 'dcAgentSection' },
|
|
5172
|
+
h('h3', null, taskTitle),
|
|
5173
|
+
h('div', { className: 'dcAgentChips' },
|
|
5174
|
+
mappings.map((mapping) => h('span', { key: mapping.sourceField, className: 'dcAgentChip' },
|
|
5175
|
+
`${mapping.sourceField} → ${resultFieldLabel(mapping.targetField)}`))),
|
|
5176
|
+
h('p', { className: 'dcAgentHint' }, `任务目标 ${objectives.length} 项 · 补全字段 ${fieldSelection.length} 项`),
|
|
5177
|
+
),
|
|
5178
|
+
canAction('run-quality') ? h('button', {
|
|
5179
|
+
type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !runtime.rows.length,
|
|
5180
|
+
onClick: runProfile,
|
|
5181
|
+
}, busy ? '体检中…' : '生成质量体检报告') : null,
|
|
5182
|
+
);
|
|
5183
|
+
} else pane = h('div', { className: 'dcAgentPane' },
|
|
4513
5184
|
h('p', { className: 'dcAgentHint' }, '确认任务名称、字段映射、匹配规则和输出字段。保存草稿不会调用企查查;确认规则后才可进入质量体检。'),
|
|
5185
|
+
canAction('import-data') ? h('section', { className: 'dcAgentSection' },
|
|
5186
|
+
h('div', { className: 'dcAgentRow' },
|
|
5187
|
+
h('h3', null, '当前来源'),
|
|
5188
|
+
!intakeEditorOpen ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5189
|
+
onClick: () => { actions.setError(null); setIntakeEditorOpen(true); } }, '重新导入') : null,
|
|
5190
|
+
),
|
|
5191
|
+
h('p', { className: 'dcAgentHint' }, `${intake.fileName} · ${dataset?.rowCount || 0} 行`),
|
|
5192
|
+
showIntakeEditor ? h('div', { className: 'dcAgentIntakeEditor' },
|
|
5193
|
+
h('div', { className: 'dcAgentIntakeSourceActions' },
|
|
5194
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5195
|
+
onClick: (event) => event.currentTarget.nextElementSibling?.click() }, '选择文件(自动解析)'),
|
|
5196
|
+
h('input', { type: 'file', accept: '.csv,.json,.xlsx,.xls,image/png,image/jpeg,image/webp',
|
|
5197
|
+
style: { display: 'none' }, disabled: busy, 'aria-label': '选择数据文件', onChange: handleFile }),
|
|
5198
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5199
|
+
onClick: () => openImageIntake() }, '图片识别(粘贴 / 选择)'),
|
|
5200
|
+
),
|
|
5201
|
+
h('textarea', { className: 'dcAgentTextarea', value: input, disabled: busy,
|
|
5202
|
+
'aria-label': '粘贴数据', placeholder: '粘贴 CSV、JSON 或逐行企业名单',
|
|
5203
|
+
onInput: (event) => { actions.setInput(event.target.value); actions.setError(null); } }),
|
|
5204
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !intake.pendingText,
|
|
5205
|
+
'aria-label': '解析数据', onClick: handleParse }, busy ? '解析中…' : '解析粘贴内容'),
|
|
5206
|
+
) : null,
|
|
5207
|
+
) : null,
|
|
4514
5208
|
h('label', { className: 'dcAgentFormField' },
|
|
4515
5209
|
h('span', null, '任务名称'),
|
|
4516
5210
|
h('input', { className: 'dcAgentField', value: taskTitle, maxLength: 120, onChange: (event) => actions.setTaskTitle(event.target.value) }),
|
|
@@ -4635,12 +5329,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4635
5329
|
type: 'button',
|
|
4636
5330
|
className: 'dcAgentButton is-primary',
|
|
4637
5331
|
disabled: busy,
|
|
4638
|
-
onClick: () =>
|
|
5332
|
+
onClick: () => navigate(requiresQcc ? 'match' : 'enrich'),
|
|
4639
5333
|
}, requiresQcc ? '下一步:匹配核验' : '下一步:本地清洗补全'),
|
|
4640
5334
|
),
|
|
4641
|
-
) : h('div', { className: 'dcAgentProfileEmpty' },
|
|
5335
|
+
) : canAction('run-quality') ? h('div', { className: 'dcAgentProfileEmpty' },
|
|
4642
5336
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData, 'aria-label': '生成体检报告', onClick: runProfile }, busy ? '体检中…' : '生成体检报告'),
|
|
4643
|
-
),
|
|
5337
|
+
) : h('p', { className: 'dcAgentHint' }, '当前没有可恢复的质量体检明细。'),
|
|
4644
5338
|
),
|
|
4645
5339
|
);
|
|
4646
5340
|
} else if (step === 'match') {
|
|
@@ -4663,19 +5357,20 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4663
5357
|
h('section', { className: 'dcAgentMatchBlock', 'aria-label': '准备任务说明' },
|
|
4664
5358
|
h('div', { className: 'dcAgentMatchBlockHead' },
|
|
4665
5359
|
h('h4', null, '准备任务说明'),
|
|
4666
|
-
h('span', { className: 'dcAgentMatchScope' }, h('b', null,
|
|
5360
|
+
h('span', { className: 'dcAgentMatchScope' }, h('b', null,
|
|
5361
|
+
dataset?.rowCount ?? cachedTask?.source?.rowCount ?? runtime.rows.length), '条记录'),
|
|
4667
5362
|
),
|
|
4668
5363
|
h('p', { className: 'dcAgentHint' }, `已选择 ${fieldSelection.length} 个补全字段。Host 自动检查所需工具并计算去重主体与调用上界。`),
|
|
4669
5364
|
h('p', { className: 'dcAgentHint' }, '回填后可修改;点击发送才开始查询,使用你已连接的企查查 MCP 账号及额度。'),
|
|
4670
5365
|
),
|
|
4671
5366
|
),
|
|
4672
|
-
h('div', { className: 'dcAgentMatchActions' },
|
|
5367
|
+
canAction('prepare-qcc-command') ? h('div', { className: 'dcAgentMatchActions' },
|
|
4673
5368
|
h('button', { type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !hasData || !cachedTask?.id, onClick: runQcc }, busy ? '检查并生成中…' : '生成可编辑任务说明'),
|
|
4674
|
-
),
|
|
5369
|
+
) : null,
|
|
4675
5370
|
),
|
|
4676
5371
|
qccRun ? h('section', { className: 'dcAgentSection' },
|
|
4677
5372
|
h('h3', null, `任务 ${qccRun.runId} · ${qccRun.state}`),
|
|
4678
|
-
(
|
|
5373
|
+
canAction('resolve-candidate') && (qccRun.reviewQueue || []).length ? h('label', { className: 'dcAgentCheck' },
|
|
4679
5374
|
h('input', { type: 'checkbox', checked: paidConfirmed, 'aria-label': '确认候选或重试的企查查调用', onChange: (event) => actions.setPaidConfirmed(event.target.checked) }),
|
|
4680
5375
|
'确认主体或重试会继续查询,使用当前连接的企查查账号;额度或费用由该账号自行承担。',
|
|
4681
5376
|
) : null,
|
|
@@ -4695,10 +5390,9 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4695
5390
|
item.candidates.map((candidate) => h('div', { key: candidate.creditNo, className: 'dcAgentCandidate' },
|
|
4696
5391
|
h('div', null, h('b', null, candidate.companyName || '未命名候选'), h('small', null, candidate.creditNo)),
|
|
4697
5392
|
h('small', null, `${candidate.status || '状态未知'} · ${(candidate.legalRep || []).join('、') || '法人未知'}`),
|
|
4698
|
-
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: () => resolveCandidate(item, candidate) }, '确认此主体'),
|
|
5393
|
+
canAction('resolve-candidate') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: () => resolveCandidate(item, candidate) }, '确认此主体') : null,
|
|
4699
5394
|
)),
|
|
4700
5395
|
)),
|
|
4701
|
-
(qccRun.errors || []).some((item) => item.error?.retryable) ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: retryFailures }, '重试可恢复失败项') : null,
|
|
4702
5396
|
) : null,
|
|
4703
5397
|
);
|
|
4704
5398
|
} else if (step === 'enrich') {
|
|
@@ -4710,10 +5404,10 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4710
5404
|
),
|
|
4711
5405
|
h('section', { className: 'dcAgentSection' },
|
|
4712
5406
|
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
|
-
),
|
|
5407
|
+
canAction('run-local-clean') || canAction('run-local-complete') ? h('div', { className: 'dcAgentRow' },
|
|
5408
|
+
canAction('run-local-clean') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行清洗', onClick: runClean }, busy ? '处理中…' : '执行确定性清洗') : null,
|
|
5409
|
+
canAction('run-local-complete') ? h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !hasData, 'aria-label': '执行补全', onClick: runComplete }, busy ? '处理中…' : '本地规则补全') : null,
|
|
5410
|
+
) : null,
|
|
4717
5411
|
clean ? h('div', { className: 'dcAgentGrid' },
|
|
4718
5412
|
stat('总数', clean.total), stat('保留', clean.kept, 'good'), stat('剔除', clean.dropped, 'bad'),
|
|
4719
5413
|
stat('缺失关键字段', clean.badMissing, clean.badMissing > 0 ? 'bad' : null),
|
|
@@ -4727,13 +5421,28 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4727
5421
|
stat('QCC 已补全', qccEnrichedCount, qccEnrichedCount > 0 ? 'good' : null),
|
|
4728
5422
|
stat('待核验', qccReviewCount, qccReviewCount > 0 ? 'warn' : null),
|
|
4729
5423
|
),
|
|
4730
|
-
h('div', { className: 'dcAgentRow' }, h('button', {
|
|
5424
|
+
canAction('create-local-artifacts') ? h('div', { className: 'dcAgentRow' }, h('button', {
|
|
5425
|
+
type: 'button', className: 'dcAgentButton is-primary', disabled: busy || !exportRows()?.length,
|
|
5426
|
+
onClick: () => createAndDownload('complete', 'xlsx'),
|
|
5427
|
+
}, '生成结果')) : null,
|
|
4731
5428
|
);
|
|
4732
5429
|
} else if (step === 'download') {
|
|
4733
|
-
const
|
|
5430
|
+
const artifactsByKind = new Map();
|
|
5431
|
+
for (const artifact of cachedTask?.artifacts || []) {
|
|
5432
|
+
if (artifact.format === 'xlsx' && (artifact.kind !== 'review' || artifact.rowCount > 0)) {
|
|
5433
|
+
artifactsByKind.set(artifact.kind, artifact);
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
const availableArtifacts = [...artifactsByKind.values()];
|
|
4734
5437
|
const resultArtifacts = availableArtifacts.filter(artifact => artifact.kind !== 'report');
|
|
4735
5438
|
const reportArtifact = availableArtifacts.find(artifact => artifact.kind === 'report');
|
|
4736
|
-
const canCreateArtifacts = Boolean(exportRows()?.length) &&
|
|
5439
|
+
const canCreateArtifacts = Boolean(exportRows()?.length) && canAction('create-local-artifacts');
|
|
5440
|
+
const retryableErrors = (qccRun?.errors || []).filter((item) => item.error?.retryable);
|
|
5441
|
+
const canRetryFailures = canAction('retry-failed')
|
|
5442
|
+
&& cachedTask?.runtimeCapabilities?.liveRunAvailable
|
|
5443
|
+
&& retryableErrors.length > 0;
|
|
5444
|
+
const canRetryDelivery = canAction('retry-delivery')
|
|
5445
|
+
&& cachedTask?.runtimeCapabilities?.liveRunAvailable;
|
|
4737
5446
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4738
5447
|
h('p', { className: 'dcAgentHint' }, '结果文件由 Host 保存;有失败、未匹配或待核验记录时展示异常清单。任务或插件重启后,可从任务历史继续下载;无需再次调用企查查。'),
|
|
4739
5448
|
h('div', { className: 'dcAgentResultSummary' }, h('div', { className: 'dcAgentGrid' },
|
|
@@ -4770,10 +5479,21 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4770
5479
|
),
|
|
4771
5480
|
!canCreateArtifacts ? h('p', { className: 'dcAgentHint' }, '请先完成规则确认与清洗补全;历史任务若尚未生成制品,需要重新上传源文件。') : null,
|
|
4772
5481
|
),
|
|
5482
|
+
canRetryFailures ? h('section', { className: 'dcAgentSection' },
|
|
5483
|
+
h('label', { className: 'dcAgentCheck' },
|
|
5484
|
+
h('input', { type: 'checkbox', checked: paidConfirmed, 'aria-label': '确认重试的企查查调用', onChange: (event) => actions.setPaidConfirmed(event.target.checked) }),
|
|
5485
|
+
'重试会继续查询,使用当前连接的企查查账号;额度或费用由该账号自行承担。',
|
|
5486
|
+
),
|
|
5487
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy || !paidConfirmed, onClick: retryFailures },
|
|
5488
|
+
`重试可恢复失败项 · ${retryableErrors.length}`),
|
|
5489
|
+
) : null,
|
|
5490
|
+
canRetryDelivery ? h('button', {
|
|
5491
|
+
type: 'button', className: 'dcAgentButton', disabled: busy, onClick: retryDelivery,
|
|
5492
|
+
}, '重新生成结果文件') : null,
|
|
4773
5493
|
);
|
|
4774
5494
|
} else {
|
|
4775
5495
|
pane = h('div', { className: 'dcAgentPane' },
|
|
4776
|
-
dataset ? h('section', { className: 'dcAgentSection dcAgentIntakeSummary', 'aria-label': '当前导入数据', role: 'status' },
|
|
5496
|
+
dataset || cachedTask?.source ? h('section', { className: 'dcAgentSection dcAgentIntakeSummary', 'aria-label': '当前导入数据', role: 'status' },
|
|
4777
5497
|
h('div', { className: 'dcAgentIntakeSummaryHead' },
|
|
4778
5498
|
h('div', null,
|
|
4779
5499
|
h('h3', null, intake.available ? `已解析 ${intake.count} 条数据` : '已保存导入摘要'),
|
|
@@ -4781,8 +5501,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4781
5501
|
),
|
|
4782
5502
|
),
|
|
4783
5503
|
h('p', { className: 'dcAgentHint' }, intake.available
|
|
4784
|
-
? '
|
|
4785
|
-
|
|
5504
|
+
? canAction('import-data')
|
|
5505
|
+
? '数据已导入。请核对下方完整名单;需要替换来源时点击“重新导入”。'
|
|
5506
|
+
: '这是已导入来源的只读摘要。更换名单请从任务历史新建任务并复用配置。'
|
|
5507
|
+
: canAction('import-data')
|
|
5508
|
+
? '当前页面没有完整原始数据。请重新导入原文件;历史下载制品仍可从任务历史获取。'
|
|
5509
|
+
: '当前页面只保留 Host 来源摘要;已有制品可从结果下载或任务历史获取。'),
|
|
4786
5510
|
) : null,
|
|
4787
5511
|
showIntakeEditor ? h('section', { id: 'dc-agent-intake-editor', className: 'dcAgentSection dcAgentIntakeEditor', 'aria-label': dataset ? '重新导入数据' : '导入数据' },
|
|
4788
5512
|
h('header', { className: 'dcAgentIntakeEditorHeader' },
|
|
@@ -4840,16 +5564,47 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4840
5564
|
}) : null,
|
|
4841
5565
|
hasData ? h('p', { className: 'dcAgentHint' }, '请核对完整名单。识别清单不代表已完成企查查匹配。核对后进入「规则与体检」,确认规则生成质量体检;再进入「主体匹配」直接生成可编辑任务说明。Host 自动检查工具和调用范围,回填中央对话框后点击发送才开始查询。') : null,
|
|
4842
5566
|
h('div', { className: 'dcAgentUploadActions' },
|
|
4843
|
-
hasData && !intakeEditorOpen ? h('button', {
|
|
5567
|
+
hasData && !intakeEditorOpen && canAction('import-data') ? h('button', {
|
|
4844
5568
|
type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
4845
5569
|
'aria-expanded': false, 'aria-controls': 'dc-agent-intake-editor',
|
|
4846
5570
|
onClick: () => { actions.setError(null); setIntakeEditorOpen(true); },
|
|
4847
5571
|
}, '重新导入') : null,
|
|
4848
|
-
hasData
|
|
5572
|
+
hasData && (canAction('import-data') || reviewingImportedDataset) ? h('button', {
|
|
5573
|
+
type: 'button', className: 'dcAgentButton is-primary',
|
|
5574
|
+
disabled: busy || !intake.available || intake.pendingText,
|
|
5575
|
+
onClick: () => navigate('rules'),
|
|
5576
|
+
}, reviewingImportedDataset ? '确认名单,进入规则与体检' : '已核对清单,下一步:字段映射与规则') : null,
|
|
4849
5577
|
),
|
|
4850
5578
|
);
|
|
4851
5579
|
}
|
|
4852
5580
|
|
|
5581
|
+
const runtimeRecoveryPane = runtimeRecoveryAllowed ? h('div', { className: 'dcAgentPane' },
|
|
5582
|
+
h('section', { className: 'dcAgentSection', role: 'status' },
|
|
5583
|
+
h('h3', null, '重新载入原任务数据'),
|
|
5584
|
+
h('p', { className: 'dcAgentHint' }, cachedTask.source?.checksum
|
|
5585
|
+
? '原始名单未持久化。请选择或粘贴同一份数据;Host 校验一致后只恢复本页运行时数据,不改变任务状态。'
|
|
5586
|
+
: '该旧任务缺少同源校验信息,不能恢复写操作。'),
|
|
5587
|
+
cachedTask.source?.checksum ? h('div', { className: 'dcAgentRow' },
|
|
5588
|
+
h('button', { type: 'button', className: 'dcAgentButton', disabled: busy,
|
|
5589
|
+
onClick: (event) => event.currentTarget.nextElementSibling?.click() }, '选择原文件'),
|
|
5590
|
+
h('input', { type: 'file', accept: '.csv,.json,.xlsx,.xls', style: { display: 'none' },
|
|
5591
|
+
disabled: busy, 'aria-label': '重新载入原任务文件', onChange: recoverRuntimeFile }),
|
|
5592
|
+
) : null,
|
|
5593
|
+
cachedTask.source?.checksum ? h('textarea', {
|
|
5594
|
+
className: 'dcAgentTextarea', value: input, disabled: busy,
|
|
5595
|
+
placeholder: '也可以粘贴原 CSV、JSON 或逐行企业名单',
|
|
5596
|
+
'aria-label': '重新载入原任务文本',
|
|
5597
|
+
onInput: (event) => { actions.setInput(event.target.value); actions.setError(null); },
|
|
5598
|
+
}) : null,
|
|
5599
|
+
cachedTask.source?.checksum ? h('button', {
|
|
5600
|
+
type: 'button', className: 'dcAgentButton', disabled: busy || !String(input || '').trim(),
|
|
5601
|
+
onClick: recoverRuntimeText,
|
|
5602
|
+
}, '校验并恢复') : null,
|
|
5603
|
+
h('button', { type: 'button', className: 'dcAgentButton', onClick: () => startNewWorkflow(true) },
|
|
5604
|
+
'新建任务并复用配置'),
|
|
5605
|
+
),
|
|
5606
|
+
) : null;
|
|
5607
|
+
|
|
4853
5608
|
return h('section', {
|
|
4854
5609
|
className: 'dcAgentWorkbenchContent',
|
|
4855
5610
|
'data-dsh-plugin': 'data-cleaning-agent',
|
|
@@ -4858,54 +5613,75 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
4858
5613
|
},
|
|
4859
5614
|
h('nav', { className: 'dcAgentManagement', 'aria-label': '工作台管理' },
|
|
4860
5615
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step !== 'history',
|
|
4861
|
-
onClick: () =>
|
|
5616
|
+
onClick: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload') }, '当前任务'),
|
|
4862
5617
|
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'history',
|
|
4863
|
-
onClick: () =>
|
|
5618
|
+
onClick: () => navigate('history') }, '任务历史'),
|
|
4864
5619
|
),
|
|
4865
5620
|
step !== 'history' ? h('nav', { className: 'dcAgentStepper', 'aria-label': '清洗流程',
|
|
4866
5621
|
style: { '--dc-stage-count': STEPS.length } },
|
|
4867
|
-
STEPS.map((st) =>
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
'
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
5622
|
+
STEPS.map((st) => {
|
|
5623
|
+
const access = cachedTask
|
|
5624
|
+
? resolveStageAccess(st.key, cachedTask.flow)
|
|
5625
|
+
: st.key === 'upload' ? 'current' : 'locked';
|
|
5626
|
+
const locked = access === 'locked';
|
|
5627
|
+
const lockHint = locked
|
|
5628
|
+
? `尚未开放,请先完成“${currentStageLabel}”`
|
|
5629
|
+
: st.label;
|
|
5630
|
+
return h('button', {
|
|
5631
|
+
key: st.key,
|
|
5632
|
+
type: 'button',
|
|
5633
|
+
className: `dcAgentStep${(step === 'profile' ? 'rules' : step) === st.key ? ' is-active' : ''}`,
|
|
5634
|
+
'aria-label': st.label,
|
|
5635
|
+
title: lockHint,
|
|
5636
|
+
'aria-current': (step === 'profile' ? 'rules' : step) === st.key ? 'step' : undefined,
|
|
5637
|
+
'aria-disabled': locked ? 'true' : undefined,
|
|
5638
|
+
'aria-description': locked ? lockHint : undefined,
|
|
5639
|
+
'data-access': access,
|
|
5640
|
+
onClick: () => navigate(st.key),
|
|
5641
|
+
}, h('span', { className: 'dcAgentStepIcon' }, h(StageIcon, { kind: st.icon })),
|
|
5642
|
+
h('span', { className: 'dcAgentStepLabel' }, st.label));
|
|
5643
|
+
}),
|
|
4877
5644
|
) : null,
|
|
4878
5645
|
h('div', { className: 'dcAgentWbBody' },
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
5646
|
+
step !== 'history' ? h('section', { className: 'dcAgentTaskContext', 'aria-label': '任务状态与流程说明' },
|
|
5647
|
+
cachedTask ? h(WorkflowTaskContext, {
|
|
5648
|
+
task: cachedTask,
|
|
5649
|
+
stageLabel: viewedStageLabel,
|
|
5650
|
+
viewAccess,
|
|
5651
|
+
currentStageLabel,
|
|
5652
|
+
statusLabel: taskStatusLabel,
|
|
5653
|
+
stageCompleted: viewedStageCompleted,
|
|
5654
|
+
timeLabel: taskTimeLabel,
|
|
5655
|
+
timeValue: taskTimestamp,
|
|
5656
|
+
sourceLabel: taskSourceLabel,
|
|
5657
|
+
rowCount: taskRowCount,
|
|
5658
|
+
fieldCount: taskFieldCount,
|
|
5659
|
+
hasResultSummary: hasTaskResultSummary,
|
|
5660
|
+
completedMetric: taskCompletedMetric,
|
|
5661
|
+
reviewMetric: taskReviewMetric,
|
|
5662
|
+
failedMetric: taskFailedMetric,
|
|
5663
|
+
commandProgress,
|
|
5664
|
+
onReturn: reviewingImportedDataset
|
|
5665
|
+
? null
|
|
5666
|
+
: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload'),
|
|
4889
5667
|
}) : 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 和图片名单。原值保留、多候选人工核验;企查查调用使用当前用户自己的连接与额度。'),
|
|
5668
|
+
h(WorkflowGuide),
|
|
4898
5669
|
) : null,
|
|
5670
|
+
error ? h('div', {
|
|
5671
|
+
className: error.startsWith('流程提示:') ? 'dcAgentNavigationNotice' : 'dcAgentError',
|
|
5672
|
+
role: error.startsWith('流程提示:') ? 'status' : 'alert',
|
|
5673
|
+
'aria-live': 'polite',
|
|
5674
|
+
}, error) : null,
|
|
4899
5675
|
['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: () =>
|
|
5676
|
+
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'rules', onClick: () => navigate('rules') }, '字段映射与规则'),
|
|
5677
|
+
h('button', { type: 'button', className: 'dcAgentButton', 'aria-pressed': step === 'profile', onClick: () => navigate('profile') }, '质量体检报告'),
|
|
4902
5678
|
) : null,
|
|
4903
|
-
workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))
|
|
5679
|
+
runtimeRecoveryPane || (workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))
|
|
4904
5680
|
? h('section', { className: 'dcAgentSection', role: 'status' },
|
|
4905
5681
|
h('p', null, workflowNavigationIssue(step, cachedTask, hasData, profile, qccRun, Boolean((clean || complete) && exportRows()?.length))),
|
|
4906
5682
|
h('button', { type: 'button', className: 'dcAgentButton',
|
|
4907
|
-
onClick: () =>
|
|
4908
|
-
: pane,
|
|
5683
|
+
onClick: () => navigate(cachedTask?.flow?.currentStage || cachedTask?.stage || 'upload') }, '返回当前节点'))
|
|
5684
|
+
: pane),
|
|
4909
5685
|
),
|
|
4910
5686
|
);
|
|
4911
5687
|
}
|
|
@@ -5064,7 +5840,12 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5064
5840
|
react.useEffect(() => {
|
|
5065
5841
|
instance.actions.openAt(null, props.scope.sessionId);
|
|
5066
5842
|
return adapter.attach(props.scope, { tabId: props.tab.id, store: props.store,
|
|
5067
|
-
navigate: destination =>
|
|
5843
|
+
navigate: destination => navigateWorkflow(
|
|
5844
|
+
instance.actions,
|
|
5845
|
+
props.scope.sessionId,
|
|
5846
|
+
destination || instance.getSnapshot().step,
|
|
5847
|
+
instance.getSnapshot().workflowTask,
|
|
5848
|
+
) });
|
|
5068
5849
|
}, [instance, props.scope.sessionId, props.tab.id, props.store]);
|
|
5069
5850
|
return h(WorkbenchContent, { useStore, actions: instance.actions, visible: props.visible,
|
|
5070
5851
|
sendSessionCommand: (sessionId, prompt) => sendQccAgentCommand(ctx, sessionId, prompt),
|
|
@@ -5139,13 +5920,29 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5139
5920
|
open(sessionId, step) {
|
|
5140
5921
|
if (disposed || !sessionId) return false;
|
|
5141
5922
|
const instance = instanceFor(sessionId);
|
|
5142
|
-
|
|
5923
|
+
const destination = workflowDestination(
|
|
5924
|
+
step,
|
|
5925
|
+
workflowTaskBySession.get(String(sessionId)) ?? instance.getSnapshot().workflowTask,
|
|
5926
|
+
instance.getSnapshot().step,
|
|
5927
|
+
);
|
|
5928
|
+
instance.actions.openAt(destination.stage, sessionId);
|
|
5143
5929
|
const sessions = typeof ctx.get === 'function' ? ctx.get('sessions') : ctx.sessions;
|
|
5144
5930
|
const summary = sessions?.list?.getSnapshot?.()?.byId?.[sessionId];
|
|
5145
5931
|
openingSession = sessionId;
|
|
5146
|
-
|
|
5147
|
-
|
|
5932
|
+
let opened;
|
|
5933
|
+
try { opened = adapter.open({ sessionId, ...(summary?.cwd ? { cwd: summary.cwd } : {}) },
|
|
5934
|
+
destination.stage); }
|
|
5148
5935
|
finally { openingSession = null; }
|
|
5936
|
+
if (destination.compatibility) {
|
|
5937
|
+
instance.actions.setError('页面与 Host 流程版本不一致,当前任务已切换为只读模式;请完整重启并升级 Host。');
|
|
5938
|
+
} else if (destination.access === 'locked') {
|
|
5939
|
+
instance.actions.setError(lockedWorkflowNotice(
|
|
5940
|
+
step,
|
|
5941
|
+
workflowTaskBySession.get(String(sessionId)) ?? instance.getSnapshot().workflowTask,
|
|
5942
|
+
destination.stage,
|
|
5943
|
+
));
|
|
5944
|
+
}
|
|
5945
|
+
return opened;
|
|
5149
5946
|
},
|
|
5150
5947
|
};
|
|
5151
5948
|
sessionWorkbenchController = controller;
|
|
@@ -5154,7 +5951,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5154
5951
|
[WORKBENCH_OPEN_EVENT]: event => {
|
|
5155
5952
|
const detail = event.detail || {};
|
|
5156
5953
|
const actions = controller.actionsFor(detail.sessionId);
|
|
5157
|
-
if (detail.task?.id) cacheWorkflowTask(actions, detail.sessionId, detail.task);
|
|
5954
|
+
if (detail.task?.id) cacheWorkflowTask(actions, detail.sessionId, detail.task, 'restore');
|
|
5158
5955
|
controller.open(detail.sessionId, detail.step);
|
|
5159
5956
|
},
|
|
5160
5957
|
[WORKBENCH_DATASET_EVENT]: event => {
|
|
@@ -5307,6 +6104,11 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5307
6104
|
visibleCatalogFields,
|
|
5308
6105
|
installCapabilityMount,
|
|
5309
6106
|
PromptDialog,
|
|
6107
|
+
normalizeViewStage,
|
|
6108
|
+
resolveStageAccess,
|
|
6109
|
+
workflowDestination,
|
|
6110
|
+
lockedWorkflowNotice,
|
|
6111
|
+
navigateWorkflow,
|
|
5310
6112
|
workflowNavigationIssue,
|
|
5311
6113
|
buildTaskPrompt,
|
|
5312
6114
|
DatasetReview,
|
|
@@ -5320,6 +6122,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5320
6122
|
requestPromptImage,
|
|
5321
6123
|
guessMappings,
|
|
5322
6124
|
intakeState,
|
|
6125
|
+
restoreParsedRuntime,
|
|
5323
6126
|
projectCompletionResult,
|
|
5324
6127
|
mappingRecommendations,
|
|
5325
6128
|
mappedOutputFields,
|
|
@@ -5340,6 +6143,7 @@ body:has(.dcAgentPromptBackdrop) { overflow: hidden; }
|
|
|
5340
6143
|
observeHomePhase,
|
|
5341
6144
|
ensureWorkflowTask,
|
|
5342
6145
|
ensureEditableWorkflowTask,
|
|
6146
|
+
refreshHostedTask,
|
|
5343
6147
|
queueWorkflowOperation,
|
|
5344
6148
|
taskBelongsToSession,
|
|
5345
6149
|
workflowOriginLabel,
|