@jungjaehoon/mama-os 0.18.2 → 0.19.0

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 (171) hide show
  1. package/dist/agent/agent-loop.d.ts +25 -0
  2. package/dist/agent/agent-loop.d.ts.map +1 -1
  3. package/dist/agent/agent-loop.js +67 -14
  4. package/dist/agent/agent-loop.js.map +1 -1
  5. package/dist/agent/code-act/host-bridge.d.ts.map +1 -1
  6. package/dist/agent/code-act/host-bridge.js +98 -0
  7. package/dist/agent/code-act/host-bridge.js.map +1 -1
  8. package/dist/agent/code-act/type-definition-generator.d.ts.map +1 -1
  9. package/dist/agent/code-act/type-definition-generator.js +0 -1
  10. package/dist/agent/code-act/type-definition-generator.js.map +1 -1
  11. package/dist/agent/gateway-tool-executor.d.ts +36 -1
  12. package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
  13. package/dist/agent/gateway-tool-executor.js +938 -54
  14. package/dist/agent/gateway-tool-executor.js.map +1 -1
  15. package/dist/agent/gateway-tools.md +9 -0
  16. package/dist/agent/managed-agent-runtime-sync.d.ts +36 -0
  17. package/dist/agent/managed-agent-runtime-sync.d.ts.map +1 -0
  18. package/dist/agent/managed-agent-runtime-sync.js +207 -0
  19. package/dist/agent/managed-agent-runtime-sync.js.map +1 -0
  20. package/dist/agent/managed-agent-validation.d.ts +4 -0
  21. package/dist/agent/managed-agent-validation.d.ts.map +1 -0
  22. package/dist/agent/managed-agent-validation.js +84 -0
  23. package/dist/agent/managed-agent-validation.js.map +1 -0
  24. package/dist/agent/os-agent-capabilities.md +400 -0
  25. package/dist/agent/skill-loader.d.ts +2 -0
  26. package/dist/agent/skill-loader.d.ts.map +1 -1
  27. package/dist/agent/skill-loader.js +28 -0
  28. package/dist/agent/skill-loader.js.map +1 -1
  29. package/dist/agent/tool-registry.d.ts.map +1 -1
  30. package/dist/agent/tool-registry.js +66 -0
  31. package/dist/agent/tool-registry.js.map +1 -1
  32. package/dist/agent/types.d.ts +2 -1
  33. package/dist/agent/types.d.ts.map +1 -1
  34. package/dist/agent/types.js.map +1 -1
  35. package/dist/api/agent-handler.d.ts +34 -0
  36. package/dist/api/agent-handler.d.ts.map +1 -0
  37. package/dist/api/agent-handler.js +216 -0
  38. package/dist/api/agent-handler.js.map +1 -0
  39. package/dist/api/graph-api-types.d.ts +4 -0
  40. package/dist/api/graph-api-types.d.ts.map +1 -1
  41. package/dist/api/graph-api.d.ts +2 -2
  42. package/dist/api/graph-api.d.ts.map +1 -1
  43. package/dist/api/graph-api.js +480 -51
  44. package/dist/api/graph-api.js.map +1 -1
  45. package/dist/api/index.d.ts.map +1 -1
  46. package/dist/api/index.js +4 -0
  47. package/dist/api/index.js.map +1 -1
  48. package/dist/api/token-handler.d.ts +1 -0
  49. package/dist/api/token-handler.d.ts.map +1 -1
  50. package/dist/api/token-handler.js +4 -3
  51. package/dist/api/token-handler.js.map +1 -1
  52. package/dist/api/ui-command-handler.d.ts +48 -0
  53. package/dist/api/ui-command-handler.d.ts.map +1 -0
  54. package/dist/api/ui-command-handler.js +160 -0
  55. package/dist/api/ui-command-handler.js.map +1 -0
  56. package/dist/cli/commands/start.d.ts.map +1 -1
  57. package/dist/cli/commands/start.js +127 -1
  58. package/dist/cli/commands/start.js.map +1 -1
  59. package/dist/cli/config/config-manager.d.ts.map +1 -1
  60. package/dist/cli/config/config-manager.js +16 -31
  61. package/dist/cli/config/config-manager.js.map +1 -1
  62. package/dist/cli/runtime/agent-loop-init.d.ts.map +1 -1
  63. package/dist/cli/runtime/agent-loop-init.js +31 -7
  64. package/dist/cli/runtime/agent-loop-init.js.map +1 -1
  65. package/dist/cli/runtime/api-routes-init.d.ts +3 -0
  66. package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
  67. package/dist/cli/runtime/api-routes-init.js +283 -34
  68. package/dist/cli/runtime/api-routes-init.js.map +1 -1
  69. package/dist/cli/runtime/gateway-init.d.ts +2 -1
  70. package/dist/cli/runtime/gateway-init.d.ts.map +1 -1
  71. package/dist/cli/runtime/gateway-init.js +5 -1
  72. package/dist/cli/runtime/gateway-init.js.map +1 -1
  73. package/dist/connectors/framework/raw-store.d.ts +4 -0
  74. package/dist/connectors/framework/raw-store.d.ts.map +1 -1
  75. package/dist/connectors/framework/raw-store.js +33 -10
  76. package/dist/connectors/framework/raw-store.js.map +1 -1
  77. package/dist/db/agent-store.d.ts +115 -0
  78. package/dist/db/agent-store.d.ts.map +1 -0
  79. package/dist/db/agent-store.js +248 -0
  80. package/dist/db/agent-store.js.map +1 -0
  81. package/dist/db/migrations/agent-activity-validation-columns.d.ts +3 -0
  82. package/dist/db/migrations/agent-activity-validation-columns.d.ts.map +1 -0
  83. package/dist/db/migrations/agent-activity-validation-columns.js +22 -0
  84. package/dist/db/migrations/agent-activity-validation-columns.js.map +1 -0
  85. package/dist/db/migrations/agent-metrics-response-avg.d.ts +3 -0
  86. package/dist/db/migrations/agent-metrics-response-avg.d.ts.map +1 -0
  87. package/dist/db/migrations/agent-metrics-response-avg.js +19 -0
  88. package/dist/db/migrations/agent-metrics-response-avg.js.map +1 -0
  89. package/dist/db/migrations/agent-store-tables.d.ts +3 -0
  90. package/dist/db/migrations/agent-store-tables.d.ts.map +1 -0
  91. package/dist/db/migrations/agent-store-tables.js +59 -0
  92. package/dist/db/migrations/agent-store-tables.js.map +1 -0
  93. package/dist/db/migrations/token-usage-agent-version.d.ts +3 -0
  94. package/dist/db/migrations/token-usage-agent-version.d.ts.map +1 -0
  95. package/dist/db/migrations/token-usage-agent-version.js +16 -0
  96. package/dist/db/migrations/token-usage-agent-version.js.map +1 -0
  97. package/dist/db/migrations/validation-session-tables.d.ts +3 -0
  98. package/dist/db/migrations/validation-session-tables.d.ts.map +1 -0
  99. package/dist/db/migrations/validation-session-tables.js +59 -0
  100. package/dist/db/migrations/validation-session-tables.js.map +1 -0
  101. package/dist/gateways/message-router.d.ts +10 -0
  102. package/dist/gateways/message-router.d.ts.map +1 -1
  103. package/dist/gateways/message-router.js +188 -14
  104. package/dist/gateways/message-router.js.map +1 -1
  105. package/dist/gateways/types.d.ts +1 -1
  106. package/dist/gateways/types.d.ts.map +1 -1
  107. package/dist/multi-agent/agent-process-manager.js +1 -1
  108. package/dist/multi-agent/agent-process-manager.js.map +1 -1
  109. package/dist/multi-agent/conductor-persona.d.ts +13 -0
  110. package/dist/multi-agent/conductor-persona.d.ts.map +1 -0
  111. package/dist/multi-agent/conductor-persona.js +157 -0
  112. package/dist/multi-agent/conductor-persona.js.map +1 -0
  113. package/dist/multi-agent/dashboard-agent-persona.d.ts +1 -1
  114. package/dist/multi-agent/dashboard-agent-persona.d.ts.map +1 -1
  115. package/dist/multi-agent/dashboard-agent-persona.js +7 -3
  116. package/dist/multi-agent/dashboard-agent-persona.js.map +1 -1
  117. package/dist/multi-agent/delegation-manager.d.ts +5 -0
  118. package/dist/multi-agent/delegation-manager.d.ts.map +1 -1
  119. package/dist/multi-agent/delegation-manager.js +37 -0
  120. package/dist/multi-agent/delegation-manager.js.map +1 -1
  121. package/dist/multi-agent/ultrawork.d.ts +3 -0
  122. package/dist/multi-agent/ultrawork.d.ts.map +1 -1
  123. package/dist/multi-agent/ultrawork.js +9 -0
  124. package/dist/multi-agent/ultrawork.js.map +1 -1
  125. package/dist/validation/session-service.d.ts +72 -0
  126. package/dist/validation/session-service.d.ts.map +1 -0
  127. package/dist/validation/session-service.js +298 -0
  128. package/dist/validation/session-service.js.map +1 -0
  129. package/dist/validation/store.d.ts +25 -0
  130. package/dist/validation/store.d.ts.map +1 -0
  131. package/dist/validation/store.js +200 -0
  132. package/dist/validation/store.js.map +1 -0
  133. package/dist/validation/types.d.ts +119 -0
  134. package/dist/validation/types.d.ts.map +1 -0
  135. package/dist/validation/types.js +57 -0
  136. package/dist/validation/types.js.map +1 -0
  137. package/package.json +3 -3
  138. package/public/viewer/js/modules/agents.js +1148 -0
  139. package/public/viewer/js/modules/chat.js +20 -11
  140. package/public/viewer/js/modules/connector-feed.js +35 -0
  141. package/public/viewer/js/modules/dashboard.js +49 -0
  142. package/public/viewer/js/modules/memory.js +32 -0
  143. package/public/viewer/js/modules/settings.js +34 -79
  144. package/public/viewer/js/modules/wiki.js +59 -4
  145. package/public/viewer/js/utils/api.js +70 -0
  146. package/public/viewer/js/utils/dom.js +3 -0
  147. package/public/viewer/js/utils/ui-commands.js +93 -0
  148. package/public/viewer/log-viewer.html +2 -2
  149. package/public/viewer/src/modules/agents.ts +1299 -0
  150. package/public/viewer/src/modules/chat.ts +23 -14
  151. package/public/viewer/src/modules/connector-feed.ts +35 -0
  152. package/public/viewer/src/modules/dashboard.ts +50 -0
  153. package/public/viewer/src/modules/memory.ts +31 -0
  154. package/public/viewer/src/modules/settings.ts +36 -96
  155. package/public/viewer/src/modules/wiki.ts +73 -6
  156. package/public/viewer/src/types/global.d.ts +0 -9
  157. package/public/viewer/src/utils/api.ts +156 -2
  158. package/public/viewer/src/utils/dom.ts +6 -1
  159. package/public/viewer/src/utils/ui-commands.ts +118 -0
  160. package/public/viewer/viewer.css +105 -10
  161. package/public/viewer/viewer.html +1868 -777
  162. package/scripts/generate-gateway-tools.ts +5 -1
  163. package/public/viewer/js/modules/playground.js +0 -148
  164. package/public/viewer/js/modules/skills.js +0 -451
  165. package/public/viewer/src/modules/playground.ts +0 -173
  166. package/public/viewer/src/modules/skills.ts +0 -491
  167. package/templates/playgrounds/cron-workflow-lab.html +0 -1601
  168. package/templates/playgrounds/mama-log-viewer.html +0 -1341
  169. package/templates/playgrounds/skill-lab-playground.html +0 -1625
  170. package/templates/playgrounds/wave-visualizer.html +0 -694
  171. package/templates/skills/playground.md +0 -197
