@jungjaehoon/mama-os 0.1.1

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 (356) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +643 -0
  3. package/dist/agent/agent-loop.d.ts +98 -0
  4. package/dist/agent/agent-loop.d.ts.map +1 -0
  5. package/dist/agent/agent-loop.js +417 -0
  6. package/dist/agent/agent-loop.js.map +1 -0
  7. package/dist/agent/auto-recall.d.ts +48 -0
  8. package/dist/agent/auto-recall.d.ts.map +1 -0
  9. package/dist/agent/auto-recall.js +178 -0
  10. package/dist/agent/auto-recall.js.map +1 -0
  11. package/dist/agent/claude-cli-wrapper.d.ts +130 -0
  12. package/dist/agent/claude-cli-wrapper.d.ts.map +1 -0
  13. package/dist/agent/claude-cli-wrapper.js +227 -0
  14. package/dist/agent/claude-cli-wrapper.js.map +1 -0
  15. package/dist/agent/claude-client.d.ts +50 -0
  16. package/dist/agent/claude-client.d.ts.map +1 -0
  17. package/dist/agent/claude-client.js +214 -0
  18. package/dist/agent/claude-client.js.map +1 -0
  19. package/dist/agent/gateway-tool-executor.d.ts +75 -0
  20. package/dist/agent/gateway-tool-executor.d.ts.map +1 -0
  21. package/dist/agent/gateway-tool-executor.js +348 -0
  22. package/dist/agent/gateway-tool-executor.js.map +1 -0
  23. package/dist/agent/index.d.ts +13 -0
  24. package/dist/agent/index.d.ts.map +1 -0
  25. package/dist/agent/index.js +18 -0
  26. package/dist/agent/index.js.map +1 -0
  27. package/dist/agent/mcp-executor.d.ts +75 -0
  28. package/dist/agent/mcp-executor.d.ts.map +1 -0
  29. package/dist/agent/mcp-executor.js +307 -0
  30. package/dist/agent/mcp-executor.js.map +1 -0
  31. package/dist/agent/session-pool.d.ts +148 -0
  32. package/dist/agent/session-pool.d.ts.map +1 -0
  33. package/dist/agent/session-pool.js +272 -0
  34. package/dist/agent/session-pool.js.map +1 -0
  35. package/dist/agent/streaming-callback-manager.d.ts +85 -0
  36. package/dist/agent/streaming-callback-manager.d.ts.map +1 -0
  37. package/dist/agent/streaming-callback-manager.js +103 -0
  38. package/dist/agent/streaming-callback-manager.js.map +1 -0
  39. package/dist/agent/types.d.ts +437 -0
  40. package/dist/agent/types.d.ts.map +1 -0
  41. package/dist/agent/types.js +29 -0
  42. package/dist/agent/types.js.map +1 -0
  43. package/dist/api/cron-handler.d.ts +44 -0
  44. package/dist/api/cron-handler.d.ts.map +1 -0
  45. package/dist/api/cron-handler.js +195 -0
  46. package/dist/api/cron-handler.js.map +1 -0
  47. package/dist/api/error-handler.d.ts +22 -0
  48. package/dist/api/error-handler.d.ts.map +1 -0
  49. package/dist/api/error-handler.js +104 -0
  50. package/dist/api/error-handler.js.map +1 -0
  51. package/dist/api/heartbeat-handler.d.ts +49 -0
  52. package/dist/api/heartbeat-handler.d.ts.map +1 -0
  53. package/dist/api/heartbeat-handler.js +91 -0
  54. package/dist/api/heartbeat-handler.js.map +1 -0
  55. package/dist/api/index.d.ts +61 -0
  56. package/dist/api/index.d.ts.map +1 -0
  57. package/dist/api/index.js +145 -0
  58. package/dist/api/index.js.map +1 -0
  59. package/dist/api/types.d.ts +156 -0
  60. package/dist/api/types.d.ts.map +1 -0
  61. package/dist/api/types.js +62 -0
  62. package/dist/api/types.js.map +1 -0
  63. package/dist/auth/index.d.ts +7 -0
  64. package/dist/auth/index.d.ts.map +1 -0
  65. package/dist/auth/index.js +11 -0
  66. package/dist/auth/index.js.map +1 -0
  67. package/dist/auth/oauth-manager.d.ts +59 -0
  68. package/dist/auth/oauth-manager.d.ts.map +1 -0
  69. package/dist/auth/oauth-manager.js +237 -0
  70. package/dist/auth/oauth-manager.js.map +1 -0
  71. package/dist/auth/types.d.ts +92 -0
  72. package/dist/auth/types.d.ts.map +1 -0
  73. package/dist/auth/types.js +23 -0
  74. package/dist/auth/types.js.map +1 -0
  75. package/dist/cli/commands/init.d.ts +19 -0
  76. package/dist/cli/commands/init.d.ts.map +1 -0
  77. package/dist/cli/commands/init.js +155 -0
  78. package/dist/cli/commands/init.js.map +1 -0
  79. package/dist/cli/commands/run.d.ts +19 -0
  80. package/dist/cli/commands/run.d.ts.map +1 -0
  81. package/dist/cli/commands/run.js +89 -0
  82. package/dist/cli/commands/run.js.map +1 -0
  83. package/dist/cli/commands/setup.d.ts +19 -0
  84. package/dist/cli/commands/setup.d.ts.map +1 -0
  85. package/dist/cli/commands/setup.js +134 -0
  86. package/dist/cli/commands/setup.js.map +1 -0
  87. package/dist/cli/commands/start.d.ts +24 -0
  88. package/dist/cli/commands/start.d.ts.map +1 -0
  89. package/dist/cli/commands/start.js +1073 -0
  90. package/dist/cli/commands/start.js.map +1 -0
  91. package/dist/cli/commands/status.d.ts +10 -0
  92. package/dist/cli/commands/status.d.ts.map +1 -0
  93. package/dist/cli/commands/status.js +85 -0
  94. package/dist/cli/commands/status.js.map +1 -0
  95. package/dist/cli/commands/stop.d.ts +10 -0
  96. package/dist/cli/commands/stop.d.ts.map +1 -0
  97. package/dist/cli/commands/stop.js +65 -0
  98. package/dist/cli/commands/stop.js.map +1 -0
  99. package/dist/cli/config/config-manager.d.ts +51 -0
  100. package/dist/cli/config/config-manager.d.ts.map +1 -0
  101. package/dist/cli/config/config-manager.js +216 -0
  102. package/dist/cli/config/config-manager.js.map +1 -0
  103. package/dist/cli/config/types.d.ts +172 -0
  104. package/dist/cli/config/types.d.ts.map +1 -0
  105. package/dist/cli/config/types.js +48 -0
  106. package/dist/cli/config/types.js.map +1 -0
  107. package/dist/cli/index.d.ts +8 -0
  108. package/dist/cli/index.d.ts.map +1 -0
  109. package/dist/cli/index.js +92 -0
  110. package/dist/cli/index.js.map +1 -0
  111. package/dist/cli/utils/pid-manager.d.ts +66 -0
  112. package/dist/cli/utils/pid-manager.d.ts.map +1 -0
  113. package/dist/cli/utils/pid-manager.js +167 -0
  114. package/dist/cli/utils/pid-manager.js.map +1 -0
  115. package/dist/concurrency/index.d.ts +13 -0
  116. package/dist/concurrency/index.d.ts.map +1 -0
  117. package/dist/concurrency/index.js +22 -0
  118. package/dist/concurrency/index.js.map +1 -0
  119. package/dist/concurrency/lane-manager.d.ts +113 -0
  120. package/dist/concurrency/lane-manager.d.ts.map +1 -0
  121. package/dist/concurrency/lane-manager.js +245 -0
  122. package/dist/concurrency/lane-manager.js.map +1 -0
  123. package/dist/concurrency/session-key.d.ts +41 -0
  124. package/dist/concurrency/session-key.d.ts.map +1 -0
  125. package/dist/concurrency/session-key.js +61 -0
  126. package/dist/concurrency/session-key.js.map +1 -0
  127. package/dist/concurrency/types.d.ts +69 -0
  128. package/dist/concurrency/types.d.ts.map +1 -0
  129. package/dist/concurrency/types.js +16 -0
  130. package/dist/concurrency/types.js.map +1 -0
  131. package/dist/gateways/channel-history.d.ts +102 -0
  132. package/dist/gateways/channel-history.d.ts.map +1 -0
  133. package/dist/gateways/channel-history.js +181 -0
  134. package/dist/gateways/channel-history.js.map +1 -0
  135. package/dist/gateways/context-injector.d.ts +74 -0
  136. package/dist/gateways/context-injector.d.ts.map +1 -0
  137. package/dist/gateways/context-injector.js +121 -0
  138. package/dist/gateways/context-injector.js.map +1 -0
  139. package/dist/gateways/discord.d.ts +122 -0
  140. package/dist/gateways/discord.d.ts.map +1 -0
  141. package/dist/gateways/discord.js +602 -0
  142. package/dist/gateways/discord.js.map +1 -0
  143. package/dist/gateways/index.d.ts +30 -0
  144. package/dist/gateways/index.d.ts.map +1 -0
  145. package/dist/gateways/index.js +49 -0
  146. package/dist/gateways/index.js.map +1 -0
  147. package/dist/gateways/message-router.d.ts +116 -0
  148. package/dist/gateways/message-router.d.ts.map +1 -0
  149. package/dist/gateways/message-router.js +315 -0
  150. package/dist/gateways/message-router.js.map +1 -0
  151. package/dist/gateways/message-splitter.d.ts +54 -0
  152. package/dist/gateways/message-splitter.d.ts.map +1 -0
  153. package/dist/gateways/message-splitter.js +146 -0
  154. package/dist/gateways/message-splitter.js.map +1 -0
  155. package/dist/gateways/plugin-loader.d.ts +76 -0
  156. package/dist/gateways/plugin-loader.d.ts.map +1 -0
  157. package/dist/gateways/plugin-loader.js +221 -0
  158. package/dist/gateways/plugin-loader.js.map +1 -0
  159. package/dist/gateways/session-store.d.ts +77 -0
  160. package/dist/gateways/session-store.d.ts.map +1 -0
  161. package/dist/gateways/session-store.js +233 -0
  162. package/dist/gateways/session-store.js.map +1 -0
  163. package/dist/gateways/slack.d.ts +90 -0
  164. package/dist/gateways/slack.d.ts.map +1 -0
  165. package/dist/gateways/slack.js +281 -0
  166. package/dist/gateways/slack.js.map +1 -0
  167. package/dist/gateways/telegram.d.ts +79 -0
  168. package/dist/gateways/telegram.d.ts.map +1 -0
  169. package/dist/gateways/telegram.js +207 -0
  170. package/dist/gateways/telegram.js.map +1 -0
  171. package/dist/gateways/types.d.ts +340 -0
  172. package/dist/gateways/types.d.ts.map +1 -0
  173. package/dist/gateways/types.js +6 -0
  174. package/dist/gateways/types.js.map +1 -0
  175. package/dist/index.d.ts +7 -0
  176. package/dist/index.d.ts.map +1 -0
  177. package/dist/index.js +26 -0
  178. package/dist/index.js.map +1 -0
  179. package/dist/memory/memory-logger.d.ts +47 -0
  180. package/dist/memory/memory-logger.d.ts.map +1 -0
  181. package/dist/memory/memory-logger.js +126 -0
  182. package/dist/memory/memory-logger.js.map +1 -0
  183. package/dist/onboarding/all-tools.d.ts +18 -0
  184. package/dist/onboarding/all-tools.d.ts.map +1 -0
  185. package/dist/onboarding/all-tools.js +149 -0
  186. package/dist/onboarding/all-tools.js.map +1 -0
  187. package/dist/onboarding/autonomous-discovery-tools.d.ts +13 -0
  188. package/dist/onboarding/autonomous-discovery-tools.d.ts.map +1 -0
  189. package/dist/onboarding/autonomous-discovery-tools.js +268 -0
  190. package/dist/onboarding/autonomous-discovery-tools.js.map +1 -0
  191. package/dist/onboarding/bootstrap-template.d.ts +5 -0
  192. package/dist/onboarding/bootstrap-template.d.ts.map +1 -0
  193. package/dist/onboarding/bootstrap-template.js +142 -0
  194. package/dist/onboarding/bootstrap-template.js.map +1 -0
  195. package/dist/onboarding/complete-autonomous-prompt.d.ts +13 -0
  196. package/dist/onboarding/complete-autonomous-prompt.d.ts.map +1 -0
  197. package/dist/onboarding/complete-autonomous-prompt.js +1220 -0
  198. package/dist/onboarding/complete-autonomous-prompt.js.map +1 -0
  199. package/dist/onboarding/onboarding-state.d.ts +70 -0
  200. package/dist/onboarding/onboarding-state.d.ts.map +1 -0
  201. package/dist/onboarding/onboarding-state.js +184 -0
  202. package/dist/onboarding/onboarding-state.js.map +1 -0
  203. package/dist/onboarding/personality-quiz.d.ts +35 -0
  204. package/dist/onboarding/personality-quiz.d.ts.map +1 -0
  205. package/dist/onboarding/personality-quiz.js +219 -0
  206. package/dist/onboarding/personality-quiz.js.map +1 -0
  207. package/dist/onboarding/phase-5-summary.d.ts +22 -0
  208. package/dist/onboarding/phase-5-summary.d.ts.map +1 -0
  209. package/dist/onboarding/phase-5-summary.js +151 -0
  210. package/dist/onboarding/phase-5-summary.js.map +1 -0
  211. package/dist/onboarding/phase-6-security.d.ts +33 -0
  212. package/dist/onboarding/phase-6-security.d.ts.map +1 -0
  213. package/dist/onboarding/phase-6-security.js +473 -0
  214. package/dist/onboarding/phase-6-security.js.map +1 -0
  215. package/dist/onboarding/phase-7-integrations.d.ts +66 -0
  216. package/dist/onboarding/phase-7-integrations.d.ts.map +1 -0
  217. package/dist/onboarding/phase-7-integrations.js +619 -0
  218. package/dist/onboarding/phase-7-integrations.js.map +1 -0
  219. package/dist/onboarding/phase-8-demo.d.ts +43 -0
  220. package/dist/onboarding/phase-8-demo.d.ts.map +1 -0
  221. package/dist/onboarding/phase-8-demo.js +346 -0
  222. package/dist/onboarding/phase-8-demo.js.map +1 -0
  223. package/dist/onboarding/phase-9-finalization.d.ts +22 -0
  224. package/dist/onboarding/phase-9-finalization.d.ts.map +1 -0
  225. package/dist/onboarding/phase-9-finalization.js +375 -0
  226. package/dist/onboarding/phase-9-finalization.js.map +1 -0
  227. package/dist/onboarding/ritual-prompt.d.ts +2 -0
  228. package/dist/onboarding/ritual-prompt.d.ts.map +1 -0
  229. package/dist/onboarding/ritual-prompt.js +285 -0
  230. package/dist/onboarding/ritual-prompt.js.map +1 -0
  231. package/dist/onboarding/ritual-tools.d.ts +13 -0
  232. package/dist/onboarding/ritual-tools.d.ts.map +1 -0
  233. package/dist/onboarding/ritual-tools.js +93 -0
  234. package/dist/onboarding/ritual-tools.js.map +1 -0
  235. package/dist/runners/cli-runner.d.ts +59 -0
  236. package/dist/runners/cli-runner.d.ts.map +1 -0
  237. package/dist/runners/cli-runner.js +190 -0
  238. package/dist/runners/cli-runner.js.map +1 -0
  239. package/dist/runners/index.d.ts +11 -0
  240. package/dist/runners/index.d.ts.map +1 -0
  241. package/dist/runners/index.js +15 -0
  242. package/dist/runners/index.js.map +1 -0
  243. package/dist/runners/types.d.ts +81 -0
  244. package/dist/runners/types.d.ts.map +1 -0
  245. package/dist/runners/types.js +31 -0
  246. package/dist/runners/types.js.map +1 -0
  247. package/dist/scheduler/cron-scheduler.d.ts +115 -0
  248. package/dist/scheduler/cron-scheduler.d.ts.map +1 -0
  249. package/dist/scheduler/cron-scheduler.js +320 -0
  250. package/dist/scheduler/cron-scheduler.js.map +1 -0
  251. package/dist/scheduler/heartbeat.d.ts +53 -0
  252. package/dist/scheduler/heartbeat.d.ts.map +1 -0
  253. package/dist/scheduler/heartbeat.js +160 -0
  254. package/dist/scheduler/heartbeat.js.map +1 -0
  255. package/dist/scheduler/index.d.ts +22 -0
  256. package/dist/scheduler/index.d.ts.map +1 -0
  257. package/dist/scheduler/index.js +31 -0
  258. package/dist/scheduler/index.js.map +1 -0
  259. package/dist/scheduler/job-lock.d.ts +85 -0
  260. package/dist/scheduler/job-lock.d.ts.map +1 -0
  261. package/dist/scheduler/job-lock.js +137 -0
  262. package/dist/scheduler/job-lock.js.map +1 -0
  263. package/dist/scheduler/recovery.d.ts +78 -0
  264. package/dist/scheduler/recovery.d.ts.map +1 -0
  265. package/dist/scheduler/recovery.js +124 -0
  266. package/dist/scheduler/recovery.js.map +1 -0
  267. package/dist/scheduler/schedule-store.d.ts +112 -0
  268. package/dist/scheduler/schedule-store.d.ts.map +1 -0
  269. package/dist/scheduler/schedule-store.js +259 -0
  270. package/dist/scheduler/schedule-store.js.map +1 -0
  271. package/dist/scheduler/token-keep-alive.d.ts +49 -0
  272. package/dist/scheduler/token-keep-alive.d.ts.map +1 -0
  273. package/dist/scheduler/token-keep-alive.js +102 -0
  274. package/dist/scheduler/token-keep-alive.js.map +1 -0
  275. package/dist/scheduler/types.d.ts +96 -0
  276. package/dist/scheduler/types.d.ts.map +1 -0
  277. package/dist/scheduler/types.js +21 -0
  278. package/dist/scheduler/types.js.map +1 -0
  279. package/dist/setup/setup-prompt.d.ts +2 -0
  280. package/dist/setup/setup-prompt.d.ts.map +1 -0
  281. package/dist/setup/setup-prompt.js +138 -0
  282. package/dist/setup/setup-prompt.js.map +1 -0
  283. package/dist/setup/setup-server.d.ts +8 -0
  284. package/dist/setup/setup-server.d.ts.map +1 -0
  285. package/dist/setup/setup-server.js +71 -0
  286. package/dist/setup/setup-server.js.map +1 -0
  287. package/dist/setup/setup-tools.d.ts +13 -0
  288. package/dist/setup/setup-tools.d.ts.map +1 -0
  289. package/dist/setup/setup-tools.js +103 -0
  290. package/dist/setup/setup-tools.js.map +1 -0
  291. package/dist/setup/setup-websocket.d.ts +6 -0
  292. package/dist/setup/setup-websocket.d.ts.map +1 -0
  293. package/dist/setup/setup-websocket.js +312 -0
  294. package/dist/setup/setup-websocket.js.map +1 -0
  295. package/dist/skills/index.d.ts +10 -0
  296. package/dist/skills/index.d.ts.map +1 -0
  297. package/dist/skills/index.js +26 -0
  298. package/dist/skills/index.js.map +1 -0
  299. package/dist/skills/skill-executor.d.ts +48 -0
  300. package/dist/skills/skill-executor.d.ts.map +1 -0
  301. package/dist/skills/skill-executor.js +483 -0
  302. package/dist/skills/skill-executor.js.map +1 -0
  303. package/dist/skills/skill-loader.d.ts +40 -0
  304. package/dist/skills/skill-loader.d.ts.map +1 -0
  305. package/dist/skills/skill-loader.js +225 -0
  306. package/dist/skills/skill-loader.js.map +1 -0
  307. package/dist/skills/skill-matcher.d.ts +33 -0
  308. package/dist/skills/skill-matcher.d.ts.map +1 -0
  309. package/dist/skills/skill-matcher.js +190 -0
  310. package/dist/skills/skill-matcher.js.map +1 -0
  311. package/dist/skills/types.d.ts +123 -0
  312. package/dist/skills/types.d.ts.map +1 -0
  313. package/dist/skills/types.js +12 -0
  314. package/dist/skills/types.js.map +1 -0
  315. package/dist/tools/browser-tool.d.ts +149 -0
  316. package/dist/tools/browser-tool.d.ts.map +1 -0
  317. package/dist/tools/browser-tool.js +257 -0
  318. package/dist/tools/browser-tool.js.map +1 -0
  319. package/package.json +84 -0
  320. package/public/favicon.ico +0 -0
  321. package/public/setup.html +1026 -0
  322. package/public/viewer/icons/icon-192.png +0 -0
  323. package/public/viewer/icons/icon-512.png +0 -0
  324. package/public/viewer/js/modules/chat.js +1587 -0
  325. package/public/viewer/js/modules/dashboard.js +275 -0
  326. package/public/viewer/js/modules/graph.js +997 -0
  327. package/public/viewer/js/modules/memory.js +353 -0
  328. package/public/viewer/js/modules/settings.js +255 -0
  329. package/public/viewer/js/utils/api.js +169 -0
  330. package/public/viewer/js/utils/dom.js +92 -0
  331. package/public/viewer/js/utils/format.js +192 -0
  332. package/public/viewer/manifest.json +26 -0
  333. package/public/viewer/sw.js +131 -0
  334. package/public/viewer/viewer.css +500 -0
  335. package/public/viewer/viewer.html +1535 -0
  336. package/scripts/postinstall.js +118 -0
  337. package/templates/skills/document-analyze.md +63 -0
  338. package/templates/skills/heartbeat-report.md +75 -0
  339. package/templates/skills/image-translate.md +67 -0
  340. package/templates/workspace/skill-forge/DESIGN.md +115 -0
  341. package/templates/workspace/skill-forge/agents/architect.ts +295 -0
  342. package/templates/workspace/skill-forge/agents/developer.ts +364 -0
  343. package/templates/workspace/skill-forge/agents/qa.ts +313 -0
  344. package/templates/workspace/skill-forge/claude-api.ts +353 -0
  345. package/templates/workspace/skill-forge/discord-ui.ts +580 -0
  346. package/templates/workspace/skill-forge/error-handler.ts +354 -0
  347. package/templates/workspace/skill-forge/mama-integration.ts +357 -0
  348. package/templates/workspace/skill-forge/orchestrator.ts +495 -0
  349. package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/README.md +24 -0
  350. package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/index.ts +79 -0
  351. package/templates/workspace/skill-forge/output/generated-skills/skills/hello-world/types.ts +17 -0
  352. package/templates/workspace/skill-forge/package.json +21 -0
  353. package/templates/workspace/skill-forge/state/session.json +132 -0
  354. package/templates/workspace/skill-forge/test-e2e.ts +139 -0
  355. package/templates/workspace/skill-forge/tsconfig.json +20 -0
  356. package/templates/workspace/skill-forge/types.ts +159 -0
