@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,234 +0,0 @@
1
- """
2
- Generate intent classification detail Excel from items_*.json + results_*.json.
3
-
4
- Columns: L1 Intent | L2 Intent | Case Text
5
- L1 and L2 cells are merged across rows of the same value.
6
-
7
- Usage:
8
- python3 scripts/gen_excel.py [--brand "Acme"]
9
- """
10
-
11
- import os, sys
12
- sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
13
- from bootstrap import *
14
-
15
- import json, argparse
16
- import openpyxl
17
-
18
-
19
- def _read_state() -> dict:
20
- _sf = STATE_DIR / "progress.json"
21
- if _sf.exists():
22
- try:
23
- return json.loads(_sf.read_text(encoding="utf-8"))
24
- except Exception:
25
- pass
26
- return {}
27
-
28
-
29
- def _write_state(data: dict) -> None:
30
- _sf = STATE_DIR / "progress.json"
31
- _sf.parent.mkdir(parents=True, exist_ok=True)
32
- _sf.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
33
- from openpyxl.styles import PatternFill, Font, Alignment, Border, Side
34
- from openpyxl.utils import get_column_letter
35
-
36
-
37
- COLOR_PALETTE = [
38
- "FEF3C7", "DBEAFE", "D1FAE5", "FCE7F3", "EDE9FE",
39
- "FEE2E2", "ECFDF5", "FFF7ED", "F0F9FF", "F7FEE7",
40
- ]
41
-
42
-
43
- def thin_border():
44
- s = Side(style="thin", color="D1D5DB")
45
- return Border(left=s, right=s, top=s, bottom=s)
46
-
47
-
48
- def build_excel(items, results, brand=""):
49
- wb = openpyxl.Workbook()
50
- ws = wb.active
51
- ws.title = "Intent Detail"
52
-
53
- all_intents = results.get("intents", []) + results.get("new_intents", [])
54
- l1_color = {}
55
- for i, intent in enumerate(all_intents):
56
- l1_color[intent["name"]] = COLOR_PALETTE[i % len(COLOR_PALETTE)]
57
-
58
- l1_canonical_l2s = {}
59
- for intent in all_intents:
60
- l1_canonical_l2s[intent["name"]] = {
61
- s["name"] for s in intent.get("sub_intents", []) if s["name"] != "Other"
62
- }
63
-
64
- def canonical_l2(l1, l2):
65
- allowed = l1_canonical_l2s.get(l1, set())
66
- if not allowed:
67
- return l2 or "Other"
68
- return l2 if l2 in allowed else "Other"
69
-
70
- l1_order = {intent["name"]: i for i, intent in enumerate(all_intents)}
71
- items_sorted = sorted(items, key=lambda x: (
72
- l1_order.get(x["l1"], 999),
73
- canonical_l2(x["l1"], x.get("l2") or ""),
74
- ))
75
-
76
- headers = ["L1意图", "L2意图", "分类依据(LLM)", "title", "first_message", "messages"]
77
- col_widths = [22, 28, 45, 25, 55, 70]
78
-
79
- header_fill = PatternFill("solid", fgColor="1E3A5F")
80
- header_font = Font(bold=True, color="FFFFFF", size=11)
81
- header_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
82
-
83
- for col, (h, w) in enumerate(zip(headers, col_widths), start=1):
84
- cell = ws.cell(row=1, column=col, value=h)
85
- cell.fill = header_fill
86
- cell.font = header_font
87
- cell.alignment = header_align
88
- cell.border = thin_border()
89
- ws.column_dimensions[get_column_letter(col)].width = w
90
-
91
- ws.row_dimensions[1].height = 30
92
- ws.freeze_panes = "A2"
93
-
94
- row_num = 2
95
-
96
- for item in items_sorted:
97
- l1 = item["l1"]
98
- l2 = item.get("l2") or "(unclassified)"
99
- title_val = item.get("title", "")
100
- first_msg_val = item.get("first_message", "") or item.get("text", "")
101
- messages_val = item.get("messages", "")
102
- raw_text = item.get("text", "")
103
- text_val = raw_text[:1000] + f"...[截断 {len(raw_text)-1000} 字]" if len(raw_text) > 1000 else raw_text
104
- color = l1_color.get(l1, "F9FAFB")
105
-
106
- fill = PatternFill("solid", fgColor=color)
107
- border = thin_border()
108
-
109
- c1 = ws.cell(row=row_num, column=1, value=l1)
110
- c1.fill = fill
111
- c1.border = border
112
- c1.alignment = Alignment(horizontal="center", vertical="top", wrap_text=True)
113
- c1.font = Font(bold=True, size=10)
114
-
115
- l2_display = canonical_l2(l1, l2)
116
- l2_label = l2_display
117
- if item.get("is_new_l2") and l2_display not in ("Other", ""):
118
- l2_label = f"★ {l2_display}"
119
- if item.get("is_new_l1"):
120
- l2_label = f"[新] {l2_display}"
121
-
122
- c2 = ws.cell(row=row_num, column=2, value=l2_label)
123
- c2.fill = PatternFill("solid", fgColor="F8FAFC")
124
- c2.border = border
125
- c2.alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
126
- c2.font = Font(size=10)
127
-
128
- for col_idx, val in [(3, text_val), (4, title_val), (5, first_msg_val), (6, messages_val)]:
129
- cx = ws.cell(row=row_num, column=col_idx, value=val)
130
- cx.fill = PatternFill("solid", fgColor="FFFFFF")
131
- cx.border = border
132
- cx.alignment = Alignment(horizontal="left", vertical="top", wrap_text=True)
133
- cx.font = Font(size=9)
134
- ws.row_dimensions[row_num].height = max(15, min(80, len(first_msg_val) // 3))
135
-
136
- row_num += 1
137
-
138
- # Summary sheet
139
- ws2 = wb.create_sheet("Summary")
140
- ws2.column_dimensions["A"].width = 28
141
- ws2.column_dimensions["B"].width = 28
142
- ws2.column_dimensions["C"].width = 10
143
- ws2.column_dimensions["D"].width = 10
144
-
145
- h_fill = PatternFill("solid", fgColor="1E3A5F")
146
- h_font = Font(bold=True, color="FFFFFF", size=11)
147
- for col, val in enumerate(["一级意图", "二级意图", "数量", "占比 %"], start=1):
148
- c = ws2.cell(row=1, column=col, value=val)
149
- c.fill = h_fill
150
- c.font = h_font
151
- c.alignment = Alignment(horizontal="center", vertical="center")
152
- c.border = thin_border()
153
- ws2.row_dimensions[1].height = 28
154
- ws2.freeze_panes = "A2"
155
-
156
- sr = 2
157
- for intent in all_intents:
158
- l1n = intent["name"]
159
- color = l1_color.get(l1n, "F9FAFB")
160
- fill = PatternFill("solid", fgColor=color)
161
- subs = intent.get("sub_intents", [])
162
- if not subs:
163
- c = ws2.cell(row=sr, column=1, value=l1n)
164
- c.fill = fill; c.font = Font(bold=True, size=10); c.border = thin_border()
165
- c.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
166
- ws2.cell(row=sr, column=2, value="(no sub-intents)").border = thin_border()
167
- ws2.cell(row=sr, column=3, value=intent["count"]).border = thin_border()
168
- ws2.cell(row=sr, column=4, value=round(intent["pct"], 1)).border = thin_border()
169
- sr += 1
170
- else:
171
- l1_start_row = sr
172
- for sub in subs:
173
- c = ws2.cell(row=sr, column=1, value=l1n)
174
- c.fill = fill; c.font = Font(bold=True, size=10); c.border = thin_border()
175
- c.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
176
- ws2.cell(row=sr, column=2, value=sub["name"]).border = thin_border()
177
- ws2.cell(row=sr, column=2).alignment = Alignment(horizontal="left", vertical="center")
178
- ws2.cell(row=sr, column=3, value=sub["count"]).border = thin_border()
179
- ws2.cell(row=sr, column=3).alignment = Alignment(horizontal="center")
180
- ws2.cell(row=sr, column=4, value=round(sub["pct"], 1)).border = thin_border()
181
- ws2.cell(row=sr, column=4).alignment = Alignment(horizontal="center")
182
- sr += 1
183
- if sr - 1 > l1_start_row:
184
- ws2.merge_cells(start_row=l1_start_row, start_column=1,
185
- end_row=sr - 1, end_column=1)
186
- ws2.cell(row=l1_start_row, column=1).alignment = Alignment(
187
- horizontal="center", vertical="center", wrap_text=True)
188
-
189
- return wb
190
-
191
-
192
- def main():
193
- parser = argparse.ArgumentParser()
194
- parser.add_argument("--brand", default="")
195
- parser.add_argument("--results", default=None,
196
- help="Override results JSON path (default: read from state)")
197
- parser.add_argument("--items", default=None,
198
- help="Override items JSON path (default: read from state)")
199
- args = parser.parse_args()
200
-
201
- state = _read_state()
202
- results_path = args.results or state.get("results_path")
203
- items_path = args.items or state.get("items_path")
204
-
205
- if not results_path or not os.path.exists(results_path):
206
- raise FileNotFoundError(
207
- f"Results file missing (state.results_path={results_path}). Run classify.py first."
208
- )
209
- if not items_path or not os.path.exists(items_path):
210
- raise FileNotFoundError(
211
- f"Items file missing (state.items_path={items_path}). "
212
- "Run classify.py with --xlsx first."
213
- )
214
-
215
- with open(items_path) as f:
216
- items = json.load(f)
217
- with open(results_path) as f:
218
- results = json.load(f)
219
-
220
- wb = build_excel(items, results, args.brand)
221
-
222
- OUTPUTS_DIR.mkdir(parents=True, exist_ok=True)
223
- out_path = str(OUTPUTS_DIR / f"detail_{timestamp()}.xlsx")
224
- wb.save(out_path)
225
- print(f"[gen_excel] Excel -> {out_path} ({len(items)} rows)")
226
-
227
- state = _read_state()
228
- state["excel_path"] = out_path
229
- state["updated_at"] = timestamp()
230
- _write_state(state)
231
-
232
-
233
- if __name__ == "__main__":
234
- main()
@@ -1,249 +0,0 @@
1
- #!/usr/bin/env python3
2
- """POC 测试手册 HTML 生成器。
3
-
4
- 读取 poc_test.py 输出的 results JSON + 一份模板 + 一份 CSS,
5
- 渲染成单文件 HTML(图片 base64 嵌入,无外部依赖、可邮件直发)。
6
-
7
- 模板路由:
8
- .html → Jinja2 渲染(推荐);py 侧传 by_agent dict,模板自己写循环
9
- .md → 传统 markdown 渲染(旧模板,保留供回退)
10
-
11
- Jinja2 模板可用变量:
12
- 顶层:title, brand_name, brand_website, generated_at, total_cases,
13
- console_url, agents_url, account_email, account_password,
14
- scene_count, by_agent
15
- by_agent:dict[str, list[case]],已过滤掉无 AI 回复的 case 和空 agent,
16
- case 字段:name, agent_name, agent_id, scenario, results, stopped_reason
17
- results 字段:turn, input, content, chatId, handoff, ...
18
-
19
- 旧 .md 模板额外占位符({{var}} 替换):
20
- scene_tags_html, agent_sections_html
21
-
22
- 用法:
23
- python3 gen_poc_manual.py --results results.json \\
24
- --output manual.html \\
25
- [--template assets/poc_test_template.html] \\
26
- [--css assets/default_manual.css] \\
27
- [--brand-name WOLTU] [--brand-website https://woltu.de] \\
28
- [--account-email a@b.com] [--password 123456] [--title "POC 测试手册"]
29
- """
30
-
31
- import argparse
32
- import base64
33
- import json
34
- import mimetypes
35
- import os
36
- import re
37
- import sys
38
- from collections import defaultdict
39
- from datetime import datetime
40
- from pathlib import Path
41
-
42
- sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
43
- from bootstrap import * # noqa: F401,F403
44
-
45
- import markdown # noqa: E402
46
- from jinja2 import Environment, BaseLoader # noqa: E402
47
-
48
- ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets"
49
- DEFAULT_CONSOLE_URL = "https://apps.voc.ai/chatbot#/setting"
50
- DEFAULT_AGENTS_URL = "https://apps.voc.ai/chatbot#/agents"
51
- DEFAULT_TEMPLATE = str(ASSETS_DIR / "poc_test_template.html")
52
- DEFAULT_CSS = str(ASSETS_DIR / "default_manual.css")
53
-
54
-
55
- # ── Helpers ──────────────────────────────────────────────────────────────────
56
-
57
- def _esc(s: str) -> str:
58
- return (s or "").replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
59
-
60
-
61
- def _has_reply(case: dict) -> bool:
62
- """case 中是否有至少一轮 AI 实际回复了内容(过滤掉全部出错 / 无 content 的 case)。"""
63
- return any(t.get("content") for t in case.get("results", []))
64
-
65
-
66
- # ── MD-path rendering (legacy, kept for rollback) ────────────────────────────
67
-
68
- def _render_dialogues(turns) -> str:
69
- rows = []
70
- for t in turns:
71
- u = _esc(t.get("input", ""))
72
- b = _esc(t.get("content", "")).replace("\n", "<br>")
73
- rows.append(
74
- f'<div class="msg"><span class="label">👤 User</span>'
75
- f'<div class="bubble user-bubble">{u}</div></div>'
76
- )
77
- rows.append(
78
- f'<div class="msg"><span class="label">🤖 AI</span>'
79
- f'<div class="bubble bot-bubble">{b}</div></div>'
80
- )
81
- return "\n".join(rows)
82
-
83
-
84
- def _render_cases(cases) -> str:
85
- out = []
86
- for c in cases:
87
- results = c.get("results", [])
88
- chat_id = results[-1].get("chatId", "—") if results else "—"
89
- scenario = c.get("scenario") or c.get("name", "")
90
- case_id = c.get("name", "")
91
- out.append(f"""<div class="case-card">
92
- <div class="case-header">
93
- <span class="case-id">{_esc(case_id)}</span>
94
- <span class="case-scenario">{_esc(scenario)}</span>
95
- <span class="chat-id-badge">Chat ID: <code>{_esc(chat_id)}</code></span>
96
- </div>
97
- <div class="dialogue">{_render_dialogues(results)}</div>
98
- </div>""")
99
- return "\n".join(out)
100
-
101
-
102
- def _render_agent_sections(by_agent: dict) -> str:
103
- out = []
104
- for aname, cases in by_agent.items():
105
- visible = [c for c in cases if _has_reply(c)]
106
- if not visible:
107
- continue
108
- aid = visible[0].get("agent_id", "")
109
- out.append(
110
- f'<section class="agent-section">'
111
- f'<h2>🤖 {_esc(aname)} <span class="agent-meta">Agent ID: {aid}</span></h2>'
112
- f'{_render_cases(visible)}</section>'
113
- )
114
- return "\n".join(out)
115
-
116
-
117
- def _render_scene_tags(by_agent: dict) -> str:
118
- return "".join(f'<span class="scene-tag">{_esc(n)}</span>' for n in by_agent.keys())
119
-
120
-
121
- def render_template(tpl: str, ctx: dict) -> str:
122
- """轻量模板替换:仅 {{var}} 占位符(旧 .md 路径使用)。"""
123
- def _sub(match):
124
- key = match.group(1).strip()
125
- val = ctx.get(key, "")
126
- return str(val) if val is not None else ""
127
- return re.sub(r"\{\{\s*([\w\.]+)\s*\}\}", _sub, tpl)
128
-
129
-
130
- # ── Image embedding ───────────────────────────────────────────────────────────
131
-
132
- def _embed_images_as_base64(html: str, base_dir: Path) -> str:
133
- """把 <img src="<rel_path>"> 替换成 data: base64 内嵌。
134
-
135
- 跳过远程 URL(http/https/data:)。本地路径找不到时打 warning 但不阻断渲染。
136
- """
137
- def _sub(m):
138
- prefix = m.group(1)
139
- src = m.group(2)
140
- suffix = m.group(3)
141
- if src.startswith(("http://", "https://", "data:")):
142
- return m.group(0)
143
- img_path = (base_dir / src).resolve()
144
- if not img_path.exists():
145
- print(f"[warn] image not found, kept original src: {img_path}", file=sys.stderr)
146
- return m.group(0)
147
- mime, _ = mimetypes.guess_type(str(img_path))
148
- if not mime:
149
- mime = "image/png"
150
- b64 = base64.b64encode(img_path.read_bytes()).decode("ascii")
151
- return f'{prefix}data:{mime};base64,{b64}{suffix}'
152
- return re.sub(r'(<img[^>]*\ssrc=")([^"]+)(")', _sub, html)
153
-
154
-
155
- def _wrap_html(body: str, title: str, css: str) -> str:
156
- return f"""<!doctype html>
157
- <html lang="zh-CN">
158
- <head>
159
- <meta charset="utf-8">
160
- <meta name="viewport" content="width=device-width, initial-scale=1">
161
- <title>{_esc(title)}</title>
162
- <style>
163
- {css}
164
- </style>
165
- </head>
166
- <body>
167
- {body}
168
- </body>
169
- </html>
170
- """
171
-
172
-
173
- # ── Main ──────────────────────────────────────────────────────────────────────
174
-
175
- def main():
176
- parser = argparse.ArgumentParser(description="POC 测试手册 HTML 生成器")
177
- parser.add_argument("--results", required=True, help="poc_test.py 输出的 results JSON 路径")
178
- parser.add_argument("--output", required=True, help="输出 HTML 路径")
179
- parser.add_argument("--title", default="Solvea POC 测试手册", help="手册标题")
180
- parser.add_argument("--brand-name", default="", help="客户品牌名")
181
- parser.add_argument("--brand-website", default="", help="客户品牌官网 URL")
182
- parser.add_argument("--console-url", default=DEFAULT_CONSOLE_URL)
183
- parser.add_argument("--agents-url", default=DEFAULT_AGENTS_URL)
184
- parser.add_argument("--account-email", default=os.environ.get("SOLVEA_ACCOUNT_EMAIL", "请联系实施顾问获取"))
185
- parser.add_argument("--password", default="123456")
186
- args = parser.parse_args()
187
-
188
- summary = json.loads(Path(args.results).read_text(encoding="utf-8"))
189
- results = summary.get("results", [])
190
-
191
- # 按 agent 分组,并过滤掉无 AI 回复的 case 和全空的 agent section
192
- by_agent_raw: dict = defaultdict(list)
193
- for r in results:
194
- by_agent_raw[r.get("agent_name") or "(unknown)"].append(r)
195
-
196
- by_agent = {
197
- aname: visible
198
- for aname, cases in by_agent_raw.items()
199
- if (visible := [c for c in cases if _has_reply(c)])
200
- }
201
-
202
- tpl_path = Path(DEFAULT_TEMPLATE)
203
- tpl = tpl_path.read_text(encoding="utf-8")
204
-
205
- ctx = {
206
- "title": args.title,
207
- "brand_name": args.brand_name,
208
- "brand_website": args.brand_website,
209
- "generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
210
- "total_cases": len(results),
211
- "console_url": args.console_url,
212
- "agents_url": args.agents_url,
213
- "account_email": args.account_email,
214
- "account_password": args.password,
215
- "scene_count": len(by_agent),
216
- "by_agent": by_agent,
217
- }
218
-
219
- if tpl_path.suffix.lower() == ".html":
220
- # Jinja2 渲染:模板自带 CSS + 循环,直接输出完整 HTML
221
- env = Environment(loader=BaseLoader(), autoescape=False)
222
- template = env.from_string(tpl)
223
- html = template.render(**ctx)
224
- html = _embed_images_as_base64(html, base_dir=tpl_path.parent)
225
- else:
226
- # 旧 .md 路径(保留供回退)
227
- md_ctx = {
228
- **ctx,
229
- "scene_tags_html": _render_scene_tags(by_agent),
230
- "agent_sections_html": _render_agent_sections(by_agent_raw),
231
- }
232
- rendered = render_template(tpl, md_ctx)
233
- body = markdown.markdown(
234
- rendered,
235
- extensions=["tables", "fenced_code", "md_in_html", "sane_lists"],
236
- )
237
- body = _embed_images_as_base64(body, base_dir=tpl_path.parent)
238
- css = Path(args.css).read_text(encoding="utf-8")
239
- html = _wrap_html(body, title=args.title, css=css)
240
-
241
- out_path = Path(args.output)
242
- out_path.parent.mkdir(parents=True, exist_ok=True)
243
- out_path.write_text(html, encoding="utf-8")
244
- size_kb = out_path.stat().st_size / 1024
245
- print(f" Manual rendered → {out_path} ({size_kb:.1f} KB)", file=sys.stderr)
246
-
247
-
248
- if __name__ == "__main__":
249
- main()
@@ -1,208 +0,0 @@
1
- """
2
- HTML report generator for poc skill.
3
-
4
- Reads classification results (workspace/state/poc/progress.json -> results_path) and
5
- renders a polished HTML report under workspace/outputs/poc/.
6
-
7
- Usage:
8
- python3 scripts/gen_report.py [--brand "Acme"]
9
- """
10
-
11
- import os, sys
12
- sys.path.insert(0, os.path.join(os.environ.get("SOLVEA_LIB_DIR", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "__lib__"))), "skill-runtime"))
13
- from bootstrap import *
14
-
15
- import json, argparse
16
- from uuid import uuid4
17
-
18
-
19
- def _read_state() -> dict:
20
- _sf = STATE_DIR / "progress.json"
21
- if _sf.exists():
22
- try:
23
- return json.loads(_sf.read_text(encoding="utf-8"))
24
- except Exception:
25
- pass
26
- return {}
27
-
28
-
29
- def _write_state(data: dict) -> None:
30
- _sf = STATE_DIR / "progress.json"
31
- _sf.parent.mkdir(parents=True, exist_ok=True)
32
- _sf.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
33
-
34
- from render import render_html
35
-
36
- COLOR_PALETTE = [
37
- "#ef4444", "#f97316", "#eab308", "#22c55e",
38
- "#14b8a6", "#3b82f6", "#8b5cf6", "#ec4899",
39
- "#06b6d4", "#84cc16",
40
- ]
41
-
42
- CIRCLE_NUMS = "①②③④⑤⑥⑦⑧⑨⑩"
43
-
44
-
45
- def build_config_from_results(results, brand_name=""):
46
- def uid():
47
- return uuid4().hex
48
-
49
- agents = []
50
- all_intents = results.get("intents", []) + results.get("new_intents", [])
51
- meta = results.get("meta", {})
52
- total = meta.get("total", 0)
53
-
54
- for i, intent in enumerate(all_intents):
55
- sub_intents = intent.get("sub_intents", [])
56
- is_new = intent.get("is_new", False)
57
- agent_id = f"intent-{i}"
58
-
59
- l2_nodes = []
60
- for j, s in enumerate(sub_intents):
61
- sample_preview = s["samples"][0][:50] + "..." if s.get("samples") else ""
62
- l2_nodes.append({
63
- "id": f"n2-{uid()[:8]}",
64
- "name": s["name"],
65
- "desc": f"{s['pct']:.1f}% — {sample_preview}" if sample_preview else f"{s['pct']:.1f}%",
66
- "count": s["count"],
67
- "definition": s.get("definition", ""),
68
- "is_shared": False,
69
- "shared_agents": [],
70
- "samples": s.get("samples", []),
71
- })
72
-
73
- nodes_ac = []
74
- for s in sub_intents[:3]:
75
- nodes_ac.append({
76
- "name": f"节点 · {s['name']}",
77
- "trigger": f"客户投诉:{s['name']}",
78
- "action": f"<code>LLM</code> 处理 {s['name']},引导解决",
79
- })
80
-
81
- agents.append({
82
- "id": agent_id,
83
- "name": f"{CIRCLE_NUMS[i % len(CIRCLE_NUMS)]} {intent['name']}",
84
- "color": COLOR_PALETTE[i % len(COLOR_PALETTE)],
85
- "l1_intents": [intent["name"]],
86
- "total_count": intent["count"],
87
- "total_pct": intent["pct"],
88
- "is_new": is_new,
89
- "intent_config": {
90
- "definition": intent.get("definition", ""),
91
- "excludes": [],
92
- "examples": intent.get("samples", [])[:3],
93
- "covers": [s["name"] for s in sub_intents],
94
- },
95
- "agent_config": {
96
- "type": "chatflow",
97
- "nodes": nodes_ac,
98
- "footer": f"<b>结束:</b>解决{intent['name']}问题 / 转人工",
99
- },
100
- "l2_nodes": l2_nodes,
101
- })
102
-
103
- edges = []
104
- for a in agents:
105
- ag_html_id = f"ag-{a['id'].replace('_', '-')}"
106
- color = a.get("color", "#6b7280")
107
- for node in a["l2_nodes"]:
108
- count = node.get("count", 0)
109
- if count > 0:
110
- edges.append({"from": ag_html_id, "to": node["id"], "count": count, "color": color})
111
-
112
- total_nodes = sum(len(a["l2_nodes"]) for a in agents)
113
- n_known = meta.get("n_known", len(results.get("intents", [])))
114
- n_new = meta.get("n_new", len(results.get("new_intents", [])))
115
-
116
- rows = []
117
- for a in agents:
118
- n = len(a["l2_nodes"])
119
- badge = f"{n} 节点 ✓" if n <= 2 else (f"{n} 节点 ⚠️" if n <= 3 else f"{n} 节点 ❌")
120
- rows.append({
121
- "agent_name": a["name"],
122
- "color": a["color"],
123
- "node_count": n,
124
- "badge": badge,
125
- })
126
-
127
- l2_total = sum(s["count"] for intent in all_intents for s in intent.get("sub_intents", []))
128
- opt_notes = (
129
- f"✅ 已知意图 {n_known} 个,新发现意图 {n_new} 个,共 {n_known + n_new} 个 L1 意图\n\n"
130
- f"📊 数据基础:{total:,} 条对话 / L2 分类:{l2_total:,} 条"
131
- )
132
- if n_new > 0:
133
- new_names = [i["name"] for i in results.get("new_intents", [])]
134
- opt_notes += f"\n\n⚡ 新发现意图:{' · '.join(new_names)}"
135
-
136
- return {
137
- "agents": agents,
138
- "filter_block": {"types": ["未分类投诉"], "count": 0},
139
- "node_summary": {
140
- "rows": rows,
141
- "total_nodes": total_nodes,
142
- "optimization_notes": opt_notes,
143
- },
144
- "shared_nodes": [],
145
- "edges": edges,
146
- }
147
-
148
-
149
- def _resolve_results_path(state: dict) -> str:
150
- path = state.get("results_path")
151
- if not path:
152
- raise FileNotFoundError(
153
- "No results_path in state. Run classify.py first."
154
- )
155
- if not os.path.exists(path):
156
- raise FileNotFoundError(
157
- f"Results file missing: {path}. Run classify.py first."
158
- )
159
- return path
160
-
161
-
162
- def main():
163
- parser = argparse.ArgumentParser(description="Generate poc intent classification HTML report")
164
- parser.add_argument("--brand", default="")
165
- parser.add_argument("--results", default=None,
166
- help="Override results JSON path (default: read from progress.json)")
167
- args = parser.parse_args()
168
-
169
- if args.results:
170
- results_path = args.results
171
- if not os.path.exists(results_path):
172
- raise FileNotFoundError(f"Results file missing: {results_path}")
173
- else:
174
- state = _read_state()
175
- results_path = _resolve_results_path(state)
176
-
177
- with open(results_path) as f:
178
- results = json.load(f)
179
-
180
- config = build_config_from_results(results, brand_name=args.brand)
181
-
182
- meta = results.get("meta", {})
183
- data_compat = {
184
- "_meta": {"total": meta.get("total", 0), "brand": args.brand},
185
- "tree": {},
186
- "turn_distribution": {},
187
- "fuzzy_samples": {},
188
- }
189
-
190
- project_id = os.environ.get("SOLVEA_PROJECT_ID", "")
191
- html = render_html(config, data_compat, brand=args.brand, project_id=project_id)
192
-
193
- OUTPUTS_DIR.mkdir(parents=True, exist_ok=True)
194
- report_path = str(OUTPUTS_DIR / f"report_{timestamp()}.html")
195
- with open(report_path, "w", encoding="utf-8") as f:
196
- f.write(html)
197
- print(f"[gen_report] Report -> {report_path}")
198
-
199
- state = _read_state()
200
- state["report_path"] = report_path
201
- state["updated_at"] = timestamp()
202
- _write_state(state)
203
-
204
- print(f"[gen_report] Done.")
205
-
206
-
207
- if __name__ == "__main__":
208
- main()