@shulex/cxclawctl 0.1.2 → 0.1.4

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.
Files changed (208) hide show
  1. package/README.md +25 -22
  2. package/capabilities.lock.json +535 -131
  3. package/dist/cli.js +2203 -127
  4. package/dist/cli.js.map +1 -1
  5. package/package.json +20 -6
  6. package/requirements-cli.txt +0 -2
  7. package/skills/__lib__/__init__.py +0 -0
  8. package/skills/__lib__/skill-runtime/bootstrap.py +0 -134
  9. package/skills/__lib__/solvea-api/__init__.py +0 -0
  10. package/skills/__lib__/solvea-api/client.py +0 -1944
  11. package/skills/__lib__/solvea-api/dashboard.py +0 -100
  12. package/skills/__lib__/solvea-api/solvea_api.py +0 -1341
  13. package/skills/agent-analytics/SKILL.md +0 -74
  14. package/skills/agent-analytics/references/chat-report-fields.md +0 -36
  15. package/skills/agent-analytics/references/report-fields.md +0 -130
  16. package/skills/agent-analytics/references/transcript.md +0 -110
  17. package/skills/agent-builder/SKILL.md +0 -42
  18. package/skills/agent-builder/interfaces/fallback-chatflow.md +0 -47
  19. package/skills/agent-builder/references/chatflow/graph-spec.md +0 -259
  20. package/skills/agent-builder/references/chatflow/node-selection.md +0 -89
  21. package/skills/agent-builder/references/chatflow/nodes/agent.md +0 -350
  22. package/skills/agent-builder/references/chatflow/nodes/business-track.md +0 -43
  23. package/skills/agent-builder/references/chatflow/nodes/code.md +0 -157
  24. package/skills/agent-builder/references/chatflow/nodes/collect-order.md +0 -47
  25. package/skills/agent-builder/references/chatflow/nodes/finish.md +0 -34
  26. package/skills/agent-builder/references/chatflow/nodes/human-review.md +0 -60
  27. package/skills/agent-builder/references/chatflow/nodes/if-else.md +0 -100
  28. package/skills/agent-builder/references/chatflow/nodes/intent.md +0 -79
  29. package/skills/agent-builder/references/chatflow/nodes/knowledge-reply.md +0 -43
  30. package/skills/agent-builder/references/chatflow/nodes/llm.md +0 -117
  31. package/skills/agent-builder/references/chatflow/nodes/note.md +0 -38
  32. package/skills/agent-builder/references/chatflow/nodes/question.md +0 -97
  33. package/skills/agent-builder/references/chatflow/nodes/rag-reply.md +0 -68
  34. package/skills/agent-builder/references/chatflow/nodes/reply.md +0 -97
  35. package/skills/agent-builder/references/chatflow/nodes/start.md +0 -35
  36. package/skills/agent-builder/references/chatflow/nodes/sub-graph.md +0 -48
  37. package/skills/agent-builder/references/chatflow/nodes/tool.md +0 -138
  38. package/skills/agent-builder/references/chatflow/nodes/transfer-to-human.md +0 -63
  39. package/skills/agent-builder/references/chatflow/nodes/variable-assignment.md +0 -52
  40. package/skills/agent-builder/references/chatflow/nodes/variable-extract.md +0 -50
  41. package/skills/agent-builder/references/chatflow/nodes/variable-process.md +0 -70
  42. package/skills/agent-builder/references/chatflow/overview.md +0 -53
  43. package/skills/agent-builder/references/chatflow/visual-translation.md +0 -64
  44. package/skills/agent-builder/references/chatflow/workflow.md +0 -134
  45. package/skills/agent-builder/references/multi-agent/create-update.md +0 -185
  46. package/skills/agent-builder/references/multi-agent/graph-spec.md +0 -246
  47. package/skills/agent-builder/references/multi-agent/graph-structure.md +0 -12
  48. package/skills/agent-builder/references/multi-agent/migrate-to-chatflow.md +0 -13
  49. package/skills/agent-builder/references/multi-agent/migration/large.md +0 -554
  50. package/skills/agent-builder/references/multi-agent/migration/principles.md +0 -446
  51. package/skills/agent-builder/references/multi-agent/migration/small.md +0 -225
  52. package/skills/agent-builder/references/multi-agent/orchestration.md +0 -11
  53. package/skills/agent-builder/references/multi-agent/overview.md +0 -48
  54. package/skills/agent-builder/references/multi-agent/update.md +0 -128
  55. package/skills/agent-builder/references/multi-agent/workflow.md +0 -87
  56. package/skills/agent-builder/references/single-agent/overview.md +0 -48
  57. package/skills/agent-builder/references/single-agent/prompt.md +0 -16
  58. package/skills/agent-builder/references/single-agent/tools.md +0 -36
  59. package/skills/agent-builder/references/single-agent/voice.md +0 -46
  60. package/skills/agent-builder/references/tools/http-tool.md +0 -315
  61. package/skills/agent-builder/references/tools/mock-tool.md +0 -279
  62. package/skills/agent-builder/references/tools/overview.md +0 -46
  63. package/skills/agent-builder/references/tools/tool-config.md +0 -43
  64. package/skills/agent-builder/references/tools/x-token.md +0 -34
  65. package/skills/agent-builder/scripts/chatflow/chatflow_builder.py +0 -3458
  66. package/skills/agent-builder/scripts/chatflow/validate_graph.py +0 -1697
  67. package/skills/agent-builder/scripts/copy_marketplace_agent.py +0 -61
  68. package/skills/agent-builder/scripts/multi-agent/legacy_graph_analyzer.py +0 -5974
  69. package/skills/agent-builder/scripts/multi-agent/replay_compare.py +0 -679
  70. package/skills/agent-builder/scripts/patch_agent_tools.py +0 -135
  71. package/skills/agent-builder/scripts/tools/validate_tool_config.py +0 -384
  72. package/skills/agent-tester/SKILL.md +0 -35
  73. package/skills/agent-tester/batch-test/overview.md +0 -86
  74. package/skills/agent-tester/scripts/batch_test_sheet.py +0 -213
  75. package/skills/agent-tester/single-test/badcase-retest.md +0 -85
  76. package/skills/agent-tester/single-test/direct-test.md +0 -24
  77. package/skills/agent-tester/single-test/overview.md +0 -142
  78. package/skills/delivery/SKILL.md +0 -112
  79. package/skills/delivery/interfaces/classify.md +0 -38
  80. package/skills/delivery/interfaces/create-agents.md +0 -89
  81. package/skills/delivery/interfaces/delivery-test.md +0 -67
  82. package/skills/delivery/references/flow.md +0 -40
  83. package/skills/delivery/references/validation.md +0 -15
  84. package/skills/delivery/scripts/batch_test.py +0 -704
  85. package/skills/delivery/scripts/classify.py +0 -22
  86. package/skills/delivery/scripts/create_agents.py +0 -695
  87. package/skills/delivery/scripts/gen_delivery_report.py +0 -335
  88. package/skills/delivery/scripts/gen_excel.py +0 -234
  89. package/skills/delivery/scripts/gen_report.py +0 -208
  90. package/skills/delivery/scripts/plan.py +0 -163
  91. package/skills/delivery/scripts/render.py +0 -713
  92. package/skills/delivery/scripts/render_summary.py +0 -38
  93. package/skills/delivery/scripts/sample_history_cases.py +0 -109
  94. package/skills/kb-builder/SKILL.md +0 -80
  95. package/skills/kb-builder/interfaces/qa-upload.md +0 -55
  96. package/skills/kb-builder/interfaces/web-kb.md +0 -84
  97. package/skills/kb-builder/references/directory.md +0 -373
  98. package/skills/kb-builder/references/export.md +0 -282
  99. package/skills/kb-builder/references/isolation-keywords.md +0 -113
  100. package/skills/kb-builder/references/knowledge.md +0 -153
  101. package/skills/kb-builder/references/product.md +0 -52
  102. package/skills/kb-builder/references/qa-from-help-center.md +0 -58
  103. package/skills/kb-builder/references/qa-from-pdf.md +0 -94
  104. package/skills/kb-builder/references/qa-from-tickets.md +0 -168
  105. package/skills/kb-builder/references/qa-from-web.md +0 -98
  106. package/skills/kb-builder/references/qa-quality.md +0 -73
  107. package/skills/kb-builder/references/reply-template.md +0 -237
  108. package/skills/kb-builder/references/tag-isolation-migration.md +0 -61
  109. package/skills/kb-builder/references/tags.md +0 -45
  110. package/skills/kb-builder/references/upload.md +0 -130
  111. package/skills/kb-builder/scripts/batch_extract_ticket_qa.py +0 -333
  112. package/skills/kb-builder/scripts/build_directory_plan.py +0 -144
  113. package/skills/kb-builder/scripts/crawl_site.py +0 -433
  114. package/skills/kb-builder/scripts/create_dirs.py +0 -146
  115. package/skills/kb-builder/scripts/export_knowledge.py +0 -454
  116. package/skills/kb-builder/scripts/extract_ticket_qa.py +0 -441
  117. package/skills/kb-builder/scripts/extract_web_qa.py +0 -118
  118. package/skills/kb-builder/scripts/import_qa.py +0 -219
  119. package/skills/kb-builder/scripts/import_source_documents.py +0 -143
  120. package/skills/kb-builder/scripts/import_templates.py +0 -409
  121. package/skills/poc/SKILL.md +0 -125
  122. package/skills/poc/assets/default_manual.css +0 -279
  123. package/skills/poc/assets/notice_escalate.png +0 -0
  124. package/skills/poc/assets/notice_escalate2.png +0 -0
  125. package/skills/poc/assets/notice_new_chat.png +0 -0
  126. package/skills/poc/assets/poc_test_template.html +0 -645
  127. package/skills/poc/assets/poc_test_template.md +0 -104
  128. package/skills/poc/assets/step3_preview.png +0 -0
  129. package/skills/poc/assets/step4_order_note.png +0 -0
  130. package/skills/poc/interfaces/classify.md +0 -38
  131. package/skills/poc/interfaces/create-agents.md +0 -108
  132. package/skills/poc/interfaces/poc-test.md +0 -94
  133. package/skills/poc/references/flow.md +0 -47
  134. package/skills/poc/references/kb-directory.md +0 -22
  135. package/skills/poc/references/validation.md +0 -17
  136. package/skills/poc/scripts/classify.py +0 -22
  137. package/skills/poc/scripts/create_agents.py +0 -818
  138. package/skills/poc/scripts/gen_excel.py +0 -234
  139. package/skills/poc/scripts/gen_poc_manual.py +0 -249
  140. package/skills/poc/scripts/gen_report.py +0 -208
  141. package/skills/poc/scripts/inspect_chatflow.py +0 -209
  142. package/skills/poc/scripts/plan.py +0 -178
  143. package/skills/poc/scripts/poc_test.py +0 -495
  144. package/skills/poc/scripts/render.py +0 -713
  145. package/skills/poc/scripts/render_summary.py +0 -41
  146. package/skills/runtime-inspector/SKILL.md +0 -51
  147. package/skills/runtime-inspector/references/frontend-rendering.md +0 -126
  148. package/skills/runtime-inspector/references/model.md +0 -59
  149. package/skills/runtime-inspector/references/sandbox.md +0 -131
  150. package/skills/runtime-inspector/scripts/model_inspect.py +0 -68
  151. package/skills/runtime-inspector/scripts/prepare.py +0 -98
  152. package/skills/runtime-inspector/scripts/process.py +0 -84
  153. package/skills/runtime-inspector/scripts/read_state.py +0 -32
  154. package/skills/runtime-inspector/scripts/sandbox_inspect.py +0 -463
  155. package/skills/runtime-inspector/scripts/test_claude_proxy.py +0 -49
  156. package/skills/runtime-inspector/scripts/test_openai_proxy.py +0 -41
  157. package/skills/runtime-inspector/scripts/verify_persistence.py +0 -101
  158. package/skills/runtime-inspector/scripts/write_test.py +0 -41
  159. package/skills/setup/SKILL.md +0 -17
  160. package/skills/shulex-support/SKILL.md +0 -39
  161. package/skills/shulex-support/references/badcase-analysis/overview.md +0 -34
  162. package/skills/shulex-support/references/badcase-analysis/text/conversation-record.md +0 -105
  163. package/skills/shulex-support/references/badcase-analysis/text/core-flow.md +0 -89
  164. package/skills/shulex-support/references/badcase-analysis/text/log-query-rules.md +0 -36
  165. package/skills/shulex-support/references/badcase-analysis/text/m1-entry.md +0 -53
  166. package/skills/shulex-support/references/badcase-analysis/text/m10-chatflow-service.md +0 -259
  167. package/skills/shulex-support/references/badcase-analysis/text/m11-algorithm-service.md +0 -148
  168. package/skills/shulex-support/references/badcase-analysis/text/m12-quality-inspection.md +0 -57
  169. package/skills/shulex-support/references/badcase-analysis/text/m2-intent.md +0 -120
  170. package/skills/shulex-support/references/badcase-analysis/text/m3-strategy.md +0 -126
  171. package/skills/shulex-support/references/badcase-analysis/text/m4-aifield.md +0 -121
  172. package/skills/shulex-support/references/badcase-analysis/text/m5-agent.md +0 -208
  173. package/skills/shulex-support/references/badcase-analysis/text/m6-tool.md +0 -115
  174. package/skills/shulex-support/references/badcase-analysis/text/m7-reply.md +0 -72
  175. package/skills/shulex-support/references/badcase-analysis/text/m8-error.md +0 -92
  176. package/skills/shulex-support/references/badcase-analysis/text/m9-knowledge-search.md +0 -147
  177. package/skills/shulex-support/references/badcase-analysis/text/sls-query.md +0 -283
  178. package/skills/shulex-support/references/badcase-analysis/voice/core-flow.md +0 -77
  179. package/skills/shulex-support/references/badcase-analysis/voice/dependencies.md +0 -19
  180. package/skills/shulex-support/references/badcase-analysis/voice/platform-api.md +0 -23
  181. package/skills/shulex-support/references/badcase-analysis/voice/sls-playbook.md +0 -31
  182. package/skills/shulex-support/references/product-docs.md +0 -70
  183. package/skills/shulex-support/references/tickets.md +0 -184
  184. package/skills/shulex-support/scripts/support.py +0 -427
  185. package/skills/solvea-desk/SKILL.md +0 -39
  186. package/skills/solvea-desk/references/report/fields.md +0 -82
  187. package/skills/solvea-desk/references/report/report-design.md +0 -200
  188. package/skills/solvea-desk/references/report/report-template.html +0 -44
  189. package/skills/solvea-desk/references/report/report.css +0 -517
  190. package/skills/solvea-desk/references/report/workflow.md +0 -464
  191. package/skills/solvea-desk/references/tickets/query.md +0 -103
  192. package/skills/solvea-desk/references/tickets/tags.md +0 -83
  193. package/skills/solvea-desk/scripts/report/fetch_sources.py +0 -580
  194. package/skills/solvea-desk/scripts/report/render_report.py +0 -763
  195. package/skills/solvea-desk/scripts/tickets/desk_api.py +0 -73
  196. package/skills/solvea-desk/scripts/tickets/fetch_tickets.py +0 -403
  197. package/skills/solvea-desk/scripts/tickets/ticket_tags.py +0 -305
  198. package/skills/solvea-kit/SKILL.md +0 -84
  199. package/skills/solvea-kit/references/agent.md +0 -145
  200. package/skills/solvea-kit/references/chat.md +0 -244
  201. package/skills/solvea-kit/references/intent.md +0 -69
  202. package/skills/solvea-kit/references/plugin.md +0 -53
  203. package/skills/solvea-kit/references/quality-inspection.md +0 -75
  204. package/skills/solvea-kit/references/strategy.md +0 -62
  205. package/skills/solvea-kit/references/tagging.md +0 -231
  206. package/skills/solvea-kit/references/variables.md +0 -53
  207. package/skills/solvea-kit/scripts/generate_label_import_file.py +0 -151
  208. 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()">&#215;</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)