thursday-agent 0.5.0 → 0.7.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 (667) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +3 -2
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.json +8 -1
  6. package/.next/routes-manifest.json +18 -6
  7. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/server/app/_global-error.html +1 -1
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  14. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +13 -13
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
  18. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  20. package/.next/server/app/api/artifact/route.js +10 -6
  21. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  22. package/.next/server/app/api/bot/memory/files/route.js +5 -4
  23. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
  24. package/.next/server/app/api/bot/memory/route.js +8 -8
  25. package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
  26. package/.next/server/app/api/bot/route.js +8 -8
  27. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  28. package/.next/server/app/api/bot/thread/[id]/route/app-paths-manifest.json +3 -0
  29. package/.next/server/app/api/bot/thread/[id]/route.js +15 -0
  30. package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -0
  31. package/.next/server/app/api/bot/thread/[id]/route_client-reference-manifest.js +3 -0
  32. package/.next/server/app/api/bot/thread/route/app-paths-manifest.json +3 -0
  33. package/.next/server/app/api/bot/thread/route.js +15 -0
  34. package/.next/server/app/api/bot/thread/route.js.nft.json +1 -0
  35. package/.next/server/app/api/bot/thread/route_client-reference-manifest.js +3 -0
  36. package/.next/server/app/api/config/route.js +7 -7
  37. package/.next/server/app/api/config/route.js.nft.json +1 -1
  38. package/.next/server/app/api/events/route.js +3 -3
  39. package/.next/server/app/api/events/route.js.nft.json +1 -1
  40. package/.next/server/app/api/file/[...path]/route.js +4 -4
  41. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  42. package/.next/server/app/api/llm-model/catalog/route.js +8 -8
  43. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  44. package/.next/server/app/api/llm-model/credits/route.js +8 -8
  45. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
  46. package/.next/server/app/api/llm-model/route.js +8 -8
  47. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  48. package/.next/server/app/api/llm-model/subscription/route.js +8 -8
  49. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
  50. package/.next/server/app/api/mcp/[name]/route.js +6 -6
  51. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  52. package/.next/server/app/api/mcp/oauth/callback/route.js +4 -4
  53. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  54. package/.next/server/app/api/mcp/route.js +6 -6
  55. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  56. package/.next/server/app/api/mcp/tools/route.js +6 -6
  57. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  58. package/.next/server/app/api/memory/edit/route.js +15 -15
  59. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
  60. package/.next/server/app/api/memory/route.js +7 -6
  61. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  62. package/.next/server/app/api/skills/[source]/[name]/route.js +6 -6
  63. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/skills/route.js +6 -6
  65. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  66. package/.next/server/app/api/thursday/call/route.js +10 -11
  67. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  68. package/.next/server/app/api/thursday/skills/route.js +9 -10
  69. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  70. package/.next/server/app/api/thursday/tool-call/route.js +15 -15
  71. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  72. package/.next/server/app/api/workspace/missing/route/app-paths-manifest.json +3 -0
  73. package/.next/server/app/api/workspace/missing/route/build-manifest.json +12 -0
  74. package/.next/server/app/api/workspace/missing/route/server-reference-manifest.json +4 -0
  75. package/.next/server/app/api/workspace/missing/route.js +12 -0
  76. package/.next/server/app/api/workspace/missing/route.js.map +5 -0
  77. package/.next/server/app/api/workspace/missing/route.js.nft.json +1 -0
  78. package/.next/server/app/api/workspace/missing/route_client-reference-manifest.js +3 -0
  79. package/.next/server/app/api/workspace/route.js +5 -5
  80. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  81. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  82. package/.next/server/app/artifact/[...path]/page.js +4 -3
  83. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  84. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  86. package/.next/server/app/page/server-reference-manifest.json +67 -45
  87. package/.next/server/app/page.js +11 -9
  88. package/.next/server/app/page.js.nft.json +1 -1
  89. package/.next/server/app/page_client-reference-manifest.js +1 -1
  90. package/.next/server/app-paths-manifest.json +3 -2
  91. package/.next/server/chunks/[root-of-the-server]__0-06t3f._.js +8 -0
  92. package/.next/server/chunks/[root-of-the-server]__02846fb._.js +8 -0
  93. package/.next/server/chunks/[root-of-the-server]__05luceg._.js +3 -0
  94. package/.next/server/chunks/[root-of-the-server]__06--3q3._.js +8 -0
  95. package/.next/server/chunks/[root-of-the-server]__06l05n_._.js +3 -0
  96. package/.next/server/chunks/[root-of-the-server]__07bnah_._.js +3 -0
  97. package/.next/server/chunks/[root-of-the-server]__08eae20._.js +8 -0
  98. package/.next/server/chunks/[root-of-the-server]__0_ue8oe._.js +3 -0
  99. package/.next/server/chunks/[root-of-the-server]__0btqrct._.js +3 -0
  100. package/.next/server/chunks/[root-of-the-server]__0c9fgca._.js +5 -0
  101. package/.next/server/chunks/[root-of-the-server]__0h_x6q6._.js +5 -0
  102. package/.next/server/chunks/{[root-of-the-server]__0pa2n68._.js → [root-of-the-server]__0kxrvxf._.js} +2 -2
  103. package/.next/server/chunks/{[root-of-the-server]__0q0vvt2._.js → [root-of-the-server]__0ov3iir._.js} +2 -2
  104. package/.next/server/chunks/[root-of-the-server]__0qwtlmx._.js +8 -0
  105. package/.next/server/chunks/[root-of-the-server]__0sm5xvm._.js +3 -0
  106. package/.next/server/chunks/[root-of-the-server]__0zp35ul._.js +5 -0
  107. package/.next/server/chunks/[root-of-the-server]__144u9d_._.js +3 -0
  108. package/.next/server/chunks/[root-of-the-server]__18-2tsq._.js +5 -0
  109. package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +3 -0
  110. package/.next/server/chunks/[root-of-the-server]__1e2365e._.js +8 -0
  111. package/.next/server/chunks/[root-of-the-server]__1fix932._.js +8 -0
  112. package/.next/server/chunks/{[root-of-the-server]__0ce8nf8._.js → [root-of-the-server]__1g3t7vn._.js} +3 -3
  113. package/.next/server/chunks/[root-of-the-server]__1kp-83u._.js +5 -0
  114. package/.next/server/chunks/[root-of-the-server]__1kv8p3e._.js +5 -0
  115. package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +2 -3
  116. package/.next/server/chunks/_02szwk8._.js +5 -0
  117. package/.next/server/chunks/_05h2cii._.js +1 -1
  118. package/.next/server/chunks/_06c9kie._.js +216 -0
  119. package/.next/server/chunks/{_1x6kd1r._.js → _07y2gj-._.js} +1 -1
  120. package/.next/server/chunks/_0a43a-0._.js +3 -0
  121. package/.next/server/chunks/_0bujjey._.js +5 -0
  122. package/.next/server/chunks/_0c9j6rb._.js +1 -1
  123. package/.next/server/chunks/_0dkd549._.js +3 -0
  124. package/.next/server/chunks/_0ej1kvv._.js +3 -0
  125. package/.next/server/chunks/_0f6ts7z._.js +3 -0
  126. package/.next/server/chunks/_0gm9hyv._.js +55 -0
  127. package/.next/server/chunks/_0i55en5._.js +25 -0
  128. package/.next/server/chunks/_0k50n27._.js +18 -0
  129. package/.next/server/chunks/_0khihjk._.js +3 -3
  130. package/.next/server/chunks/_0n74zsf._.js +5 -5
  131. package/.next/server/chunks/_0nvm9ef._.js +3 -0
  132. package/.next/server/chunks/_0p56fn0._.js +3 -0
  133. package/.next/server/chunks/_0recufk._.js +1 -1
  134. package/.next/server/chunks/_0xbkz10._.js +3 -0
  135. package/.next/server/chunks/_10tkr26._.js +3 -0
  136. package/.next/server/chunks/{_0ioz3nd._.js → _11u2kp7._.js} +1 -1
  137. package/.next/server/chunks/_11ujpya._.js +3 -0
  138. package/.next/server/chunks/_127nx_p._.js +5 -0
  139. package/.next/server/chunks/_17l5ygt._.js +1 -1
  140. package/.next/server/chunks/_19uauj6._.js +3 -0
  141. package/.next/server/chunks/_1_5mnq4._.js +5 -5
  142. package/.next/server/chunks/_1abl6t9._.js +55 -0
  143. package/.next/server/chunks/_1d0j961._.js +3 -3
  144. package/.next/server/chunks/_1dbixxz._.js +3 -0
  145. package/.next/server/chunks/_1dc_d1q._.js +98 -0
  146. package/.next/server/chunks/_1drgfx4._.js +5 -5
  147. package/.next/server/chunks/_1e48czp._.js +3 -3
  148. package/.next/server/chunks/_1f31u5x._.js +5 -0
  149. package/.next/server/chunks/_1gnjfli._.js +3 -0
  150. package/.next/server/chunks/_1kg70wc._.js +3 -3
  151. package/.next/server/chunks/_1klbcft._.js +5 -0
  152. package/.next/server/chunks/_1krd9rf._.js +4 -1
  153. package/.next/server/chunks/_1kwmzab._.js +3 -0
  154. package/.next/server/chunks/{_1pf4qe-._.js → _1oxj9fa._.js} +1 -1
  155. package/.next/server/chunks/{_1f46eie._.js → _1pcmdbb._.js} +1 -1
  156. package/.next/server/chunks/_1qgix-j._.js +3 -0
  157. package/.next/server/chunks/{lib_logger_ts_1exwjut._.js → _1rpchta._.js} +5 -5
  158. package/.next/server/chunks/_1vsej-s._.js +5 -0
  159. package/.next/server/chunks/_1yx0p_s._.js +5 -0
  160. package/.next/server/chunks/{_0rnf-6n._.js → _20fsr_z._.js} +6 -6
  161. package/.next/server/chunks/_2128hkj._.js +5 -0
  162. package/.next/server/chunks/_next-internal_server_app_api_bot_thread_[id]_route_actions_1fstjn2.js +3 -0
  163. package/.next/server/chunks/_next-internal_server_app_api_bot_thread_route_actions_003ccso.js +3 -0
  164. package/.next/server/chunks/_next-internal_server_app_api_workspace_missing_route_actions_1fidi6p.js +3 -0
  165. package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
  166. package/.next/server/chunks/features_0-cnt0l._.js +4 -0
  167. package/.next/server/chunks/features_0hdaci0._.js +4 -0
  168. package/.next/server/chunks/features_1e39j7-._.js +8 -0
  169. package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +1 -1
  170. package/.next/server/chunks/features_memory_1uqvksn._.js +1 -1
  171. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js → node_modules_consola_dist_chunks_prompt_mjs_0a7-pbw._.js} +1 -1
  172. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_17k8irj._.js → node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js} +1 -1
  173. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js → node_modules_consola_dist_chunks_prompt_mjs_107pxfn._.js} +1 -1
  174. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js → node_modules_consola_dist_chunks_prompt_mjs_15hbel8._.js} +1 -1
  175. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_18i-23e._.js +3 -0
  176. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1bewgf3._.js +3 -0
  177. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1nlww1m._.js +3 -0
  178. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1q77nfx._.js +3 -0
  179. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xqdzx_._.js +3 -0
  180. package/.next/server/chunks/{node_modules_zod_v4_1801ax7._.js → node_modules_zod_v4_0lkux5-._.js} +2 -2
  181. package/.next/server/chunks/{node_modules_zod_v4_1cha_bw._.js → node_modules_zod_v4_1jmr9cq._.js} +2 -2
  182. package/.next/server/chunks/{node_modules_zod_v4_0ubdflj._.js → node_modules_zod_v4_1yrxmek._.js} +2 -2
  183. package/.next/server/chunks/node_modules_zod_v4_classic_errors_0tp0y-q.js +3 -0
  184. package/.next/server/chunks/ssr/[root-of-the-server]__0-e011f._.js +3 -0
  185. package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
  186. package/.next/server/chunks/ssr/[root-of-the-server]__0lyi8i2._.js +1 -1
  187. package/.next/server/chunks/ssr/[root-of-the-server]__0pd8yr7._.js +3 -0
  188. package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
  189. package/.next/server/chunks/ssr/[root-of-the-server]__18dthbp._.js +3 -0
  190. package/.next/server/chunks/ssr/[root-of-the-server]__18e68w8._.js +3 -0
  191. package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
  192. package/.next/server/chunks/ssr/[root-of-the-server]__1nygfik._.js +5 -0
  193. package/.next/server/chunks/ssr/[root-of-the-server]__1sdphb0._.js +8 -0
  194. package/.next/server/chunks/ssr/[root-of-the-server]__1vdmfbe._.js +3 -0
  195. package/.next/server/chunks/ssr/[root-of-the-server]__1waa3rc._.js +85 -0
  196. package/.next/server/chunks/ssr/_0-2qsxo._.js +3 -0
  197. package/.next/server/chunks/ssr/_00rehl_._.js +3 -0
  198. package/.next/server/chunks/ssr/{_1lz_cq2._.js → _02oc_h5._.js} +76 -97
  199. package/.next/server/chunks/ssr/_05yghx6._.js +3 -0
  200. package/.next/server/chunks/ssr/_07nbwap._.js +3 -0
  201. package/.next/server/chunks/ssr/_08c4z-9._.js +3 -0
  202. package/.next/server/chunks/ssr/_0_qfdlw._.js +4 -0
  203. package/.next/server/chunks/ssr/{_0m2lsd1._.js → _0b6_jv-._.js} +2 -2
  204. package/.next/server/chunks/ssr/_0evkpto._.js +3 -0
  205. package/.next/server/chunks/ssr/_0geg212._.js +3 -0
  206. package/.next/server/chunks/ssr/{_1jdgjv9._.js → _0hxt2ul._.js} +2 -2
  207. package/.next/server/chunks/ssr/_0i_6q1w._.js +2 -2
  208. package/.next/server/chunks/ssr/_0lu1n_x._.js +1 -1
  209. package/.next/server/chunks/ssr/_0no7i1o._.js +3 -0
  210. package/.next/server/chunks/ssr/_0tzg--b._.js +3 -0
  211. package/.next/server/chunks/ssr/{_15j2_8f._.js → _0wzaht-._.js} +2 -2
  212. package/.next/server/chunks/ssr/_0z-g0uy._.js +3 -0
  213. package/.next/server/chunks/ssr/_114ylv7._.js +7 -0
  214. package/.next/server/chunks/ssr/_123y-ah._.js +3 -0
  215. package/.next/server/chunks/ssr/_12c-iwh._.js +3 -0
  216. package/.next/server/chunks/ssr/_12w42gi._.js +3 -0
  217. package/.next/server/chunks/ssr/_161kqav._.js +3 -0
  218. package/.next/server/chunks/ssr/_167hchk._.js +3 -0
  219. package/.next/server/chunks/ssr/{_0ap2fzn._.js → _16c6_xm._.js} +2 -2
  220. package/.next/server/chunks/ssr/_18opah7._.js +3 -0
  221. package/.next/server/chunks/ssr/_1_krfph._.js +3 -0
  222. package/.next/server/chunks/ssr/{_0rzucul._.js → _1_x5spz._.js} +3 -3
  223. package/.next/server/chunks/ssr/_1a61fc9._.js +3 -0
  224. package/.next/server/chunks/ssr/_1ey7qn-._.js +70 -0
  225. package/.next/server/chunks/ssr/_1g9vs7q._.js +3 -0
  226. package/.next/server/chunks/ssr/_1jmz33w._.js +3 -0
  227. package/.next/server/chunks/ssr/_1rgtxhr._.js +5 -0
  228. package/.next/server/chunks/ssr/_1slp25y._.js +3 -0
  229. package/.next/server/chunks/ssr/_1w_t7h7._.js +101 -0
  230. package/.next/server/chunks/ssr/{_0s99bgb._.js → _1wyfaq_._.js} +9 -3
  231. package/.next/server/chunks/ssr/_200gt-m._.js +102 -0
  232. package/.next/server/chunks/ssr/components_ui_tabs_tsx_0ej40b4._.js +3 -0
  233. package/.next/server/chunks/ssr/features_bot_thread_query_ts_023eklt._.js +4 -0
  234. package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
  235. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  236. package/.next/server/chunks/ssr/features_workspace_00jz2hu._.js +8 -0
  237. package/.next/server/chunks/ssr/{_1-teee5._.js → lib_logger_ts_0-olsze._.js} +2 -7
  238. package/.next/server/chunks/ssr/{node_modules_05-n6dz._.js → node_modules_02ai7br._.js} +1 -1
  239. package/.next/server/chunks/ssr/{node_modules_1chvq5f._.js → node_modules_02deexj._.js} +1 -1
  240. package/.next/server/chunks/ssr/{node_modules_0m383r2._.js → node_modules_07za262._.js} +1 -1
  241. package/.next/server/chunks/ssr/{node_modules_07dsgkx._.js → node_modules_0_s9get._.js} +1 -1
  242. package/.next/server/chunks/ssr/{node_modules_1a0kmjl._.js → node_modules_0n3c7h0._.js} +1 -1
  243. package/.next/server/chunks/ssr/{node_modules_0zeg3x_._.js → node_modules_0od6jwr._.js} +1 -1
  244. package/.next/server/chunks/ssr/{node_modules_1y79i83._.js → node_modules_0rdt7pk._.js} +1 -1
  245. package/.next/server/chunks/ssr/{node_modules_17bwo43._.js → node_modules_0tusx4d._.js} +3 -3
  246. package/.next/server/chunks/ssr/{node_modules_04sksn9._.js → node_modules_0w1z7kz._.js} +1 -1
  247. package/.next/server/chunks/ssr/{node_modules_1uyk91w._.js → node_modules_0wdupfz._.js} +1 -1
  248. package/.next/server/chunks/ssr/{node_modules_0ifp389._.js → node_modules_1720w0a._.js} +1 -1
  249. package/.next/server/chunks/ssr/{node_modules_0u9da1h._.js → node_modules_18lsg7d._.js} +1 -1
  250. package/.next/server/chunks/ssr/{node_modules_1k6n600._.js → node_modules_1b94fxf._.js} +1 -1
  251. package/.next/server/chunks/ssr/{node_modules_20c28js._.js → node_modules_1ecdo1s._.js} +1 -1
  252. package/.next/server/chunks/ssr/{node_modules_0fkeku3._.js → node_modules_1fa9ag7._.js} +1 -1
  253. package/.next/server/chunks/ssr/{node_modules_1j8j4pt._.js → node_modules_1fv-_si._.js} +1 -1
  254. package/.next/server/chunks/ssr/{node_modules_0-dr_o7._.js → node_modules_1hf461g._.js} +1 -1
  255. package/.next/server/chunks/ssr/{node_modules_1lz0oxc._.js → node_modules_1m71ta-._.js} +1 -1
  256. package/.next/server/chunks/ssr/{node_modules_1t77e4n._.js → node_modules_1n93n-g._.js} +1 -1
  257. package/.next/server/chunks/ssr/{node_modules_16mkxuo._.js → node_modules_1n9lqu9._.js} +1 -1
  258. package/.next/server/chunks/ssr/{node_modules_1k7z2j6._.js → node_modules_1npnsvd._.js} +1 -1
  259. package/.next/server/chunks/ssr/{node_modules_056qgf-._.js → node_modules_1rn2r0n._.js} +1 -1
  260. package/.next/server/chunks/ssr/{node_modules_0x3-ply._.js → node_modules_1seuurl._.js} +2 -2
  261. package/.next/server/chunks/ssr/{node_modules_0bvopy2._.js → node_modules_1vq-mfk._.js} +1 -1
  262. package/.next/server/chunks/ssr/{node_modules_0iur59s._.js → node_modules_1vzyw1t._.js} +1 -1
  263. package/.next/server/chunks/ssr/{node_modules_1k8j8n0._.js → node_modules_1w359w4._.js} +1 -1
  264. package/.next/server/chunks/ssr/{node_modules_1x0ztf4._.js → node_modules_1yajbl6._.js} +1 -1
  265. package/.next/server/chunks/ssr/{node_modules_1-y7nni._.js → node_modules_20eqhy8._.js} +1 -1
  266. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0bkwd0u._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_019sbis._.js} +1 -1
  267. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0p9a9su._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0s6piq3._.js} +1 -1
  268. package/.next/server/chunks/ssr/{node_modules_@vercel_oidc_dist_1o553h-._.js → node_modules_@vercel_oidc_dist_05itbb1._.js} +1 -1
  269. package/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_trash_mjs_0px2gzr._.js +3 -0
  270. package/.next/server/middleware-build-manifest.js +3 -3
  271. package/.next/server/pages/404.html +1 -1
  272. package/.next/server/pages/500.html +1 -1
  273. package/.next/server/server-reference-manifest.js +1 -1
  274. package/.next/server/server-reference-manifest.json +68 -46
  275. package/.next/static/chunks/07vqbk_9t4em4.js +2 -0
  276. package/.next/static/chunks/08krja1zx7nqg.js +1 -0
  277. package/.next/static/chunks/0g4b348l0pve7.js +1 -0
  278. package/.next/static/chunks/0qcuwdk132jjt.js +1 -0
  279. package/.next/static/chunks/0vlgzqo9ka90v.js +1 -0
  280. package/.next/static/chunks/{11r2yp_yh9_qn.js → 0z69gvysmave7.js} +1 -1
  281. package/.next/static/chunks/0zrupxgci5gqc.js +1 -0
  282. package/.next/static/chunks/12fvz1w5ows75.js +1 -0
  283. package/.next/static/chunks/14t03ned286iu.js +1 -0
  284. package/.next/static/chunks/156xxbkrz5lzu.js +18 -0
  285. package/.next/static/chunks/1fz14reod4lft.js +1 -0
  286. package/.next/static/chunks/1g23dbabl-vkr.js +1 -0
  287. package/.next/static/chunks/1ohslk4568tm3.js +1 -0
  288. package/.next/static/chunks/1qy_rc3oxfxok.js +1 -0
  289. package/.next/static/chunks/1sbmwswc9k2-1.js +1 -0
  290. package/.next/static/chunks/1vqezbhjm3goq.js +1 -0
  291. package/.next/static/chunks/1zea7qdl-7365.js +1 -0
  292. package/.next/static/chunks/1zvqumkg7kmo0.css +1 -0
  293. package/.next/static/chunks/26hpqt3y6bogl.js +12 -0
  294. package/.next/static/chunks/2_j5mqm6d-z18.js +99 -0
  295. package/.next/static/chunks/2dt6tu7a22svs.js +1 -0
  296. package/.next/static/chunks/2h5ls3d86kya6.js +1 -0
  297. package/.next/static/chunks/2wc6y1ht6dfuy.js +1 -0
  298. package/.next/static/chunks/{435atsnhs7ksu.js → 2xr0zpll7thyi.js} +2 -2
  299. package/.next/static/chunks/31jztc-yq30m7.js +1 -0
  300. package/.next/static/chunks/391af0i8eljzk.js +1 -0
  301. package/.next/static/chunks/3bfwpww3m3x4u.js +1 -0
  302. package/.next/static/chunks/{38gweqdo4jpfj.js → 3izg0a3k87q1-.js} +5 -5
  303. package/.next/static/chunks/3p2s2cjokx1aa.js +5 -0
  304. package/.next/static/chunks/{408ueu4hfvcg7.js → 41s3fml5oivsa.js} +1 -1
  305. package/README.md +45 -193
  306. package/app/api/events/app-event.ts +6 -4
  307. package/app/api/query-key.ts +25 -16
  308. package/bin/database.mjs +37 -0
  309. package/bin/thursday.mjs +44 -28
  310. package/components/ui/custom-icon.tsx +0 -60
  311. package/components/ui/radio-group.tsx +37 -0
  312. package/components/ui/shiny-text.tsx +33 -6
  313. package/database/migrations/{20260910173049_numerous_spirit → 20260916145701_clever_multiple_man}/migration.sql +80 -24
  314. package/database/migrations/{20260910173049_numerous_spirit → 20260916145701_clever_multiple_man}/snapshot.json +689 -110
  315. package/database/migrations/20260916160713_thread_cancelled/migration.sql +2 -0
  316. package/database/migrations/{20260911030320_opposite_jack_power → 20260916160713_thread_cancelled}/snapshot.json +706 -84
  317. package/docs/how-it-works.md +40 -35
  318. package/docs/images/keeps-going.png +0 -0
  319. package/docs/images/two-minds.png +0 -0
  320. package/docs/live-calls.md +236 -0
  321. package/docs/thread-rooms.md +71 -0
  322. package/features/ai/chatgpt.ts +1 -2
  323. package/features/ai/live.schema.ts +128 -0
  324. package/features/ai/load-tools.ts +152 -79
  325. package/features/ai/model.schema.ts +2 -120
  326. package/features/ai/model.ts +52 -88
  327. package/features/ai/prompts/bot.prompt.ts +91 -214
  328. package/features/ai/prompts/live.prompt.ts +169 -0
  329. package/features/ai/prompts/prompt-helper.ts +92 -32
  330. package/features/ai/prompts/thursday.prompt.ts +164 -190
  331. package/features/ai/tools/bot.tool.ts +38 -145
  332. package/features/ai/tools/call.tool.ts +33 -5
  333. package/features/ai/tools/connected.ts +5 -3
  334. package/features/ai/tools/mcp.tool.ts +7 -4
  335. package/features/ai/tools/memory.tool.ts +35 -31
  336. package/features/ai/tools/skills.tool.ts +11 -6
  337. package/features/ai/tools/studio.tool.ts +26 -21
  338. package/features/ai/tools/tool-name.ts +3 -5
  339. package/features/ai/tools/workspace.tool.ts +33 -10
  340. package/features/artifact/artifact.action.ts +2 -2
  341. package/features/artifact/artifact.query.ts +112 -61
  342. package/features/artifact/artifact.schema.ts +8 -6
  343. package/features/artifact/components/artifact-setting.tsx +83 -33
  344. package/features/bot/bot.action.ts +43 -36
  345. package/features/bot/bot.memory.ts +137 -20
  346. package/features/bot/bot.query.ts +28 -11
  347. package/features/bot/bot.run.ts +296 -744
  348. package/features/bot/bot.runner.ts +463 -864
  349. package/features/bot/bot.schema.ts +93 -82
  350. package/features/bot/bot.seed.ts +141 -64
  351. package/features/bot/components/bot-mark.tsx +324 -232
  352. package/features/bot/components/bot-room.tsx +1613 -697
  353. package/features/bot/components/bot-roster.tsx +8 -13
  354. package/features/bot/components/bot-setting.tsx +58 -42
  355. package/features/bot/components/bot-tool.tsx +55 -87
  356. package/features/bot/components/mark-palette.tsx +35 -32
  357. package/features/bot/components/path-chips.tsx +33 -3
  358. package/features/bot/components/thread-badge.tsx +34 -0
  359. package/features/bot/components/thread-reply.tsx +639 -0
  360. package/features/bot/components/thread-setting.tsx +674 -0
  361. package/features/bot/mark.const.ts +79 -31
  362. package/features/bot/room.query.ts +889 -0
  363. package/features/bot/room.schema.ts +64 -0
  364. package/features/bot/seed-bots.ts +2 -1
  365. package/features/bot/thread.query.ts +971 -0
  366. package/features/bot/thread.store.ts +587 -0
  367. package/features/config/components/voice-key.tsx +113 -88
  368. package/features/config/config.const.ts +5 -9
  369. package/features/connectors/mcp.schema.ts +0 -2
  370. package/features/intro/components/intro.tsx +169 -139
  371. package/features/memory/components/memory-setting.tsx +2 -7
  372. package/features/memory/memory.schema.ts +14 -23
  373. package/features/settings/components/setting-ui.tsx +0 -44
  374. package/features/settings/components/settings.tsx +12 -12
  375. package/features/settings/settings.alert.ts +3 -3
  376. package/features/settings/settings.store.ts +1 -1
  377. package/features/skills/skills.schema.ts +0 -2
  378. package/features/thursday/ascii.const.ts +74 -0
  379. package/features/thursday/components/ascii-field.tsx +24 -44
  380. package/features/thursday/components/ascii-orb.tsx +525 -381
  381. package/features/thursday/components/call-log.tsx +46 -44
  382. package/features/thursday/components/face.tsx +40 -10
  383. package/features/thursday/components/side-captions.tsx +273 -0
  384. package/features/thursday/components/tab-state.tsx +14 -6
  385. package/features/thursday/components/thursday-mark.tsx +1 -0
  386. package/features/thursday/components/thursday-setting.tsx +633 -333
  387. package/features/thursday/components/thursday.tsx +315 -274
  388. package/features/thursday/components/voice-picker.tsx +255 -0
  389. package/features/thursday/face.store.ts +5 -0
  390. package/features/thursday/thursday.action.ts +96 -72
  391. package/features/thursday/thursday.query.ts +49 -30
  392. package/features/thursday/thursday.schema.ts +37 -35
  393. package/features/thursday/thursday.store.ts +14 -7
  394. package/features/thursday/tool-call.ts +3 -3
  395. package/features/thursday/tool-line.ts +5 -3
  396. package/features/thursday/use-thursday.ts +801 -457
  397. package/features/workspace/file-kind.ts +9 -0
  398. package/features/workspace/workspace.query.ts +17 -0
  399. package/features/workspace/workspace.ts +82 -36
  400. package/hooks/use-hotkey.ts +3 -2
  401. package/lib/live/live.schema.ts +26 -0
  402. package/lib/live/live.server.ts +153 -0
  403. package/lib/live/live.session.ts +574 -0
  404. package/lib/{realtime/realtime.tap.ts → live/live.tap.ts} +41 -12
  405. package/lib/live/live.transport.ts +152 -0
  406. package/lib/protocol/use-server-pages.ts +9 -5
  407. package/lib/utils.ts +21 -20
  408. package/package.json +2 -2
  409. package/public/voices/alloy.ogg +0 -0
  410. package/public/voices/ash.ogg +0 -0
  411. package/public/voices/ballad.ogg +0 -0
  412. package/public/voices/beacon.ogg +0 -0
  413. package/public/voices/bossa.ogg +0 -0
  414. package/public/voices/cedar.ogg +0 -0
  415. package/public/voices/cinder.ogg +0 -0
  416. package/public/voices/coral.ogg +0 -0
  417. package/public/voices/delta.ogg +0 -0
  418. package/public/voices/echo.ogg +0 -0
  419. package/public/voices/gleam.ogg +0 -0
  420. package/public/voices/marin.ogg +0 -0
  421. package/public/voices/meridian.ogg +0 -0
  422. package/public/voices/quartz.ogg +0 -0
  423. package/public/voices/ripple.ogg +0 -0
  424. package/public/voices/sage.ogg +0 -0
  425. package/public/voices/shimmer.ogg +0 -0
  426. package/public/voices/stone.ogg +0 -0
  427. package/public/voices/tempo.ogg +0 -0
  428. package/public/voices/verse.ogg +0 -0
  429. package/public/voices/vesper.ogg +0 -0
  430. package/public/voices/willow.ogg +0 -0
  431. package/scripts/artifact-paths.test.mts +38 -0
  432. package/scripts/bot-context.test.mts +1408 -0
  433. package/scripts/live-history.test.mts +115 -0
  434. package/scripts/live-session.test.mts +938 -0
  435. package/server.js +1 -1
  436. package/skills/browser/SKILL.md +11 -1
  437. package/skills/computer/SKILL.md +1 -1
  438. package/skills/find-skills/SKILL.md +3 -8
  439. package/skills/interactive-page/SKILL.md +12 -7
  440. package/skills/interactive-page/references/diagram.md +89 -0
  441. package/skills/interactive-page/scripts/archify/LICENSE +22 -0
  442. package/skills/interactive-page/scripts/archify/README.md +25 -0
  443. package/skills/interactive-page/scripts/archify/assets/JetBrainsMono-OFL.txt +93 -0
  444. package/skills/interactive-page/scripts/archify/assets/template.html +14934 -0
  445. package/skills/interactive-page/scripts/archify/bin/archify.mjs +2126 -0
  446. package/skills/interactive-page/scripts/archify/bin/open-artifact.mjs +86 -0
  447. package/skills/interactive-page/scripts/archify/bin/preview.mjs +653 -0
  448. package/skills/interactive-page/scripts/archify/bin/visual-check.mjs +829 -0
  449. package/skills/interactive-page/scripts/archify/delta/architecture-delta.mjs +1223 -0
  450. package/skills/interactive-page/scripts/archify/examples/agent-run.lifecycle.json +60 -0
  451. package/skills/interactive-page/scripts/archify/examples/agent-tool-call.workflow.json +94 -0
  452. package/skills/interactive-page/scripts/archify/examples/async-job-roundtrip.sequence.json +61 -0
  453. package/skills/interactive-page/scripts/archify/examples/cache-miss-request.sequence.json +82 -0
  454. package/skills/interactive-page/scripts/archify/examples/checkout-platform.base.architecture.json +31 -0
  455. package/skills/interactive-page/scripts/archify/examples/checkout-platform.head.architecture.json +31 -0
  456. package/skills/interactive-page/scripts/archify/examples/deployment-release.lifecycle.json +49 -0
  457. package/skills/interactive-page/scripts/archify/examples/event-stream.dataflow.json +57 -0
  458. package/skills/interactive-page/scripts/archify/examples/incident-response.workflow.json +64 -0
  459. package/skills/interactive-page/scripts/archify/examples/product-analytics.dataflow.json +76 -0
  460. package/skills/interactive-page/scripts/archify/examples/production-deployment.architecture.json +71 -0
  461. package/skills/interactive-page/scripts/archify/examples/release-delivery.workflow.json +62 -0
  462. package/skills/interactive-page/scripts/archify/examples/web-app.architecture.json +46 -0
  463. package/skills/interactive-page/scripts/archify/migrations/workflow-v2.mjs +279 -0
  464. package/skills/interactive-page/scripts/archify/recipes/scenarios.mjs +284 -0
  465. package/skills/interactive-page/scripts/archify/references/authoring-contract.md +243 -0
  466. package/skills/interactive-page/scripts/archify/references/delivery-contract.md +120 -0
  467. package/skills/interactive-page/scripts/archify/references/viewer-runtime.md +45 -0
  468. package/skills/interactive-page/scripts/archify/renderers/architecture/grid.mjs +62 -0
  469. package/skills/interactive-page/scripts/archify/renderers/architecture/render-architecture.mjs +1078 -0
  470. package/skills/interactive-page/scripts/archify/renderers/dataflow/README.md +104 -0
  471. package/skills/interactive-page/scripts/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  472. package/skills/interactive-page/scripts/archify/renderers/lifecycle/README.md +115 -0
  473. package/skills/interactive-page/scripts/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  474. package/skills/interactive-page/scripts/archify/renderers/sequence/README.md +114 -0
  475. package/skills/interactive-page/scripts/archify/renderers/sequence/render-sequence.mjs +464 -0
  476. package/skills/interactive-page/scripts/archify/renderers/shared/brand-marks.mjs +563 -0
  477. package/skills/interactive-page/scripts/archify/renderers/shared/cli.mjs +218 -0
  478. package/skills/interactive-page/scripts/archify/renderers/shared/desktop-readability.mjs +26 -0
  479. package/skills/interactive-page/scripts/archify/renderers/shared/diagnostics.mjs +127 -0
  480. package/skills/interactive-page/scripts/archify/renderers/shared/engineering-profiles.mjs +157 -0
  481. package/skills/interactive-page/scripts/archify/renderers/shared/generated-brand-marks.mjs +3 -0
  482. package/skills/interactive-page/scripts/archify/renderers/shared/generated-validators.mjs +13 -0
  483. package/skills/interactive-page/scripts/archify/renderers/shared/geometry.mjs +1423 -0
  484. package/skills/interactive-page/scripts/archify/renderers/shared/i18n.mjs +595 -0
  485. package/skills/interactive-page/scripts/archify/renderers/shared/layout-report.mjs +40 -0
  486. package/skills/interactive-page/scripts/archify/renderers/shared/legend.mjs +217 -0
  487. package/skills/interactive-page/scripts/archify/renderers/shared/output-path.mjs +340 -0
  488. package/skills/interactive-page/scripts/archify/renderers/shared/repository-evidence.mjs +238 -0
  489. package/skills/interactive-page/scripts/archify/renderers/shared/repository-location.mjs +58 -0
  490. package/skills/interactive-page/scripts/archify/renderers/shared/text-fit.mjs +49 -0
  491. package/skills/interactive-page/scripts/archify/renderers/shared/utils.mjs +232 -0
  492. package/skills/interactive-page/scripts/archify/renderers/shared/validator.mjs +86 -0
  493. package/skills/interactive-page/scripts/archify/renderers/workflow/README.md +223 -0
  494. package/skills/interactive-page/scripts/archify/renderers/workflow/render-workflow.mjs +35 -0
  495. package/skills/interactive-page/scripts/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  496. package/skills/interactive-page/scripts/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  497. package/skills/interactive-page/scripts/archify/schemas/README.md +211 -0
  498. package/skills/interactive-page/scripts/archify/schemas/architecture.schema.json +178 -0
  499. package/skills/interactive-page/scripts/archify/schemas/common.schema.json +115 -0
  500. package/skills/interactive-page/scripts/archify/schemas/dataflow.schema.json +243 -0
  501. package/skills/interactive-page/scripts/archify/schemas/lifecycle.schema.json +266 -0
  502. package/skills/interactive-page/scripts/archify/schemas/sequence.schema.json +223 -0
  503. package/skills/interactive-page/scripts/archify/schemas/workflow.schema.json +428 -0
  504. package/skills/interactive-page/scripts/archify/scripts/check-render-output.mjs +916 -0
  505. package/skills/interactive-page/scripts/archify/scripts/render-examples.mjs +26 -0
  506. package/skills/interactive-page/scripts/bundle-artifact.sh +9 -8
  507. package/skills/skill-creator/SKILL.md +4 -5
  508. package/.next/server/app/api/bot/task/[id]/route/app-paths-manifest.json +0 -3
  509. package/.next/server/app/api/bot/task/[id]/route.js +0 -16
  510. package/.next/server/app/api/bot/task/[id]/route.js.nft.json +0 -1
  511. package/.next/server/app/api/bot/task/[id]/route_client-reference-manifest.js +0 -3
  512. package/.next/server/app/api/bot/task/route/app-paths-manifest.json +0 -3
  513. package/.next/server/app/api/bot/task/route.js +0 -16
  514. package/.next/server/app/api/bot/task/route.js.nft.json +0 -1
  515. package/.next/server/app/api/bot/task/route_client-reference-manifest.js +0 -3
  516. package/.next/server/chunks/[root-of-the-server]__026u77e._.js +0 -5
  517. package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +0 -3
  518. package/.next/server/chunks/[root-of-the-server]__050zri4._.js +0 -3
  519. package/.next/server/chunks/[root-of-the-server]__0bg9710._.js +0 -3
  520. package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +0 -3
  521. package/.next/server/chunks/[root-of-the-server]__0in-d5s._.js +0 -3
  522. package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +0 -3
  523. package/.next/server/chunks/[root-of-the-server]__0pa9k-f._.js +0 -8
  524. package/.next/server/chunks/[root-of-the-server]__0qi_m0e._.js +0 -8
  525. package/.next/server/chunks/[root-of-the-server]__0tq0m_k._.js +0 -3
  526. package/.next/server/chunks/[root-of-the-server]__0xep6j_._.js +0 -3
  527. package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +0 -5
  528. package/.next/server/chunks/[root-of-the-server]__11n256_._.js +0 -5
  529. package/.next/server/chunks/[root-of-the-server]__144l7qt._.js +0 -8
  530. package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +0 -5
  531. package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +0 -3
  532. package/.next/server/chunks/[root-of-the-server]__1fizn31._.js +0 -3
  533. package/.next/server/chunks/[root-of-the-server]__1jg8t86._.js +0 -8
  534. package/.next/server/chunks/[root-of-the-server]__1lldzy9._.js +0 -3
  535. package/.next/server/chunks/[root-of-the-server]__1llj3-b._.js +0 -3
  536. package/.next/server/chunks/[root-of-the-server]__1p8m3f4._.js +0 -3
  537. package/.next/server/chunks/[root-of-the-server]__1peqvsi._.js +0 -3
  538. package/.next/server/chunks/[root-of-the-server]__1w8gm5h._.js +0 -10
  539. package/.next/server/chunks/[root-of-the-server]__1xt39nn._.js +0 -8
  540. package/.next/server/chunks/[root-of-the-server]__1zb8mfo._.js +0 -8
  541. package/.next/server/chunks/_0-73of8._.js +0 -55
  542. package/.next/server/chunks/_0-fjql4._.js +0 -31
  543. package/.next/server/chunks/_00xh_q0._.js +0 -25
  544. package/.next/server/chunks/_051qh8u._.js +0 -3
  545. package/.next/server/chunks/_05sovvj._.js +0 -3
  546. package/.next/server/chunks/_091bi8d._.js +0 -3
  547. package/.next/server/chunks/_0fzqt2y._.js +0 -142
  548. package/.next/server/chunks/_0lr92vd._.js +0 -3
  549. package/.next/server/chunks/_0mecv0f._.js +0 -31
  550. package/.next/server/chunks/_0mh8j4m._.js +0 -3
  551. package/.next/server/chunks/_0sb7tlz._.js +0 -3
  552. package/.next/server/chunks/_0si7xzn._.js +0 -3
  553. package/.next/server/chunks/_0u10wmw._.js +0 -55
  554. package/.next/server/chunks/_0wp4le3._.js +0 -3
  555. package/.next/server/chunks/_0y5fg-x._.js +0 -3
  556. package/.next/server/chunks/_1230wbx._.js +0 -10
  557. package/.next/server/chunks/_12nheja._.js +0 -3
  558. package/.next/server/chunks/_13510xh._.js +0 -3
  559. package/.next/server/chunks/_15hq89o._.js +0 -3
  560. package/.next/server/chunks/_18-1wcc._.js +0 -55
  561. package/.next/server/chunks/_18d7-5p._.js +0 -3
  562. package/.next/server/chunks/_1c78vw5._.js +0 -237
  563. package/.next/server/chunks/_1h5_8sk._.js +0 -3
  564. package/.next/server/chunks/_1hdnxb7._.js +0 -3
  565. package/.next/server/chunks/_1j8k_j5._.js +0 -3
  566. package/.next/server/chunks/_1nula3v._.js +0 -3
  567. package/.next/server/chunks/_1php4eq._.js +0 -3
  568. package/.next/server/chunks/_1yuv4vp._.js +0 -3
  569. package/.next/server/chunks/_1zkfbaa._.js +0 -3
  570. package/.next/server/chunks/_next-internal_server_app_api_bot_task_[id]_route_actions_1_k_8u3.js +0 -3
  571. package/.next/server/chunks/_next-internal_server_app_api_bot_task_route_actions_0xln6ra.js +0 -3
  572. package/.next/server/chunks/features_0rpfxbk._.js +0 -3
  573. package/.next/server/chunks/features_1qf3cei._.js +0 -8
  574. package/.next/server/chunks/features_bot_0zt92oh._.js +0 -3
  575. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1jidylq._.js +0 -3
  576. package/.next/server/chunks/ssr/[externals]__0dizgek._.js +0 -3
  577. package/.next/server/chunks/ssr/[root-of-the-server]__0-nob52._.js +0 -4
  578. package/.next/server/chunks/ssr/[root-of-the-server]__092knlj._.js +0 -3
  579. package/.next/server/chunks/ssr/[root-of-the-server]__0mz33a7._.js +0 -3
  580. package/.next/server/chunks/ssr/[root-of-the-server]__0s4wb56._.js +0 -3
  581. package/.next/server/chunks/ssr/[root-of-the-server]__0s9dq5u._.js +0 -3
  582. package/.next/server/chunks/ssr/[root-of-the-server]__0sd3izg._.js +0 -3
  583. package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +0 -3
  584. package/.next/server/chunks/ssr/[root-of-the-server]__1-13tcn._.js +0 -43
  585. package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +0 -61
  586. package/.next/server/chunks/ssr/[root-of-the-server]__1zidpfl._.js +0 -3
  587. package/.next/server/chunks/ssr/_001_ms1._.js +0 -33
  588. package/.next/server/chunks/ssr/_01n3o0m._.js +0 -3
  589. package/.next/server/chunks/ssr/_04z2c7b._.js +0 -3
  590. package/.next/server/chunks/ssr/_06tssji._.js +0 -3
  591. package/.next/server/chunks/ssr/_0fk4c7d._.js +0 -3
  592. package/.next/server/chunks/ssr/_0h42fze._.js +0 -3
  593. package/.next/server/chunks/ssr/_0h7zdni._.js +0 -3
  594. package/.next/server/chunks/ssr/_0mggveb._.js +0 -3
  595. package/.next/server/chunks/ssr/_0nbpc_8._.js +0 -3
  596. package/.next/server/chunks/ssr/_0q0zan6._.js +0 -70
  597. package/.next/server/chunks/ssr/_0zcjrbg._.js +0 -3
  598. package/.next/server/chunks/ssr/_0zhoc3r._.js +0 -3
  599. package/.next/server/chunks/ssr/_11j92tu._.js +0 -3
  600. package/.next/server/chunks/ssr/_13fke0x._.js +0 -3
  601. package/.next/server/chunks/ssr/_18cymkq._.js +0 -3
  602. package/.next/server/chunks/ssr/_1ebxla1._.js +0 -3
  603. package/.next/server/chunks/ssr/_1nj4niv._.js +0 -42
  604. package/.next/server/chunks/ssr/_1ov37if._.js +0 -3
  605. package/.next/server/chunks/ssr/_1ovmc9s._.js +0 -3
  606. package/.next/server/chunks/ssr/_1sji22n._.js +0 -3
  607. package/.next/server/chunks/ssr/_1vi_uo7._.js +0 -3
  608. package/.next/server/chunks/ssr/_1vyd1ji._.js +0 -3
  609. package/.next/server/chunks/ssr/_1xx4zlb._.js +0 -3
  610. package/.next/server/chunks/ssr/components_ui_1y1l7yj._.js +0 -3
  611. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +0 -3
  612. package/.next/server/chunks/ssr/lib_realtime_12wg_dd._.js +0 -27
  613. package/.next/server/chunks/ssr/lib_realtime_15h_v1v._.js +0 -27
  614. package/.next/server/chunks/ssr/node_modules_lucide-react_dist_esm_icons_0npjf8-._.js +0 -3
  615. package/.next/static/chunks/0n3jle9kbqfr2.js +0 -40
  616. package/.next/static/chunks/0r5qcctujhxli.js +0 -1
  617. package/.next/static/chunks/10teww7wt-f6h.js +0 -1
  618. package/.next/static/chunks/126fj4pga_72k.js +0 -1
  619. package/.next/static/chunks/159udiexf6dom.js +0 -2
  620. package/.next/static/chunks/18a96rkhi4_yd.js +0 -1
  621. package/.next/static/chunks/18gi7o2ibrgh8.js +0 -3
  622. package/.next/static/chunks/1dkjg87pxk0n9.js +0 -28
  623. package/.next/static/chunks/1q9z1tohil7xn.js +0 -1
  624. package/.next/static/chunks/1sjgfets224re.js +0 -1
  625. package/.next/static/chunks/1wszsr83t01gh.js +0 -1
  626. package/.next/static/chunks/22_zkas21rpna.js +0 -1
  627. package/.next/static/chunks/2fh4-3z_q8b_w.js +0 -1
  628. package/.next/static/chunks/2h0sd2vumhb15.js +0 -1
  629. package/.next/static/chunks/2szintyibu0s5.js +0 -1
  630. package/.next/static/chunks/2t4vhi3m8ahya.js +0 -1
  631. package/.next/static/chunks/2tigdtz3dpcy8.js +0 -1
  632. package/.next/static/chunks/3-b6b7entqazs.js +0 -1
  633. package/.next/static/chunks/3786h5vayt80-.js +0 -28
  634. package/.next/static/chunks/3cq5g494yuke8.css +0 -1
  635. package/.next/static/chunks/3dmbhsv2u_8kj.js +0 -1
  636. package/.next/static/chunks/3hibbjnt85-k6.js +0 -1
  637. package/.next/static/chunks/3i0a5px-oufgc.js +0 -1
  638. package/.next/static/chunks/3wexk9qo8zuj3.js +0 -18
  639. package/.next/static/chunks/3xk7yqgomx7yq.js +0 -1
  640. package/.next/static/chunks/3xre8bu_pk1p_.js +0 -1
  641. package/.next/static/chunks/3zv6t3i77xhr_.js +0 -1
  642. package/database/migrations/20260911030320_opposite_jack_power/migration.sql +0 -1
  643. package/docs/images/task-thread.png +0 -0
  644. package/features/bot/components/task-badge.tsx +0 -42
  645. package/features/bot/components/task-reply.tsx +0 -328
  646. package/features/bot/components/task-setting.tsx +0 -524
  647. package/features/bot/task.query.ts +0 -1079
  648. package/features/bot/task.store.ts +0 -487
  649. package/lib/realtime/client-secret.ts +0 -86
  650. package/lib/realtime/open-session.ts +0 -31
  651. package/lib/realtime/openai.ts +0 -35
  652. package/lib/realtime/realtime.audio.ts +0 -196
  653. package/lib/realtime/realtime.driver.ts +0 -489
  654. package/lib/realtime/realtime.protocol.ts +0 -393
  655. package/lib/realtime/realtime.schema.ts +0 -37
  656. package/lib/realtime/realtime.session.ts +0 -106
  657. package/lib/realtime/realtime.transport.ts +0 -384
  658. package/lib/realtime/xai.ts +0 -39
  659. /package/.next/server/app/api/bot/{task → thread}/[id]/route/build-manifest.json +0 -0
  660. /package/.next/server/app/api/bot/{task → thread}/[id]/route/server-reference-manifest.json +0 -0
  661. /package/.next/server/app/api/bot/{task → thread}/[id]/route.js.map +0 -0
  662. /package/.next/server/app/api/bot/{task → thread}/route/build-manifest.json +0 -0
  663. /package/.next/server/app/api/bot/{task → thread}/route/server-reference-manifest.json +0 -0
  664. /package/.next/server/app/api/bot/{task → thread}/route.js.map +0 -0
  665. /package/.next/static/{G6-gNoyGDEcgAsq-L4PE1 → yyRVBc0xxYQsh3GpbQX9b}/_buildManifest.js +0 -0
  666. /package/.next/static/{G6-gNoyGDEcgAsq-L4PE1 → yyRVBc0xxYQsh3GpbQX9b}/_clientMiddlewareManifest.js +0 -0
  667. /package/.next/static/{G6-gNoyGDEcgAsq-L4PE1 → yyRVBc0xxYQsh3GpbQX9b}/_ssgManifest.js +0 -0