@@ -0,0 +1,1026 @@
1
+ <!doctype html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>MAMA Setup Wizard v0.1 - Bootstrap Ritual</title>
7
+ <style>
8
+ :root {
9
+ /* Neutral palette with warm undertones */
10
+ --bg-primary: #fafafa;
11
+ --bg-secondary: #ffffff;
12
+ --bg-tertiary: #f5f5f4;
13
+ --bg-chat: #fafaf9;
14
+
15
+ /* Text colors */
16
+ --text-primary: #1c1917;
17
+ --text-secondary: #57534e;
18
+ --text-tertiary: #a8a29e;
19
+ --text-inverse: #ffffff;
20
+
21
+ /* Accent - warm terracotta/coral */
22
+ --accent: #d97706;
23
+ --accent-soft: #fef3c7;
24
+ --accent-hover: #b45309;
25
+
26
+ /* Borders */
27
+ --border-light: #e7e5e4;
28
+ --border-medium: #d6d3d1;
29
+
30
+ /* Shadows */
31
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
32
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06);
33
+ --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.08);
34
+
35
+ /* Transitions */
36
+ --transition-fast: 150ms ease;
37
+ --transition-normal: 200ms ease;
38
+ --transition-slow: 300ms ease;
39
+
40
+ /* Spacing */
41
+ --space-xs: 4px;
42
+ --space-sm: 8px;
43
+ --space-md: 16px;
44
+ --space-lg: 24px;
45
+ --space-xl: 32px;
46
+
47
+ /* Border radius */
48
+ --radius-sm: 6px;
49
+ --radius-md: 10px;
50
+ --radius-lg: 16px;
51
+ --radius-xl: 20px;
52
+ --radius-full: 9999px;
53
+ }
54
+
55
+ * {
56
+ margin: 0;
57
+ padding: 0;
58
+ box-sizing: border-box;
59
+ }
60
+
61
+ body {
62
+ font-family:
63
+ 'SF Pro Display',
64
+ -apple-system,
65
+ BlinkMacSystemFont,
66
+ 'Segoe UI',
67
+ system-ui,
68
+ sans-serif;
69
+ background: var(--bg-primary);
70
+ min-height: 100vh;
71
+ display: flex;
72
+ justify-content: center;
73
+ align-items: center;
74
+ padding: var(--space-md);
75
+ -webkit-font-smoothing: antialiased;
76
+ -moz-osx-font-smoothing: grayscale;
77
+ }
78
+
79
+ /* Subtle background texture */
80
+ body::before {
81
+ content: '';
82
+ position: fixed;
83
+ inset: 0;
84
+ background:
85
+ radial-gradient(ellipse at top, rgba(217, 119, 6, 0.03) 0%, transparent 50%),
86
+ radial-gradient(ellipse at bottom right, rgba(120, 113, 108, 0.02) 0%, transparent 50%);
87
+ pointer-events: none;
88
+ z-index: -1;
89
+ }
90
+
91
+ .container {
92
+ background: var(--bg-secondary);
93
+ border-radius: var(--radius-xl);
94
+ box-shadow: var(--shadow-lg);
95
+ border: 1px solid var(--border-light);
96
+ width: 100%;
97
+ max-width: 720px;
98
+ height: min(90vh, 800px);
99
+ display: flex;
100
+ flex-direction: column;
101
+ overflow: hidden;
102
+ animation: containerFadeIn 0.5s ease-out;
103
+ }
104
+
105
+ @keyframes containerFadeIn {
106
+ from {
107
+ opacity: 0;
108
+ transform: translateY(8px);
109
+ }
110
+ to {
111
+ opacity: 1;
112
+ transform: translateY(0);
113
+ }
114
+ }
115
+
116
+ .header {
117
+ background: var(--bg-secondary);
118
+ color: var(--text-primary);
119
+ padding: var(--space-lg) var(--space-lg) var(--space-md);
120
+ text-align: left;
121
+ border-bottom: 1px solid var(--border-light);
122
+ display: flex;
123
+ align-items: center;
124
+ gap: var(--space-md);
125
+ }
126
+
127
+ .header-icon {
128
+ width: 40px;
129
+ height: 40px;
130
+ background: var(--accent-soft);
131
+ border-radius: var(--radius-md);
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ font-size: 20px;
136
+ flex-shrink: 0;
137
+ }
138
+
139
+ .header-text {
140
+ flex: 1;
141
+ min-width: 0;
142
+ }
143
+
144
+ .header h1 {
145
+ font-size: 18px;
146
+ font-weight: 600;
147
+ letter-spacing: -0.02em;
148
+ color: var(--text-primary);
149
+ }
150
+
151
+ .header p {
152
+ font-size: 13px;
153
+ color: var(--text-secondary);
154
+ margin-top: 2px;
155
+ }
156
+
157
+ .progress-bar-container {
158
+ padding: var(--space-md) var(--space-lg);
159
+ background: var(--bg-secondary);
160
+ border-bottom: 1px solid var(--border-light);
161
+ display: none;
162
+ }
163
+
164
+ .progress-bar-container.active {
165
+ display: block;
166
+ }
167
+
168
+ .progress-label {
169
+ font-size: 12px;
170
+ color: var(--text-secondary);
171
+ margin-bottom: var(--space-sm);
172
+ display: flex;
173
+ align-items: center;
174
+ gap: var(--space-sm);
175
+ }
176
+
177
+ .progress-bar {
178
+ display: flex;
179
+ gap: 6px;
180
+ align-items: center;
181
+ }
182
+
183
+ .progress-step {
184
+ flex: 1;
185
+ height: 4px;
186
+ background: var(--border-light);
187
+ border-radius: var(--radius-full);
188
+ transition: background var(--transition-normal);
189
+ }
190
+
191
+ .progress-step.completed {
192
+ background: var(--accent);
193
+ }
194
+
195
+ .progress-step.current {
196
+ background: var(--accent);
197
+ animation: progressPulse 1.5s ease-in-out infinite;
198
+ }
199
+
200
+ @keyframes progressPulse {
201
+ 0%,
202
+ 100% {
203
+ opacity: 1;
204
+ }
205
+ 50% {
206
+ opacity: 0.5;
207
+ }
208
+ }
209
+
210
+ .quiz-choices {
211
+ display: grid;
212
+ grid-template-columns: 1fr;
213
+ gap: var(--space-sm);
214
+ margin-top: var(--space-md);
215
+ animation: choicesFadeIn 0.4s ease-out;
216
+ }
217
+
218
+ @keyframes choicesFadeIn {
219
+ from {
220
+ opacity: 0;
221
+ transform: translateY(10px);
222
+ }
223
+ to {
224
+ opacity: 1;
225
+ transform: translateY(0);
226
+ }
227
+ }
228
+
229
+ .choice-button {
230
+ padding: var(--space-md);
231
+ background: var(--bg-secondary);
232
+ border: 2px solid var(--border-light);
233
+ border-radius: var(--radius-lg);
234
+ font-size: 14px;
235
+ font-family: inherit;
236
+ text-align: left;
237
+ cursor: pointer;
238
+ transition: all var(--transition-fast);
239
+ display: flex;
240
+ align-items: flex-start;
241
+ gap: var(--space-sm);
242
+ }
243
+
244
+ .choice-button:hover {
245
+ border-color: var(--accent);
246
+ background: var(--accent-soft);
247
+ transform: translateX(4px);
248
+ }
249
+
250
+ .choice-button:active {
251
+ transform: translateX(2px);
252
+ }
253
+
254
+ .choice-letter {
255
+ font-weight: 700;
256
+ color: var(--accent);
257
+ flex-shrink: 0;
258
+ min-width: 24px;
259
+ }
260
+
261
+ .choice-text {
262
+ flex: 1;
263
+ line-height: 1.5;
264
+ color: var(--text-primary);
265
+ }
266
+
267
+ .chat-messages {
268
+ flex: 1;
269
+ overflow-y: auto;
270
+ padding: var(--space-lg);
271
+ background: var(--bg-chat);
272
+ scroll-behavior: smooth;
273
+ }
274
+
275
+ .message {
276
+ margin-bottom: var(--space-lg);
277
+ display: flex;
278
+ flex-direction: column;
279
+ animation: messageSlideIn 0.3s ease-out;
280
+ }
281
+
282
+ @keyframes messageSlideIn {
283
+ from {
284
+ opacity: 0;
285
+ transform: translateY(8px);
286
+ }
287
+ to {
288
+ opacity: 1;
289
+ transform: translateY(0);
290
+ }
291
+ }
292
+
293
+ .message.assistant {
294
+ align-items: flex-start;
295
+ }
296
+
297
+ .message.user {
298
+ align-items: flex-end;
299
+ }
300
+
301
+ .message-header {
302
+ font-size: 11px;
303
+ font-weight: 500;
304
+ text-transform: uppercase;
305
+ letter-spacing: 0.04em;
306
+ margin-bottom: var(--space-sm);
307
+ color: var(--text-tertiary);
308
+ padding: 0 var(--space-xs);
309
+ }
310
+
311
+ .message-content {
312
+ max-width: 85%;
313
+ padding: var(--space-md);
314
+ border-radius: var(--radius-lg);
315
+ line-height: 1.6;
316
+ font-size: 15px;
317
+ word-wrap: break-word;
318
+ transition: transform var(--transition-fast);
319
+ }
320
+
321
+ .message.assistant .message-content {
322
+ background: var(--bg-secondary);
323
+ color: var(--text-primary);
324
+ border: 1px solid var(--border-light);
325
+ border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) var(--radius-sm);
326
+ box-shadow: var(--shadow-sm);
327
+ }
328
+
329
+ .message.user .message-content {
330
+ background: var(--text-primary);
331
+ color: var(--text-inverse);
332
+ border-radius: var(--radius-lg) var(--radius-lg) var(--radius-sm) var(--radius-lg);
333
+ }
334
+
335
+ .message-content a {
336
+ color: var(--accent);
337
+ text-decoration: none;
338
+ border-bottom: 1px solid currentColor;
339
+ transition: opacity var(--transition-fast);
340
+ }
341
+
342
+ .message-content a:hover {
343
+ opacity: 0.7;
344
+ }
345
+
346
+ .message.user .message-content a {
347
+ color: var(--accent-soft);
348
+ }
349
+
350
+ .message-content code {
351
+ background: rgba(0, 0, 0, 0.06);
352
+ padding: 2px 6px;
353
+ border-radius: var(--radius-sm);
354
+ font-family: 'SF Mono', 'Fira Code', 'Monaco', monospace;
355
+ font-size: 13px;
356
+ letter-spacing: -0.01em;
357
+ }
358
+
359
+ .message.user .message-content code {
360
+ background: rgba(255, 255, 255, 0.15);
361
+ }
362
+
363
+ .status-message {
364
+ text-align: center;
365
+ color: var(--text-secondary);
366
+ font-size: 13px;
367
+ padding: var(--space-sm) var(--space-md);
368
+ background: var(--bg-tertiary);
369
+ border-radius: var(--radius-full);
370
+ margin: var(--space-md) auto;
371
+ max-width: fit-content;
372
+ animation: statusFadeIn 0.3s ease-out;
373
+ }
374
+
375
+ @keyframes statusFadeIn {
376
+ from {
377
+ opacity: 0;
378
+ transform: scale(0.95);
379
+ }
380
+ to {
381
+ opacity: 1;
382
+ transform: scale(1);
383
+ }
384
+ }
385
+
386
+ .status-message.success {
387
+ background: #ecfdf5;
388
+ color: #047857;
389
+ }
390
+
391
+ .status-message.error {
392
+ background: #fef2f2;
393
+ color: #dc2626;
394
+ }
395
+
396
+ .input-area {
397
+ padding: var(--space-md) var(--space-lg) var(--space-lg);
398
+ background: var(--bg-secondary);
399
+ border-top: 1px solid var(--border-light);
400
+ display: flex;
401
+ gap: var(--space-sm);
402
+ align-items: flex-end;
403
+ }
404
+
405
+ .input-wrapper {
406
+ flex: 1;
407
+ position: relative;
408
+ }
409
+
410
+ #user-input {
411
+ width: 100%;
412
+ padding: 14px 18px;
413
+ border: 1px solid var(--border-medium);
414
+ border-radius: var(--radius-lg);
415
+ font-size: 15px;
416
+ font-family: inherit;
417
+ outline: none;
418
+ background: var(--bg-secondary);
419
+ color: var(--text-primary);
420
+ transition:
421
+ border-color var(--transition-fast),
422
+ box-shadow var(--transition-fast);
423
+ resize: none;
424
+ }
425
+
426
+ #user-input::placeholder {
427
+ color: var(--text-tertiary);
428
+ }
429
+
430
+ #user-input:focus {
431
+ border-color: var(--accent);
432
+ box-shadow: 0 0 0 3px var(--accent-soft);
433
+ }
434
+
435
+ #user-input:disabled {
436
+ background: var(--bg-tertiary);
437
+ cursor: not-allowed;
438
+ opacity: 0.6;
439
+ }
440
+
441
+ #send-button {
442
+ padding: 14px 20px;
443
+ background: var(--text-primary);
444
+ color: var(--text-inverse);
445
+ border: none;
446
+ border-radius: var(--radius-lg);
447
+ font-size: 14px;
448
+ font-weight: 500;
449
+ font-family: inherit;
450
+ cursor: pointer;
451
+ transition:
452
+ background var(--transition-fast),
453
+ transform var(--transition-fast),
454
+ box-shadow var(--transition-fast);
455
+ display: flex;
456
+ align-items: center;
457
+ gap: var(--space-sm);
458
+ white-space: nowrap;
459
+ }
460
+
461
+ #send-button:hover:not(:disabled) {
462
+ background: var(--text-secondary);
463
+ transform: translateY(-1px);
464
+ box-shadow: var(--shadow-md);
465
+ }
466
+
467
+ #send-button:active:not(:disabled) {
468
+ transform: translateY(0);
469
+ }
470
+
471
+ #send-button:disabled {
472
+ background: var(--border-medium);
473
+ cursor: not-allowed;
474
+ transform: none;
475
+ box-shadow: none;
476
+ }
477
+
478
+ /* Send button arrow icon */
479
+ #send-button::after {
480
+ content: '';
481
+ width: 14px;
482
+ height: 14px;
483
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='white' stroke-width='2.5'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75'/%3E%3C/svg%3E");
484
+ background-size: contain;
485
+ background-repeat: no-repeat;
486
+ flex-shrink: 0;
487
+ }
488
+
489
+ .typing-indicator {
490
+ display: none;
491
+ align-items: center;
492
+ gap: 6px;
493
+ padding: var(--space-md);
494
+ background: var(--bg-secondary);
495
+ border: 1px solid var(--border-light);
496
+ border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) var(--radius-sm);
497
+ max-width: fit-content;
498
+ box-shadow: var(--shadow-sm);
499
+ animation: messageSlideIn 0.3s ease-out;
500
+ }
501
+
502
+ .typing-indicator.active {
503
+ display: flex;
504
+ }
505
+
506
+ .typing-dot {
507
+ width: 6px;
508
+ height: 6px;
509
+ background: var(--text-tertiary);
510
+ border-radius: 50%;
511
+ animation: typingPulse 1.4s infinite;
512
+ }
513
+
514
+ .typing-dot:nth-child(2) {
515
+ animation-delay: 0.15s;
516
+ }
517
+
518
+ .typing-dot:nth-child(3) {
519
+ animation-delay: 0.3s;
520
+ }
521
+
522
+ @keyframes typingPulse {
523
+ 0%,
524
+ 60%,
525
+ 100% {
526
+ opacity: 0.4;
527
+ transform: scale(1);
528
+ }
529
+ 30% {
530
+ opacity: 1;
531
+ transform: scale(1.1);
532
+ }
533
+ }
534
+
535
+ /* Custom scrollbar */
536
+ .chat-messages::-webkit-scrollbar {
537
+ width: 6px;
538
+ }
539
+
540
+ .chat-messages::-webkit-scrollbar-track {
541
+ background: transparent;
542
+ }
543
+
544
+ .chat-messages::-webkit-scrollbar-thumb {
545
+ background: var(--border-medium);
546
+ border-radius: var(--radius-full);
547
+ }
548
+
549
+ .chat-messages::-webkit-scrollbar-thumb:hover {
550
+ background: var(--text-tertiary);
551
+ }
552
+
553
+ /* Mobile responsive */
554
+ @media (max-width: 640px) {
555
+ body {
556
+ padding: 0;
557
+ align-items: stretch;
558
+ }
559
+
560
+ .container {
561
+ border-radius: 0;
562
+ height: 100vh;
563
+ max-width: none;
564
+ border: none;
565
+ }
566
+
567
+ .header {
568
+ padding: 12px;
569
+ }
570
+
571
+ .header h1 {
572
+ font-size: 15px;
573
+ }
574
+
575
+ .header p {
576
+ font-size: 11px;
577
+ }
578
+
579
+ .chat-messages {
580
+ padding: 12px;
581
+ }
582
+
583
+ .message {
584
+ margin-bottom: 12px;
585
+ }
586
+
587
+ .message-header {
588
+ font-size: 10px;
589
+ margin-bottom: 4px;
590
+ }
591
+
592
+ .message-content {
593
+ max-width: 95%;
594
+ padding: 10px 12px;
595
+ font-size: 13px;
596
+ line-height: 1.5;
597
+ }
598
+
599
+ .input-area {
600
+ padding: 12px;
601
+ gap: 8px;
602
+ }
603
+
604
+ #user-input {
605
+ padding: 10px 12px;
606
+ font-size: 16px; /* Prevents zoom on iOS */
607
+ }
608
+
609
+ #send-button {
610
+ padding: 10px 14px;
611
+ font-size: 13px;
612
+ }
613
+
614
+ #send-button span {
615
+ display: none;
616
+ }
617
+
618
+ #send-button::after {
619
+ width: 18px;
620
+ height: 18px;
621
+ }
622
+ }
623
+
624
+ /* Reduced motion */
625
+ @media (prefers-reduced-motion: reduce) {
626
+ *,
627
+ *::before,
628
+ *::after {
629
+ animation-duration: 0.01ms !important;
630
+ animation-iteration-count: 1 !important;
631
+ transition-duration: 0.01ms !important;
632
+ }
633
+ }
634
+
635
+ /* Dark mode support */
636
+ @media (prefers-color-scheme: dark) {
637
+ :root {
638
+ --bg-primary: #0a0a0a;
639
+ --bg-secondary: #171717;
640
+ --bg-tertiary: #262626;
641
+ --bg-chat: #0f0f0f;
642
+
643
+ --text-primary: #fafafa;
644
+ --text-secondary: #a3a3a3;
645
+ --text-tertiary: #737373;
646
+ --text-inverse: #0a0a0a;
647
+
648
+ --accent: #f59e0b;
649
+ --accent-soft: rgba(245, 158, 11, 0.15);
650
+
651
+ --border-light: #262626;
652
+ --border-medium: #404040;
653
+
654
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
655
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
656
+ --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.4);
657
+ }
658
+
659
+ body::before {
660
+ background:
661
+ radial-gradient(ellipse at top, rgba(245, 158, 11, 0.05) 0%, transparent 50%),
662
+ radial-gradient(ellipse at bottom right, rgba(64, 64, 64, 0.1) 0%, transparent 50%);
663
+ }
664
+
665
+ .message.user .message-content {
666
+ background: var(--accent);
667
+ color: var(--text-inverse);
668
+ }
669
+
670
+ .message-content code {
671
+ background: rgba(255, 255, 255, 0.1);
672
+ }
673
+
674
+ .message.user .message-content code {
675
+ background: rgba(0, 0, 0, 0.2);
676
+ }
677
+
678
+ .status-message.success {
679
+ background: rgba(16, 185, 129, 0.15);
680
+ color: #34d399;
681
+ }
682
+
683
+ .status-message.error {
684
+ background: rgba(239, 68, 68, 0.15);
685
+ color: #f87171;
686
+ }
687
+
688
+ #send-button {
689
+ background: var(--accent);
690
+ color: var(--text-inverse);
691
+ }
692
+
693
+ #send-button:hover:not(:disabled) {
694
+ background: var(--accent-hover);
695
+ }
696
+
697
+ #send-button::after {
698
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%230a0a0a' stroke-width='2.5'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75'/%3E%3C/svg%3E");
699
+ }
700
+ }
701
+
702
+ /* Focus visible for accessibility */
703
+ :focus-visible {
704
+ outline: 2px solid var(--accent);
705
+ outline-offset: 2px;
706
+ }
707
+
708
+ button:focus:not(:focus-visible) {
709
+ outline: none;
710
+ }
711
+ </style>
712
+ </head>
713
+ <body>
714
+ <div class="container">
715
+ <div class="header">
716
+ <div class="header-icon">✦</div>
717
+ <div class="header-text">
718
+ <h1>MAMA Setup</h1>
719
+ <p>Powered by Claude</p>
720
+ </div>
721
+ </div>
722
+
723
+ <div class="progress-bar-container" id="progress-container">
724
+ <div class="progress-label">
725
+ <span id="progress-text">Getting to know each other...</span>
726
+ </div>
727
+ <div class="progress-bar" id="progress-bar">
728
+ <div class="progress-step"></div>
729
+ <div class="progress-step"></div>
730
+ <div class="progress-step"></div>
731
+ <div class="progress-step"></div>
732
+ <div class="progress-step"></div>
733
+ <div class="progress-step"></div>
734
+ <div class="progress-step"></div>
735
+ </div>
736
+ </div>
737
+
738
+ <div class="chat-messages" id="messages"></div>
739
+
740
+ <div class="input-area">
741
+ <div class="input-wrapper">
742
+ <input
743
+ type="text"
744
+ id="user-input"
745
+ placeholder="Type your message..."
746
+ autocomplete="off"
747
+ />
748
+ </div>
749
+ <button id="send-button"><span>Send</span></button>
750
+ </div>
751
+ </div>
752
+
753
+ <script>
754
+ const messagesDiv = document.getElementById('messages');
755
+ const userInput = document.getElementById('user-input');
756
+ const sendButton = document.getElementById('send-button');
757
+
758
+ let ws = null;
759
+ let isConnecting = false;
760
+
761
+ function connectWebSocket() {
762
+ if (isConnecting || (ws && ws.readyState === WebSocket.OPEN)) {
763
+ return;
764
+ }
765
+
766
+ isConnecting = true;
767
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
768
+ const wsUrl = `${protocol}//${window.location.host}/setup-ws`;
769
+
770
+ ws = new WebSocket(wsUrl);
771
+
772
+ ws.onopen = () => {
773
+ console.log('WebSocket connected');
774
+ isConnecting = false;
775
+ setInputEnabled(true);
776
+
777
+ const browserLang = navigator.language || navigator.userLanguage || 'en';
778
+ ws.send(
779
+ JSON.stringify({
780
+ type: 'init',
781
+ language: browserLang,
782
+ })
783
+ );
784
+ };
785
+
786
+ ws.onmessage = (event) => {
787
+ const data = JSON.parse(event.data);
788
+ handleMessage(data);
789
+ };
790
+
791
+ ws.onerror = (error) => {
792
+ console.error('WebSocket error:', error);
793
+ isConnecting = false;
794
+ showStatus('Connection error occurred.', 'error');
795
+ };
796
+
797
+ ws.onclose = () => {
798
+ console.log('WebSocket disconnected');
799
+ isConnecting = false;
800
+ setInputEnabled(false);
801
+
802
+ setTimeout(() => {
803
+ if (!isConnecting) {
804
+ connectWebSocket();
805
+ }
806
+ }, 3000);
807
+ };
808
+ }
809
+
810
+ function handleMessage(data) {
811
+ console.log('[DEBUG] Received message:', data);
812
+ switch (data.type) {
813
+ case 'assistant_message':
814
+ console.log('[DEBUG] Adding assistant message:', data.content);
815
+ hideTyping();
816
+ addMessage('assistant', data.content, data.choices);
817
+ break;
818
+
819
+ case 'progress':
820
+ console.log('[DEBUG] Updating progress:', data.step, '/', data.total);
821
+ updateProgress(data.step, data.total, data.label);
822
+ break;
823
+
824
+ case 'config_updated':
825
+ showStatus(`✓ 설정 저장됨: ${data.key}`, 'success');
826
+ break;
827
+
828
+ case 'ritual_complete':
829
+ hideProgress();
830
+ showStatus('✨ Bootstrap ritual complete! Transitioning to setup...', 'success');
831
+ break;
832
+
833
+ case 'setup_complete':
834
+ showStatus('✅ 설정 완료! "mama start"로 서버를 시작하세요.', 'success');
835
+ setInputEnabled(false);
836
+ break;
837
+
838
+ case 'redirect':
839
+ console.log('[DEBUG] Redirect received:', data.url);
840
+ showStatus(data.message || '✨ Redirecting...', 'success');
841
+ setInputEnabled(false);
842
+ // Delay redirect to show completion message
843
+ setTimeout(() => {
844
+ window.location.href = data.url || '/viewer';
845
+ }, 2000);
846
+ break;
847
+
848
+ case 'error':
849
+ hideTyping();
850
+ showStatus(`오류: ${data.message}`, 'error');
851
+ break;
852
+
853
+ default:
854
+ console.warn('[DEBUG] Unknown message type:', data.type);
855
+ }
856
+ }
857
+
858
+ function sendMessage(messageText) {
859
+ const message = messageText || userInput.value.trim();
860
+ if (!message || !ws || ws.readyState !== WebSocket.OPEN) {
861
+ return;
862
+ }
863
+
864
+ addMessage('user', message);
865
+ if (!messageText) {
866
+ userInput.value = '';
867
+ }
868
+
869
+ ws.send(
870
+ JSON.stringify({
871
+ type: 'user_message',
872
+ content: message,
873
+ })
874
+ );
875
+
876
+ showTyping();
877
+ }
878
+
879
+ function addMessage(role, content, choices) {
880
+ const messageDiv = document.createElement('div');
881
+ messageDiv.className = `message ${role}`;
882
+
883
+ const headerDiv = document.createElement('div');
884
+ headerDiv.className = 'message-header';
885
+ headerDiv.textContent = role === 'assistant' ? 'Claude' : 'You';
886
+
887
+ const contentDiv = document.createElement('div');
888
+ contentDiv.className = 'message-content';
889
+ contentDiv.innerHTML = formatMessage(content);
890
+
891
+ messageDiv.appendChild(headerDiv);
892
+ messageDiv.appendChild(contentDiv);
893
+
894
+ if (choices && choices.length > 0) {
895
+ const choicesDiv = createChoicesUI(choices);
896
+ messageDiv.appendChild(choicesDiv);
897
+ }
898
+
899
+ const typingIndicator = messagesDiv.querySelector('.typing-indicator');
900
+ if (typingIndicator) {
901
+ messagesDiv.insertBefore(messageDiv, typingIndicator);
902
+ } else {
903
+ messagesDiv.appendChild(messageDiv);
904
+ }
905
+
906
+ scrollToBottom();
907
+ }
908
+
909
+ function createChoicesUI(choices) {
910
+ const container = document.createElement('div');
911
+ container.className = 'quiz-choices';
912
+
913
+ choices.forEach((choice) => {
914
+ const button = document.createElement('button');
915
+ button.className = 'choice-button';
916
+ button.innerHTML = `
917
+ <span class="choice-letter">${choice.id.toUpperCase()}</span>
918
+ <span class="choice-text">${escapeHtml(choice.text)}</span>
919
+ `;
920
+ button.onclick = () => selectChoice(choice.id, container);
921
+ container.appendChild(button);
922
+ });
923
+
924
+ return container;
925
+ }
926
+
927
+ function selectChoice(choiceId, container) {
928
+ const buttons = container.querySelectorAll('.choice-button');
929
+ buttons.forEach((btn) => (btn.disabled = true));
930
+
931
+ sendMessage(choiceId.toUpperCase());
932
+ }
933
+
934
+ function escapeHtml(text) {
935
+ const div = document.createElement('div');
936
+ div.textContent = text;
937
+ return div.innerHTML;
938
+ }
939
+
940
+ function updateProgress(step, total, label) {
941
+ const progressContainer = document.getElementById('progress-container');
942
+ const progressText = document.getElementById('progress-text');
943
+ const progressBar = document.getElementById('progress-bar');
944
+ const steps = progressBar.querySelectorAll('.progress-step');
945
+
946
+ progressContainer.classList.add('active');
947
+ if (label) {
948
+ progressText.textContent = label;
949
+ }
950
+
951
+ steps.forEach((stepEl, index) => {
952
+ stepEl.classList.remove('completed', 'current');
953
+ if (index < step - 1) {
954
+ stepEl.classList.add('completed');
955
+ } else if (index === step - 1) {
956
+ stepEl.classList.add('current');
957
+ }
958
+ });
959
+ }
960
+
961
+ function hideProgress() {
962
+ const progressContainer = document.getElementById('progress-container');
963
+ progressContainer.classList.remove('active');
964
+ }
965
+
966
+ function formatMessage(text) {
967
+ text = text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
968
+ text = text.replace(/\n/g, '<br>');
969
+ text = text.replace(/`([^`]+)`/g, '<code>$1</code>');
970
+ text = text.replace(/(https?:\/\/[^\s]+)/g, '<a href="$1" target="_blank">$1</a>');
971
+ return text;
972
+ }
973
+
974
+ function showTyping() {
975
+ let typingDiv = messagesDiv.querySelector('.typing-indicator');
976
+ if (!typingDiv) {
977
+ typingDiv = document.createElement('div');
978
+ typingDiv.className = 'typing-indicator';
979
+ typingDiv.innerHTML =
980
+ '<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>';
981
+ messagesDiv.appendChild(typingDiv);
982
+ }
983
+ typingDiv.classList.add('active');
984
+ scrollToBottom();
985
+ }
986
+
987
+ function hideTyping() {
988
+ const typingDiv = messagesDiv.querySelector('.typing-indicator');
989
+ if (typingDiv) {
990
+ typingDiv.classList.remove('active');
991
+ }
992
+ }
993
+
994
+ function showStatus(message, type = 'info') {
995
+ const statusDiv = document.createElement('div');
996
+ statusDiv.className = `status-message ${type}`;
997
+ statusDiv.textContent = message;
998
+ messagesDiv.appendChild(statusDiv);
999
+ scrollToBottom();
1000
+
1001
+ setTimeout(() => {
1002
+ statusDiv.remove();
1003
+ }, 5000);
1004
+ }
1005
+
1006
+ function setInputEnabled(enabled) {
1007
+ userInput.disabled = !enabled;
1008
+ sendButton.disabled = !enabled;
1009
+ }
1010
+
1011
+ function scrollToBottom() {
1012
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
1013
+ }
1014
+
1015
+ sendButton.addEventListener('click', sendMessage);
1016
+ userInput.addEventListener('keypress', (e) => {
1017
+ if (e.key === 'Enter' && !e.shiftKey) {
1018
+ e.preventDefault();
1019
+ sendMessage();
1020
+ }
1021
+ });
1022
+
1023
+ connectWebSocket();
1024
+ </script>
1025
+ </body>
1026
+ </html>