@@ -1,1625 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>Skill Lab</title>
7
- <style>
8
- :root {
9
- --bg: #f4f6fb;
10
- --panel: #ffffff;
11
- --soft: #f7f9fd;
12
- --line: #dce3ef;
13
- --text: #111827;
14
- --muted: #5c6b82;
15
- --accent: #0d6a62;
16
- --accent-soft: #d7f1ec;
17
- --accent-dark: #094f49;
18
- --warn: #b76b00;
19
- --warn-soft: #fff4e0;
20
- --danger: #b42318;
21
- --danger-soft: #ffeaea;
22
- --ok: #16794a;
23
- --ok-soft: #e2f9ec;
24
- --radius: 12px;
25
- --shadow: 0 2px 12px rgba(0,0,0,0.06);
26
- }
27
- * { box-sizing: border-box; margin: 0; }
28
- body {
29
- font-family: 'Pretendard', 'Inter', system-ui, sans-serif;
30
- background: var(--bg);
31
- color: var(--text);
32
- height: 100vh;
33
- overflow: hidden;
34
- }
35
- .app {
36
- display: grid;
37
- grid-template-columns: 260px 1fr;
38
- height: 100vh;
39
- }
40
-
41
- /* Sidebar */
42
- .sidebar {
43
- background: var(--panel);
44
- border-right: 1px solid var(--line);
45
- display: flex;
46
- flex-direction: column;
47
- overflow: hidden;
48
- }
49
- .sidebar-header {
50
- padding: 16px;
51
- border-bottom: 1px solid var(--line);
52
- }
53
- .sidebar-header h1 { font-size: 18px; font-weight: 800; letter-spacing: -0.02em; }
54
- .sidebar-header p { font-size: 11px; color: var(--muted); margin-top: 4px; }
55
- .new-skill-btn {
56
- display: flex;
57
- align-items: center;
58
- gap: 8px;
59
- width: calc(100% - 24px);
60
- margin: 12px 12px 8px;
61
- padding: 10px 14px;
62
- border: 2px dashed var(--accent);
63
- border-radius: var(--radius);
64
- background: var(--accent-soft);
65
- color: var(--accent-dark);
66
- font-weight: 700;
67
- font-size: 13px;
68
- cursor: pointer;
69
- transition: all 0.15s;
70
- }
71
- .new-skill-btn:hover { background: var(--accent); color: white; border-color: var(--accent); }
72
- .skill-list { flex: 1; overflow-y: auto; padding: 4px 8px; }
73
- .skill-item {
74
- padding: 10px 12px;
75
- border-radius: 10px;
76
- cursor: pointer;
77
- transition: background 0.12s;
78
- display: grid;
79
- gap: 3px;
80
- margin-bottom: 2px;
81
- }
82
- .skill-item:hover { background: var(--soft); }
83
- .skill-item.active { background: var(--accent-soft); }
84
- .skill-item .skill-name {
85
- font-size: 13px;
86
- font-weight: 700;
87
- display: flex;
88
- align-items: center;
89
- gap: 6px;
90
- }
91
- .skill-item .skill-meta { font-size: 11px; color: var(--muted); }
92
- .badge-sm {
93
- display: inline-block;
94
- padding: 1px 7px;
95
- border-radius: 999px;
96
- font-size: 10px;
97
- font-weight: 700;
98
- }
99
- .badge-draft { background: var(--warn-soft); color: var(--warn); }
100
- .badge-published { background: var(--ok-soft); color: var(--ok); }
101
-
102
- /* Main */
103
- .main { display: flex; flex-direction: column; overflow: hidden; }
104
-
105
- /* Steps bar */
106
- .steps-bar {
107
- display: flex;
108
- align-items: center;
109
- gap: 0;
110
- padding: 14px 20px;
111
- background: var(--panel);
112
- border-bottom: 1px solid var(--line);
113
- flex-wrap: wrap;
114
- }
115
- .step-dot {
116
- display: flex;
117
- align-items: center;
118
- gap: 8px;
119
- font-size: 12px;
120
- font-weight: 700;
121
- color: var(--muted);
122
- cursor: pointer;
123
- padding: 6px 12px;
124
- border-radius: 999px;
125
- transition: all 0.15s;
126
- white-space: nowrap;
127
- }
128
- .step-dot:hover { background: var(--soft); }
129
- .step-dot.active { background: var(--accent-soft); color: var(--accent-dark); }
130
- .step-dot.done { color: var(--ok); }
131
- .step-dot .num {
132
- width: 22px; height: 22px;
133
- border-radius: 50%;
134
- display: flex; align-items: center; justify-content: center;
135
- font-size: 11px; font-weight: 800;
136
- background: var(--line); color: var(--muted);
137
- flex-shrink: 0;
138
- }
139
- .step-dot.active .num { background: var(--accent); color: white; }
140
- .step-dot.done .num { background: var(--ok); color: white; }
141
- .step-arrow { color: var(--line); font-size: 14px; margin: 0 4px; flex-shrink: 0; }
142
-
143
- /* Content */
144
- .content { flex: 1; overflow-y: auto; padding: 20px; }
145
- .step-panel { display: none; }
146
- .step-panel.active { display: block; }
147
-
148
- /* Cards */
149
- .card {
150
- background: var(--panel);
151
- border: 1px solid var(--line);
152
- border-radius: var(--radius);
153
- box-shadow: var(--shadow);
154
- margin-bottom: 16px;
155
- }
156
- .card-head {
157
- padding: 12px 16px;
158
- font-size: 14px;
159
- font-weight: 800;
160
- border-bottom: 1px solid var(--line);
161
- }
162
- .card-body { padding: 16px; }
163
-
164
- /* Form */
165
- .field { margin-bottom: 14px; }
166
- .field:last-child { margin-bottom: 0; }
167
- .field label {
168
- display: block;
169
- font-size: 12px; font-weight: 700;
170
- color: var(--muted);
171
- margin-bottom: 5px;
172
- text-transform: uppercase;
173
- letter-spacing: 0.03em;
174
- }
175
- .field .hint { font-size: 11px; color: var(--muted); margin-top: 4px; }
176
- input[type="text"], textarea, select {
177
- width: 100%;
178
- padding: 10px 12px;
179
- border: 1px solid var(--line);
180
- border-radius: 10px;
181
- font: inherit;
182
- font-size: 13px;
183
- color: var(--text);
184
- background: var(--soft);
185
- transition: border-color 0.15s;
186
- }
187
- input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent); }
188
- textarea { resize: vertical; min-height: 80px; line-height: 1.5; }
189
-
190
- /* Presets */
191
- .preset-grid {
192
- display: grid;
193
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
194
- gap: 10px;
195
- margin-bottom: 16px;
196
- }
197
- .preset-card {
198
- border: 2px solid var(--line);
199
- border-radius: var(--radius);
200
- padding: 14px;
201
- cursor: pointer;
202
- transition: all 0.15s;
203
- text-align: center;
204
- }
205
- .preset-card:hover { border-color: var(--accent); background: var(--accent-soft); }
206
- .preset-card.selected { border-color: var(--accent); background: var(--accent-soft); }
207
- .preset-card .preset-icon { font-size: 28px; margin-bottom: 6px; }
208
- .preset-card .preset-name { font-size: 13px; font-weight: 700; }
209
- .preset-card .preset-desc { font-size: 11px; color: var(--muted); margin-top: 3px; }
210
-
211
- /* Prompt/Skill preview */
212
- .prompt-box {
213
- background: #0f1a2e;
214
- color: #d7e6ff;
215
- border-radius: var(--radius);
216
- padding: 16px;
217
- font-family: 'IBM Plex Mono', 'D2Coding', monospace;
218
- font-size: 12px;
219
- line-height: 1.6;
220
- white-space: pre-wrap;
221
- word-break: break-word;
222
- max-height: 400px;
223
- overflow-y: auto;
224
- }
225
- .skill-preview {
226
- background: var(--soft);
227
- border: 1px solid var(--line);
228
- border-radius: var(--radius);
229
- padding: 16px;
230
- font-family: 'IBM Plex Mono', 'D2Coding', monospace;
231
- font-size: 12px;
232
- line-height: 1.6;
233
- white-space: pre-wrap;
234
- word-break: break-word;
235
- max-height: 500px;
236
- overflow-y: auto;
237
- }
238
-
239
- /* Timeline */
240
- .timeline { display: grid; gap: 8px; }
241
- .timeline-item { display: grid; grid-template-columns: 40px 1fr; gap: 10px; }
242
- .timeline-dot { display: flex; flex-direction: column; align-items: center; }
243
- .timeline-dot .dot {
244
- width: 12px; height: 12px; border-radius: 50%;
245
- background: var(--accent); border: 2px solid var(--accent-soft);
246
- }
247
- .timeline-dot .line-seg { width: 2px; flex: 1; background: var(--line); margin-top: 4px; }
248
- .timeline-content {
249
- background: var(--soft);
250
- border: 1px solid var(--line);
251
- border-radius: 10px;
252
- padding: 10px 12px;
253
- font-size: 12px;
254
- }
255
- .timeline-content .ver { font-weight: 800; }
256
- .timeline-content .summary { color: var(--muted); margin-top: 3px; }
257
-
258
- /* Chat */
259
- .chat-area {
260
- border: 1px solid var(--line);
261
- border-radius: var(--radius);
262
- background: var(--soft);
263
- max-height: 300px;
264
- overflow-y: auto;
265
- padding: 12px;
266
- display: flex;
267
- flex-direction: column;
268
- gap: 8px;
269
- }
270
- .chat-msg {
271
- max-width: 85%;
272
- padding: 10px 14px;
273
- border-radius: 14px;
274
- font-size: 13px;
275
- line-height: 1.45;
276
- word-break: break-word;
277
- }
278
- .chat-msg.user {
279
- align-self: flex-end;
280
- background: var(--accent);
281
- color: white;
282
- border-bottom-right-radius: 4px;
283
- }
284
- .chat-msg.system {
285
- align-self: flex-start;
286
- background: var(--panel);
287
- border: 1px solid var(--line);
288
- border-bottom-left-radius: 4px;
289
- }
290
- .chat-input-row { display: flex; gap: 8px; margin-top: 8px; }
291
- .chat-input-row input { flex: 1; }
292
-
293
- /* Buttons */
294
- .btn {
295
- display: inline-flex;
296
- align-items: center;
297
- gap: 6px;
298
- padding: 9px 16px;
299
- border: 1px solid var(--line);
300
- border-radius: 10px;
301
- font: inherit;
302
- font-size: 13px;
303
- font-weight: 700;
304
- cursor: pointer;
305
- transition: all 0.12s;
306
- background: var(--panel);
307
- color: var(--text);
308
- }
309
- .btn:hover { background: var(--soft); }
310
- .btn-accent { background: var(--accent); color: white; border-color: var(--accent-dark); }
311
- .btn-accent:hover { background: var(--accent-dark); }
312
- .btn-ok { background: var(--ok-soft); color: var(--ok); border-color: var(--ok); }
313
- .btn-warn { background: var(--warn-soft); color: var(--warn); border-color: var(--warn); }
314
- .btn-danger { background: var(--danger-soft); color: var(--danger); border-color: var(--danger); }
315
- .btn-row { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
316
- .two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
317
-
318
- /* Empty */
319
- .empty-state { text-align: center; padding: 60px 20px; color: var(--muted); }
320
- .empty-state .icon { font-size: 48px; margin-bottom: 12px; }
321
- .empty-state h2 { font-size: 18px; color: var(--text); margin-bottom: 6px; }
322
- .empty-state p { font-size: 13px; }
323
-
324
- /* Status */
325
- .status-bar {
326
- padding: 8px 20px;
327
- background: var(--panel);
328
- border-top: 1px solid var(--line);
329
- display: flex;
330
- align-items: center;
331
- gap: 16px;
332
- font-size: 11px;
333
- color: var(--muted);
334
- }
335
- .status-bar .dot-indicator {
336
- width: 8px; height: 8px; border-radius: 50%; background: var(--ok);
337
- }
338
-
339
- /* Drop zone */
340
- .dropzone {
341
- border: 2px dashed var(--line);
342
- border-radius: var(--radius);
343
- padding: 28px 20px;
344
- text-align: center;
345
- cursor: pointer;
346
- transition: all 0.2s;
347
- background: var(--soft);
348
- margin-bottom: 16px;
349
- position: relative;
350
- }
351
- .dropzone:hover, .dropzone.dragover {
352
- border-color: var(--accent);
353
- background: var(--accent-soft);
354
- }
355
- .dropzone .drop-icon { font-size: 36px; margin-bottom: 8px; }
356
- .dropzone .drop-main { font-size: 14px; font-weight: 700; color: var(--text); }
357
- .dropzone .drop-sub { font-size: 12px; color: var(--muted); margin-top: 4px; }
358
- .dropzone input[type="file"] {
359
- position: absolute;
360
- inset: 0;
361
- opacity: 0;
362
- cursor: pointer;
363
- }
364
- .file-info {
365
- display: flex;
366
- align-items: center;
367
- gap: 10px;
368
- padding: 10px 14px;
369
- border: 1px solid var(--accent);
370
- border-radius: var(--radius);
371
- background: var(--accent-soft);
372
- margin-bottom: 16px;
373
- }
374
- .file-info .file-icon { font-size: 24px; }
375
- .file-info .file-detail { flex: 1; }
376
- .file-info .file-name { font-size: 13px; font-weight: 700; }
377
- .file-info .file-meta { font-size: 11px; color: var(--muted); }
378
- .file-info .file-remove {
379
- background: none;
380
- border: none;
381
- color: var(--danger);
382
- font-size: 18px;
383
- cursor: pointer;
384
- padding: 4px;
385
- }
386
-
387
- .sidebar-toggle {
388
- display: none;
389
- position: fixed;
390
- top: 12px;
391
- left: 12px;
392
- z-index: 1000;
393
- padding: 10px 16px;
394
- background: var(--accent);
395
- color: white;
396
- border: none;
397
- border-radius: var(--radius);
398
- font-weight: 700;
399
- font-size: 13px;
400
- cursor: pointer;
401
- box-shadow: var(--shadow);
402
- }
403
-
404
- @media (max-width: 768px) {
405
- .app { grid-template-columns: 1fr; }
406
- .sidebar {
407
- display: none;
408
- position: fixed;
409
- top: 0;
410
- left: 0;
411
- height: 100vh;
412
- width: 260px;
413
- z-index: 999;
414
- box-shadow: 2px 0 12px rgba(0,0,0,0.15);
415
- }
416
- .sidebar.open { display: flex; }
417
- .sidebar-toggle { display: block; }
418
- .two-col { grid-template-columns: 1fr; }
419
- }
420
- </style>
421
- </head>
422
- <body>
423
- <button class="sidebar-toggle" onclick="toggleSidebar()">☰ Skills</button>
424
- <div class="app">
425
- <aside class="sidebar" id="sidebar">
426
- <div class="sidebar-header">
427
- <h1>Skill Lab</h1>
428
- <p>Create · Edit · Validate skills</p>
429
- </div>
430
- <button class="new-skill-btn" onclick="newSkill()">+ Create New Skill</button>
431
- <div class="skill-list" id="skillList"></div>
432
- </aside>
433
-
434
- <div class="main">
435
- <div class="steps-bar" id="stepsBar"></div>
436
-
437
- <div class="content" id="contentArea">
438
- <!-- Step 1: Define -->
439
- <div class="step-panel" id="step-define">
440
- <div class="card">
441
- <div class="card-head">What would you like to automate?</div>
442
- <div class="card-body">
443
- <!-- Drop zone -->
444
- <div class="dropzone" id="dropZone">
445
- <input type="file" id="fileInput" />
446
- <div class="drop-icon">📁</div>
447
- <div class="drop-main">Drag a file here or click to select</div>
448
- <div class="drop-sub">.pptx .docx .xlsx .csv .pdf etc. — Tool chain is detected automatically</div>
449
- </div>
450
- <div id="fileInfoArea"></div>
451
-
452
- <div class="preset-grid" id="presetGrid"></div>
453
- <div class="field">
454
- <label>Purpose</label>
455
- <textarea id="purposeInput" placeholder="e.g. Translate a Japanese PPTX to English and save without breaking the layout"></textarea>
456
- <div class="hint">Write freely in natural language. The LLM will structure it.</div>
457
- </div>
458
- <div class="two-col">
459
- <div class="field">
460
- <label>Input File / Data</label>
461
- <input type="text" id="inputTypeField" placeholder="e.g. .pptx, .csv, API response" />
462
- </div>
463
- <div class="field">
464
- <label>Output Format</label>
465
- <input type="text" id="outputTypeField" placeholder="e.g. translated .pptx, cleaned .csv" />
466
- </div>
467
- </div>
468
- <div class="field">
469
- <label>Constraints / Notes (optional)</label>
470
- <textarea id="constraintField" rows="3" placeholder="e.g. Layout must not break, do not translate proper nouns, glossary required"></textarea>
471
- </div>
472
- <div class="btn-row">
473
- <button class="btn btn-accent" onclick="goToStep('prompt')">Generate Prompt →</button>
474
- </div>
475
- </div>
476
- </div>
477
- </div>
478
-
479
- <!-- Step 2: Prompt -->
480
- <div class="step-panel" id="step-prompt">
481
- <div class="card">
482
- <div class="card-head">Detected Tool Chain</div>
483
- <div class="card-body" id="toolchainInfo">
484
- <div style="color:var(--muted);font-size:12px;">Required tools are automatically detected based on input file type.</div>
485
- </div>
486
- </div>
487
- <div class="card">
488
- <div class="card-head">Generated Prompt</div>
489
- <div class="card-body">
490
- <p style="font-size:12px;color:var(--muted);margin-bottom:12px;">
491
- This prompt includes file reception paths, required libraries, and processing script guides. You can edit it directly before sending.
492
- </p>
493
- <div class="prompt-box" id="promptPreview" contenteditable="true"></div>
494
- <div class="btn-row">
495
- <button class="btn" onclick="goToStep('define')">← Edit</button>
496
- <button class="btn" onclick="copyPrompt()">Copy</button>
497
- <button class="btn btn-accent" onclick="sendPrompt()">Send to Chat →</button>
498
- </div>
499
- </div>
500
- </div>
501
- </div>
502
-
503
- <!-- Step 3: Review -->
504
- <div class="step-panel" id="step-review">
505
- <div class="two-col">
506
- <div class="card">
507
- <div class="card-head">SKILL.md Draft</div>
508
- <div class="card-body">
509
- <div class="skill-preview" id="skillPreview">Waiting for LLM response...
510
-
511
- After sending to Chat, the generated SKILL.md will appear here.
512
- You can also paste (Ctrl+V) a response into this area.</div>
513
- <div class="btn-row">
514
- <button class="btn" onclick="copySkill()">Copy SKILL.md</button>
515
- <button class="btn" onclick="pasteSkillManual()">Paste</button>
516
- </div>
517
- </div>
518
- </div>
519
- <div class="card">
520
- <div class="card-head">Validation Results</div>
521
- <div class="card-body">
522
- <div id="validationResults">
523
- <div style="color:var(--muted);font-size:13px;">Validation runs automatically once a SKILL.md is loaded.</div>
524
- </div>
525
- </div>
526
- </div>
527
- </div>
528
- <div class="btn-row">
529
- <button class="btn" onclick="goToStep('prompt')">← Prompt</button>
530
- <button class="btn btn-accent" onclick="goToStep('refine')">Add Feedback →</button>
531
- <button class="btn btn-ok" onclick="goToStep('publish')">Go to Publish →</button>
532
- </div>
533
- </div>
534
-
535
- <!-- Step 4: Refine -->
536
- <div class="step-panel" id="step-refine">
537
- <div class="card">
538
- <div class="card-head">Feedback Loop</div>
539
- <div class="card-body">
540
- <p style="font-size:12px;color:var(--muted);margin-bottom:12px;">
541
- Enter your changes in natural language. A revision prompt will be generated and sent to the LLM.
542
- </p>
543
- <div class="chat-area" id="chatArea"></div>
544
- <div class="chat-input-row">
545
- <input type="text" id="feedbackInput" placeholder="e.g. Change tone to formal, add retry logic" onkeydown="if(event.key==='Enter')sendFeedback()" />
546
- <button class="btn btn-accent" onclick="sendFeedback()">Send</button>
547
- </div>
548
- </div>
549
- </div>
550
- <div class="card">
551
- <div class="card-head">Current SKILL.md <button class="btn" style="float:right;font-size:11px;padding:2px 10px;" onclick="toggleRefinePreview()">Collapse/Expand</button></div>
552
- <div class="card-body" id="refinePreviewBody">
553
- <pre id="refineSkillPreview" style="max-height:300px;overflow:auto;font-size:12px;background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:10px;white-space:pre-wrap;word-break:break-all;"></pre>
554
- </div>
555
- </div>
556
- <div class="card">
557
- <div class="card-head">Version History</div>
558
- <div class="card-body">
559
- <div class="timeline" id="versionTimeline"></div>
560
- </div>
561
- </div>
562
- <div class="btn-row">
563
- <button class="btn" onclick="goToStep('review')">← View Draft</button>
564
- <button class="btn btn-ok" onclick="goToStep('publish')">Go to Publish →</button>
565
- </div>
566
- </div>
567
-
568
- <!-- Step 5: Publish -->
569
- <div class="step-panel" id="step-publish">
570
- <div class="card">
571
- <div class="card-head">Final Review & Publish</div>
572
- <div class="card-body">
573
- <div class="two-col">
574
- <div>
575
- <div class="field">
576
- <label>Skill Name</label>
577
- <input type="text" id="publishName" />
578
- </div>
579
- <div class="field">
580
- <label>Save Path</label>
581
- <input type="text" id="publishPath" />
582
- </div>
583
- <div class="field">
584
- <label>Version</label>
585
- <input type="text" id="publishVersion" readonly />
586
- </div>
587
- </div>
588
- <div>
589
- <div class="field">
590
- <label>Iterations</label>
591
- <div id="publishIterations" style="font-size:24px;font-weight:800;color:var(--accent);padding:8px 0;">0</div>
592
- </div>
593
- <div class="field">
594
- <label>Status</label>
595
- <div id="publishStatus"></div>
596
- </div>
597
- </div>
598
- </div>
599
- <div class="field" style="margin-top:16px;">
600
- <label>Final SKILL.md</label>
601
- <div class="skill-preview" id="finalSkillPreview" style="max-height:300px;"></div>
602
- </div>
603
- <div class="btn-row">
604
- <button class="btn" onclick="goToStep('refine')">← Continue Editing</button>
605
- <button class="btn btn-ok" onclick="publishSkill()">Approve & Publish</button>
606
- <button class="btn btn-accent" onclick="sendSaveCommand()">Save File → Chat</button>
607
- </div>
608
- </div>
609
- </div>
610
- </div>
611
-
612
- <!-- Empty state -->
613
- <div class="step-panel active" id="step-empty">
614
- <div class="empty-state">
615
- <div class="icon">🧪</div>
616
- <h2>Skill Lab</h2>
617
- <p>Select an existing skill from the left, or create a new one.</p>
618
- <button class="btn btn-accent" style="margin-top:16px;" onclick="newSkill()">+ Create New Skill</button>
619
- </div>
620
- </div>
621
- </div>
622
-
623
- <div class="status-bar">
624
- <div class="dot-indicator" id="statusDot"></div>
625
- <span id="statusText">Ready</span>
626
- <span style="margin-left:auto;" id="statusVersion"></span>
627
- </div>
628
- </div>
629
- </div>
630
-
631
- <script>
632
- function toggleSidebar() {
633
- var sidebar = document.getElementById('sidebar');
634
- if (sidebar) sidebar.classList.toggle('open');
635
- }
636
-
637
- var STEPS = ['define','prompt','review','refine','publish'];
638
- var STEP_LABELS = {define:'Define',prompt:'Prompt',review:'Review Draft',refine:'Feedback',publish:'Publish'};
639
-
640
- // --- Tool chain registry: file type -> processing tools ---
641
- var TOOLCHAINS = {
642
- '.pptx': {
643
- lib: 'python-pptx',
644
- install: 'pip install python-pptx',
645
- runtime: 'python3',
646
- fileReceive: 'webchat upload / Discord-Slack attach / local path',
647
- inboundPath: '~/.mama/workspace/media/inbound/',
648
- processing: [
649
- 'Extract text per slide using python-pptx (shape.text_frame)',
650
- 'Call LLM translation or translation API',
651
- 'Copy original PPTX and replace with translated text',
652
- 'Verify layout/font/position preservation'
653
- ],
654
- outputPath: '~/.mama/workspace/media/outbound/',
655
- scriptTemplate: 'from pptx import Presentation\\nprs = Presentation(input_path)\\nfor slide in prs.slides:\\n for shape in slide.shapes:\\n if shape.has_text_frame:\\n for para in shape.text_frame.paragraphs:\\n # translate para.text\\nprs.save(output_path)',
656
- risks: ['Font embedding missing may cause garbled text', 'Text length changes may cause layout overflow', 'Text inside charts/SmartArt requires separate handling']
657
- },
658
- '.csv': {
659
- lib: 'pandas',
660
- install: 'pip install pandas',
661
- runtime: 'python3',
662
- fileReceive: 'webchat upload / local path',
663
- inboundPath: '~/.mama/workspace/media/inbound/',
664
- processing: [
665
- 'Load with pandas.read_csv()',
666
- 'Remove duplicates (drop_duplicates)',
667
- 'Handle NULLs (fillna / dropna)',
668
- 'Normalize types (astype)',
669
- 'Save results (to_csv)'
670
- ],
671
- outputPath: '~/.mama/workspace/media/outbound/',
672
- scriptTemplate: 'import pandas as pd\\ndf = pd.read_csv(input_path)\\ndf = df.drop_duplicates()\\ndf = df.dropna(subset=required_cols)\\ndf.to_csv(output_path, index=False)',
673
- risks: ['Encoding issues (UTF-8/EUC-KR)', 'Large files may exceed memory', 'Column type auto-inference errors']
674
- },
675
- '.docx': {
676
- lib: 'python-docx',
677
- install: 'pip install python-docx',
678
- runtime: 'python3',
679
- fileReceive: 'webchat upload / local path',
680
- inboundPath: '~/.mama/workspace/media/inbound/',
681
- processing: [
682
- 'Load document with python-docx',
683
- 'Extract/transform text per paragraph',
684
- 'Preserve table/list structure',
685
- 'Maintain image paths'
686
- ],
687
- outputPath: '~/.mama/workspace/media/outbound/',
688
- scriptTemplate: 'from docx import Document\\ndoc = Document(input_path)\\nfor para in doc.paragraphs:\\n # process para.text\\ndoc.save(output_path)',
689
- risks: ['Complex formatting (headers/footers/footnotes) may be lost', 'Image reference path changes may cause breakage']
690
- },
691
- '.xlsx': {
692
- lib: 'openpyxl',
693
- install: 'pip install openpyxl',
694
- runtime: 'python3',
695
- fileReceive: 'webchat upload / local path',
696
- inboundPath: '~/.mama/workspace/media/inbound/',
697
- processing: [
698
- 'Load with openpyxl.load_workbook()',
699
- 'Process data per sheet/cell',
700
- 'Preserve formulas/formatting',
701
- 'Save results'
702
- ],
703
- outputPath: '~/.mama/workspace/media/outbound/',
704
- scriptTemplate: 'from openpyxl import load_workbook\\nwb = load_workbook(input_path)\\nfor sheet in wb.sheetnames:\\n ws = wb[sheet]\\n # process cells\\nwb.save(output_path)',
705
- risks: ['Macros (.xlsm) not supported', 'Formula language locale differences', 'Chart data requires separate handling']
706
- },
707
- '.pdf': {
708
- lib: 'pymupdf (fitz)',
709
- install: 'pip install pymupdf',
710
- runtime: 'python3',
711
- fileReceive: 'webchat upload / local path',
712
- inboundPath: '~/.mama/workspace/media/inbound/',
713
- processing: [
714
- 'Load PDF with fitz.open()',
715
- 'Extract text per page',
716
- 'Extract images (optional)',
717
- 'Convert to markdown/text'
718
- ],
719
- outputPath: '~/.mama/workspace/media/outbound/',
720
- scriptTemplate: 'import fitz\\ndoc = fitz.open(input_path)\\nfor page in doc:\\n text = page.get_text()\\n # process text',
721
- risks: ['Scanned PDFs require OCR (tesseract)', 'Table structure extraction may be inaccurate', 'Encrypted PDFs cannot be processed']
722
- }
723
- };
724
-
725
- function detectToolchain(inputType) {
726
- if (!inputType) return null;
727
- var lower = inputType.toLowerCase();
728
- var keys = Object.keys(TOOLCHAINS);
729
- for (var i = 0; i < keys.length; i++) {
730
- if (lower.indexOf(keys[i]) >= 0) return TOOLCHAINS[keys[i]];
731
- }
732
- return null;
733
- }
734
-
735
- var PRESETS = [
736
- {id:'pptx-translate',icon:'\uD83D\uDCCA',name:'PPTX Translation',desc:'Automate slide translation',
737
- purpose:'Translate a Japanese PPTX to English, preserve layout',input:'.pptx',output:'translated .pptx',
738
- constraints:'No layout breakage\nKeep proper nouns as-is\nGlossary support'},
739
- {id:'csv-clean',icon:'\uD83D\uDCCB',name:'CSV Cleanup',desc:'Automate data cleaning',
740
- purpose:'Remove duplicates, handle NULLs, normalize types in a CSV file',input:'.csv',output:'cleaned .csv',
741
- constraints:'Preserve original column order\nNo data loss'},
742
- {id:'doc-convert',icon:'\uD83D\uDCC4',name:'Document Conversion',desc:'Automate format conversion',
743
- purpose:'Convert between DOCX/MD/HTML, preserve styles',input:'.docx, .md, .html',output:'converted document',
744
- constraints:'Maintain image paths\nPreserve table/list structure'},
745
- {id:'xlsx-process',icon:'\uD83D\uDCCA',name:'Excel Processing',desc:'Automate spreadsheets',
746
- purpose:'Process, translate, and analyze Excel data',input:'.xlsx',output:'processed .xlsx',
747
- constraints:'Preserve formulas\nMaintain formatting\nPreserve sheet structure'},
748
- {id:'pdf-extract',icon:'\uD83D\uDCC3',name:'PDF Extraction',desc:'Extract PDF text/images',
749
- purpose:'Extract text/tables/images from PDF and convert',input:'.pdf',output:'extracted .md or .txt',
750
- constraints:'Preserve table structure as much as possible\nMaintain page order'},
751
- {id:'blank',icon:'\uD83C\uDD95',name:'Blank Canvas',desc:'Start from scratch',
752
- purpose:'',input:'',output:'',constraints:''}
753
- ];
754
-
755
- var FILE_ICONS = {'.pptx':'\uD83D\uDCCA','.csv':'\uD83D\uDCCB','.docx':'\uD83D\uDCC4','.xlsx':'\uD83D\uDCCA','.pdf':'\uD83D\uDCC3','.md':'\uD83D\uDCDD','.html':'\uD83C\uDF10','.txt':'\uD83D\uDCC4'};
756
-
757
- var state = {
758
- skills: [],
759
- activeSkillId: null,
760
- currentStep: 'empty',
761
- stepsDone: {},
762
- version: {major:1, minor:0, patch:0},
763
- history: [],
764
- chatLog: [],
765
- skillContent: '',
766
- iterations: 0,
767
- published: false,
768
- droppedFile: null
769
- };
770
-
771
- function loadSkillList() {
772
- var saved = [];
773
- try { saved = JSON.parse(localStorage.getItem('skillLab_skills') || '[]'); } catch(e){}
774
-
775
- Promise.all([
776
- fetch('/api/workspace/skills').then(function(r){ return r.json(); }).catch(function(){ return {skills:[]}; }),
777
- fetch('/api/skills').then(function(r){ return r.json(); }).catch(function(){ return {skills:[]}; })
778
- ]).then(function(results) {
779
- var workspaceSkills = results[0].skills || [];
780
- var registrySkills = results[1].skills || [];
781
-
782
- var ids = {};
783
- saved.forEach(function(s){ ids[s.id] = true; });
784
-
785
- workspaceSkills.forEach(function(s) {
786
- if (!ids[s.id]) {
787
- saved.push({id: s.id, name: s.id, status: 'published', version: 'v1.0.0', path: s.path, _source: 'workspace'});
788
- ids[s.id] = true;
789
- }
790
- });
791
-
792
- registrySkills.forEach(function(s) {
793
- if (!ids[s.id]) {
794
- saved.push({
795
- id: s.id, name: s.name || s.id, status: 'published',
796
- version: 'v1.0.0', path: '',
797
- _source: s.source || 'mama', _registrySource: s.source || 'mama'
798
- });
799
- ids[s.id] = true;
800
- }
801
- });
802
-
803
- state.skills = saved;
804
- persistSkills();
805
- renderSkillList();
806
- });
807
- }
808
-
809
- function persistSkills() {
810
- try { localStorage.setItem('skillLab_skills', JSON.stringify(state.skills)); } catch(e){}
811
- }
812
-
813
- function persistSkillContent(id, content) {
814
- if (!id || !content) return;
815
- try { localStorage.setItem('skillLab_content_'+id, content); } catch(e){}
816
- }
817
-
818
- function loadSkillContent(id) {
819
- try { return localStorage.getItem('skillLab_content_'+id) || ''; } catch(e){ return ''; }
820
- }
821
-
822
- function renderSkillList() {
823
- var html = state.skills.map(function(s) {
824
- var cls = s.id === state.activeSkillId ? 'skill-item active' : 'skill-item';
825
- var badge = s.status === 'published'
826
- ? '<span class="badge-sm badge-published">published</span>'
827
- : '<span class="badge-sm badge-draft">draft</span>';
828
- var sourceTag = s._registrySource
829
- ? ' <span style="font-size:9px;color:var(--accent);font-weight:700;">'+esc(s._registrySource)+'</span>'
830
- : '';
831
- return '<div class="'+cls+'" data-skill-id="'+esc(s.id)+'" role="button" tabindex="0">'
832
- + '<div class="skill-name">'+esc(s.name)+' '+badge+sourceTag+'</div>'
833
- + '<div class="skill-meta">'+esc(s.version)+' \u00B7 '+esc(s.path||s._registrySource||'')+'</div>'
834
- + '</div>';
835
- }).join('');
836
- document.getElementById('skillList').innerHTML = html;
837
- // Event delegation for skill selection
838
- document.querySelectorAll('.skill-item[data-skill-id]').forEach(function(item) {
839
- item.addEventListener('click', function() {
840
- var skillId = this.getAttribute('data-skill-id');
841
- selectSkill(skillId);
842
- });
843
- item.addEventListener('keydown', function(e) {
844
- if (e.key === 'Enter' || e.key === ' ') {
845
- e.preventDefault();
846
- this.click();
847
- }
848
- });
849
- });
850
- }
851
-
852
- function selectSkill(id) {
853
- var skill = state.skills.find(function(s){return s.id===id;});
854
- if (!skill) return;
855
- state.activeSkillId = id;
856
- renderSkillList();
857
-
858
- var saved = loadSkillContent(id);
859
- if (saved) {
860
- state.skillContent = saved;
861
- } else if (skill._registrySource) {
862
- state.skillContent = 'Loading file...';
863
- var src = encodeURIComponent(skill._registrySource);
864
- fetch('/api/skills/'+encodeURIComponent(id)+'/readme?source='+src)
865
- .then(function(r){ return r.json(); })
866
- .then(function(data){
867
- if (state.activeSkillId !== id) return; // selection changed
868
- if (data.content) {
869
- state.skillContent = data.content;
870
- persistSkillContent(id, data.content);
871
- if (state.currentStep === 'review') {
872
- document.getElementById('skillPreview').textContent = state.skillContent;
873
- }
874
- if (state.currentStep === 'publish') renderPublish();
875
- updateStatus('Skill loaded', skill.version);
876
- }
877
- })
878
- .catch(function(){ state.skillContent = '(Failed to read file)'; });
879
- } else if (skill.path) {
880
- state.skillContent = 'Loading file...';
881
- fetch('/api/workspace/skills/'+encodeURIComponent(id)+'/content')
882
- .then(function(r){ return r.json(); })
883
- .then(function(data){
884
- if (state.activeSkillId !== id) return; // selection changed
885
- if (data.content) {
886
- state.skillContent = data.content;
887
- persistSkillContent(id, data.content);
888
- if (state.currentStep === 'review') {
889
- document.getElementById('skillPreview').textContent = state.skillContent;
890
- }
891
- if (state.currentStep === 'publish') renderPublish();
892
- updateStatus('Skill loaded', skill.version);
893
- }
894
- })
895
- .catch(function(){ state.skillContent = '(Failed to read file)'; });
896
- } else {
897
- state.skillContent = '(No skill content)';
898
- }
899
- state.currentStep = 'review';
900
- state.stepsDone = {define:true, prompt:true};
901
- state.version = parseVersion(skill.version);
902
- state.iterations = 0;
903
- state.published = skill.status === 'published';
904
- state.chatLog = [{role:'system',text:'Existing skill "'+skill.name+'" selected. Enter your changes.'}];
905
- state.history = [{ver:skill.version, summary:'Existing version', time:now()}];
906
-
907
- renderSteps();
908
- showStep('review');
909
- document.getElementById('skillPreview').textContent = state.skillContent;
910
- document.getElementById('promptPreview').textContent = saved ? '(Loaded from cache)' : '(File read request sent)';
911
- updateStatus('Skill loaded: '+skill.name, skill.version);
912
- }
913
-
914
- function newSkill() {
915
- var id = 'skill-'+Date.now();
916
- state.activeSkillId = id;
917
- state.currentStep = 'define';
918
- state.stepsDone = {};
919
- state.version = {major:1,minor:0,patch:0};
920
- state.history = [];
921
- state.chatLog = [];
922
- state.skillContent = '';
923
- state.iterations = 0;
924
- state.published = false;
925
-
926
- state.skills.push({id: id, name: id, status: 'draft', version: 'v1.0.0', path: ''});
927
- persistSkills();
928
-
929
- document.getElementById('purposeInput').value = '';
930
- document.getElementById('inputTypeField').value = '';
931
- document.getElementById('outputTypeField').value = '';
932
- document.getElementById('constraintField').value = '';
933
- state.droppedFile = null;
934
- renderFileInfo();
935
- clearPresetSelection();
936
- renderSkillList();
937
- renderSteps();
938
- showStep('define');
939
- updateStatus('New skill creation started', 'v1.0.0');
940
- }
941
-
942
- function renderPresets() {
943
- var html = PRESETS.map(function(p) {
944
- var tc = detectToolchain(p.input);
945
- var libTag = tc ? '<div style="font-size:10px;color:var(--accent);margin-top:4px;font-weight:700;">'+esc(tc.lib)+'</div>' : '';
946
- return '<div class="preset-card" data-preset="'+esc(p.id)+'" role="button" tabindex="0">'
947
- + '<div class="preset-icon">'+p.icon+'</div>'
948
- + '<div class="preset-name">'+esc(p.name)+'</div>'
949
- + '<div class="preset-desc">'+esc(p.desc)+'</div>'
950
- + libTag
951
- + '</div>';
952
- }).join('');
953
- document.getElementById('presetGrid').innerHTML = html;
954
- // Event delegation for preset selection
955
- document.querySelectorAll('.preset-card[data-preset]').forEach(function(card) {
956
- card.addEventListener('click', function() {
957
- var presetId = this.getAttribute('data-preset');
958
- applyPreset(presetId);
959
- });
960
- card.addEventListener('keydown', function(e) {
961
- if (e.key === 'Enter' || e.key === ' ') {
962
- e.preventDefault();
963
- this.click();
964
- }
965
- });
966
- });
967
- }
968
-
969
- function applyPreset(id) {
970
- var p = PRESETS.find(function(x){return x.id===id;});
971
- if (!p) return;
972
- document.getElementById('purposeInput').value = p.purpose;
973
- document.getElementById('inputTypeField').value = p.input;
974
- document.getElementById('outputTypeField').value = p.output;
975
- document.getElementById('constraintField').value = p.constraints;
976
- clearPresetSelection();
977
- var el = document.querySelector('[data-preset="'+id+'"]');
978
- if (el) el.classList.add('selected');
979
- }
980
-
981
- function clearPresetSelection() {
982
- document.querySelectorAll('.preset-card').forEach(function(el){el.classList.remove('selected');});
983
- }
984
-
985
- function renderSteps() {
986
- var html = STEPS.map(function(s, i) {
987
- var cls = 'step-dot';
988
- if (s === state.currentStep) cls += ' active';
989
- else if (state.stepsDone[s]) cls += ' done';
990
- var numText = state.stepsDone[s] ? '\u2713' : String(i+1);
991
- var arrow = i < STEPS.length-1 ? '<span class="step-arrow">\u2192</span>' : '';
992
- return '<div class="'+cls+'" data-step="'+esc(s)+'" role="button" tabindex="0">'
993
- + '<span class="num">'+numText+'</span>'
994
- + STEP_LABELS[s]
995
- + '</div>' + arrow;
996
- }).join('');
997
- document.getElementById('stepsBar').innerHTML = html;
998
- // Event delegation for step navigation
999
- document.querySelectorAll('.step-dot[data-step]').forEach(function(dot) {
1000
- dot.addEventListener('click', function() {
1001
- var stepName = this.getAttribute('data-step');
1002
- goToStep(stepName);
1003
- });
1004
- dot.addEventListener('keydown', function(e) {
1005
- if (e.key === 'Enter' || e.key === ' ') {
1006
- e.preventDefault();
1007
- this.click();
1008
- }
1009
- });
1010
- });
1011
- }
1012
-
1013
- function goToStep(step) {
1014
- if (state.currentStep !== 'empty' && state.currentStep !== step) {
1015
- state.stepsDone[state.currentStep] = true;
1016
- }
1017
- state.currentStep = step;
1018
- if (step === 'prompt') buildPrompt();
1019
- if (step === 'review') renderReview();
1020
- if (step === 'refine') renderRefine();
1021
- if (step === 'publish') renderPublish();
1022
- renderSteps();
1023
- showStep(step);
1024
- }
1025
-
1026
- function showStep(step) {
1027
- document.querySelectorAll('.step-panel').forEach(function(el){el.classList.remove('active');});
1028
- var panel = document.getElementById('step-'+step);
1029
- if (panel) panel.classList.add('active');
1030
- }
1031
-
1032
- function buildPrompt() {
1033
- var purpose = document.getElementById('purposeInput').value.trim();
1034
- var inputType = document.getElementById('inputTypeField').value.trim();
1035
- var outputType = document.getElementById('outputTypeField').value.trim();
1036
- var constraints = document.getElementById('constraintField').value.trim();
1037
- var ver = versionStr();
1038
- var tc = detectToolchain(inputType);
1039
-
1040
- var lines = [];
1041
- lines.push('Generate an executable SKILL.md file based on the following requirements.');
1042
-
1043
- // Include dropped file info
1044
- if (state.droppedFile) {
1045
- lines.push('');
1046
- lines.push('## Uploaded File');
1047
- lines.push('- File name: '+state.droppedFile.name);
1048
- lines.push('- Extension: '+state.droppedFile.ext);
1049
- lines.push('- Size: '+state.droppedFile.sizeMB+' MB');
1050
- lines.push('- Saved at: ~/.mama/workspace/media/inbound/'+state.droppedFile.name);
1051
- lines.push('- Create a skill that processes this file');
1052
- }
1053
-
1054
- lines.push('');
1055
- lines.push('## Requirements');
1056
- lines.push('- Purpose: '+(purpose||'(not specified)'));
1057
- lines.push('- Input: '+(inputType||'(not specified)'));
1058
- lines.push('- Output: '+(outputType||'(not specified)'));
1059
- if (constraints) {
1060
- lines.push('- Constraints:');
1061
- constraints.split('\n').forEach(function(c){
1062
- c = c.trim();
1063
- if (c) lines.push(' - '+c);
1064
- });
1065
- }
1066
-
1067
- // --- File reception ---
1068
- lines.push('');
1069
- lines.push('## File Reception Path (skill must handle this)');
1070
- if (tc) {
1071
- lines.push('- Reception method: '+tc.fileReceive);
1072
- lines.push('- Upload path: '+tc.inboundPath);
1073
- lines.push('- Output path: '+tc.outputPath);
1074
- } else {
1075
- lines.push('- Reception method: webchat upload / Discord-Slack attach / local path');
1076
- lines.push('- Upload path: ~/.mama/workspace/media/inbound/');
1077
- lines.push('- Output path: ~/.mama/workspace/media/outbound/');
1078
- }
1079
- lines.push('- Workflow Step 1 must include file existence check + extension validation');
1080
-
1081
- // --- Toolchain ---
1082
- if (tc) {
1083
- lines.push('');
1084
- lines.push('## Required Tool Chain (must be specified in the skill)');
1085
- lines.push('- Library: '+tc.lib);
1086
- lines.push('- Install: `'+tc.install+'`');
1087
- lines.push('- Runtime: '+tc.runtime);
1088
- lines.push('');
1089
- lines.push('### Processing Steps');
1090
- tc.processing.forEach(function(step, i) {
1091
- lines.push((i+1)+'. '+step);
1092
- });
1093
- lines.push('');
1094
- lines.push('### Reference Script Template');
1095
- lines.push('```python');
1096
- lines.push(tc.scriptTemplate.replace(/\\n/g, '\n'));
1097
- lines.push('```');
1098
- lines.push('');
1099
- lines.push('### Known Risks (reflect in skill Constraints/Forbidden)');
1100
- tc.risks.forEach(function(r) {
1101
- lines.push('- \u26A0\uFE0F '+r);
1102
- });
1103
- }
1104
-
1105
- // --- SKILL.md format rules ---
1106
- lines.push('');
1107
- lines.push('## SKILL.md Format Rules');
1108
- lines.push('1. YAML frontmatter required: name, description, keywords (array)');
1109
- lines.push('2. Sections: Goal, Workflow (step-by-step), Input/Output Contract, Constraints, Forbidden');
1110
- lines.push('3. Workflow Step 1 must be "File reception and validation" (path check, extension check, file existence check)');
1111
- lines.push('4. Include required library install commands in the Workflow');
1112
- lines.push('5. Include specific bash/python execution instructions in each Workflow step');
1113
- lines.push('6. Specify failure behavior rules (no silent fallback, explicit error messages)');
1114
- lines.push('7. Include verifiable success conditions (AC)');
1115
- lines.push('8. Include a final step that delivers the output file path to the user');
1116
- lines.push('');
1117
- lines.push('## Meta');
1118
- lines.push('- Skill ID: '+(state.activeSkillId||'new-skill'));
1119
- lines.push('- Version: '+ver);
1120
- lines.push('- Iteration: #'+state.iterations);
1121
- lines.push('');
1122
- lines.push('Respond with the entire SKILL.md in a code block.');
1123
-
1124
- document.getElementById('promptPreview').textContent = lines.join('\n');
1125
-
1126
- // Show toolchain info card
1127
- renderToolchainInfo(tc);
1128
- }
1129
-
1130
- function renderToolchainInfo(tc) {
1131
- var container = document.getElementById('toolchainInfo');
1132
- if (!container) return;
1133
- if (!tc) {
1134
- container.innerHTML = '<div style="color:var(--muted);font-size:12px;">No matching tool chain for the input file type. The LLM will suggest appropriate tools.</div>';
1135
- return;
1136
- }
1137
- var html = '<div style="display:grid;gap:8px;">';
1138
- html += '<div style="display:flex;gap:12px;flex-wrap:wrap;">';
1139
- html += '<div style="padding:6px 12px;border-radius:8px;background:var(--accent-soft);font-size:12px;font-weight:700;color:var(--accent-dark);">\uD83D\uDCE6 '+esc(tc.lib)+'</div>';
1140
- html += '<div style="padding:6px 12px;border-radius:8px;background:var(--soft);font-size:12px;font-weight:700;border:1px solid var(--line);"><code>'+esc(tc.install)+'</code></div>';
1141
- html += '<div style="padding:6px 12px;border-radius:8px;background:var(--soft);font-size:12px;color:var(--muted);">Runtime: '+esc(tc.runtime)+'</div>';
1142
- html += '</div>';
1143
- html += '<div style="font-size:12px;color:var(--muted);">';
1144
- html += '<strong>Processing pipeline:</strong> ';
1145
- html += tc.processing.join(' \u2192 ');
1146
- html += '</div>';
1147
- if (tc.risks.length) {
1148
- html += '<div style="font-size:11px;color:var(--warn);">';
1149
- html += '\u26A0\uFE0F Known risks: '+tc.risks.join(' | ');
1150
- html += '</div>';
1151
- }
1152
- html += '</div>';
1153
- container.innerHTML = html;
1154
- }
1155
-
1156
- function copyPrompt() {
1157
- var btn = event.currentTarget || event.target; // capture before async
1158
- var text = document.getElementById('promptPreview').textContent;
1159
- navigator.clipboard.writeText(text).then(function(){
1160
- flashBtn(btn, 'Copied!');
1161
- }).catch(function() {
1162
- updateStatus('Clipboard access denied', versionStr());
1163
- });
1164
- }
1165
-
1166
- function sendPrompt() {
1167
- // Upload file first if dropped
1168
- if (state.droppedFile) {
1169
- sendFileToChat(function() {
1170
- var text = document.getElementById('promptPreview').textContent;
1171
- window.parent.postMessage({type:'playground:sendToChat', message: text}, window.location.origin);
1172
- state.iterations++;
1173
- updateStatus('Prompt sent (iteration #'+state.iterations+')', versionStr());
1174
- goToStep('review');
1175
- });
1176
- } else {
1177
- var text = document.getElementById('promptPreview').textContent;
1178
- window.parent.postMessage({type:'playground:sendToChat', message: text}, window.location.origin);
1179
- state.iterations++;
1180
- updateStatus('Prompt sent (iteration #'+state.iterations+')', versionStr());
1181
- goToStep('review');
1182
- }
1183
- }
1184
-
1185
- function renderReview() {
1186
- var preview = document.getElementById('skillPreview');
1187
- preview.textContent = state.skillContent || 'Waiting for LLM response...\n\nAfter sending to Chat, the generated SKILL.md will appear here.\nYou can also paste (Ctrl+V) a response into this area.';
1188
- renderValidation();
1189
- }
1190
-
1191
- function renderValidation() {
1192
- var content = state.skillContent;
1193
- if (!content || content.length < 20) {
1194
- document.getElementById('validationResults').innerHTML = '<div style="color:var(--muted);font-size:13px;">Validation runs automatically once a SKILL.md is loaded.</div>';
1195
- return;
1196
- }
1197
- var inputType = document.getElementById('inputTypeField').value.trim();
1198
- var tc = detectToolchain(inputType);
1199
- var checks = [
1200
- {name:'frontmatter', ok: content.indexOf('---') === 0, desc:'YAML frontmatter'},
1201
- {name:'name', ok: /name:\s*.+/i.test(content), desc:'name field'},
1202
- {name:'description', ok: /description:\s*.+/i.test(content), desc:'description field'},
1203
- {name:'keywords', ok: /keywords:/i.test(content), desc:'keywords field'},
1204
- {name:'workflow', ok: /##\s*Workflow/i.test(content), desc:'Workflow section'},
1205
- {name:'goal', ok: /##\s*Goal/i.test(content), desc:'Goal section'},
1206
- {name:'forbidden', ok: /##\s*Forbidden/i.test(content), desc:'Forbidden section'},
1207
- {name:'no-silent', ok: content.toLowerCase().indexOf('silent fallback') < 0 || content.toLowerCase().indexOf('forbidden') >= 0, desc:'No silent fallback rule'},
1208
- {name:'file-receive', ok: /inbound|upload|file.*recei|file.*path/i.test(content), desc:'File reception path specified'},
1209
- {name:'file-output', ok: /outbound|output.*path|save.*path/i.test(content), desc:'Output file path specified'},
1210
- {name:'install-cmd', ok: /pip install|npm install|apt install|install/i.test(content), desc:'Library install command included'},
1211
- {name:'exec-cmd', ok: /```(bash|python|sh)|python3|node /i.test(content), desc:'Executable code block included'}
1212
- ];
1213
- if (tc) {
1214
- checks.push({name:'lib-mentioned', ok: content.toLowerCase().indexOf(tc.lib.toLowerCase().split(' ')[0]) >= 0, desc:'Required library ('+tc.lib+') mentioned'});
1215
- }
1216
- var passCount = checks.filter(function(c){return c.ok;}).length;
1217
- var html = '<div style="font-size:14px;font-weight:800;margin-bottom:8px;color:'+(passCount===checks.length?'var(--ok)':'var(--warn)')+'">'+passCount+'/'+checks.length+' passed</div>';
1218
- html += checks.map(function(c){
1219
- var icon = c.ok ? '<span style="color:var(--ok)">\u2713</span>' : '<span style="color:var(--danger)">\u2717</span>';
1220
- return '<div style="padding:6px 0;font-size:13px;border-bottom:1px solid var(--line);">'+icon+' '+esc(c.desc)+'</div>';
1221
- }).join('');
1222
- document.getElementById('validationResults').innerHTML = html;
1223
- }
1224
-
1225
- function copySkill() {
1226
- navigator.clipboard.writeText(state.skillContent).then(function(){
1227
- flashBtn(event.target, 'Copied!');
1228
- });
1229
- }
1230
-
1231
- function pasteSkillManual() {
1232
- navigator.clipboard.readText().then(function(text){
1233
- if (text && text.length > 10) handleLLMResponse(text);
1234
- }).catch(function() {
1235
- updateStatus('Clipboard access denied — paste manually into the preview area', versionStr());
1236
- });
1237
- }
1238
-
1239
- function renderRefine() {
1240
- renderChatLog();
1241
- renderTimeline();
1242
- var preview = document.getElementById('refineSkillPreview');
1243
- if (preview) {
1244
- preview.textContent = state.skillContent || '(No SKILL.md draft yet. Please receive a draft first from the Review tab.)';
1245
- }
1246
- }
1247
-
1248
- function toggleRefinePreview() {
1249
- var body = document.getElementById('refinePreviewBody');
1250
- if (body) body.style.display = body.style.display === 'none' ? '' : 'none';
1251
- }
1252
-
1253
- function renderChatLog() {
1254
- var html = state.chatLog.map(function(m){
1255
- return '<div class="chat-msg '+m.role+'">'+esc(m.text)+'</div>';
1256
- }).join('');
1257
- var area = document.getElementById('chatArea');
1258
- area.innerHTML = html || '<div class="chat-msg system">Enter your changes and they will be sent to the LLM.</div>';
1259
- area.scrollTop = area.scrollHeight;
1260
- }
1261
-
1262
- function sendFeedback() {
1263
- var input = document.getElementById('feedbackInput');
1264
- var text = input.value.trim();
1265
- if (!text) return;
1266
-
1267
- state.chatLog.push({role:'user', text: text});
1268
- input.value = '';
1269
-
1270
- var refinePrompt = 'Revise the current SKILL.md based on the feedback below.\n\n'
1271
- + '## Feedback\n'+text+'\n\n'
1272
- + '## Current SKILL.md\n```\n'+(state.skillContent||'(none)')+'\n```\n\n'
1273
- + 'Respond with the entire revised SKILL.md in a code block.\n'
1274
- + 'Version: '+nextVersion();
1275
-
1276
- window.parent.postMessage({type:'playground:sendToChat', message: refinePrompt}, window.location.origin);
1277
- state.chatLog.push({role:'system', text:'Feedback sent to the LLM. Waiting for response...'});
1278
- state.iterations++;
1279
- state.history.unshift({ver: versionStr(), summary: text.slice(0,60), time: now()});
1280
-
1281
- renderChatLog();
1282
- renderTimeline();
1283
- updateStatus('Feedback sent (iteration #'+state.iterations+')', versionStr());
1284
- }
1285
-
1286
- function renderTimeline() {
1287
- if (!state.history.length) {
1288
- document.getElementById('versionTimeline').innerHTML = '<div style="color:var(--muted);font-size:12px;">No version history yet.</div>';
1289
- return;
1290
- }
1291
- var html = state.history.map(function(h, i) {
1292
- var isLast = i === state.history.length - 1;
1293
- return '<div class="timeline-item">'
1294
- + '<div class="timeline-dot"><div class="dot"></div>'+(isLast?'':'<div class="line-seg"></div>')+'</div>'
1295
- + '<div class="timeline-content">'
1296
- + '<div class="ver">'+esc(h.ver)+'</div>'
1297
- + '<div class="summary">'+esc(h.summary)+' \u00B7 '+esc(h.time)+'</div>'
1298
- + '</div></div>';
1299
- }).join('');
1300
- document.getElementById('versionTimeline').innerHTML = html;
1301
- }
1302
-
1303
- function renderPublish() {
1304
- var name = state.activeSkillId || 'new-skill';
1305
- document.getElementById('publishName').value = name;
1306
- document.getElementById('publishPath').value = '~/.mama/workspace/skills/'+name+'/SKILL.md';
1307
- document.getElementById('publishVersion').value = versionStr();
1308
- document.getElementById('publishIterations').textContent = String(state.iterations);
1309
- document.getElementById('publishStatus').innerHTML = state.published
1310
- ? '<span class="badge-sm badge-published">published</span>'
1311
- : '<span class="badge-sm badge-draft">draft</span>';
1312
- document.getElementById('finalSkillPreview').textContent = state.skillContent || '(No SKILL.md content)';
1313
- }
1314
-
1315
- function publishSkill() {
1316
- var name = document.getElementById('publishName').value.trim() || state.activeSkillId;
1317
- var content = state.skillContent;
1318
- if (!content) { alert('No SKILL.md content.'); return; }
1319
-
1320
- updateStatus('Saving...', versionStr());
1321
-
1322
- var isUpdate = state.published;
1323
- var url = isUpdate
1324
- ? '/api/skills/' + encodeURIComponent(name) + '/content'
1325
- : '/api/skills';
1326
- var method = isUpdate ? 'PUT' : 'POST';
1327
- var body = isUpdate
1328
- ? { content: content, source: 'mama' }
1329
- : { name: name, content: content, source: 'mama' };
1330
-
1331
- fetch(url, {
1332
- method: method,
1333
- headers: { 'Content-Type': 'application/json' },
1334
- body: JSON.stringify(body)
1335
- })
1336
- .then(function(r) {
1337
- if (!r.ok) return r.json().then(function(e) { throw new Error(e.message || 'HTTP ' + r.status); });
1338
- return r.json();
1339
- })
1340
- .then(function(result) {
1341
- state.published = true;
1342
- var existing = state.skills.find(function(s){return s.id===name;});
1343
- if (existing) {
1344
- existing.version = versionStr();
1345
- existing.status = 'published';
1346
- } else {
1347
- state.skills.push({
1348
- id: name, name: name, status: 'published',
1349
- version: versionStr(),
1350
- path: result.path || ''
1351
- });
1352
- }
1353
- state.activeSkillId = name;
1354
- persistSkills();
1355
- renderSkillList();
1356
- renderPublish();
1357
- updateStatus('Published!', versionStr());
1358
- // Notify parent viewer to refresh Skills Tab
1359
- window.parent.postMessage({type: 'skill:deployed', id: name}, window.location.origin);
1360
- })
1361
- .catch(function(err) {
1362
- alert('Save failed: ' + err.message);
1363
- updateStatus('Save failed', versionStr());
1364
- });
1365
- }
1366
-
1367
- function sendSaveCommand() {
1368
- // Now uses the same direct API path as publishSkill
1369
- publishSkill();
1370
- }
1371
-
1372
- // Listen for LLM response, paste, or skill:load from viewer
1373
- window.addEventListener('message', function(e) {
1374
- if (e.origin !== window.location.origin) return;
1375
- if (e.source !== window.parent) return;
1376
- if (e.data && e.data.type === 'playground:response') {
1377
- handleLLMResponse(e.data.content || '');
1378
- }
1379
- if (e.data && e.data.type === 'skill:load') {
1380
- var d = e.data;
1381
- var id = d.id || d.name || 'loaded-skill';
1382
- var name = d.name || id;
1383
- var content = d.content || '';
1384
-
1385
- // Check if already in list
1386
- var existing = state.skills.find(function(s){return s.id===id;});
1387
- if (!existing) {
1388
- state.skills.push({id: id, name: name, status: 'published', version: 'v1.0.0', path: ''});
1389
- persistSkills();
1390
- }
1391
- state.activeSkillId = id;
1392
- state.skillContent = content;
1393
- state.published = true;
1394
- state.currentStep = 'review';
1395
- state.stepsDone = {define:true, prompt:true};
1396
- state.chatLog = [{role:'system',text:'Skill "'+name+'" loaded in edit mode.'}];
1397
- persistSkillContent(id, content);
1398
- renderSkillList();
1399
- renderSteps();
1400
- showStep('review');
1401
- document.getElementById('skillPreview').textContent = content;
1402
- updateStatus('Skill loaded: '+name, 'v1.0.0');
1403
- }
1404
- // Handle file upload from playground
1405
- if (e.data && e.data.type === 'playground:uploadFile') {
1406
- var d = e.data;
1407
- if (!d.data || !d.fileName) {
1408
- updateStatus('Upload error: missing file data', versionStr());
1409
- return;
1410
- }
1411
- // Extract base64 data from data URI
1412
- var base64Data = d.data.split(',')[1] || d.data;
1413
- var uploadPath = '~/.mama/workspace/media/inbound/' + d.fileName;
1414
-
1415
- // Send file save request to parent/backend
1416
- fetch('/api/workspace/media/upload', {
1417
- method: 'POST',
1418
- headers: { 'Content-Type': 'application/json' },
1419
- body: JSON.stringify({
1420
- fileName: d.fileName,
1421
- fileType: d.fileType,
1422
- fileSize: d.fileSize,
1423
- data: base64Data,
1424
- path: 'inbound'
1425
- })
1426
- })
1427
- .then(function(r) { return r.json(); })
1428
- .then(function(result) {
1429
- updateStatus('File uploaded: ' + d.fileName + ' → ' + uploadPath, versionStr());
1430
- })
1431
- .catch(function(err) {
1432
- updateStatus('Upload failed: ' + err.message, versionStr());
1433
- });
1434
- }
1435
- });
1436
-
1437
- document.addEventListener('paste', function(e) {
1438
- var active = document.activeElement;
1439
- if (active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.isContentEditable)) return;
1440
- if (state.currentStep === 'review' || state.currentStep === 'refine') {
1441
- var text = (e.clipboardData || window.clipboardData).getData('text');
1442
- if (text && text.length > 30 && (text.indexOf('---') >= 0 || text.indexOf('# ') >= 0)) {
1443
- e.preventDefault();
1444
- handleLLMResponse(text);
1445
- }
1446
- }
1447
- });
1448
-
1449
- function handleLLMResponse(text) {
1450
- var extracted = extractSkillMd(text);
1451
- state.skillContent = extracted || text;
1452
- persistSkillContent(state.activeSkillId, state.skillContent);
1453
- state.chatLog.push({role:'system', text:'SKILL.md updated ('+state.skillContent.length+' chars)'});
1454
- if (state.currentStep === 'review') {
1455
- renderReview();
1456
- } else if (state.currentStep === 'refine') {
1457
- renderChatLog(); renderReview(); renderRefine();
1458
- } else {
1459
- goToStep('review');
1460
- }
1461
- updateStatus('SKILL.md received', versionStr());
1462
- }
1463
-
1464
- function extractSkillMd(text) {
1465
- var codeMatch = text.match(/```(?:markdown|md|yaml)?\s*\n([\s\S]*?)```/);
1466
- if (codeMatch) {
1467
- var inner = codeMatch[1].trim();
1468
- if (inner.indexOf('---') === 0) return inner;
1469
- }
1470
- if (text.trim().indexOf('---') === 0) return text.trim();
1471
- var fmMatch = text.match(/\n(---\s*\n[\s\S]*?\n---[\s\S]*)/);
1472
- if (fmMatch) return fmMatch[1].trim();
1473
- return null;
1474
- }
1475
-
1476
- function versionStr() { return 'v'+state.version.major+'.'+state.version.minor+'.'+state.version.patch; }
1477
- function nextVersion() { state.version.patch++; return versionStr(); }
1478
- function parseVersion(str) {
1479
- var m = (str||'').match(/v?(\d+)\.(\d+)\.(\d+)/);
1480
- return m ? {major:+m[1],minor:+m[2],patch:+m[3]} : {major:1,minor:0,patch:0};
1481
- }
1482
- function now() { return new Date().toLocaleTimeString('en-US',{hour12:false}); }
1483
- function esc(s) { return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
1484
- function flashBtn(btn, text) {
1485
- if (!btn) return;
1486
- var old = btn.textContent;
1487
- btn.textContent = text;
1488
- setTimeout(function(){btn.textContent=old;},900);
1489
- }
1490
- function updateStatus(text, ver) {
1491
- document.getElementById('statusText').textContent = text;
1492
- document.getElementById('statusVersion').textContent = ver || '';
1493
- }
1494
-
1495
- // --- Drag & Drop ---
1496
- function initDropZone() {
1497
- var zone = document.getElementById('dropZone');
1498
- var input = document.getElementById('fileInput');
1499
-
1500
- ['dragenter','dragover'].forEach(function(evt) {
1501
- zone.addEventListener(evt, function(e) {
1502
- e.preventDefault();
1503
- e.stopPropagation();
1504
- zone.classList.add('dragover');
1505
- });
1506
- });
1507
- ['dragleave','drop'].forEach(function(evt) {
1508
- zone.addEventListener(evt, function(e) {
1509
- e.preventDefault();
1510
- e.stopPropagation();
1511
- zone.classList.remove('dragover');
1512
- });
1513
- });
1514
-
1515
- zone.addEventListener('drop', function(e) {
1516
- var files = e.dataTransfer.files;
1517
- if (files.length > 0) handleFile(files[0]);
1518
- });
1519
-
1520
- input.addEventListener('change', function(e) {
1521
- if (e.target.files.length > 0) handleFile(e.target.files[0]);
1522
- });
1523
- }
1524
-
1525
- function handleFile(file) {
1526
- var name = file.name;
1527
- var ext = name.lastIndexOf('.') >= 0 ? name.slice(name.lastIndexOf('.')).toLowerCase() : '';
1528
- var sizeMB = (file.size / (1024*1024)).toFixed(2);
1529
-
1530
- state.droppedFile = {
1531
- name: name,
1532
- ext: ext,
1533
- size: file.size,
1534
- sizeMB: sizeMB,
1535
- type: file.type,
1536
- file: file
1537
- };
1538
-
1539
- // Auto-fill input type
1540
- if (ext) {
1541
- document.getElementById('inputTypeField').value = ext;
1542
- }
1543
-
1544
- // Auto-select preset
1545
- var presetMap = {'.pptx':'pptx-translate','.csv':'csv-clean','.docx':'doc-convert','.xlsx':'xlsx-process','.pdf':'pdf-extract'};
1546
- if (presetMap[ext]) {
1547
- applyPreset(presetMap[ext]);
1548
- // Keep the file name in purpose if purpose is empty
1549
- var purposeEl = document.getElementById('purposeInput');
1550
- if (!purposeEl.value.trim()) {
1551
- purposeEl.value = 'I want to process the file ' + name;
1552
- }
1553
- }
1554
-
1555
- // Auto-fill output type based on extension
1556
- var outputEl = document.getElementById('outputTypeField');
1557
- if (!outputEl.value.trim() && ext) {
1558
- outputEl.value = 'processed ' + ext;
1559
- }
1560
-
1561
- renderFileInfo();
1562
- updateStatus('File dropped: ' + name + ' (' + sizeMB + 'MB)', versionStr());
1563
- }
1564
-
1565
- function renderFileInfo() {
1566
- var area = document.getElementById('fileInfoArea');
1567
- if (!state.droppedFile) {
1568
- area.innerHTML = '';
1569
- document.getElementById('dropZone').style.display = '';
1570
- return;
1571
- }
1572
- var f = state.droppedFile;
1573
- var icon = FILE_ICONS[f.ext] || '\uD83D\uDCC1';
1574
- var tc = detectToolchain(f.ext);
1575
- var tcTag = tc ? '<span style="color:var(--accent);font-weight:700;"> \u2192 ' + esc(tc.lib) + '</span>' : '';
1576
-
1577
- area.innerHTML = '<div class="file-info">'
1578
- + '<div class="file-icon">' + icon + '</div>'
1579
- + '<div class="file-detail">'
1580
- + '<div class="file-name">' + esc(f.name) + tcTag + '</div>'
1581
- + '<div class="file-meta">' + esc(f.ext) + ' \u00B7 ' + f.sizeMB + ' MB \u00B7 inbound/' + esc(f.name) + '</div>'
1582
- + '</div>'
1583
- + '<button class="file-remove" onclick="removeFile()" title="Remove">\u2715</button>'
1584
- + '</div>';
1585
- document.getElementById('dropZone').style.display = 'none';
1586
- }
1587
-
1588
- function removeFile() {
1589
- state.droppedFile = null;
1590
- document.getElementById('fileInput').value = '';
1591
- renderFileInfo();
1592
- }
1593
-
1594
- function sendFileToChat(callback) {
1595
- if (!state.droppedFile) {
1596
- if (callback) callback();
1597
- return;
1598
- }
1599
- var f = state.droppedFile;
1600
- // Read file as base64 and send via postMessage for parent to save
1601
- var reader = new FileReader();
1602
- reader.onload = function(e) {
1603
- window.parent.postMessage({
1604
- type: 'playground:uploadFile',
1605
- fileName: f.name,
1606
- fileType: f.type,
1607
- fileSize: f.size,
1608
- data: e.target.result
1609
- }, window.location.origin);
1610
- if (callback) callback();
1611
- };
1612
- reader.onerror = function() {
1613
- updateStatus('File read failed: ' + f.name, versionStr());
1614
- if (callback) callback();
1615
- };
1616
- reader.readAsDataURL(f.file);
1617
- }
1618
-
1619
- renderPresets();
1620
- loadSkillList();
1621
- renderSteps();
1622
- initDropZone();
1623
- </script>
1624
- </body>
1625
- </html>