@@ -2,10 +2,7 @@
2
2
 
3
3
  import { useEffect, useRef } from "react";
4
4
  import { ASCII_FACE } from "@/config";
5
- import {
6
- createVoiceFollower,
7
- SPECTRUM_BANDS,
8
- } from "@/lib/realtime/realtime.tap";
5
+ import { createVoiceFollower, SPECTRUM_BANDS } from "@/lib/live/live.tap";
9
6
  import {
10
7
  ALPHA_TOP,
11
8
  CHAR_RATE,
@@ -14,32 +11,30 @@ import {
14
11
  EMOJI_POOL,
15
12
  EMOJI_RATIO,
16
13
  hash,
14
+ LETTERS,
17
15
  RAMP,
18
16
  smoothstep,
19
17
  } from "../ascii.const";
20
18
  import type { AsciiCharset } from "../face.const";
19
+ import type { FaceWord } from "../thursday.schema";
21
20
 
22
21
  export type AsciiOrbMode =
23
22
  | "idle"
24
23
  | "connecting"
24
+ /** The user's turn: the resting body, thinned out, retyping with the microphone */
25
+ | "listening"
26
+ /** Hanging up: the body draws in and goes out, leaving the field empty */
27
+ | "ending"
25
28
  | "speaking"
26
29
  /** Running a tool; alive without speaking */
27
30
  | "working"
28
31
  | "error";
29
32
 
30
- /** The list lives in face.const so the settings schema can build the enum without importing this component. */
31
- export type AsciiOrbCharset = AsciiCharset;
32
-
33
- /** Expressions the orb can wear while speaking. */
34
- export type AsciiOrbEmotion = "happy" | "sad" | "love";
35
-
36
33
  export type AsciiOrbProps = {
37
34
  className?: string;
38
35
  mode?: AsciiOrbMode;
39
36
  /** "emoji" sprinkles emoji in; "emojiOnly" is all emoji */
40
- charset?: AsciiOrbCharset;
41
- /** Expression while speaking; null for none */
42
- emotion?: AsciiOrbEmotion | null;
37
+ charset?: AsciiCharset;
43
38
  /** Glyph size (px) */
44
39
  fontSize?: number;
45
40
  /** Cell density; above 1 packs tighter */
@@ -49,6 +44,16 @@ export type AsciiOrbProps = {
49
44
  * speaking; without it a synthetic waveform keeps the orb alive (previews).
50
45
  */
51
46
  getSpectrum?: () => ArrayLike<number>;
47
+ /**
48
+ * Microphone bands, read the same way. While listening they set how fast the
49
+ * glyphs retype; without them listening is the resting body alone.
50
+ */
51
+ getMicSpectrum?: () => ArrayLike<number>;
52
+ /**
53
+ * A word to show (emote): the body steps aside, the letters light in one by
54
+ * one, hold, and go out, and the mode takes the face back. A new `at` shows it again.
55
+ */
56
+ word?: FaceWord | null;
52
57
  /** Side length (px). Everything scales with it; cell count scales with area. */
53
58
  size?: number;
54
59
  /**
@@ -64,99 +69,55 @@ const DEFAULT_COLOR: [number, number, number] = [235, 235, 235];
64
69
  /** Color easing per frame (0.05 is about 0.5s) */
65
70
  const COLOR_EASE = 0.05;
66
71
 
67
- /** ERROR: 4 cells per letter, 1 gap (24 x 5) */
68
- const ERROR_BITMAP = [
69
- "████ ███ ███ ██ ███ ",
70
- "█ █ █ █ █ █ █ █ █",
71
- "███ ███ ███ █ █ ███ ",
72
- "█ █ █ █ █ █ █ █ █ ",
73
- "████ █ █ █ █ ██ █ █",
74
- ];
75
-
76
- /** Expression bitmaps, 21 x 13; the top 7 rows are eyes, the rest mouth. */
77
- const FACE_COLS = 21;
78
- const FACE_ROWS = 13;
79
- const EYE_ROWS = 7;
80
- const FACES: Record<AsciiOrbEmotion, string[]> = {
81
- happy: [
82
- ".....................",
83
- ".....................",
84
- "....███.......███....",
85
- "...█████.....█████...",
86
- "...█████.....█████...",
87
- "....███.......███....",
88
- ".....................",
89
- ".....................",
90
- "█...................█",
91
- ".██...............██.",
92
- "...███.........███...",
93
- "......█████████......",
94
- ".....................",
95
- ],
96
- sad: [
97
- ".....................",
98
- ".....................",
99
- "....███.......███....",
100
- "...█████.....█████...",
101
- "...█████.....█████...",
102
- "....███.......███....",
103
- ".....................",
104
- ".....................",
105
- "......█████████......",
106
- "...███.........███...",
107
- ".██...............██.",
108
- "█...................█",
109
- ".....................",
110
- ],
111
- love: [
112
- ".....................",
113
- "...██.██.....██.██...",
114
- "..███████...███████..",
115
- "..███████...███████..",
116
- "...█████.....█████...",
117
- "....███.......███....",
118
- ".....█.........█.....",
119
- ".....................",
120
- "█...................█",
121
- ".██...............██.",
122
- "...███.........███...",
123
- "......█████████......",
124
- ".....................",
125
- ],
126
- };
72
+ /** A word as five rows of letter indices, one empty column between letters; -1 is no ink. */
73
+ function spell(text: string) {
74
+ const rows: number[][] = [[], [], [], [], []];
75
+ let letter = 0;
76
+ for (const char of Array.from(text.toUpperCase())) {
77
+ const glyph = LETTERS[char];
78
+ if (!glyph) continue;
79
+ if (rows[0].length) for (const row of rows) row.push(-1);
80
+ glyph.forEach((line, r) => {
81
+ for (const px of line) rows[r].push(px === "#" ? letter : -1);
82
+ });
83
+ if (char !== " ") letter++;
84
+ }
85
+ return rows;
86
+ }
127
87
 
128
- /** Expression fade out, blank, fade in (seconds) */
129
- const FACE_OUT = 0.36;
130
- const FACE_BLANK = 0.14;
131
- const FACE_IN = 0.95;
132
- /** Glyph swap rate for face cells (per second), slower than the background */
133
- const FACE_CHAR_RATE = 0.75;
88
+ const ERROR_ROWS = spell("ERROR");
134
89
 
135
90
  /** Reference size the tuning constants assume; coordinates are normalized to it. */
136
91
  const DESIGN = 680;
137
92
  /** Radius (reference units) within which cells exist. Must stay under DESIGN/2 or the canvas clips it. */
138
93
  const FIELD_R = 328;
139
94
 
140
- /** Speaking: reference-unit px the rim is pushed by each channel. */
141
- /** Where the rim sits while the voice is at the bottom of its range */
142
- const SPEAK_BASE = 184;
95
+ /** Where the rim sits while the voice is at the bottom of its range: the resting body's radius (IDLE_R), so speaking starts her own size */
96
+ const SPEAK_BASE = 170;
143
97
  /** Swell across a phrase at the top of the voice's range */
144
98
  const SPEAK_SWELL = 40;
145
- /** Kick per syllable */
146
- const SPEAK_KICK = 120;
99
+ /** Kick per syllable. Held down on purpose: a syllable should push the rim, not throw it */
100
+ const SPEAK_KICK = 85;
147
101
  /** Rotating lobes, harmonics 2 and up (the first harmonic shifts the whole circle) */
148
102
  const SPEAK_LOBES = 4;
149
103
  /** Max depth of one lobe */
150
- const SPEAK_LOBE_R = 34;
104
+ const SPEAK_LOBE_R = 28;
105
+ /**
106
+ * How fast the whole lobe pattern turns (rad/s), and how much a loud phrase adds.
107
+ * The lobes also drift against each other; this is the turn you actually see,
108
+ * since counter-drifting alone reads as shimmer rather than rotation.
109
+ */
110
+ const SPEAK_SPIN = 1.1;
111
+ const SPEAK_SPIN_VOICE = 0.9;
151
112
  /**
152
113
  * Rim range. Past SPEAK_KNEE the rim slows into SPEAK_MAX rather than stopping
153
114
  * at it, so a loud syllable still reads as a push instead of a flat edge. Max
154
115
  * stays inside FIELD_R or thrown crumbs die at the edge; min keeps a deep
155
116
  * breath from collapsing to a dot.
156
117
  */
157
- const SPEAK_MIN = 130;
158
- const SPEAK_KNEE = 244;
159
- const SPEAK_MAX = 278;
118
+ const SPEAK_MIN = 110;
119
+ const SPEAK_KNEE = 205;
120
+ const SPEAK_MAX = 235;
160
121
  /** How far a rim cell sits in or out of the rim: the edge is crumbly, not drawn with a compass */
161
122
  const SPEAK_ROUGH = 20;
162
123
  /** Share of cells a syllable throws outward; the rest stay, so what leaves is crumbs, not a ring */
@@ -169,36 +130,116 @@ const MAX_RINGS = 5;
169
130
 
170
131
  /** Per-frame values derived from the voice; every cell reads the same ones. */
171
132
  type Voice = {
172
- /** The voice inside its own range over about half a second (realtime.tap createVoiceFollower) */
133
+ /** The voice inside its own range over about half a second (live.tap createVoiceFollower) */
173
134
  phrase: number;
135
+ /** The user's voice, the same measure, from the microphone (listening) */
136
+ micPhrase: number;
137
+ /** The same voice syllable by syllable, so a word shows and not only a sentence */
138
+ micLevel: number;
174
139
  /** Spring kicked by each syllable */
175
140
  bob: number;
176
141
  bobVel: number;
177
142
  /** Lobe strength and angle */
178
143
  amp: number[];
179
144
  phase: number[];
145
+ /** The whole pattern's turn (rad), one way */
146
+ spin: number;
180
147
  /** One throw of crumbs per syllable */
181
148
  rings: { born: number; power: number }[];
182
149
  };
183
150
 
184
- /** Crossfade between modes (s) */
185
- const XFADE = 1.5;
186
- /** Time for a mode to fill in on its own (s) */
187
- const INTRO = XFADE;
151
+ /**
152
+ * Seconds each channel of the field takes to arrive, and to leave. Leaving is
153
+ * slower everywhere: a behaviour that stops winds down — crumbs already on their
154
+ * way keep coming and fade out — rather than vanishing on the frame the mode
155
+ * changes.
156
+ */
157
+ const RISE = {
158
+ scale: 0.45,
159
+ lift: 0.22,
160
+ gather: 0.3,
161
+ comet: 0.45,
162
+ speech: 0.5,
163
+ err: 0.5,
164
+ word: 0.35,
165
+ listen: 0.3,
166
+ };
167
+ const FALL = {
168
+ scale: 0.7,
169
+ lift: 0.4,
170
+ gather: 0.95,
171
+ comet: 0.9,
172
+ speech: 0.75,
173
+ err: 0.6,
174
+ word: 0.5,
175
+ listen: 0.6,
176
+ };
177
+ /** Hanging up pulls the body in faster than anything else grows back. */
178
+ const END_FALL = 0.4;
179
+
180
+ /** Moves one channel toward its target; arriving and leaving have their own times. */
181
+ function toward(
182
+ from: number,
183
+ to: number,
184
+ rise: number,
185
+ fall: number,
186
+ dt: number,
187
+ ) {
188
+ return from + (to - from) * Math.min(1, dt / (to > from ? rise : fall));
189
+ }
188
190
 
189
191
  /**
190
- * Idle radius. The max drawable radius is DESIGN/2, so this sets the budget
191
- * for SPEAK_MAX and the rings above it. The visible body is about half, since
192
- * the idle wave fades from 0.55 x IDLE_R.
192
+ * The body every mode rests at. The max drawable radius is DESIGN/2, so this
193
+ * sets the budget for SPEAK_MAX and the crumbs thrown past it: leave room, or a
194
+ * loud voice fills the box and its swell has nowhere left to read. The visible
195
+ * body is about half, since the idle wave fades from 0.55 x IDLE_R.
196
+ */
197
+ const IDLE_R = 170;
198
+
199
+ /**
200
+ * Connecting: crumbs travel in from the edge of the field to the resting body.
201
+ * Each ray carries its own crumb at its own phase, so nothing lines up into a
202
+ * ring.
203
+ */
204
+ const GATHER_R = 320;
205
+ /** Equal sectors around the circle; each is a few cells wide, so its crumb reads as a crumb and not an arc */
206
+ const GATHER_RAYS = 56;
207
+ /** One crumb's trip inward, in trips per second */
208
+ const GATHER_RATE = 0.7;
209
+ /**
210
+ * Connecting draws the body in to `DIP` of its size and fills back out over
211
+ * `GROW` as the crumbs land in it, so the gathering has somewhere to go instead
212
+ * of piling onto a body that is already full.
193
213
  */
194
- const IDLE_R = 190;
214
+ const GATHER_DIP = 0.45;
215
+ const GATHER_GROW = 1.3;
195
216
 
196
- /** Connecting: radius and width of the band that spreads and holds */
197
- const CONNECT_R = 235;
198
- const CONNECT_BAND = 6000;
217
+ /**
218
+ * Listening: the resting body, its glyphs retyping as fast as the user speaks.
219
+ * Quiet is slower than idle, and a loud voice is PACE_VOICE times that; the
220
+ * shape never moves, which is the point.
221
+ */
222
+ const LISTEN_PACE_QUIET = 0.5;
223
+ const LISTEN_PACE_VOICE = 8;
224
+ /**
225
+ * Brightness the voice adds: the field fills in as the user talks and thins out
226
+ * when they stop. Most of it follows the phrase so it holds through a sentence,
227
+ * the rest the syllable, so the words show without the body moving.
228
+ */
229
+ const LISTEN_LIFT = 0.24;
230
+ /** Seconds the retype rate takes to follow the voice (toward) */
231
+ const PACE_TIME = 0.14;
232
+ /**
233
+ * Listening also thins the body out, so the voice has gaps to show in: every
234
+ * cell's gate rises by THIN, and POP_RATE times a second each cell re-rolls up
235
+ * to POP of it, as far as the voice is loud — glyphs pop in and out with the words.
236
+ */
237
+ const LISTEN_THIN = 0.16;
238
+ const LISTEN_POP = 0.6;
239
+ const LISTEN_POP_RATE = 9;
199
240
 
200
- /** Working: a comet orbiting just outside the idle circle */
201
- const WORK_R = 238;
241
+ /** Working: a comet orbiting just outside the resting body */
242
+ const WORK_R = 210;
202
243
  /** Orbit speed (rad/s); 3.0 is about one lap per 2s */
203
244
  const WORK_SPIN = 3;
204
245
  /** Orbit band width; larger blurs more */
@@ -215,6 +256,15 @@ const ERR_STEP_OUT = 0.5;
215
256
  const ERR_FADE_OUT = 0.7;
216
257
  const ERR_CYCLE = 9.2;
217
258
 
259
+ /** A word (emote), once: letters light in one by one, hold, and go out the same way (s) */
260
+ const WORD_STEP_IN = 0.11;
261
+ const WORD_FADE_IN = 0.3;
262
+ const WORD_HOLD = 3.4;
263
+ const WORD_STEP_OUT = 0.08;
264
+ const WORD_FADE_OUT = 0.35;
265
+ /** The widest a word is drawn, as a share of the box: about her body */
266
+ const WORD_WIDTH = 0.62;
267
+
218
268
  type Cell = {
219
269
  /** Draw position on the canvas */
220
270
  x: number;
@@ -230,60 +280,41 @@ type Cell = {
230
280
  grain: number;
231
281
  /** Below this brightness the cell is empty; random gaps */
232
282
  gap: number;
233
- /** Order this cell lights up when an expression appears (0..1) */
234
- birth: number;
235
- /** Order this cell goes out when an expression leaves; differs from birth */
236
- death: number;
237
283
  /** Emoji slot in emoji mode */
238
284
  emoji: boolean;
239
285
  /** Below SPECK_SHARE, a crumb a syllable throws outward */
240
286
  speck: number;
241
287
  /** Index of the ERROR letter this cell belongs to, or -1 */
242
288
  letter: number;
289
+ /** Index of the shown word's letter this cell belongs to, or -1; laid again for each word */
290
+ word: number;
243
291
  };
244
292
 
245
- /** Index of the ERROR letter at (dx,dy), or -1 */
246
- function errorLetterAt(
293
+ /** Index of the letter at (dx,dy) of a word centred on the box, each of its cells `s` grid cells wide; -1 when none */
294
+ function letterAt(
295
+ rows: number[][],
247
296
  dx: number,
248
297
  dy: number,
249
298
  cw: number,
250
299
  ch: number,
251
300
  s: number,
252
301
  ) {
253
- const rows = ERROR_BITMAP.length;
254
- const cols = ERROR_BITMAP[0].length;
255
- const bx = Math.floor(dx / (cw * s) + cols / 2);
256
- const by = Math.floor(dy / (ch * s) + rows / 2);
257
-
258
- if (by < 0 || by >= rows) return -1;
259
- const line = ERROR_BITMAP[by];
260
- if (bx < 0 || bx >= line.length) return -1;
261
- if (line[bx] !== "█") return -1;
262
- return Math.floor(bx / 5);
302
+ const bx = Math.floor(dx / (cw * s) + rows[0].length / 2);
303
+ const by = Math.floor(dy / (ch * s) + rows.length / 2);
304
+ if (by < 0 || by >= rows.length) return -1;
305
+ return rows[by][bx] ?? -1;
263
306
  }
264
307
 
265
- /** Expression sample: 0 none, 1 eye, 2 mouth */
266
- function faceSample(
267
- em: AsciiOrbEmotion,
268
- dx: number,
269
- dy: number,
270
- cw: number,
271
- ch: number,
272
- s: number,
273
- ) {
274
- const bx = Math.floor(dx / (cw * s) + FACE_COLS / 2);
275
- const by = Math.floor(dy / (ch * s) + FACE_ROWS / 2);
276
- if (by < 0 || by >= FACE_ROWS || bx < 0 || bx >= FACE_COLS) return 0;
277
- if (FACES[em][by][bx] !== "█") return 0;
278
- return by < EYE_ROWS ? 1 : 2;
279
- }
280
-
281
- /** Idle: a soft breathing wave inside a small circle */
282
- function idleValue(cell: Cell, t: number) {
283
- const body = 1 - smoothstep(IDLE_R * 0.55, IDLE_R, cell.dist);
308
+ /**
309
+ * Idle: a soft breathing wave inside a small circle. `lift` brightens it in
310
+ * place; `scale` is the body's share of IDLE_R, which is how it opens and closes.
311
+ */
312
+ function idleValue(cell: Cell, t: number, lift = 0, scale = 1) {
313
+ const r = IDLE_R * Math.max(0.02, scale);
314
+ const body = 1 - smoothstep(r * 0.55, r, cell.dist);
284
315
  const w = Math.sin(cell.dist * 0.05 - t * 0.4);
285
316
  const breath = Math.sin(t * 0.35) * 0.07;
286
- return (0.58 + w * 0.2 + breath) * body;
317
+ return (0.58 + lift + w * 0.2 + breath) * body;
287
318
  }
288
319
 
289
320
  /** The rim's radius for a raw push: as pushed up to SPEAK_KNEE, then easing into SPEAK_MAX. */
@@ -294,142 +325,250 @@ function rimAt(raw: number) {
294
325
  }
295
326
 
296
327
  /**
297
- * Brightness of one mode; mode transitions blend two of these.
298
- * @param e seconds since the mode started
328
+ * What the field is doing right now. A mode does not draw a picture of its own:
329
+ * it names targets for these channels, and each eases toward its target on its
330
+ * own clock, so a behaviour arrives and — when the mode changes — winds down.
299
331
  */
300
- function valueFor(m: AsciiOrbMode, cell: Cell, t: number, e: number, v: Voice) {
332
+ type Field = {
333
+ /** The body's radius as a share of IDLE_R; 0 is an empty field */
334
+ scale: number;
335
+ /** Brightness added to the body in place */
336
+ lift: number;
337
+ /** Crumbs streaming in from the edge of the field (connecting) */
338
+ gather: number;
339
+ /** A comet orbiting outside the body (working) */
340
+ comet: number;
341
+ /** Her voice: the rim it pushes and the crumbs a syllable throws (speaking) */
342
+ speech: number;
343
+ /** The ERROR letters */
344
+ err: number;
345
+ /** A word's letters (emote) */
346
+ word: number;
347
+ /** Listening's thinning out */
348
+ listen: number;
349
+ };
350
+
351
+ /** Where a mode wants the field. `e` is seconds since the mode was asked for. */
352
+ function targetFor(m: AsciiOrbMode, e: number, v: Voice): Field {
353
+ const rest: Field = {
354
+ scale: 1,
355
+ lift: 0,
356
+ gather: 0,
357
+ comet: 0,
358
+ speech: 0,
359
+ err: 0,
360
+ word: 0,
361
+ listen: 0,
362
+ };
301
363
  switch (m) {
302
364
  case "idle":
303
- return idleValue(cell, t) * smoothstep(0, 1, Math.min(1, e / INTRO));
365
+ return rest;
304
366
 
305
- // ring spreads outward while the idle circle clears
367
+ // draws in first, then fills back out as the crumbs land in it
306
368
  case "connecting": {
307
- const k = smoothstep(0, 1, Math.min(1, e / 1.8));
308
- // spread far enough to reach the outer debris
309
- const d = cell.dist - CONNECT_R * k;
310
- // a wide, soft band so no crisp donut edge forms
311
- const band = Math.exp(-(d * d) / CONNECT_BAND);
312
- // scatter intensity per cell by distance and angle so no inside/outside gradient forms
313
- const grain =
314
- 0.2 +
315
- hash(Math.floor(cell.dist / 9), Math.floor(cell.angle * 11)) * 0.85 +
316
- hash(cell.seed * 137, 7) * 0.5;
317
- const ring = band * grain * k;
318
- return ring + idleValue(cell, t) * (1 - k);
369
+ const filled = 1 - Math.exp(-e / GATHER_GROW);
370
+ return {
371
+ ...rest,
372
+ scale: GATHER_DIP + (1 - GATHER_DIP) * filled,
373
+ lift: 0.06 * filled,
374
+ gather: 1,
375
+ };
319
376
  }
320
377
 
321
- /**
322
- * Speaking. Frequency is not mapped to angle: a voice's spectrum barely
323
- * moves within a sentence, so that freezes into a fixed star. The voice
324
- * gives size, syllables and lobe strength; rotation comes from time, each
325
- * lobe at its own speed in alternating directions. No line in it is clean:
326
- * the rim is crumbly, and a syllable throws crumbs rather than a ring.
327
- */
328
- case "speaking": {
329
- const grow = smoothstep(0, 1, Math.min(1, e / 0.8));
330
-
331
- let raw =
332
- IDLE_R +
333
- (SPEAK_BASE - IDLE_R + SPEAK_SWELL * v.phrase + SPEAK_KICK * v.bob) *
334
- grow;
335
- for (let i = 0; i < SPEAK_LOBES; i++) {
336
- raw +=
337
- SPEAK_LOBE_R *
338
- v.amp[i] *
339
- Math.sin((i + 2) * cell.angle + v.phase[i]) *
340
- grow;
341
- }
342
- const edge = rimAt(raw);
343
-
344
- // each cell sits a little in or out of the rim, and the offset drifts
345
- const rough =
346
- (cell.grain - 0.5) * SPEAK_ROUGH +
347
- Math.sin(cell.angle * 5 + t * 0.9 + cell.seed * 6.283) *
348
- SPEAK_ROUGH *
349
- 0.3;
350
- const d = cell.dist - (edge + rough);
351
- const rim = Math.exp(-(d * d) / 800);
352
- const core = Math.exp(-(cell.dist * cell.dist) / 6000) * 0.5;
353
- // a soft fill toward the center, with the idle wave still moving through it
354
- const inside =
355
- cell.dist < edge
356
- ? 0.3 *
357
- (1 - cell.dist / edge) ** 0.6 *
358
- (0.75 + 0.25 * Math.sin(cell.dist * 0.05 - t * 1.2))
359
- : 0;
360
-
361
- // crumbs: a sparse share of cells, each thrown past the rim at its own speed
362
- let out = 0;
363
- if (cell.speck < SPECK_SHARE) {
364
- const speed = RING_SPEED * (0.6 + cell.grain * 0.9);
365
- for (let i = 0; i < v.rings.length; i++) {
366
- const ring = v.rings[i];
367
- const age = t - ring.born;
368
- const dd = cell.dist - (edge + 6 + age * speed);
369
- out +=
370
- Math.exp(-(dd * dd) / 500) *
371
- ring.power *
372
- Math.max(0, 1 - age / RING_LIFE);
373
- }
374
- }
378
+ // the body holds still; the user's voice shows as ink, in its gaps and as the retype rate
379
+ case "listening":
380
+ return {
381
+ ...rest,
382
+ lift: LISTEN_LIFT * (0.7 * v.micPhrase + 0.3 * v.micLevel),
383
+ listen: 1,
384
+ };
375
385
 
376
- return rim + core + inside + out;
377
- }
386
+ case "speaking":
387
+ return { ...rest, speech: 1 };
378
388
 
379
- /**
380
- * Working: the idle circle stays and a comet orbits outside it. Ignores
381
- * the voice and never ends.
382
- */
383
- case "working": {
384
- const k = smoothstep(0, 1, Math.min(1, e / 0.7));
389
+ case "working":
390
+ return { ...rest, comet: 1 };
385
391
 
386
- // orbit: a thin band at a fixed radius
387
- const d = cell.dist - WORK_R;
388
- const band = Math.exp(-(d * d) / WORK_BAND);
392
+ case "ending":
393
+ return { ...rest, scale: 0 };
389
394
 
390
- // sharp head, long tail
391
- const raw = cell.angle - t * WORK_SPIN;
392
- const da = Math.atan2(Math.sin(raw), Math.cos(raw));
393
- const comet = Math.exp(-(da * da) / (da < 0 ? WORK_TAIL : WORK_HEAD));
395
+ case "error":
396
+ return { ...rest, scale: 0, err: 1 };
397
+ }
398
+ }
394
399
 
395
- // the center stays empty; a single orbit reads as "working"
396
- return band * comet * k + idleValue(cell, t) * (1 - k);
397
- }
400
+ /** Crumbs traveling in from the edge of the field to the body's rim, dimming as they land. */
401
+ function gatherValue(cell: Cell, t: number, scale: number) {
402
+ const rim = IDLE_R * scale;
403
+ if (cell.speck >= 0.55 || cell.dist < rim * 0.8) return 0;
404
+ // atan2 reaches +PI, which would open a ray past the last one
405
+ const sector = Math.min(
406
+ GATHER_RAYS - 1,
407
+ Math.floor(((cell.angle + Math.PI) / (2 * Math.PI)) * GATHER_RAYS),
408
+ );
409
+ const ray = hash(sector, 5.3);
410
+ const p = (t * GATHER_RATE + ray) % 1;
411
+ const d =
412
+ cell.dist -
413
+ (rim + (GATHER_R - rim) * (1 - p) ** 1.25) -
414
+ (cell.grain - 0.5) * 16;
415
+ // dims as it lands, so a crumb is taken into the body rather than piling on it
416
+ return Math.exp(-(d * d) / 260) * smoothstep(0, 0.2, p) * (1 - p) * 1.1;
417
+ }
398
418
 
399
- case "error": {
400
- if (cell.letter < 0) return 0;
419
+ /** A comet on a thin orbit outside the body: sharp head, long tail. */
420
+ function cometValue(cell: Cell, t: number) {
421
+ const d = cell.dist - WORK_R;
422
+ const band = Math.exp(-(d * d) / WORK_BAND);
423
+ const raw = cell.angle - t * WORK_SPIN;
424
+ const da = Math.atan2(Math.sin(raw), Math.cos(raw));
425
+ return band * Math.exp(-(da * da) / (da < 0 ? WORK_TAIL : WORK_HEAD));
426
+ }
401
427
 
402
- const pe = e % ERR_CYCLE;
403
- const li = cell.letter;
404
- const jitter = cell.seed * 0.12;
428
+ /**
429
+ * Her voice. Frequency is not mapped to angle: a voice's spectrum barely moves
430
+ * within a sentence, so that freezes into a fixed star. The voice gives the rim
431
+ * its size, its syllables and its lobes, and the whole pattern turns with time.
432
+ * No line in it is clean: the rim is crumbly, and a syllable throws crumbs
433
+ * rather than a ring.
434
+ */
435
+ function speechValue(cell: Cell, t: number, v: Voice, amount: number) {
436
+ let raw = SPEAK_BASE + SPEAK_SWELL * v.phrase + SPEAK_KICK * v.bob;
437
+ for (let i = 0; i < SPEAK_LOBES; i++) {
438
+ raw +=
439
+ SPEAK_LOBE_R *
440
+ v.amp[i] *
441
+ Math.sin((i + 2) * (cell.angle + v.spin) + v.phase[i]);
442
+ }
443
+ // the rim grows out of the resting body as she starts and settles back into it
444
+ // when she stops; without this it appears at whatever the first syllable asks
445
+ // for, which reads as the circle jumping bigger
446
+ const edge = IDLE_R + (rimAt(raw) - IDLE_R) * amount;
447
+
448
+ // each cell sits a little in or out of the rim, and the offset drifts
449
+ const rough =
450
+ (cell.grain - 0.5) * SPEAK_ROUGH +
451
+ Math.sin(cell.angle * 5 + t * 0.9 + cell.seed * 6.283) * SPEAK_ROUGH * 0.3;
452
+ const d = cell.dist - (edge + rough);
453
+ let value =
454
+ Math.exp(-(d * d) / 800) + Math.exp(-(cell.dist * cell.dist) / 6000) * 0.5;
455
+
456
+ // a soft fill out to the rim, with the idle wave still moving through it
457
+ if (cell.dist < edge) {
458
+ value +=
459
+ 0.22 *
460
+ (1 - cell.dist / edge) ** 0.6 *
461
+ (0.75 + 0.25 * Math.sin(cell.dist * 0.05 - t * 1.2));
462
+ }
405
463
 
406
- const appear = smoothstep(
407
- 0,
408
- 1,
409
- (pe - li * ERR_STEP_IN - jitter) / ERR_FADE_IN,
410
- );
411
- const vanish = smoothstep(
412
- 0,
413
- 1,
414
- (pe - ERR_HOLD_UNTIL - li * ERR_STEP_OUT - jitter) / ERR_FADE_OUT,
415
- );
416
- const on = Math.max(0, appear - vanish);
417
- const flick = 0.86 + Math.sin(t * 3.5 + cell.seed * 6) * 0.14;
418
- return on * flick;
464
+ // crumbs: a sparse share of cells, each thrown past the rim at its own speed
465
+ if (cell.speck < SPECK_SHARE) {
466
+ const speed = RING_SPEED * (0.6 + cell.grain * 0.9);
467
+ for (let i = 0; i < v.rings.length; i++) {
468
+ const ring = v.rings[i];
469
+ const age = t - ring.born;
470
+ const dd = cell.dist - (edge + 6 + age * speed);
471
+ value +=
472
+ Math.exp(-(dd * dd) / 500) *
473
+ ring.power *
474
+ Math.max(0, 1 - age / RING_LIFE);
419
475
  }
420
476
  }
477
+ return value;
478
+ }
479
+
480
+ /**
481
+ * ERROR, letter by letter: each lights up in turn and goes out the same way.
482
+ * `age` is ERROR's own clock, not the mode's, so leaving the mode fades the
483
+ * letters where they stand instead of restarting their cycle.
484
+ */
485
+ function errorValue(cell: Cell, t: number, age: number) {
486
+ if (cell.letter < 0) return 0;
487
+ const pe = age % ERR_CYCLE;
488
+ const jitter = cell.seed * 0.12;
489
+ const appear = smoothstep(
490
+ 0,
491
+ 1,
492
+ (pe - cell.letter * ERR_STEP_IN - jitter) / ERR_FADE_IN,
493
+ );
494
+ const vanish = smoothstep(
495
+ 0,
496
+ 1,
497
+ (pe - ERR_HOLD_UNTIL - cell.letter * ERR_STEP_OUT - jitter) / ERR_FADE_OUT,
498
+ );
499
+ const flick = 0.86 + Math.sin(t * 3.5 + cell.seed * 6) * 0.14;
500
+ return Math.max(0, appear - vanish) * flick;
501
+ }
502
+
503
+ /** Marks which cells ink each letter of `text`, fitted to about her body's width; returns the letter count. */
504
+ function layWord(cells: Cell[], cw: number, ch: number, text: string) {
505
+ const rows = spell(text);
506
+ const s = Math.max(
507
+ 1,
508
+ Math.min(3, Math.floor((DESIGN * WORD_WIDTH) / (rows[0].length * cw))),
509
+ );
510
+ let letters = 0;
511
+ for (const cell of cells) {
512
+ cell.word = letterAt(rows, cell.dx, cell.dy, cw, ch, s);
513
+ letters = Math.max(letters, cell.word + 1);
514
+ }
515
+ return letters;
516
+ }
517
+
518
+ /** A word's letters `age` seconds into its showing: each lights in turn, holds, and goes out in turn. */
519
+ function wordValue(cell: Cell, t: number, age: number) {
520
+ if (cell.word < 0) return 0;
521
+ const jitter = cell.seed * 0.08;
522
+ const appear = smoothstep(
523
+ 0,
524
+ 1,
525
+ (age - cell.word * WORD_STEP_IN - jitter) / WORD_FADE_IN,
526
+ );
527
+ const vanish = smoothstep(
528
+ 0,
529
+ 1,
530
+ (age - WORD_HOLD - cell.word * WORD_STEP_OUT - jitter) / WORD_FADE_OUT,
531
+ );
532
+ const flick = 0.86 + Math.sin(t * 3.5 + cell.seed * 6) * 0.14;
533
+ return Math.max(0, appear - vanish) * flick;
534
+ }
535
+
536
+ /**
537
+ * One cell's brightness: the body, plus whichever behaviours are up. Her voice
538
+ * hollows the body rather than replacing it, so there is nothing to dissolve
539
+ * between when she starts or stops.
540
+ */
541
+ function fieldValue(
542
+ cell: Cell,
543
+ t: number,
544
+ errAge: number,
545
+ wordAge: number,
546
+ f: Field,
547
+ v: Voice,
548
+ ) {
549
+ let value =
550
+ f.scale > 0.02
551
+ ? idleValue(cell, t, f.lift, f.scale) * (1 - 0.6 * f.speech)
552
+ : 0;
553
+ if (f.gather > 0.01) value += gatherValue(cell, t, f.scale) * f.gather;
554
+ if (f.comet > 0.01) value += cometValue(cell, t) * f.comet;
555
+ if (f.speech > 0.01) value += speechValue(cell, t, v, f.speech) * f.speech;
556
+ if (f.err > 0.01) value += errorValue(cell, t, errAge) * f.err;
557
+ if (f.word > 0.01) value += wordValue(cell, t, wordAge) * f.word;
558
+ return value;
421
559
  }
422
560
 
423
561
  export function AsciiOrb({
424
562
  className,
425
563
  mode = "idle",
426
564
  charset = ASCII_FACE.charset,
427
- emotion = null,
428
565
  fontSize = ASCII_FACE.fontSize.default,
429
566
  density = ASCII_FACE.density.default,
430
567
  size = DESIGN,
431
568
  color = DEFAULT_COLOR,
432
569
  getSpectrum,
570
+ getMicSpectrum,
571
+ word = null,
433
572
  }: AsciiOrbProps) {
434
573
  const hostRef = useRef<HTMLCanvasElement>(null);
435
574
  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
@@ -448,38 +587,53 @@ export function AsciiOrb({
448
587
  cur: [...color] as [number, number, number],
449
588
  target: color,
450
589
  });
451
- /** Cell pitch and bitmap scale; follow the size knob */
452
- const metricsRef = useRef({
453
- cw: 1,
454
- ch: 1,
455
- errScale: 3,
456
- faceScale: 3,
457
- size: 0,
458
- });
459
-
460
- const emoRef = useRef<{
461
- cur: AsciiOrbEmotion | null;
462
- prev: AsciiOrbEmotion | null;
463
- start: number;
464
- }>({ cur: emotion, prev: null, start: -Infinity });
590
+ /** Side (px) the grid was built for; the loop clears this much */
591
+ const boxRef = useRef(0);
465
592
 
466
593
  // the loop mounts once with no deps, so the latest getter comes through a ref
467
594
  const specRef = useRef(getSpectrum);
468
595
  specRef.current = getSpectrum;
596
+ const micRef = useRef(getMicSpectrum);
597
+ micRef.current = getMicSpectrum;
469
598
 
470
599
  const voiceRef = useRef<Voice>({
471
600
  phrase: 0,
601
+ micPhrase: 0,
602
+ micLevel: 0,
472
603
  bob: 0,
473
604
  bobVel: 0,
474
605
  amp: new Array<number>(SPEAK_LOBES).fill(0),
475
606
  // distinct start phases, or the lobes overlap into one lump at first
476
607
  phase: Array.from({ length: SPEAK_LOBES }, (_, i) => i * 2.1),
608
+ spin: 0,
477
609
  rings: [],
478
610
  });
479
611
 
480
- const curRef = useRef({ mode, start: 0 });
481
- const prevRef = useRef<{ mode: AsciiOrbMode; start: number } | null>(null);
482
- const xfadeStartRef = useRef(-Infinity);
612
+ /** The mode being asked for, and when it was asked for. */
613
+ const modeRef = useRef({ mode, start: 0 });
614
+ /**
615
+ * The field on screen. It only ever eases toward the mode's targets, so it
616
+ * starts drawn in and opens on the first frames like any other arrival.
617
+ */
618
+ const fieldRef = useRef<Field>({
619
+ scale: 0.3,
620
+ lift: 0,
621
+ gather: 0,
622
+ comet: 0,
623
+ speech: 0,
624
+ err: 0,
625
+ word: 0,
626
+ listen: 0,
627
+ });
628
+
629
+ /** The word being shown, when it started, and how many letters it has; its cells carry the rest */
630
+ const wordRef = useRef<{
631
+ text: string;
632
+ start: number;
633
+ letters: number;
634
+ } | null>(null);
635
+ /** Grid pitch in reference units, for laying a word onto cells that already exist */
636
+ const pitchRef = useRef({ cw: 1, ch: 1 });
483
637
 
484
638
  // grid is rebuilt only when size or density changes
485
639
  useEffect(() => {
@@ -493,16 +647,13 @@ export function AsciiOrb({
493
647
  const norm = DESIGN / size;
494
648
  const cwN = cw * norm;
495
649
  const chN = ch * norm;
496
- // scale ERROR and the faces so they stay inside the box at any glyph size
650
+ // scale ERROR so it stays inside the box at any glyph size
497
651
  const errScale = Math.max(
498
652
  1,
499
- Math.floor((DESIGN * 0.92) / (ERROR_BITMAP[0].length * cwN)),
500
- );
501
- const faceScale = Math.max(
502
- 1,
503
- Math.floor((DESIGN * 0.82) / (FACE_COLS * cwN)),
653
+ Math.floor((DESIGN * 0.92) / (ERROR_ROWS[0].length * cwN)),
504
654
  );
505
- metricsRef.current = { cw: cwN, ch: chN, errScale, faceScale, size };
655
+ boxRef.current = size;
656
+ pitchRef.current = { cw: cwN, ch: chN };
506
657
 
507
658
  const cells: Cell[] = [];
508
659
  const cols = Math.ceil(size / cw);
@@ -517,7 +668,7 @@ export function AsciiOrb({
517
668
  const dy = (y - size / 2) * norm;
518
669
  const dist = Math.hypot(dx, dy);
519
670
 
520
- const letter = errorLetterAt(dx, dy, cwN, chN, errScale);
671
+ const letter = letterAt(ERROR_ROWS, dx, dy, cwN, chN, errScale);
521
672
  if (letter < 0 && dist > FIELD_R) continue;
522
673
 
523
674
  cells.push({
@@ -531,17 +682,17 @@ export function AsciiOrb({
531
682
  seed: hash(c, r),
532
683
  grain: hash(c * 5.7 + 19, r * 2.3 + 53),
533
684
  gap: 0.05 + hash(c * 7.3 + 11, r * 3.1 + 5) * 0.3,
534
- // independent order per cell, so cells appear one at a time rather than in clumps
535
- birth: hash(c * 3.1 + 7, r * 5.7 + 2),
536
- death: hash(c * 9.4 + 3, r * 2.6 + 8),
537
685
  emoji: hash(c * 2.7 + 31, r * 5.9 + 17) < EMOJI_RATIO,
538
686
  speck: hash(c * 4.1 + 23, r * 6.3 + 41),
539
687
  letter,
688
+ word: -1,
540
689
  });
541
690
  }
542
691
  }
543
692
 
544
693
  cellsRef.current = cells;
694
+ // a word showing while the grid changes is laid onto the new cells
695
+ if (wordRef.current) layWord(cells, cwN, chN, wordRef.current.text);
545
696
 
546
697
  // bucketing by level keeps fillStyle changes to one per level
547
698
  bucketsRef.current = {
@@ -581,17 +732,6 @@ export function AsciiOrb({
581
732
  };
582
733
  }, [fontSize, density, size]);
583
734
 
584
- // expression change: old one leaves, brief blank, new one appears
585
- useEffect(() => {
586
- const e = emoRef.current;
587
- if (e.cur === emotion) return;
588
- emoRef.current = {
589
- cur: emotion,
590
- prev: e.cur,
591
- start: performance.now() * 0.001,
592
- };
593
- }, [emotion]);
594
-
595
735
  // every frame redraws everything, so the charset only needs a ref update
596
736
  useEffect(() => {
597
737
  charsetRef.current = charset;
@@ -602,25 +742,37 @@ export function AsciiOrb({
602
742
  colorRef.current.target = color;
603
743
  }, [color]);
604
744
 
605
- // mode change: remember the previous mode and start a crossfade
745
+ // a mode change only moves the targets; the field eases the rest of the way
606
746
  useEffect(() => {
607
- const now = performance.now() * 0.001;
608
- if (curRef.current.mode !== mode) {
609
- prevRef.current = { ...curRef.current };
610
- curRef.current = { mode, start: now };
611
- xfadeStartRef.current = now;
612
- } else {
613
- curRef.current = { mode, start: now };
614
- }
747
+ if (modeRef.current.mode === mode) return;
748
+ modeRef.current = { mode, start: performance.now() * 0.001 };
615
749
  }, [mode]);
616
750
 
751
+ // a new word starts from its first letter, in place of one still showing
752
+ useEffect(() => {
753
+ if (!word) return;
754
+ const { cw, ch } = pitchRef.current;
755
+ wordRef.current = {
756
+ text: word.text,
757
+ start: performance.now() * 0.001,
758
+ letters: layWord(cellsRef.current, cw, ch, word.text),
759
+ };
760
+ }, [word]);
761
+
617
762
  // animation loop
618
763
  useEffect(() => {
619
764
  let raf = 0;
620
765
  const follower = createVoiceFollower();
766
+ const micFollower = createVoiceFollower();
621
767
  const murmur = new Array<number>(SPECTRUM_BANDS).fill(0);
768
+ const silence = new Array<number>(SPECTRUM_BANDS).fill(0);
622
769
 
623
770
  let lastT = performance.now() * 0.001;
771
+ /** Glyph clock: it runs at the retype pace, so changing the pace never jumps a glyph */
772
+ let clock = 0;
773
+ let pace = 1;
774
+ /** Seconds ERROR has been showing (errorValue) */
775
+ let errAge = 0;
624
776
 
625
777
  const draw = (nowMs: number) => {
626
778
  const t = nowMs * 0.001;
@@ -634,7 +786,7 @@ export function AsciiOrb({
634
786
  return;
635
787
  }
636
788
  const cs = charsetRef.current;
637
- const { cw, ch, faceScale, size: box } = metricsRef.current;
789
+ const box = boxRef.current;
638
790
  bk.asciiN.fill(0);
639
791
  bk.emojiN.fill(0);
640
792
 
@@ -665,6 +817,19 @@ export function AsciiOrb({
665
817
  voice.bobVel += -voice.bob * 0.012 - voice.bobVel * 0.09;
666
818
  voice.bob += voice.bobVel;
667
819
 
820
+ // the microphone is read every frame but answers only while listening: it
821
+ // sets how fast the glyphs retype, and lifts the body across a phrase
822
+ const mic = micRef.current?.();
823
+ const micHeard = micFollower.read(mic ?? silence, dt);
824
+ voice.micPhrase = mic ? micHeard.phrase : 0;
825
+ voice.micLevel = mic ? micHeard.level : 0;
826
+ const wantPace =
827
+ mic && modeRef.current.mode === "listening"
828
+ ? LISTEN_PACE_QUIET + LISTEN_PACE_VOICE * micHeard.level
829
+ : 1;
830
+ pace = toward(pace, wantPace, PACE_TIME, PACE_TIME, dt);
831
+ clock += dt * pace;
832
+
668
833
  for (let i = 0; i < SPEAK_LOBES; i++) {
669
834
  // neighbouring bands per lobe, low lobes from low bands
670
835
  const from = Math.floor((i * heard.bands.length) / SPEAK_LOBES);
@@ -680,74 +845,70 @@ export function AsciiOrb({
680
845
  // different time constants per harmonic, or the star only scales
681
846
  voice.amp[i] += (energy * wander - voice.amp[i]) * (0.05 + i * 0.02);
682
847
  // own speed, alternating direction, so no standing wave forms
683
- voice.phase[i] += dt * (0.5 + i * 0.37) * (i % 2 ? -1 : 1);
848
+ voice.phase[i] += dt * (0.9 + i * 0.5) * (i % 2 ? -1 : 1);
684
849
  }
685
850
 
851
+ // the pattern turns one way, faster while she is mid-phrase
852
+ voice.spin += dt * (SPEAK_SPIN + SPEAK_SPIN_VOICE * voice.phrase);
853
+
686
854
  while (voice.rings.length > 0 && t - voice.rings[0].born > RING_LIFE) {
687
855
  voice.rings.shift();
688
856
  }
689
857
 
690
- const cur = curRef.current;
691
- const prev = prevRef.current;
692
- const k = smoothstep(
693
- 0,
694
- 1,
695
- Math.min(1, (t - xfadeStartRef.current) / XFADE),
696
- );
697
- const blending = prev !== null && k < 1;
698
- const keepGlyphSolid =
699
- cur.mode === "error" || (blending && prev?.mode === "error");
700
-
701
- // expression state
702
- const emo = emoRef.current;
703
- const ep = t - emo.start;
704
- // old one leaves cell by cell, blank, new one appears cell by cell
705
- const rawOut = Math.min(1, Math.max(0, ep / FACE_OUT));
706
- const rawIn = Math.min(
707
- 1,
708
- Math.max(0, (ep - FACE_OUT - FACE_BLANK) / FACE_IN),
858
+ // the field eases toward what this mode wants, each channel on its own clock
859
+ const cur = modeRef.current;
860
+ // a word takes the face from the mode until its last letter is out; an error keeps it
861
+ const shown = wordRef.current;
862
+ const wordAge = shown ? t - shown.start : 0;
863
+ const wording =
864
+ shown !== null &&
865
+ cur.mode !== "error" &&
866
+ wordAge < WORD_HOLD + shown.letters * WORD_STEP_OUT + WORD_FADE_OUT;
867
+ const want: Field = wording
868
+ ? {
869
+ scale: 0,
870
+ lift: 0,
871
+ gather: 0,
872
+ comet: 0,
873
+ speech: 0,
874
+ err: 0,
875
+ word: 1,
876
+ listen: 0,
877
+ }
878
+ : targetFor(cur.mode, t - cur.start, voice);
879
+ const f = fieldRef.current;
880
+ f.scale = toward(
881
+ f.scale,
882
+ want.scale,
883
+ RISE.scale,
884
+ cur.mode === "ending" ? END_FALL : FALL.scale,
885
+ dt,
709
886
  );
710
- const faceBusy = emo.cur !== null || ep < FACE_OUT + FACE_BLANK + FACE_IN;
711
- const faceKind = (em: AsciiOrbEmotion, cell: Cell) =>
712
- faceSample(em, cell.dx, cell.dy, cw, ch, faceScale);
713
- /** Brightness from an already sampled eye/mouth kind */
714
- const faceValue = (s: number, cell: Cell) => {
715
- if (s === 0) return 0;
716
- // brighter in the middle, slightly darker outward
717
- const shade = 0.74 + 0.26 * (1 - Math.min(1, cell.dist / 270));
718
- // slowly drifting holes; darkened cells fall under the gap threshold so the face is not a solid slab
719
- const holes =
720
- 0.42 +
721
- hash(
722
- Math.floor(cell.dx * 0.09 + t * 0.25),
723
- Math.floor(cell.dy * 0.09 - t * 0.18),
724
- ) *
725
- 0.85;
726
- return (s === 1 ? 1 : 0.88) * shade * holes;
727
- };
728
-
729
- /** A few specks of dust around the face, like idle */
730
- const faceDust = (cell: Cell) => {
731
- if (cell.seed < 0.976) return 0;
732
- // each speck floats at its own distance so they do not line up in a band
733
- const r = 195 + hash(cell.seed * 311, 9) * 160;
734
- const fall = Math.exp(-((cell.dist - r) ** 2) / 7000);
735
- // own blink rate and phase
736
- const sp = 0.5 + hash(cell.seed * 47, 21) * 1.5;
737
- return fall * (0.5 + 0.5 * Math.sin(t * sp + cell.seed * 300)) * 0.36;
738
- };
887
+ f.lift = toward(f.lift, want.lift, RISE.lift, FALL.lift, dt);
888
+ f.gather = toward(f.gather, want.gather, RISE.gather, FALL.gather, dt);
889
+ f.comet = toward(f.comet, want.comet, RISE.comet, FALL.comet, dt);
890
+ f.speech = toward(f.speech, want.speech, RISE.speech, FALL.speech, dt);
891
+ // ERROR's clock runs only in its mode and holds while the letters fade
892
+ // out; an error that finds them already gone starts from the first letter
893
+ if (cur.mode === "error") {
894
+ if (f.err <= 0.01) errAge = 0;
895
+ errAge += dt;
896
+ }
897
+ f.err = toward(f.err, want.err, RISE.err, FALL.err, dt);
898
+ f.word = toward(f.word, want.word, RISE.word, FALL.word, dt);
899
+ f.listen = toward(f.listen, want.listen, RISE.listen, FALL.listen, dt);
900
+ const solidError = f.err > 0.5;
901
+ const solidWord = f.word > 0.5;
902
+ const rate = cs === "emojiOnly" ? EMOJI_CHAR_RATE : CHAR_RATE;
739
903
 
740
904
  const all = cellsRef.current;
741
905
  for (let ci = 0; ci < all.length; ci++) {
742
906
  const cell = all[ci];
743
- let v = valueFor(cur.mode, cell, t, t - cur.start, voice);
744
-
745
- if (blending && prev) {
746
- const pv = valueFor(prev.mode, cell, t, t - prev.start, voice);
747
- v = pv + (v - pv) * k;
748
- }
907
+ let v = fieldValue(cell, t, errAge, wordAge, f, voice);
749
908
 
750
- if (!(keepGlyphSolid && cell.letter >= 0)) {
909
+ if (
910
+ !((solidError && cell.letter >= 0) || (solidWord && cell.word >= 0))
911
+ ) {
751
912
  // per-cell brightness response breaks concentric rings; multiplicative, so empty (0) stays empty
752
913
  v *= 0.66 + cell.grain * 0.72;
753
914
  // slowly drifting noise on top
@@ -766,42 +927,27 @@ export function AsciiOrb({
766
927
  v = Math.max(v, 0.9);
767
928
  }
768
929
 
769
- const gate = cell.gap + Math.sin(t * 0.28 + cell.seed * 6.283) * 0.05;
930
+ const gate =
931
+ cell.gap +
932
+ Math.sin(t * 0.28 + cell.seed * 6.283) * 0.05 +
933
+ f.listen *
934
+ (LISTEN_THIN +
935
+ (hash(
936
+ Math.floor(t * LISTEN_POP_RATE + cell.seed * 3),
937
+ cell.seed * 97,
938
+ ) -
939
+ 0.5) *
940
+ LISTEN_POP *
941
+ voice.micLevel);
770
942
  if (v < gate) v = 0;
771
943
  }
772
944
 
773
- // With an expression set, the whole field switches to it once the
774
- // previous content (blob or old face) is fully gone
775
- let solid = false;
776
- if (faceBusy) {
777
- // per-cell order with a narrow window: cells snap on and off rather than fade
778
- const gone = smoothstep(0, 1, (rawOut * 1.1 - cell.death) / 0.08);
779
- const born = smoothstep(0, 1, (rawIn * 1.1 - cell.birth) / 0.08);
780
-
781
- // sample the bitmap once per expression
782
- const kCur = emo.cur ? faceKind(emo.cur, cell) : 0;
783
- const kPrev = emo.prev ? faceKind(emo.prev, cell) : 0;
784
-
785
- const dust = faceDust(cell);
786
- const oldV = emo.prev ? faceValue(kPrev, cell) + dust : v;
787
- const newV = emo.cur ? faceValue(kCur, cell) + dust : v;
788
-
789
- v = oldV * (1 - gone) + newV * born;
790
- solid = (kCur > 0 && born > 0.05) || (kPrev > 0 && gone < 0.95);
791
- }
792
-
793
945
  v = v < 0 ? 0 : v > 1 ? 1 : v;
794
946
 
795
947
  const level = (v * (RAMP.length - 1)) | 0;
796
948
  if (level === 0) continue;
797
949
 
798
- // face cells swap glyphs slowly, or the shape drowns in noise
799
- const rate = solid
800
- ? FACE_CHAR_RATE
801
- : cs === "emojiOnly"
802
- ? EMOJI_CHAR_RATE
803
- : CHAR_RATE;
804
- const slot = (t * rate + cell.seed * 7) | 0;
950
+ const slot = (clock * rate + cell.seed * 7) | 0;
805
951
 
806
952
  const showEmoji =
807
953
  cs === "emojiOnly" ||
@@ -859,8 +1005,6 @@ export function AsciiOrb({
859
1005
  }
860
1006
  ctx.globalAlpha = 1;
861
1007
 
862
- if (!blending && prev) prevRef.current = null;
863
-
864
1008
  raf = requestAnimationFrame(draw);
865
1009
  };
866
1010