@shulex/cxclawctl 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -22
- package/dist/cli.js +2202 -127
- package/dist/cli.js.map +1 -1
- package/package.json +19 -6
- package/capabilities.lock.json +0 -898
- package/requirements-cli.txt +0 -2
- package/skills/__lib__/__init__.py +0 -0
- package/skills/__lib__/skill-runtime/bootstrap.py +0 -134
- package/skills/__lib__/solvea-api/__init__.py +0 -0
- package/skills/__lib__/solvea-api/client.py +0 -1944
- package/skills/__lib__/solvea-api/dashboard.py +0 -100
- package/skills/__lib__/solvea-api/solvea_api.py +0 -1341
- package/skills/agent-analytics/SKILL.md +0 -74
- package/skills/agent-analytics/references/chat-report-fields.md +0 -36
- package/skills/agent-analytics/references/report-fields.md +0 -130
- package/skills/agent-analytics/references/transcript.md +0 -110
- package/skills/agent-builder/SKILL.md +0 -42
- package/skills/agent-builder/interfaces/fallback-chatflow.md +0 -47
- package/skills/agent-builder/references/chatflow/graph-spec.md +0 -259
- package/skills/agent-builder/references/chatflow/node-selection.md +0 -89
- package/skills/agent-builder/references/chatflow/nodes/agent.md +0 -350
- package/skills/agent-builder/references/chatflow/nodes/business-track.md +0 -43
- package/skills/agent-builder/references/chatflow/nodes/code.md +0 -157
- package/skills/agent-builder/references/chatflow/nodes/collect-order.md +0 -47
- package/skills/agent-builder/references/chatflow/nodes/finish.md +0 -34
- package/skills/agent-builder/references/chatflow/nodes/human-review.md +0 -60
- package/skills/agent-builder/references/chatflow/nodes/if-else.md +0 -100
- package/skills/agent-builder/references/chatflow/nodes/intent.md +0 -79
- package/skills/agent-builder/references/chatflow/nodes/knowledge-reply.md +0 -43
- package/skills/agent-builder/references/chatflow/nodes/llm.md +0 -117
- package/skills/agent-builder/references/chatflow/nodes/note.md +0 -38
- package/skills/agent-builder/references/chatflow/nodes/question.md +0 -97
- package/skills/agent-builder/references/chatflow/nodes/rag-reply.md +0 -68
- package/skills/agent-builder/references/chatflow/nodes/reply.md +0 -97
- package/skills/agent-builder/references/chatflow/nodes/start.md +0 -35
- package/skills/agent-builder/references/chatflow/nodes/sub-graph.md +0 -48
- package/skills/agent-builder/references/chatflow/nodes/tool.md +0 -138
- package/skills/agent-builder/references/chatflow/nodes/transfer-to-human.md +0 -63
- package/skills/agent-builder/references/chatflow/nodes/variable-assignment.md +0 -52
- package/skills/agent-builder/references/chatflow/nodes/variable-extract.md +0 -50
- package/skills/agent-builder/references/chatflow/nodes/variable-process.md +0 -70
- package/skills/agent-builder/references/chatflow/overview.md +0 -53
- package/skills/agent-builder/references/chatflow/visual-translation.md +0 -64
- package/skills/agent-builder/references/chatflow/workflow.md +0 -134
- package/skills/agent-builder/references/multi-agent/create-update.md +0 -185
- package/skills/agent-builder/references/multi-agent/graph-spec.md +0 -246
- package/skills/agent-builder/references/multi-agent/graph-structure.md +0 -12
- package/skills/agent-builder/references/multi-agent/migrate-to-chatflow.md +0 -13
- package/skills/agent-builder/references/multi-agent/migration/large.md +0 -554
- package/skills/agent-builder/references/multi-agent/migration/principles.md +0 -446
- package/skills/agent-builder/references/multi-agent/migration/small.md +0 -225
- package/skills/agent-builder/references/multi-agent/orchestration.md +0 -11
- package/skills/agent-builder/references/multi-agent/overview.md +0 -48
- package/skills/agent-builder/references/multi-agent/update.md +0 -128
- package/skills/agent-builder/references/multi-agent/workflow.md +0 -87
- package/skills/agent-builder/references/single-agent/overview.md +0 -48
- package/skills/agent-builder/references/single-agent/prompt.md +0 -16
- package/skills/agent-builder/references/single-agent/tools.md +0 -36
- package/skills/agent-builder/references/single-agent/voice.md +0 -46
- package/skills/agent-builder/references/tools/http-tool.md +0 -315
- package/skills/agent-builder/references/tools/mock-tool.md +0 -279
- package/skills/agent-builder/references/tools/overview.md +0 -46
- package/skills/agent-builder/references/tools/tool-config.md +0 -43
- package/skills/agent-builder/references/tools/x-token.md +0 -34
- package/skills/agent-builder/scripts/chatflow/chatflow_builder.py +0 -3458
- package/skills/agent-builder/scripts/chatflow/validate_graph.py +0 -1697
- package/skills/agent-builder/scripts/copy_marketplace_agent.py +0 -61
- package/skills/agent-builder/scripts/multi-agent/legacy_graph_analyzer.py +0 -5974
- package/skills/agent-builder/scripts/multi-agent/replay_compare.py +0 -679
- package/skills/agent-builder/scripts/patch_agent_tools.py +0 -135
- package/skills/agent-builder/scripts/tools/validate_tool_config.py +0 -384
- package/skills/agent-tester/SKILL.md +0 -35
- package/skills/agent-tester/batch-test/overview.md +0 -86
- package/skills/agent-tester/scripts/batch_test_sheet.py +0 -213
- package/skills/agent-tester/single-test/badcase-retest.md +0 -85
- package/skills/agent-tester/single-test/direct-test.md +0 -24
- package/skills/agent-tester/single-test/overview.md +0 -142
- package/skills/delivery/SKILL.md +0 -112
- package/skills/delivery/interfaces/classify.md +0 -38
- package/skills/delivery/interfaces/create-agents.md +0 -89
- package/skills/delivery/interfaces/delivery-test.md +0 -67
- package/skills/delivery/references/flow.md +0 -40
- package/skills/delivery/references/validation.md +0 -15
- package/skills/delivery/scripts/batch_test.py +0 -704
- package/skills/delivery/scripts/classify.py +0 -22
- package/skills/delivery/scripts/create_agents.py +0 -695
- package/skills/delivery/scripts/gen_delivery_report.py +0 -335
- package/skills/delivery/scripts/gen_excel.py +0 -234
- package/skills/delivery/scripts/gen_report.py +0 -208
- package/skills/delivery/scripts/plan.py +0 -163
- package/skills/delivery/scripts/render.py +0 -713
- package/skills/delivery/scripts/render_summary.py +0 -38
- package/skills/delivery/scripts/sample_history_cases.py +0 -109
- package/skills/kb-builder/SKILL.md +0 -80
- package/skills/kb-builder/interfaces/qa-upload.md +0 -55
- package/skills/kb-builder/interfaces/web-kb.md +0 -84
- package/skills/kb-builder/references/directory.md +0 -373
- package/skills/kb-builder/references/export.md +0 -282
- package/skills/kb-builder/references/isolation-keywords.md +0 -113
- package/skills/kb-builder/references/knowledge.md +0 -153
- package/skills/kb-builder/references/product.md +0 -52
- package/skills/kb-builder/references/qa-from-help-center.md +0 -58
- package/skills/kb-builder/references/qa-from-pdf.md +0 -94
- package/skills/kb-builder/references/qa-from-tickets.md +0 -168
- package/skills/kb-builder/references/qa-from-web.md +0 -98
- package/skills/kb-builder/references/qa-quality.md +0 -73
- package/skills/kb-builder/references/reply-template.md +0 -237
- package/skills/kb-builder/references/tag-isolation-migration.md +0 -61
- package/skills/kb-builder/references/tags.md +0 -45
- package/skills/kb-builder/references/upload.md +0 -130
- package/skills/kb-builder/scripts/batch_extract_ticket_qa.py +0 -333
- package/skills/kb-builder/scripts/build_directory_plan.py +0 -144
- package/skills/kb-builder/scripts/crawl_site.py +0 -433
- package/skills/kb-builder/scripts/create_dirs.py +0 -146
- package/skills/kb-builder/scripts/export_knowledge.py +0 -454
- package/skills/kb-builder/scripts/extract_ticket_qa.py +0 -441
- package/skills/kb-builder/scripts/extract_web_qa.py +0 -118
- package/skills/kb-builder/scripts/import_qa.py +0 -219
- package/skills/kb-builder/scripts/import_source_documents.py +0 -143
- package/skills/kb-builder/scripts/import_templates.py +0 -409
- package/skills/poc/SKILL.md +0 -125
- package/skills/poc/assets/default_manual.css +0 -279
- package/skills/poc/assets/notice_escalate.png +0 -0
- package/skills/poc/assets/notice_escalate2.png +0 -0
- package/skills/poc/assets/notice_new_chat.png +0 -0
- package/skills/poc/assets/poc_test_template.html +0 -645
- package/skills/poc/assets/poc_test_template.md +0 -104
- package/skills/poc/assets/step3_preview.png +0 -0
- package/skills/poc/assets/step4_order_note.png +0 -0
- package/skills/poc/interfaces/classify.md +0 -38
- package/skills/poc/interfaces/create-agents.md +0 -108
- package/skills/poc/interfaces/poc-test.md +0 -94
- package/skills/poc/references/flow.md +0 -47
- package/skills/poc/references/kb-directory.md +0 -22
- package/skills/poc/references/validation.md +0 -17
- package/skills/poc/scripts/classify.py +0 -22
- package/skills/poc/scripts/create_agents.py +0 -818
- package/skills/poc/scripts/gen_excel.py +0 -234
- package/skills/poc/scripts/gen_poc_manual.py +0 -249
- package/skills/poc/scripts/gen_report.py +0 -208
- package/skills/poc/scripts/inspect_chatflow.py +0 -209
- package/skills/poc/scripts/plan.py +0 -178
- package/skills/poc/scripts/poc_test.py +0 -495
- package/skills/poc/scripts/render.py +0 -713
- package/skills/poc/scripts/render_summary.py +0 -41
- package/skills/runtime-inspector/SKILL.md +0 -51
- package/skills/runtime-inspector/references/frontend-rendering.md +0 -126
- package/skills/runtime-inspector/references/model.md +0 -59
- package/skills/runtime-inspector/references/sandbox.md +0 -131
- package/skills/runtime-inspector/scripts/model_inspect.py +0 -68
- package/skills/runtime-inspector/scripts/prepare.py +0 -98
- package/skills/runtime-inspector/scripts/process.py +0 -84
- package/skills/runtime-inspector/scripts/read_state.py +0 -32
- package/skills/runtime-inspector/scripts/sandbox_inspect.py +0 -463
- package/skills/runtime-inspector/scripts/test_claude_proxy.py +0 -49
- package/skills/runtime-inspector/scripts/test_openai_proxy.py +0 -41
- package/skills/runtime-inspector/scripts/verify_persistence.py +0 -101
- package/skills/runtime-inspector/scripts/write_test.py +0 -41
- package/skills/setup/SKILL.md +0 -17
- package/skills/shulex-support/SKILL.md +0 -39
- package/skills/shulex-support/references/badcase-analysis/overview.md +0 -34
- package/skills/shulex-support/references/badcase-analysis/text/conversation-record.md +0 -105
- package/skills/shulex-support/references/badcase-analysis/text/core-flow.md +0 -89
- package/skills/shulex-support/references/badcase-analysis/text/log-query-rules.md +0 -36
- package/skills/shulex-support/references/badcase-analysis/text/m1-entry.md +0 -53
- package/skills/shulex-support/references/badcase-analysis/text/m10-chatflow-service.md +0 -259
- package/skills/shulex-support/references/badcase-analysis/text/m11-algorithm-service.md +0 -148
- package/skills/shulex-support/references/badcase-analysis/text/m12-quality-inspection.md +0 -57
- package/skills/shulex-support/references/badcase-analysis/text/m2-intent.md +0 -120
- package/skills/shulex-support/references/badcase-analysis/text/m3-strategy.md +0 -126
- package/skills/shulex-support/references/badcase-analysis/text/m4-aifield.md +0 -121
- package/skills/shulex-support/references/badcase-analysis/text/m5-agent.md +0 -208
- package/skills/shulex-support/references/badcase-analysis/text/m6-tool.md +0 -115
- package/skills/shulex-support/references/badcase-analysis/text/m7-reply.md +0 -72
- package/skills/shulex-support/references/badcase-analysis/text/m8-error.md +0 -92
- package/skills/shulex-support/references/badcase-analysis/text/m9-knowledge-search.md +0 -147
- package/skills/shulex-support/references/badcase-analysis/text/sls-query.md +0 -283
- package/skills/shulex-support/references/badcase-analysis/voice/core-flow.md +0 -77
- package/skills/shulex-support/references/badcase-analysis/voice/dependencies.md +0 -19
- package/skills/shulex-support/references/badcase-analysis/voice/platform-api.md +0 -23
- package/skills/shulex-support/references/badcase-analysis/voice/sls-playbook.md +0 -31
- package/skills/shulex-support/references/product-docs.md +0 -70
- package/skills/shulex-support/references/tickets.md +0 -184
- package/skills/shulex-support/scripts/support.py +0 -427
- package/skills/solvea-desk/SKILL.md +0 -39
- package/skills/solvea-desk/references/report/fields.md +0 -82
- package/skills/solvea-desk/references/report/report-design.md +0 -200
- package/skills/solvea-desk/references/report/report-template.html +0 -44
- package/skills/solvea-desk/references/report/report.css +0 -517
- package/skills/solvea-desk/references/report/workflow.md +0 -464
- package/skills/solvea-desk/references/tickets/query.md +0 -103
- package/skills/solvea-desk/references/tickets/tags.md +0 -83
- package/skills/solvea-desk/scripts/report/fetch_sources.py +0 -580
- package/skills/solvea-desk/scripts/report/render_report.py +0 -763
- package/skills/solvea-desk/scripts/tickets/desk_api.py +0 -73
- package/skills/solvea-desk/scripts/tickets/fetch_tickets.py +0 -403
- package/skills/solvea-desk/scripts/tickets/ticket_tags.py +0 -305
- package/skills/solvea-kit/SKILL.md +0 -84
- package/skills/solvea-kit/references/agent.md +0 -145
- package/skills/solvea-kit/references/chat.md +0 -244
- package/skills/solvea-kit/references/intent.md +0 -69
- package/skills/solvea-kit/references/plugin.md +0 -53
- package/skills/solvea-kit/references/quality-inspection.md +0 -75
- package/skills/solvea-kit/references/strategy.md +0 -62
- package/skills/solvea-kit/references/tagging.md +0 -231
- package/skills/solvea-kit/references/variables.md +0 -53
- package/skills/solvea-kit/scripts/generate_label_import_file.py +0 -151
- package/skills/solvea-kit/scripts/intent_classify.py +0 -843
|
@@ -1,713 +0,0 @@
|
|
|
1
|
-
"""Auto-generated render module for intent-classifier.
|
|
2
|
-
Extracted from intent-optimizer; do NOT import from intent-optimizer.
|
|
3
|
-
"""
|
|
4
|
-
import json
|
|
5
|
-
|
|
6
|
-
CSS = """
|
|
7
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
8
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Segoe UI', sans-serif; background: #f0f2f5; }
|
|
9
|
-
.page-header { background: linear-gradient(135deg, #1a1a2e 0%, #0f3460 100%); color: white; padding: 24px 40px 20px; }
|
|
10
|
-
.page-header h1 { font-size: 19px; font-weight: 700; }
|
|
11
|
-
.page-header .sub { font-size: 11.5px; color: rgba(255,255,255,0.45); margin-top: 3px; }
|
|
12
|
-
.kpis { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
|
|
13
|
-
.kpi { background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; padding: 8px 16px; text-align: center; }
|
|
14
|
-
.kpi .v { font-size: 20px; font-weight: 900; }
|
|
15
|
-
.kpi .l { font-size: 10px; color: rgba(255,255,255,0.38); margin-top: 1px; text-transform: uppercase; letter-spacing: 0.3px; }
|
|
16
|
-
.c1 .v{color:#60d3f7} .c2 .v{color:#a78bfa} .c3 .v{color:#34d399} .c4 .v{color:#fbbf24} .c5 .v{color:#f87171}
|
|
17
|
-
.principle { background: #1e293b; color: rgba(255,255,255,0.7); font-size: 11px; padding: 10px 40px; border-top: 1px solid rgba(255,255,255,0.08); display: flex; gap: 24px; flex-wrap: wrap; }
|
|
18
|
-
.principle b { color: #60d3f7; }
|
|
19
|
-
#flow-outer { padding: 28px 24px 60px; overflow-x: auto; }
|
|
20
|
-
#flow { position: relative; width: 1280px; min-height: 1200px; margin: 0 auto; }
|
|
21
|
-
#svg-lines { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; }
|
|
22
|
-
.col-hdr { position: absolute; top: 0; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: white; padding: 4px 12px; border-radius: 4px; text-align: center; }
|
|
23
|
-
.agent { position: absolute; z-index: 4; background: white; border-radius: 10px; border-left: 5px solid transparent; box-shadow: 0 1px 6px rgba(0,0,0,0.08); padding: 10px 12px 28px 12px; cursor: default; }
|
|
24
|
-
.agent:hover { box-shadow: 0 4px 18px rgba(0,0,0,0.13); z-index: 10; }
|
|
25
|
-
.agent .aname { font-size: 12px; font-weight: 800; color: #111827; }
|
|
26
|
-
.agent .covers { font-size: 10px; color: #6b7280; margin-top: 3px; line-height: 1.5; }
|
|
27
|
-
.agent .covers span { display: inline-block; background: #f3f4f6; border-radius: 3px; padding: 1px 5px; margin: 1px 2px 1px 0; }
|
|
28
|
-
.agent .afoot { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
|
|
29
|
-
.agent .vol { font-size: 16px; font-weight: 900; }
|
|
30
|
-
.agent .vol-sub { font-size: 10px; color: #9ca3af; }
|
|
31
|
-
.complexity-badge { margin-left: auto; font-size: 10px; font-weight: 700; padding: 2px 8px; border-radius: 10px; white-space: nowrap; }
|
|
32
|
-
.cx-low { background: #d1fae5; color: #065f46; }
|
|
33
|
-
.cx-mid { background: #fef3c7; color: #92400e; }
|
|
34
|
-
.cx-high { background: #fee2e2; color: #991b1b; }
|
|
35
|
-
.cfg-btn { position:absolute; bottom:8px; right:10px; font-size:9px; font-weight:700; letter-spacing:0.3px; padding:2px 8px; border-radius:8px; border:1px solid #e5e7eb; background:#f9fafb; color:#6b7280; cursor:pointer; transition:all 0.15s; }
|
|
36
|
-
.cfg-btn:hover { background:#1a1a2e; color:white; border-color:#1a1a2e; }
|
|
37
|
-
.dr-hdr { padding:16px 20px 12px; position:sticky; top:0; background:white; z-index:2; border-bottom:1px solid #f3f4f6; display:flex; align-items:flex-start; gap:10px; }
|
|
38
|
-
.dr-hdr .dr-title { flex:1; }
|
|
39
|
-
.dr-hdr .dr-name { font-size:15px; font-weight:800; color:#111; }
|
|
40
|
-
.dr-hdr .dr-vol { font-size:11px; color:#9ca3af; margin-top:4px; }
|
|
41
|
-
.dr-close { font-size:18px; color:#9ca3af; cursor:pointer; line-height:1; padding:2px 4px; }
|
|
42
|
-
.dr-close:hover { color:#111; }
|
|
43
|
-
.dr-type-badge { font-size:9.5px; font-weight:800; padding:3px 9px; border-radius:12px; text-transform:uppercase; letter-spacing:0.4px; margin-top:4px; display:inline-block; }
|
|
44
|
-
.dr-single { background:#dbeafe; color:#1d4ed8; }
|
|
45
|
-
.dr-chatflow { background:#fce7f3; color:#9d174d; }
|
|
46
|
-
.dr-body { padding:14px 20px; }
|
|
47
|
-
.dr-node { margin-bottom:10px; padding:10px 12px; border-radius:8px; background:#f9fafb; border-left:3px solid #e5e7eb; }
|
|
48
|
-
.dr-node:last-child { margin-bottom:0; }
|
|
49
|
-
.dr-node-name { font-size:11px; font-weight:700; color:#111; margin-bottom:4px; }
|
|
50
|
-
.dr-node-row { font-size:10.5px; color:#4b5563; margin-bottom:3px; line-height:1.5; }
|
|
51
|
-
.dr-node-row b { color:#374151; }
|
|
52
|
-
.dr-node-row code { background:#e5e7eb; padding:1px 5px; border-radius:3px; font-size:10px; color:#374151; }
|
|
53
|
-
.dr-footer { margin:16px 20px 0; padding:10px 14px; background:#f8fafc; border-radius:8px; font-size:10.5px; color:#6b7280; line-height:1.6; }
|
|
54
|
-
.dr-footer b { color:#374151; }
|
|
55
|
-
.l2-node { position: absolute; z-index: 4; background: white; border-radius: 8px; border-left: 4px solid transparent; box-shadow: 0 1px 4px rgba(0,0,0,0.08); padding: 7px 10px 6px; }
|
|
56
|
-
.l2-node:hover { box-shadow: 0 3px 12px rgba(0,0,0,0.12); z-index: 10; }
|
|
57
|
-
.l2-node .nn { font-size: 10.5px; font-weight: 700; color: #1a1a2e; }
|
|
58
|
-
.l2-node .nc { font-size: 10px; color: #4b5563; margin-top: 1px; }
|
|
59
|
-
.l2-node .nct { font-size: 14px; font-weight: 900; margin-top: 4px; }
|
|
60
|
-
.l2-shared { border-top: 2px dashed #f59e0b; background: #fffbeb; }
|
|
61
|
-
.shared-tag { font-size: 9px; font-weight: 700; color: #d97706; background: #fef3c7; border-radius: 3px; padding: 0 4px; margin-left: 4px; }
|
|
62
|
-
.filter-block { position: absolute; z-index: 4; background: #f8fafc; border: 1.5px dashed #cbd5e1; border-radius: 10px; padding: 10px 12px; }
|
|
63
|
-
.filter-block .fn { font-size: 11.5px; font-weight: 700; color: #94a3b8; }
|
|
64
|
-
.filter-block .fc { font-size: 10px; color: #b0bec5; margin-top: 3px; }
|
|
65
|
-
.filter-block .fct { font-size: 16px; font-weight: 900; color: #cbd5e1; margin-top: 5px; }
|
|
66
|
-
|
|
67
|
-
.sm-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.55); z-index:200; align-items:center; justify-content:center; }
|
|
68
|
-
.sm-overlay.open { display:flex; }
|
|
69
|
-
.sm-box { background:white; border-radius:14px; width:600px; max-width:95vw; max-height:80vh; overflow:hidden; display:flex; flex-direction:column; box-shadow:0 16px 60px rgba(0,0,0,0.3); animation: smIn 0.2s cubic-bezier(0.34,1.56,0.64,1); }
|
|
70
|
-
@keyframes smIn { from { opacity:0; transform:scale(0.94) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
|
|
71
|
-
.sm-header { padding:18px 22px 14px; border-bottom:1px solid #f0f0f0; display:flex; align-items:flex-start; gap:10px; }
|
|
72
|
-
.sm-hdr-left { flex:1; min-width:0; }
|
|
73
|
-
.sm-title { font-size:15px; font-weight:800; color:#111; }
|
|
74
|
-
.sm-meta { font-size:11.5px; color:#6b7280; margin-top:5px; }
|
|
75
|
-
.sm-badge { display:inline-block; font-size:11px; font-weight:700; padding:1px 9px; border-radius:10px; margin-left:6px; }
|
|
76
|
-
.sm-close { font-size:22px; color:#9ca3af; cursor:pointer; line-height:1; padding:0 2px; flex-shrink:0; }
|
|
77
|
-
.sm-close:hover { color:#111; }
|
|
78
|
-
.sm-body { padding:16px 20px 20px; overflow-y:auto; flex:1; }
|
|
79
|
-
.sm-empty { text-align:center; color:#9ca3af; font-size:13px; padding:32px 0; }
|
|
80
|
-
.sm-msg { background:#f8fafc; border-radius:8px; padding:11px 14px; margin-bottom:9px; font-size:12.5px; color:#374151; line-height:1.7; border-left:3px solid #e5e7eb; }
|
|
81
|
-
.sm-msg:last-child { margin-bottom:0; }
|
|
82
|
-
.sm-msg-num { font-size:9.5px; color:#9ca3af; font-weight:700; margin-bottom:4px; text-transform:uppercase; letter-spacing:0.3px; }
|
|
83
|
-
|
|
84
|
-
.apply-btn { background: linear-gradient(135deg, #6366f1, #8b5cf6); color: white; border: none; border-radius: 8px; padding: 8px 18px; font-size: 13px; font-weight: 700; cursor: pointer; letter-spacing: 0.3px; transition: opacity 0.2s; }
|
|
85
|
-
.apply-btn:hover { opacity: 0.88; }
|
|
86
|
-
.modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:200; align-items:center; justify-content:center; }
|
|
87
|
-
.modal-overlay.open { display:flex; }
|
|
88
|
-
.modal-box { background:white; border-radius:12px; width:620px; max-width:95vw; max-height:85vh; overflow:hidden; display:flex; flex-direction:column; box-shadow:0 8px 40px rgba(0,0,0,0.25); }
|
|
89
|
-
.modal-header { padding:18px 22px 14px; border-bottom:1px solid #f0f0f0; display:flex; align-items:center; justify-content:space-between; }
|
|
90
|
-
.modal-header h3 { font-size:16px; font-weight:800; color:#111; }
|
|
91
|
-
.modal-close { font-size:20px; color:#9ca3af; cursor:pointer; line-height:1; }
|
|
92
|
-
.modal-close:hover { color:#111; }
|
|
93
|
-
.modal-body { padding:18px 22px; overflow-y:auto; flex:1; }
|
|
94
|
-
.modal-footer { padding:14px 22px; border-top:1px solid #f0f0f0; display:flex; gap:10px; justify-content:flex-end; }
|
|
95
|
-
.btn-cancel { background:#f3f4f6; color:#374151; border:none; border-radius:8px; padding:8px 18px; font-size:13px; font-weight:600; cursor:pointer; }
|
|
96
|
-
.btn-confirm { background:#6366f1; color:white; border:none; border-radius:8px; padding:8px 20px; font-size:13px; font-weight:700; cursor:pointer; }
|
|
97
|
-
.btn-confirm:hover { background:#4f46e5; }
|
|
98
|
-
.btn-confirm.danger { background:#ef4444; }
|
|
99
|
-
.btn-confirm.danger:hover { background:#dc2626; }
|
|
100
|
-
.intent-preview-table { width:100%; border-collapse:collapse; font-size:12px; }
|
|
101
|
-
.intent-preview-table th { background:#f8fafc; padding:7px 10px; text-align:left; font-weight:700; color:#374151; border-bottom:2px solid #e5e7eb; }
|
|
102
|
-
.intent-preview-table td { padding:7px 10px; border-bottom:1px solid #f3f4f6; color:#4b5563; vertical-align:top; }
|
|
103
|
-
.intent-preview-table tr:last-child td { border-bottom:none; }
|
|
104
|
-
.intent-name-cell { font-weight:700; color:#111; width:160px; }
|
|
105
|
-
.apply-status { margin-top:12px; padding:12px 14px; border-radius:8px; background:#f0fdf4; border:1px solid #86efac; font-size:12px; line-height:1.8; }
|
|
106
|
-
.apply-status.error { background:#fef2f2; border-color:#fca5a5; }
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
EDGE_JS = """
|
|
111
|
-
function getRect(id) {
|
|
112
|
-
const flow = document.getElementById('flow');
|
|
113
|
-
if (!flow) return null;
|
|
114
|
-
const fr = flow.getBoundingClientRect();
|
|
115
|
-
const el = document.getElementById(id);
|
|
116
|
-
if (!el) return null;
|
|
117
|
-
const er = el.getBoundingClientRect();
|
|
118
|
-
return { left:er.left-fr.left, top:er.top-fr.top, right:er.right-fr.left,
|
|
119
|
-
bottom:er.bottom-fr.top, w:er.width, h:er.height };
|
|
120
|
-
}
|
|
121
|
-
function drawEdges() {
|
|
122
|
-
const svg = document.getElementById('svg-lines');
|
|
123
|
-
const flow = document.getElementById('flow');
|
|
124
|
-
if (!svg || !flow || typeof EDGES === 'undefined') return;
|
|
125
|
-
svg.setAttribute('viewBox', `0 0 ${flow.offsetWidth} ${flow.offsetHeight}`);
|
|
126
|
-
const srcTot = {}, dstTot = {};
|
|
127
|
-
EDGES.forEach(e => {
|
|
128
|
-
srcTot[e.from] = (srcTot[e.from]||0) + e.count;
|
|
129
|
-
dstTot[e.to] = (dstTot[e.to] ||0) + e.count;
|
|
130
|
-
});
|
|
131
|
-
const srcCum = {}, dstCum = {};
|
|
132
|
-
const maxCount = Math.max(...EDGES.map(e=>e.count), 1);
|
|
133
|
-
EDGES.forEach(e => {
|
|
134
|
-
const sr = getRect(e.from);
|
|
135
|
-
const dr = getRect(e.to);
|
|
136
|
-
if (!sr || !dr) return;
|
|
137
|
-
const sFrac = ((srcCum[e.from]||0) + e.count/2) / srcTot[e.from];
|
|
138
|
-
const dFrac = ((dstCum[e.to] ||0) + e.count/2) / dstTot[e.to];
|
|
139
|
-
srcCum[e.from] = (srcCum[e.from]||0) + e.count;
|
|
140
|
-
dstCum[e.to] = (dstCum[e.to] ||0) + e.count;
|
|
141
|
-
const sy = sr.top + sr.h*(0.15 + sFrac*0.7);
|
|
142
|
-
const dy = dr.top + dr.h*(0.15 + dFrac*0.7);
|
|
143
|
-
const x1=sr.right, x2=dr.left, cx=(x1+x2)/2;
|
|
144
|
-
const w = Math.max(1, Math.round(e.count/maxCount*18));
|
|
145
|
-
const opacity = 0.35 + (e.count/maxCount)*0.55;
|
|
146
|
-
const path = document.createElementNS('http://www.w3.org/2000/svg','path');
|
|
147
|
-
path.setAttribute('d',`M${x1},${sy} C${cx},${sy} ${cx},${dy} ${x2},${dy}`);
|
|
148
|
-
path.setAttribute('fill','none');
|
|
149
|
-
path.setAttribute('stroke',e.color);
|
|
150
|
-
path.setAttribute('stroke-width',w);
|
|
151
|
-
path.setAttribute('stroke-opacity',opacity);
|
|
152
|
-
svg.appendChild(path);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
window.addEventListener('load', drawEdges);
|
|
156
|
-
window.addEventListener('resize', () => {
|
|
157
|
-
const svg = document.getElementById('svg-lines');
|
|
158
|
-
if (svg) while(svg.firstChild) svg.removeChild(svg.firstChild);
|
|
159
|
-
drawEdges();
|
|
160
|
-
});
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def complexity_badge(n: int) -> str:
|
|
165
|
-
if n == 0:
|
|
166
|
-
return '<div class="complexity-badge cx-low">节点 ×0 ✓✓</div>'
|
|
167
|
-
elif n <= 2:
|
|
168
|
-
return f'<div class="complexity-badge cx-low">节点 ×{n} ✓</div>'
|
|
169
|
-
elif n <= 3:
|
|
170
|
-
return f'<div class="complexity-badge cx-mid">节点 ×{n} ⚠️</div>'
|
|
171
|
-
else:
|
|
172
|
-
return f'<div class="complexity-badge cx-high">节点 ×{n} ❌</div>'
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def render_html(config: dict, data: dict, brand: str = "", project_id: str = "") -> str:
|
|
176
|
-
agents = config.get("agents", [])
|
|
177
|
-
filter_block = config.get("filter_block", {})
|
|
178
|
-
node_summary = config.get("node_summary", {})
|
|
179
|
-
edges = config.get("edges", [])
|
|
180
|
-
shared_node_ids = {s["node_id"] for s in config.get("shared_nodes", [])}
|
|
181
|
-
fuzzy_samples = data.get("fuzzy_samples", {})
|
|
182
|
-
|
|
183
|
-
tree = data.get("tree", {})
|
|
184
|
-
turn_dist = data.get("turn_distribution", {})
|
|
185
|
-
meta = data.get("_meta", {})
|
|
186
|
-
total = meta.get("total", 0)
|
|
187
|
-
brand_name = brand or meta.get("brand", "")
|
|
188
|
-
|
|
189
|
-
t1 = turn_dist.get(1, turn_dist.get("1", 0))
|
|
190
|
-
t2 = turn_dist.get(2, turn_dist.get("2", 0))
|
|
191
|
-
t3 = turn_dist.get(3, turn_dist.get("3", 0))
|
|
192
|
-
t4 = turn_dist.get(4, turn_dist.get("4", 0))
|
|
193
|
-
l1_total = tree.get("layer_1", {}).get("n_conversations", total)
|
|
194
|
-
l2_total = tree.get("layer_2", {}).get("n_conversations", 0)
|
|
195
|
-
l3_total = tree.get("layer_3", {}).get("n_conversations", 0)
|
|
196
|
-
|
|
197
|
-
n_agents = len(agents)
|
|
198
|
-
total_nodes = node_summary.get("total_nodes", 0)
|
|
199
|
-
max_nodes = max((len(a.get("l2_nodes", [])) for a in agents), default=0)
|
|
200
|
-
covered = sum(a.get("total_count", 0) for a in agents)
|
|
201
|
-
|
|
202
|
-
# ── KPIs ──
|
|
203
|
-
kpis = f"""
|
|
204
|
-
<div class="kpis">
|
|
205
|
-
<div class="kpi c1"><div class="v">{n_agents}</div><div class="l">Agent 总数(含过滤)</div></div>
|
|
206
|
-
<div class="kpi c3"><div class="v">{total_nodes}</div><div class="l">总 Agent 节点数</div></div>
|
|
207
|
-
<div class="kpi c2"><div class="v">≤{max_nodes}</div><div class="l">最大单 Agent 节点</div></div>
|
|
208
|
-
<div class="kpi c4"><div class="v">{covered:,}</div><div class="l">Agent 覆盖对话</div></div>
|
|
209
|
-
<div class="kpi c5"><div class="v">{total:,}</div><div class="l">数据总量</div></div>
|
|
210
|
-
</div>"""
|
|
211
|
-
|
|
212
|
-
# ── Flow canvas ──
|
|
213
|
-
# Layout constants
|
|
214
|
-
AG_X, AG_W = 0, 373
|
|
215
|
-
L2_X, L2_W = 490, 295
|
|
216
|
-
SUM_X, SUM_W = 905, 295
|
|
217
|
-
AG_GAP = 185 # vertical spacing between agent blocks
|
|
218
|
-
L2_NODE_H = 70 # height per L2 node slot
|
|
219
|
-
TOP_START = 50
|
|
220
|
-
COLORS = ["#6366f1","#8b5cf6","#ec4899","#f59e0b","#10b981","#3b82f6","#ef4444","#14b8a6","#f97316","#84cc16"]
|
|
221
|
-
|
|
222
|
-
# Build agent blocks HTML + track L2 node positions
|
|
223
|
-
agent_blocks = ""
|
|
224
|
-
l2_nodes_html = ""
|
|
225
|
-
l2_pos = {} # l2_node_id → (top_px)
|
|
226
|
-
|
|
227
|
-
# We'll track all L2 node ids per agent to place them
|
|
228
|
-
# L2 nodes are placed sequentially under their primary agent's vertical region
|
|
229
|
-
l2_y_cursor = TOP_START
|
|
230
|
-
|
|
231
|
-
for i, agent in enumerate(agents):
|
|
232
|
-
ag_id = f"ag-{agent['id'].replace('_','-')}"
|
|
233
|
-
color = agent.get("color", COLORS[i % len(COLORS)])
|
|
234
|
-
ag_top = TOP_START + i * AG_GAP
|
|
235
|
-
name = agent.get("name", f"Agent {i+1}")
|
|
236
|
-
count = agent.get("total_count", 0)
|
|
237
|
-
pct = agent.get("total_pct", 0.0)
|
|
238
|
-
l1_intents = agent.get("l1_intents", [])
|
|
239
|
-
l2_nodes = agent.get("l2_nodes", [])
|
|
240
|
-
n_nodes = len(l2_nodes)
|
|
241
|
-
|
|
242
|
-
covers_html = "".join(f'<span>{intent}</span>' for intent in l1_intents)
|
|
243
|
-
is_new_agent = agent.get("is_new", False)
|
|
244
|
-
new_badge = '<span style="display:inline-block;background:#fef3c7;color:#d97706;font-size:9px;font-weight:700;padding:1px 6px;border-radius:4px;margin-left:6px;vertical-align:middle;">🆕 新发现意图</span>' if is_new_agent else ''
|
|
245
|
-
|
|
246
|
-
agent_blocks += f"""
|
|
247
|
-
<!-- {name} -->
|
|
248
|
-
<div class="agent" id="{ag_id}" style="left:{AG_X}px;top:{ag_top}px;width:{AG_W}px;border-left-color:{color};">
|
|
249
|
-
<div class="aname">{name}{new_badge}</div>
|
|
250
|
-
<div class="covers">覆盖:{covers_html}</div>
|
|
251
|
-
<div class="afoot">
|
|
252
|
-
<span class="vol" style="color:{color}">{count:,}</span>
|
|
253
|
-
<span class="vol-sub">条 · {pct:.1f}%</span>
|
|
254
|
-
{complexity_badge(n_nodes)}
|
|
255
|
-
</div>
|
|
256
|
-
<button class="cfg-btn" onclick="openDrawer('{agent['id']}')">⚙ 配置</button>
|
|
257
|
-
</div>"""
|
|
258
|
-
|
|
259
|
-
# Place L2 nodes for this agent
|
|
260
|
-
# Space them starting from the agent's top position
|
|
261
|
-
node_start_y = max(l2_y_cursor, ag_top)
|
|
262
|
-
for j, node in enumerate(l2_nodes):
|
|
263
|
-
n_id = node["id"]
|
|
264
|
-
n_top = node_start_y + j * L2_NODE_H
|
|
265
|
-
l2_pos[n_id] = n_top
|
|
266
|
-
l2_y_cursor = n_top + L2_NODE_H + 4
|
|
267
|
-
|
|
268
|
-
is_shared = node.get("is_shared", False) or n_id in shared_node_ids
|
|
269
|
-
shared_class = " l2-shared" if is_shared else ""
|
|
270
|
-
shared_tag = '<span class="shared-tag">共享节点</span>' if is_shared else ""
|
|
271
|
-
n_color = color if not is_shared else "#f59e0b"
|
|
272
|
-
|
|
273
|
-
l2_nodes_html += f"""
|
|
274
|
-
<div class="l2-node{shared_class}" id="{n_id}" style="left:{L2_X}px;top:{n_top}px;width:{L2_W}px;border-left-color:{n_color};cursor:pointer;" onclick="openSamplesModal('{n_id}')" title="点击查看样本消息">
|
|
275
|
-
<div class="nn">{node['name']}{shared_tag}</div>
|
|
276
|
-
<div class="nc">{node.get('desc','')}</div>
|
|
277
|
-
<div class="nct" style="color:{n_color};">{node.get('count',0):,} <span style="font-size:11px;font-weight:400;color:#9ca3af;">条</span></div>
|
|
278
|
-
</div>"""
|
|
279
|
-
|
|
280
|
-
# Filter block
|
|
281
|
-
filter_html = ""
|
|
282
|
-
if filter_block and filter_block.get("count", 0) > 0:
|
|
283
|
-
filter_top = TOP_START + len(agents) * AG_GAP
|
|
284
|
-
types_str = " · ".join(filter_block.get("types", []))
|
|
285
|
-
f_count = filter_block.get("count", 0)
|
|
286
|
-
filter_html = f"""
|
|
287
|
-
<div class="filter-block" id="ag-filter" style="left:{AG_X}px;top:{filter_top}px;width:{AG_W}px;">
|
|
288
|
-
<div class="fn">🚫 过滤层(无需 Agent)</div>
|
|
289
|
-
<div class="fc">{types_str}</div>
|
|
290
|
-
<div class="fct">{f_count:,} <span style="font-size:11px;font-weight:400;color:#94a3b8">条 · 直接过滤,不路由</span></div>
|
|
291
|
-
</div>"""
|
|
292
|
-
|
|
293
|
-
# Node summary panel
|
|
294
|
-
sum_rows = ""
|
|
295
|
-
for row in node_summary.get("rows", []):
|
|
296
|
-
sum_rows += f"""
|
|
297
|
-
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px;font-size:11px;">
|
|
298
|
-
<span style="color:{row.get('color','#6b7280')}">{row.get('agent_name','')}</span>
|
|
299
|
-
<span style="font-weight:800;color:{row.get('color','#6b7280')}">{row.get('badge','')}</span>
|
|
300
|
-
</div>"""
|
|
301
|
-
|
|
302
|
-
opt_notes = node_summary.get("optimization_notes", "")
|
|
303
|
-
opt_html = ""
|
|
304
|
-
for line in opt_notes.split("\\n"):
|
|
305
|
-
line = line.strip()
|
|
306
|
-
if not line:
|
|
307
|
-
opt_html += "<br>"
|
|
308
|
-
continue
|
|
309
|
-
if line.startswith("✅"):
|
|
310
|
-
opt_html += f'<span style="color:#22c55e;font-weight:700;">{line}</span><br>'
|
|
311
|
-
elif line.startswith("⚡"):
|
|
312
|
-
opt_html += f'<span style="color:#f59e0b;font-weight:700;">{line}</span><br>'
|
|
313
|
-
elif line.startswith("📊"):
|
|
314
|
-
opt_html += f'<span style="color:#3b82f6;font-weight:700;">{line}</span><br>'
|
|
315
|
-
else:
|
|
316
|
-
opt_html += f'{line}<br>'
|
|
317
|
-
|
|
318
|
-
canvas_height = max(1200, l2_y_cursor + 200, TOP_START + (len(agents)+1)*AG_GAP + 200)
|
|
319
|
-
sum_top = min(TOP_START + 2*AG_GAP, canvas_height - 400)
|
|
320
|
-
|
|
321
|
-
summary_panel = f"""
|
|
322
|
-
<div style="position:absolute;top:{sum_top}px;left:{SUM_X}px;width:{SUM_W}px;z-index:5;
|
|
323
|
-
background:white;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,0.10);padding:14px 18px;">
|
|
324
|
-
<div style="font-size:11px;font-weight:700;color:#111;margin-bottom:10px;text-transform:uppercase;letter-spacing:0.5px;">节点统计</div>
|
|
325
|
-
{sum_rows}
|
|
326
|
-
<hr style="border:none;border-top:1px solid #f3f4f6;margin:8px 0;">
|
|
327
|
-
<div style="display:flex;justify-content:space-between;font-size:12px;font-weight:800;color:#111;">
|
|
328
|
-
<span>合计</span><span>{total_nodes} 节点</span>
|
|
329
|
-
</div>
|
|
330
|
-
<div style="margin-top:10px;font-size:10px;color:#6b7280;line-height:1.7;">
|
|
331
|
-
{opt_html}
|
|
332
|
-
</div>
|
|
333
|
-
</div>"""
|
|
334
|
-
|
|
335
|
-
# Build AGENT_CONFIGS JS for drawer
|
|
336
|
-
agent_configs_js = _build_agent_configs_js(agents, fuzzy_samples)
|
|
337
|
-
|
|
338
|
-
# Build L2 samples data for click-to-samples modal
|
|
339
|
-
l2_samples_data = {}
|
|
340
|
-
for _ag in agents:
|
|
341
|
-
for _nd in _ag.get("l2_nodes", []):
|
|
342
|
-
_samps = _nd.get("samples", [])
|
|
343
|
-
if _samps or _nd.get("count", 0) > 0:
|
|
344
|
-
l2_samples_data[_nd["id"]] = {
|
|
345
|
-
"name": _nd["name"],
|
|
346
|
-
"definition": _nd.get("definition", ""),
|
|
347
|
-
"count": _nd.get("count", 0),
|
|
348
|
-
"samples": _samps,
|
|
349
|
-
}
|
|
350
|
-
l2_samples_js = json.dumps(l2_samples_data, ensure_ascii=False)
|
|
351
|
-
|
|
352
|
-
# Edges JS
|
|
353
|
-
edges_js = json.dumps(edges, ensure_ascii=False)
|
|
354
|
-
|
|
355
|
-
html = f"""<!DOCTYPE html>
|
|
356
|
-
<html lang="zh-CN">
|
|
357
|
-
<head>
|
|
358
|
-
<meta charset="UTF-8">
|
|
359
|
-
<title>{brand_name or "意图分析"} — Agent 意图设计图</title>
|
|
360
|
-
<style>{CSS}</style>
|
|
361
|
-
</head>
|
|
362
|
-
<body>
|
|
363
|
-
|
|
364
|
-
<div class="page-header" style="display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:12px;">
|
|
365
|
-
<div style="flex:1;min-width:0;">
|
|
366
|
-
<h1>{brand_name or "意图分析"} — 意图设计图</h1>
|
|
367
|
-
<div class="sub">{brand_name + ' · ' if brand_name else ''}基于 {total:,} 条对话 · 一个意图 = 一个 Agent · 线宽 = 对话量 · L1→L2→L3 层层流转</div>
|
|
368
|
-
{kpis}
|
|
369
|
-
</div>
|
|
370
|
-
<div style="padding-top:4px;">
|
|
371
|
-
<button class="apply-btn" onclick="openApplyModal()">🚀 批量应用意图</button>
|
|
372
|
-
</div>
|
|
373
|
-
</div>
|
|
374
|
-
|
|
375
|
-
<div class="principle">
|
|
376
|
-
<span>· <b>设计原则1</b>:一个意图 = 一个 Agent,首轮入口唯一</span>
|
|
377
|
-
<span>· <b>设计原则2</b>:Agent 内部节点越少越好(维护成本低,稳定性高)</span>
|
|
378
|
-
<span>· <b>设计原则3</b>:共享节点(黄色)说明两个 Agent 可合并——但需权衡决策树差异</span>
|
|
379
|
-
<span>· <b>设计原则4</b>:总节点数固定,意图分合决定每个 Agent 的复杂度分布</span>
|
|
380
|
-
</div>
|
|
381
|
-
|
|
382
|
-
<div id="flow-outer">
|
|
383
|
-
<div id="flow" style="min-height:{canvas_height}px;">
|
|
384
|
-
<svg id="svg-lines"></svg>
|
|
385
|
-
|
|
386
|
-
<div class="col-hdr" style="left:0;width:{AG_W}px;top:4px;background:#2563eb;">路由层 · 首轮判断意图 → 进入 Agent</div>
|
|
387
|
-
<div class="col-hdr" style="left:{L2_X}px;width:{L2_W}px;top:4px;background:#7c3aed;">Agent 内部节点 · 多轮处理</div>
|
|
388
|
-
<div class="col-hdr" style="left:{SUM_X}px;width:{SUM_W}px;top:4px;background:#0f766e;">总结 · 节点统计与优化</div>
|
|
389
|
-
|
|
390
|
-
{agent_blocks}
|
|
391
|
-
{filter_html}
|
|
392
|
-
{l2_nodes_html}
|
|
393
|
-
{summary_panel}
|
|
394
|
-
|
|
395
|
-
</div>
|
|
396
|
-
</div>
|
|
397
|
-
|
|
398
|
-
<!-- SAMPLES MODAL -->
|
|
399
|
-
<div class="sm-overlay" id="sm-overlay" onclick="closeSamplesModal()">
|
|
400
|
-
<div class="sm-box" onclick="event.stopPropagation()">
|
|
401
|
-
<div class="sm-header">
|
|
402
|
-
<div class="sm-hdr-left">
|
|
403
|
-
<div class="sm-title" id="sm-title"></div>
|
|
404
|
-
<div class="sm-meta" id="sm-meta"></div>
|
|
405
|
-
</div>
|
|
406
|
-
<span class="sm-close" onclick="closeSamplesModal()">×</span>
|
|
407
|
-
</div>
|
|
408
|
-
<div class="sm-body" id="sm-body"></div>
|
|
409
|
-
</div>
|
|
410
|
-
</div>
|
|
411
|
-
|
|
412
|
-
<!-- DRAWER OVERLAY -->
|
|
413
|
-
<div id="cfg-overlay" onclick="closeDrawer()" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.35);z-index:100;"></div>
|
|
414
|
-
<!-- DRAWER PANEL -->
|
|
415
|
-
<div id="cfg-drawer" style="display:none;position:fixed;top:0;right:0;width:440px;height:100vh;background:white;z-index:101;overflow-y:auto;box-shadow:-4px 0 24px rgba(0,0,0,0.18);transform:translateX(100%);transition:transform 0.25s ease;">
|
|
416
|
-
<div id="cfg-drawer-inner" style="padding:0 0 40px;"></div>
|
|
417
|
-
</div>
|
|
418
|
-
|
|
419
|
-
<script>
|
|
420
|
-
const TENANT = '{project_id}';
|
|
421
|
-
const REPORT_TOKEN = '__RT__';
|
|
422
|
-
const EDGES = {edges_js};
|
|
423
|
-
const L2_SAMPLES = {l2_samples_js};
|
|
424
|
-
|
|
425
|
-
function openSamplesModal(nodeId) {{
|
|
426
|
-
document.body.style.overflow = 'hidden';
|
|
427
|
-
var data = L2_SAMPLES[nodeId];
|
|
428
|
-
if (!data) return;
|
|
429
|
-
var el = document.getElementById(nodeId);
|
|
430
|
-
var color = el ? el.style.borderLeftColor : '#6b7280';
|
|
431
|
-
document.getElementById('sm-title').innerHTML =
|
|
432
|
-
data.name + ' <span class="sm-badge" style="background:' + color + '22;color:' + color + ';">' + data.count + ' 条</span>';
|
|
433
|
-
document.getElementById('sm-meta').textContent = data.definition || '以下为该子意图的样本消息';
|
|
434
|
-
var samples = data.samples || [];
|
|
435
|
-
var body = document.getElementById('sm-body');
|
|
436
|
-
if (samples.length === 0) {{
|
|
437
|
-
body.innerHTML = '<div class="sm-empty">暂无样本消息</div>';
|
|
438
|
-
}} else {{
|
|
439
|
-
body.innerHTML = samples.map(function(s, i) {{
|
|
440
|
-
return '<div class="sm-msg"><div class="sm-msg-num">样本 ' + (i+1) + '</div>' + s + '</div>';
|
|
441
|
-
}}).join('');
|
|
442
|
-
}}
|
|
443
|
-
document.getElementById('sm-overlay').classList.add('open');
|
|
444
|
-
}}
|
|
445
|
-
function closeSamplesModal() {{
|
|
446
|
-
document.body.style.overflow = '';
|
|
447
|
-
document.getElementById('sm-overlay').classList.remove('open');
|
|
448
|
-
}}
|
|
449
|
-
document.addEventListener('keydown', function(e) {{ if (e.key === 'Escape') closeSamplesModal(); }});
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
{EDGE_JS}
|
|
454
|
-
|
|
455
|
-
const AGENT_CONFIGS = {agent_configs_js};
|
|
456
|
-
|
|
457
|
-
function openDrawer(key) {{
|
|
458
|
-
const cfg = AGENT_CONFIGS[key];
|
|
459
|
-
if (!cfg) return;
|
|
460
|
-
const c = cfg.color;
|
|
461
|
-
const agentTypeBadge = cfg.agent_type === 'single'
|
|
462
|
-
? '<span class="dr-type-badge dr-single">Single Agent</span>'
|
|
463
|
-
: '<span class="dr-type-badge dr-chatflow">Chatflow Agent</span>';
|
|
464
|
-
|
|
465
|
-
const examplesHtml = (cfg.examples||[]).map(e =>
|
|
466
|
-
`<div style="padding:3px 0;color:#4b5563;font-size:10.5px;">· ${{e}}</div>`
|
|
467
|
-
).join('');
|
|
468
|
-
|
|
469
|
-
const coversHtml = (cfg.covers||[]).map(cv =>
|
|
470
|
-
`<span style="display:inline-block;background:#f3f4f6;border-radius:3px;padding:1px 6px;margin:2px 3px 2px 0;font-size:10px;color:#374151;">${{cv}}</span>`
|
|
471
|
-
).join('');
|
|
472
|
-
|
|
473
|
-
const excludesHtml = (cfg.excludes||[]).map(e =>
|
|
474
|
-
`<div style="font-size:10.5px;color:#7f1d1d;padding:2px 0;line-height:1.6;">✗ ${{e}}</div>`
|
|
475
|
-
).join('');
|
|
476
|
-
|
|
477
|
-
const splitHtml = cfg.split_note
|
|
478
|
-
? `<div style="margin-top:8px;padding:8px 10px;border-radius:6px;background:#fff7ed;border-left:3px solid #f97316;font-size:10.5px;color:#4b5563;line-height:1.6;">${{cfg.split_note}}</div>`
|
|
479
|
-
: '';
|
|
480
|
-
|
|
481
|
-
// Fuzzy match samples section
|
|
482
|
-
let fuzzyHtml = '';
|
|
483
|
-
const fuzzyData = cfg.fuzzy_samples || {{}};
|
|
484
|
-
const fuzzyIntents = Object.keys(fuzzyData);
|
|
485
|
-
if (fuzzyIntents.length > 0) {{
|
|
486
|
-
let fuzzyItems = '';
|
|
487
|
-
fuzzyIntents.forEach(intentName => {{
|
|
488
|
-
const fd = fuzzyData[intentName];
|
|
489
|
-
const total = fd.total_fuzzy || 0;
|
|
490
|
-
const samples = fd.samples || [];
|
|
491
|
-
fuzzyItems += `<div style="font-size:10px;font-weight:700;color:#92400e;margin-bottom:4px;">⚠ ${{total}} 条对话因相似性归入 "${{intentName}}"(非精确匹配)</div>`;
|
|
492
|
-
samples.forEach(s => {{
|
|
493
|
-
const msg = s.first_msg && s.first_msg.length > 50 ? s.first_msg.slice(0, 50) + '...' : (s.first_msg || '');
|
|
494
|
-
fuzzyItems += `<div style="font-size:10px;color:#78350f;padding:2px 0;line-height:1.5;">· [${{s.chat_id}}] "${{msg}}"</div>`;
|
|
495
|
-
}});
|
|
496
|
-
}});
|
|
497
|
-
fuzzyHtml = `
|
|
498
|
-
<div style="margin:12px 0 6px;">
|
|
499
|
-
<div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.4px;margin-bottom:5px;">模糊匹配样本</div>
|
|
500
|
-
<div style="background:#fffbeb;border-left:3px solid #f59e0b;border-radius:0 6px 6px 0;padding:9px 12px;">
|
|
501
|
-
${{fuzzyItems}}
|
|
502
|
-
</div>
|
|
503
|
-
</div>`;
|
|
504
|
-
}}
|
|
505
|
-
|
|
506
|
-
const nodesHtml = (cfg.nodes||[]).map(n => `
|
|
507
|
-
<div class="dr-node" style="border-left-color:${{c}};">
|
|
508
|
-
<div class="dr-node-name">${{n.name}}</div>
|
|
509
|
-
<div class="dr-node-row"><b>触发条件:</b>${{n.trigger}}</div>
|
|
510
|
-
<div class="dr-node-row"><b>动作:</b>${{n.action}}</div>
|
|
511
|
-
</div>`).join('');
|
|
512
|
-
|
|
513
|
-
document.getElementById('cfg-drawer-inner').innerHTML = `
|
|
514
|
-
<div class="dr-hdr" style="border-top:4px solid ${{c}};">
|
|
515
|
-
<div class="dr-title">
|
|
516
|
-
<div class="dr-name">${{cfg.name}}</div>
|
|
517
|
-
<div class="dr-vol" style="margin-top:3px;">${{cfg.vol}}</div>
|
|
518
|
-
</div>
|
|
519
|
-
<span class="dr-close" onclick="closeDrawer()">✕</span>
|
|
520
|
-
</div>
|
|
521
|
-
<div class="dr-body">
|
|
522
|
-
<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;">
|
|
523
|
-
<div style="width:3px;height:16px;background:${{c}};border-radius:2px;flex-shrink:0;"></div>
|
|
524
|
-
<div style="font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:0.5px;color:#111;">① 意图配置</div>
|
|
525
|
-
<div style="font-size:9px;color:#9ca3af;">Solvea 路由层 → 先建意图</div>
|
|
526
|
-
</div>
|
|
527
|
-
<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-bottom:10px;">
|
|
528
|
-
<div style="font-family:monospace;font-size:12px;font-weight:700;color:#111;background:#f3f4f6;border:1px solid #e5e7eb;display:inline-block;padding:3px 10px;border-radius:4px;">${{cfg.intent_id}}</div>
|
|
529
|
-
<div style="font-size:11.5px;font-weight:700;color:#374151;">${{cfg.display_name}}</div>
|
|
530
|
-
</div>
|
|
531
|
-
<div style="margin-bottom:4px;">${{coversHtml}}</div>
|
|
532
|
-
<div style="margin:10px 0 8px;">
|
|
533
|
-
<div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.4px;margin-bottom:5px;">意图定义</div>
|
|
534
|
-
<div style="background:#eff6ff;border-left:3px solid ${{c}};border-radius:0 6px 6px 0;padding:9px 12px;font-size:11px;color:#1e3a5f;line-height:1.7;">${{cfg.definition}}</div>
|
|
535
|
-
</div>
|
|
536
|
-
<div style="margin-bottom:10px;">
|
|
537
|
-
<div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.4px;margin-bottom:5px;">排除条件(边界)</div>
|
|
538
|
-
<div style="background:#fef2f2;border-radius:6px;padding:8px 11px;">${{excludesHtml}}</div>
|
|
539
|
-
</div>
|
|
540
|
-
<div style="margin-bottom:6px;">
|
|
541
|
-
<div style="font-size:10px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:0.4px;margin-bottom:5px;">触发示例语句</div>
|
|
542
|
-
<div style="background:#f9fafb;border-radius:6px;padding:8px 11px;">${{examplesHtml}}</div>
|
|
543
|
-
</div>
|
|
544
|
-
${{splitHtml}}
|
|
545
|
-
${{fuzzyHtml}}
|
|
546
|
-
<div style="display:flex;align-items:center;gap:8px;margin:18px 0 10px;">
|
|
547
|
-
<div style="width:3px;height:16px;background:${{c}};border-radius:2px;flex-shrink:0;"></div>
|
|
548
|
-
<div style="font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:0.5px;color:#111;">② Agent 配置</div>
|
|
549
|
-
<div>${{agentTypeBadge}}</div>
|
|
550
|
-
</div>
|
|
551
|
-
${{nodesHtml}}
|
|
552
|
-
</div>
|
|
553
|
-
<div class="dr-footer">${{cfg.footer}}</div>`;
|
|
554
|
-
|
|
555
|
-
const drawer = document.getElementById('cfg-drawer');
|
|
556
|
-
const overlay = document.getElementById('cfg-overlay');
|
|
557
|
-
drawer.style.display = 'block';
|
|
558
|
-
overlay.style.display = 'block';
|
|
559
|
-
requestAnimationFrame(() => {{ drawer.style.transform = 'translateX(0)'; }});
|
|
560
|
-
}}
|
|
561
|
-
|
|
562
|
-
function closeDrawer() {{
|
|
563
|
-
const drawer = document.getElementById('cfg-drawer');
|
|
564
|
-
drawer.style.transform = 'translateX(100%)';
|
|
565
|
-
setTimeout(() => {{
|
|
566
|
-
drawer.style.display = 'none';
|
|
567
|
-
document.getElementById('cfg-overlay').style.display = 'none';
|
|
568
|
-
}}, 260);
|
|
569
|
-
}}
|
|
570
|
-
|
|
571
|
-
function openApplyModal() {{
|
|
572
|
-
const intents = Object.values(AGENT_CONFIGS).map(cfg => ({{
|
|
573
|
-
name: cfg.display_name || cfg.name,
|
|
574
|
-
description: cfg.definition || '',
|
|
575
|
-
}}));
|
|
576
|
-
const tbody = intents.map(it =>
|
|
577
|
-
`<tr><td class="intent-name-cell">${{it.name}}</td><td>${{it.description.slice(0,120)}}${{it.description.length>120?'…':''}}</td></tr>`
|
|
578
|
-
).join('');
|
|
579
|
-
document.getElementById('apply-preview-body').innerHTML = tbody;
|
|
580
|
-
document.getElementById('apply-count').textContent = intents.length;
|
|
581
|
-
document.getElementById('apply-step').value = '1';
|
|
582
|
-
document.getElementById('apply-modal-title').textContent = '批量应用意图 · 预览';
|
|
583
|
-
document.getElementById('apply-confirm-btn').textContent = '确认应用';
|
|
584
|
-
document.getElementById('apply-confirm-btn').className = 'btn-confirm';
|
|
585
|
-
document.getElementById('apply-warning').style.display = 'none';
|
|
586
|
-
document.getElementById('apply-status').style.display = 'none';
|
|
587
|
-
document.getElementById('apply-modal').classList.add('open');
|
|
588
|
-
}}
|
|
589
|
-
|
|
590
|
-
function closeApplyModal() {{
|
|
591
|
-
document.getElementById('apply-modal').classList.remove('open');
|
|
592
|
-
}}
|
|
593
|
-
|
|
594
|
-
async function confirmApply() {{
|
|
595
|
-
const step = document.getElementById('apply-step').value;
|
|
596
|
-
if (step === '1') {{
|
|
597
|
-
// Move to step 2: irreversible warning
|
|
598
|
-
document.getElementById('apply-step').value = '2';
|
|
599
|
-
document.getElementById('apply-modal-title').textContent = '⚠️ 最终确认';
|
|
600
|
-
document.getElementById('apply-warning').style.display = 'block';
|
|
601
|
-
document.getElementById('apply-confirm-btn').textContent = '确认,不可撤销';
|
|
602
|
-
document.getElementById('apply-confirm-btn').className = 'btn-confirm danger';
|
|
603
|
-
return;
|
|
604
|
-
}}
|
|
605
|
-
// Step 2: actually apply
|
|
606
|
-
const btn = document.getElementById('apply-confirm-btn');
|
|
607
|
-
btn.disabled = true;
|
|
608
|
-
btn.textContent = '应用中…';
|
|
609
|
-
const intents = Object.values(AGENT_CONFIGS).map(cfg => ({{
|
|
610
|
-
name: cfg.display_name || cfg.name,
|
|
611
|
-
description: cfg.definition || '',
|
|
612
|
-
}}));
|
|
613
|
-
try {{
|
|
614
|
-
const resp = await fetch(`https://slg.ericstory.me/intent/apply/${{TENANT}}?rt=${{REPORT_TOKEN}}`, {{
|
|
615
|
-
method: 'POST',
|
|
616
|
-
headers: {{ 'Content-Type': 'application/json' }},
|
|
617
|
-
body: JSON.stringify({{ intents }}),
|
|
618
|
-
}});
|
|
619
|
-
const result = await resp.json();
|
|
620
|
-
const statusEl = document.getElementById('apply-status');
|
|
621
|
-
statusEl.style.display = 'block';
|
|
622
|
-
if (resp.ok) {{
|
|
623
|
-
statusEl.className = 'apply-status';
|
|
624
|
-
const detailLines = (result.details || []).join('<br>');
|
|
625
|
-
statusEl.innerHTML = `✅ 完成 · 创建 ${{result.created}} · 更新 ${{result.updated}} · 失败 ${{result.failed}}<br><br>${{detailLines}}`;
|
|
626
|
-
document.getElementById('apply-confirm-btn').style.display = 'none';
|
|
627
|
-
document.getElementById('apply-modal-cancel-btn').textContent = '关闭';
|
|
628
|
-
}} else {{
|
|
629
|
-
statusEl.className = 'apply-status error';
|
|
630
|
-
statusEl.textContent = `❌ 失败: ${{result.error || JSON.stringify(result)}}`;
|
|
631
|
-
btn.disabled = false;
|
|
632
|
-
btn.textContent = '重试';
|
|
633
|
-
}}
|
|
634
|
-
}} catch (e) {{
|
|
635
|
-
document.getElementById('apply-status').style.display = 'block';
|
|
636
|
-
document.getElementById('apply-status').className = 'apply-status error';
|
|
637
|
-
document.getElementById('apply-status').textContent = `❌ 网络错误: ${{e.message}}`;
|
|
638
|
-
btn.disabled = false;
|
|
639
|
-
btn.textContent = '重试';
|
|
640
|
-
}}
|
|
641
|
-
}}
|
|
642
|
-
</script>
|
|
643
|
-
|
|
644
|
-
<!-- APPLY MODAL -->
|
|
645
|
-
<div class="modal-overlay" id="apply-modal" onclick="if(event.target===this)closeApplyModal()">
|
|
646
|
-
<div class="modal-box">
|
|
647
|
-
<div class="modal-header">
|
|
648
|
-
<h3 id="apply-modal-title">批量应用意图 · 预览</h3>
|
|
649
|
-
<span class="modal-close" onclick="closeApplyModal()">✕</span>
|
|
650
|
-
</div>
|
|
651
|
-
<div class="modal-body">
|
|
652
|
-
<div style="font-size:13px;color:#374151;margin-bottom:12px;">
|
|
653
|
-
将把以下 <b><span id="apply-count">0</span> 个意图</b>应用到当前 Solvea 项目中。
|
|
654
|
-
已有同名意图将被更新,不存在则新建。
|
|
655
|
-
</div>
|
|
656
|
-
<table class="intent-preview-table">
|
|
657
|
-
<thead><tr><th>意图名称</th><th>意图定义</th></tr></thead>
|
|
658
|
-
<tbody id="apply-preview-body"></tbody>
|
|
659
|
-
</table>
|
|
660
|
-
<div id="apply-warning" style="display:none;margin-top:14px;padding:12px 14px;border-radius:8px;background:#fff7ed;border:1px solid #fdba74;font-size:12.5px;color:#92400e;line-height:1.7;">
|
|
661
|
-
⚠️ <b>此操作不可撤销。</b>继续将直接修改 Solvea 生产数据库中的意图配置,请确认无误后再继续。
|
|
662
|
-
</div>
|
|
663
|
-
<div id="apply-status" style="display:none;" class="apply-status"></div>
|
|
664
|
-
<input type="hidden" id="apply-step" value="1">
|
|
665
|
-
</div>
|
|
666
|
-
<div class="modal-footer">
|
|
667
|
-
<button class="btn-cancel" id="apply-modal-cancel-btn" onclick="closeApplyModal()">取消</button>
|
|
668
|
-
<button class="btn-confirm" id="apply-confirm-btn" onclick="confirmApply()">确认应用</button>
|
|
669
|
-
</div>
|
|
670
|
-
</div>
|
|
671
|
-
</div>
|
|
672
|
-
|
|
673
|
-
</body>
|
|
674
|
-
</html>"""
|
|
675
|
-
|
|
676
|
-
return html
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
def _build_agent_configs_js(agents: list, fuzzy_samples: dict = None) -> str:
|
|
680
|
-
"""Build the AGENT_CONFIGS JS object for drawer."""
|
|
681
|
-
fuzzy_samples = fuzzy_samples or {}
|
|
682
|
-
configs = {}
|
|
683
|
-
for agent in agents:
|
|
684
|
-
ag_id = agent["id"]
|
|
685
|
-
ic = agent.get("intent_config", {})
|
|
686
|
-
ac = agent.get("agent_config", {})
|
|
687
|
-
count = agent.get("total_count", 0)
|
|
688
|
-
pct = agent.get("total_pct", 0.0)
|
|
689
|
-
|
|
690
|
-
# Collect fuzzy samples for this agent's L1 intents
|
|
691
|
-
agent_fuzzy = {}
|
|
692
|
-
for l1_intent in agent.get("l1_intents", []):
|
|
693
|
-
if l1_intent in fuzzy_samples:
|
|
694
|
-
agent_fuzzy[l1_intent] = fuzzy_samples[l1_intent]
|
|
695
|
-
|
|
696
|
-
configs[ag_id] = {
|
|
697
|
-
"name": agent.get("name", ag_id),
|
|
698
|
-
"color": agent.get("color", "#6b7280"),
|
|
699
|
-
"vol": f"{count:,}条 · {pct:.1f}%",
|
|
700
|
-
"intent_id": ic.get("id", ag_id),
|
|
701
|
-
"display_name": ic.get("display_name", ""),
|
|
702
|
-
"definition": ic.get("definition", ""),
|
|
703
|
-
"excludes": ic.get("excludes", []),
|
|
704
|
-
"examples": ic.get("examples", []),
|
|
705
|
-
"covers": ic.get("covers", []),
|
|
706
|
-
"split_note": ic.get("split_note"),
|
|
707
|
-
"agent_type": ac.get("type", "chatflow"),
|
|
708
|
-
"nodes": ac.get("nodes", []),
|
|
709
|
-
"footer": ac.get("footer", ""),
|
|
710
|
-
"is_new": agent.get("is_new", False),
|
|
711
|
-
"fuzzy_samples": agent_fuzzy,
|
|
712
|
-
}
|
|
713
|
-
return json.dumps(configs, ensure_ascii=False, indent=2)
|