thursday-agent 0.1.2 → 0.3.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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +5 -1
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +5 -5
- package/.next/routes-manifest.json +27 -3
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +2 -2
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +2 -2
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +14 -14
- package/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js +3 -3
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/bot/memory/files/route.js +10 -0
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -0
- package/.next/server/app/api/bot/memory/files/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/memory/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/bot/memory/route/build-manifest.json +12 -0
- package/.next/server/app/api/bot/memory/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/bot/memory/route.js +14 -0
- package/.next/server/app/api/bot/memory/route.js.map +5 -0
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -0
- package/.next/server/app/api/bot/memory/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/route.js +7 -7
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/[id]/route.js +9 -9
- package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/route.js +8 -8
- package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +6 -6
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js +4 -5
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js +3 -3
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js +10 -9
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/llm-model/credits/route/build-manifest.json +12 -0
- package/.next/server/app/api/llm-model/credits/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/llm-model/credits/route.js +16 -0
- package/.next/server/app/api/llm-model/credits/route.js.map +5 -0
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -0
- package/.next/server/app/api/llm-model/credits/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/llm-model/route.js +10 -8
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/llm-model/subscription/route/build-manifest.json +12 -0
- package/.next/server/app/api/llm-model/subscription/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/llm-model/subscription/route.js +15 -0
- package/.next/server/app/api/llm-model/subscription/route.js.map +5 -0
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -0
- package/.next/server/app/api/llm-model/subscription/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/mcp/[name]/route.js +5 -5
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js +6 -6
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js +5 -5
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js +5 -5
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/memory/edit/route/build-manifest.json +12 -0
- package/.next/server/app/api/memory/edit/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/memory/edit/route.js +24 -0
- package/.next/server/app/api/memory/edit/route.js.map +5 -0
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -0
- package/.next/server/app/api/memory/{tidy → edit}/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/memory/route.js +7 -8
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js +3 -3
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +3 -3
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js +9 -7
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js +9 -7
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +16 -15
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js +4 -5
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
- package/.next/server/app/artifact/[...path]/page.js +5 -6
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js +3 -4
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/favicon.ico.body +0 -0
- package/.next/server/app/page/server-reference-manifest.json +66 -77
- package/.next/server/app/page.js +11 -11
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +5 -1
- package/.next/server/chunks/[externals]__0dzuk6_._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__01dkp4a._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__02xjoto._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0ce8nf8._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0j7z0r3._.js +12 -0
- package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0pa2n68._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0q0vvt2._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__11n256_._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__12cabg9._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__15bjnu5._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +10 -0
- package/.next/server/chunks/[root-of-the-server]__1fizn31._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1llj3-b._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +11 -0
- package/.next/server/chunks/[root-of-the-server]__1p8m3f4._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +8 -0
- package/.next/server/chunks/{_1hzrsvg._.js → _0-73of8._.js} +6 -6
- package/.next/server/chunks/{_1cbk7ms._.js → _0-fjql4._.js} +10 -10
- package/.next/server/chunks/_00xh_q0._.js +27 -0
- package/.next/server/chunks/{_0y8is-s._.js → _03a0897._.js} +1 -1
- package/.next/server/chunks/{_0pppbb5._.js → _03kp0jp._.js} +166 -35
- package/.next/server/chunks/_051qh8u._.js +3 -0
- package/.next/server/chunks/{node_modules_next_04q-o58._.js → _05dla15._.js} +2 -2
- package/.next/server/chunks/_05h2cii._.js +1 -1
- package/.next/server/chunks/_0c9j6rb._.js +49 -0
- package/.next/server/chunks/_0fzqt2y._.js +74 -44
- package/.next/server/chunks/{_0kl37zl._.js → _0ioz3nd._.js} +1 -1
- package/.next/server/chunks/_0iwfby5._.js +3 -0
- package/.next/server/chunks/{_0hin38_._.js → _0khihjk._.js} +5 -5
- package/.next/server/chunks/_0mecv0f._.js +26 -0
- package/.next/server/chunks/_0mh8j4m._.js +3 -0
- package/.next/server/chunks/{_1icgbi8._.js → _0n74zsf._.js} +2 -2
- package/.next/server/chunks/_0obojvv._.js +3 -0
- package/.next/server/chunks/_0recufk._.js +49 -0
- package/.next/server/chunks/{_1t8fv7b._.js → _0rnf-6n._.js} +2 -2
- package/.next/server/chunks/_0sb7tlz._.js +3 -0
- package/.next/server/chunks/{_0jgm3b8._.js → _0u10wmw._.js} +6 -6
- package/.next/server/chunks/{_12nr4xc._.js → _0u4aiy2._.js} +2 -2
- package/.next/server/chunks/_0wp4le3._.js +1 -1
- package/.next/server/chunks/_0y5fg-x._.js +1 -1
- package/.next/server/chunks/_1230wbx._.js +10 -0
- package/.next/server/chunks/_12nheja._.js +3 -0
- package/.next/server/chunks/_13510xh._.js +3 -0
- package/.next/server/chunks/_15hq89o._.js +1 -1
- package/.next/server/chunks/_17l5ygt._.js +49 -0
- package/.next/server/chunks/{_0ks6_sg._.js → _18-1wcc._.js} +6 -6
- package/.next/server/chunks/_1_5mnq4._.js +18 -0
- package/.next/server/chunks/{_0tzfesa._.js → _1_9mjx-._.js} +1 -1
- package/.next/server/chunks/_1d0j961._.js +6 -0
- package/.next/server/chunks/_1d5zmy8._.js +1 -1
- package/.next/server/chunks/_1drgfx4._.js +18 -0
- package/.next/server/chunks/{_1skgy7b._.js → _1e48czp._.js} +5 -5
- package/.next/server/chunks/_1f46eie._.js +3 -0
- package/.next/server/chunks/_1h5_8sk._.js +3 -0
- package/.next/server/chunks/_1hdnxb7._.js +3 -0
- package/.next/server/chunks/_1j8k_j5._.js +3 -0
- package/.next/server/chunks/{_10qam6o._.js → _1kg70wc._.js} +5 -5
- package/.next/server/chunks/_1krd9rf._.js +3 -0
- package/.next/server/chunks/_1nc0eui._.js +11 -0
- package/.next/server/chunks/_1nula3v._.js +3 -0
- package/.next/server/chunks/_1pf4qe-._.js +3 -0
- package/.next/server/chunks/_1php4eq._.js +1 -1
- package/.next/server/chunks/_1qxheoa._.js +3 -0
- package/.next/server/chunks/_1x6kd1r._.js +3 -0
- package/.next/server/chunks/_1yuv4vp._.js +1 -1
- package/.next/server/chunks/_1zjrdag._.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_bot_memory_files_route_actions_1_xd_8f.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_bot_memory_route_actions_1a8xfr7.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_llm-model_credits_route_actions_0v424pb.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_llm-model_subscription_route_actions_0-udcm5.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_memory_edit_route_actions_02sfnmr.js +3 -0
- package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
- package/.next/server/chunks/features_0rpfxbk._.js +3 -0
- package/.next/server/chunks/features_1qf3cei._.js +3 -0
- package/.next/server/chunks/features_bot_0zt92oh._.js +3 -0
- package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +1 -1
- package/.next/server/chunks/features_memory_1uqvksn._.js +6 -0
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tf290y._.js → node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0228f43._.js → node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0ir-vo2._.js → node_modules_consola_dist_chunks_prompt_mjs_0j61-ma._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0otw8ef._.js → node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js} +1 -1
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_11zjo-1._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1pycqsq._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y49572._.js +3 -0
- package/.next/server/chunks/ssr/[externals]__0dizgek._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0_69wqr._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0i8ie7f._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0jsmmb-._.js → [root-of-the-server]__0mz33a7._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0tbkdlx._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0f6s48x._.js → [root-of-the-server]__0xpcv9w._.js} +3 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0xtssj3._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0zkv7z_._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +57 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__15_uhip._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1cyjywu._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1eh7hid._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1gtlw6v._.js +43 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +4 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1pduf5n._.js +3 -0
- package/.next/server/chunks/ssr/{_1mznrxk._.js → _001_ms1._.js} +3 -3
- package/.next/server/chunks/ssr/_01_6m32._.js +3 -0
- package/.next/server/chunks/ssr/_01n3o0m._.js +3 -0
- package/.next/server/chunks/ssr/_07uchnc._.js +3 -0
- package/.next/server/chunks/ssr/_0ap2fzn._.js +3 -0
- package/.next/server/chunks/ssr/_0c-ql2m._.js +3 -0
- package/.next/server/chunks/ssr/_0h42fze._.js +3 -0
- package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
- package/.next/server/chunks/ssr/_0i_6q1w._.js +41 -0
- package/.next/server/chunks/ssr/_0kbkmar._.js +3 -0
- package/.next/server/chunks/ssr/_0lu1n_x._.js +3 -0
- package/.next/server/chunks/ssr/{_077-g6s._.js → _0mb8zc8._.js} +3 -3
- package/.next/server/chunks/ssr/_0mo2w06._.js +3 -0
- package/.next/server/chunks/ssr/{_13punpm._.js → _0q0zan6._.js} +2 -2
- package/.next/server/chunks/ssr/{_0gd4vaa._.js → _0u2jv_x._.js} +94 -58
- package/.next/server/chunks/ssr/_0y0tiht._.js +4 -0
- package/.next/server/chunks/ssr/_0zhoc3r._.js +3 -0
- package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
- package/.next/server/chunks/ssr/_13fke0x._.js +3 -0
- package/.next/server/chunks/ssr/{_0q_2f2x._.js → _13z3f85._.js} +2 -2
- package/.next/server/chunks/ssr/_14_lw10._.js +3 -0
- package/.next/server/chunks/ssr/_15j2_8f._.js +3 -0
- package/.next/server/chunks/ssr/{_0lc_7c8._.js → _1eoqg_g._.js} +2 -2
- package/.next/server/chunks/ssr/_1sji22n._.js +3 -0
- package/.next/server/chunks/ssr/_1spzm8z._.js +3 -0
- package/.next/server/chunks/ssr/{_19_o9d-._.js → _1vi_uo7._.js} +2 -2
- package/.next/server/chunks/ssr/_1vyd1ji._.js +3 -0
- package/.next/server/chunks/ssr/_1xvgn77._.js +3 -0
- package/.next/server/chunks/ssr/_20kd_n0._.js +42 -0
- package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +3 -0
- package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
- package/.next/server/chunks/ssr/lib_date-like_ts_02is0pw._.js +3 -0
- package/.next/server/chunks/ssr/lib_realtime_12wg_dd._.js +27 -0
- package/.next/server/chunks/ssr/lib_realtime_15h_v1v._.js +27 -0
- package/.next/server/chunks/ssr/{node_modules_1d-is1o._.js → node_modules_063xwpp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ub40gk._.js → node_modules_0bvraul._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1nhw9qg._.js → node_modules_0eu1va6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0xwx4u-._.js → node_modules_0h50b8n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_043-236._.js → node_modules_0h5hnr1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1t0y5jt._.js → node_modules_0h7u60n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1k29j1j._.js → node_modules_0lm7_vm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_16i4o_3._.js → node_modules_0mwym0g._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1y8_3vg._.js → node_modules_0rjg6t1._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_0x3-ply._.js +3 -0
- package/.next/server/chunks/ssr/{node_modules_108wd2o._.js → node_modules_0y_cded._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1jatz7w._.js → node_modules_0z5tsu8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ls96je._.js → node_modules_0zp9r92._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_06kvvuo._.js → node_modules_1-hc6uv._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_588_4._.js → node_modules_113tz0l._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ipn1ho._.js → node_modules_124bue5._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_17bwo43._.js +55 -0
- package/.next/server/chunks/ssr/{node_modules_0_j7-g4._.js → node_modules_1_n3ztw._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_20ynonm._.js → node_modules_1bc7gu8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hn_sd5._.js → node_modules_1bgb6-f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0bue6gy._.js → node_modules_1h4a4pp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0fbw5-u._.js → node_modules_1i7u1ia._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ee8gav._.js → node_modules_1juq3hs._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_fvxnt._.js → node_modules_1l0qtdu._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_13f2md6._.js → node_modules_1t9cyee._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0y2-av4._.js → node_modules_1uuj9x1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00d6675._.js → node_modules_1vqb6po._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1v0077l._.js → node_modules_1xx28h1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_092s8nx._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0d3skpl._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1nvexgu._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0i44kk5._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_@vercel_oidc_dist_0qe30a_._.js +3 -0
- package/.next/server/chunks/ssr/node_modules_consola_dist_chunks_prompt_mjs_05cwqv2._.js +47 -0
- package/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
- package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +2 -2
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +67 -78
- package/.next/static/chunks/{31ttnjt6g35a0.js → 0ev4hyghq0mbb.js} +1 -1
- package/.next/static/chunks/0r5qcctujhxli.js +1 -0
- package/.next/static/chunks/0tmgrnlje14-s.js +3 -0
- package/.next/static/chunks/0x79tx04dxbpp.js +1 -0
- package/.next/static/chunks/0xzgvc0fkdb6t.js +1 -0
- package/.next/static/chunks/10teww7wt-f6h.js +1 -0
- package/.next/static/chunks/14hgtsjg-kael.css +1 -0
- package/.next/static/chunks/17donp69y4do7.js +40 -0
- package/.next/static/chunks/1dkjg87pxk0n9.js +28 -0
- package/.next/static/chunks/1ekc7ul556kbl.js +1 -0
- package/.next/static/chunks/{1khakdu_iuedc.js → 1grhjo7a5a0_-.js} +1 -1
- package/.next/static/chunks/1kq8h8wi_e0bc.js +53 -0
- package/.next/static/chunks/1okqiww3pnwka.js +1 -0
- package/.next/static/chunks/26-pr2c9ro222.js +1 -0
- package/.next/static/chunks/{2ihkf6l2jowo9.js → 280t0_e99i276.js} +3 -3
- package/.next/static/chunks/28cizcdaabd4h.js +1 -0
- package/.next/static/chunks/2a0k6jlq0_v1u.js +2 -0
- package/.next/static/chunks/2fzs9fm8yarvq.js +1 -0
- package/.next/static/chunks/2h0sd2vumhb15.js +1 -0
- package/.next/static/chunks/2hw2f6zmt7fmt.js +1 -0
- package/.next/static/chunks/2t4vhi3m8ahya.js +1 -0
- package/.next/static/chunks/2tigdtz3dpcy8.js +1 -0
- package/.next/static/chunks/3111pj5wnjgos.js +1 -0
- package/.next/static/chunks/3786h5vayt80-.js +28 -0
- package/.next/static/chunks/{3aquirz5h71da.js → 3agpgkwaa321p.js} +1 -1
- package/.next/static/chunks/3cfi_q6d_wtci.js +1 -0
- package/.next/static/chunks/3ok9_x74rip3v.js +1 -0
- package/.next/static/chunks/3xk7yqgomx7yq.js +1 -0
- package/.next/static/chunks/3xpbkl66ssib5.js +38 -0
- package/.next/static/media/favicon.37o1pjldwi2uc.ico +0 -0
- package/README.md +6 -4
- package/app/api/bot/memory/files/route.ts +14 -0
- package/app/api/bot/memory/route.ts +5 -0
- package/app/api/events/app-event.client.ts +24 -0
- package/app/api/events/app-event.server.ts +45 -0
- package/app/api/events/app-event.ts +42 -0
- package/app/api/llm-model/catalog/route.ts +12 -0
- package/app/api/llm-model/credits/route.ts +11 -0
- package/app/api/llm-model/route.ts +26 -0
- package/app/api/llm-model/subscription/route.ts +11 -0
- package/app/api/memory/edit/route.ts +7 -0
- package/app/api/memory/route.ts +21 -0
- package/app/api/query-key.ts +159 -0
- package/app/api/thursday/tool-call/route.ts +67 -0
- package/bin/thursday.mjs +3 -0
- package/components/ui/button-group.tsx +45 -0
- package/components/ui/custom-icon.tsx +259 -0
- package/components/ui/dropdown-menu.tsx +271 -0
- package/components/ui/folded-text.tsx +68 -0
- package/components/ui/input-group.tsx +157 -0
- package/components/ui/json-view.tsx +240 -0
- package/components/ui/shiny-text.tsx +55 -0
- package/components/ui/text-type.tsx +213 -0
- package/components/ui/theme-sync.tsx +9 -0
- package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260910173049_numerous_spirit}/migration.sql +30 -33
- package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260910173049_numerous_spirit}/snapshot.json +124 -145
- package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
- package/database/migrations/20260911030320_opposite_jack_power/snapshot.json +1158 -0
- package/docs/how-it-works.md +59 -0
- package/docs/images/keeps-going.png +0 -0
- package/docs/images/task-thread.png +0 -0
- package/docs/images/two-minds.png +0 -0
- package/features/ai/chatgpt.ts +617 -0
- package/features/ai/components/chatgpt-sign-in.tsx +66 -0
- package/features/ai/components/model-picker.tsx +17 -1
- package/features/ai/components/provider-icon.tsx +1 -0
- package/features/ai/load-tools.ts +105 -39
- package/features/ai/model.schema.ts +232 -34
- package/features/ai/model.ts +169 -24
- package/features/ai/prompts/bot.prompt.ts +245 -59
- package/features/ai/prompts/memory-edit.prompt.ts +33 -0
- package/features/ai/prompts/prompt-helper.ts +75 -30
- package/features/ai/prompts/thursday.prompt.ts +42 -17
- package/features/ai/tools/bot.tool.ts +32 -30
- package/features/ai/tools/connected.ts +17 -4
- package/features/ai/tools/mcp.tool.ts +5 -5
- package/features/ai/tools/memory.tool.ts +142 -74
- package/features/ai/tools/search.tool.ts +136 -47
- package/features/ai/tools/studio.tool.ts +5 -5
- package/features/ai/tools/tool-name.ts +2 -3
- package/features/ai/tools/workspace.tool.ts +33 -4
- package/features/bot/bot.action.ts +24 -7
- package/features/bot/bot.memory.ts +89 -0
- package/features/bot/bot.query.ts +41 -6
- package/features/bot/bot.run.ts +516 -210
- package/features/bot/bot.runner.ts +463 -93
- package/features/bot/bot.schema.ts +84 -7
- package/features/bot/bot.seed.ts +89 -16
- package/features/bot/components/bot-mark.tsx +122 -20
- package/features/bot/components/bot-room.tsx +519 -391
- package/features/bot/components/bot-setting.tsx +832 -341
- package/features/bot/components/bot-tool.tsx +2 -6
- package/features/bot/components/mark-palette.tsx +54 -0
- package/features/bot/components/path-chips.tsx +1 -1
- package/features/bot/components/task-badge.tsx +26 -6
- package/features/bot/components/task-reply.tsx +22 -33
- package/features/bot/components/task-setting.tsx +20 -17
- package/features/bot/task.query.ts +233 -33
- package/features/bot/task.store.ts +53 -9
- package/features/config/components/config-setting.tsx +265 -34
- package/features/config/components/models-badge.tsx +42 -0
- package/features/config/config.action.ts +12 -0
- package/features/config/config.const.ts +38 -2
- package/features/connectors/mcp.manager.ts +10 -2
- package/features/intro/components/intro.tsx +47 -46
- package/features/memory/components/memory-edit.tsx +338 -0
- package/features/memory/components/memory-setting.tsx +86 -79
- package/features/memory/memory.action.ts +3 -43
- package/features/memory/memory.edit.ts +87 -0
- package/features/memory/memory.query.ts +33 -44
- package/features/memory/memory.schema.ts +50 -97
- package/features/settings/components/settings.tsx +2 -0
- package/features/settings/settings.alert.ts +3 -1
- package/features/skills/components/skills-setting.tsx +9 -1
- package/features/thursday/components/ascii-orb.tsx +106 -90
- package/features/thursday/components/boot.tsx +7 -0
- package/features/thursday/components/call-log.tsx +219 -92
- package/features/thursday/components/face.tsx +6 -4
- package/features/thursday/components/tab-state.tsx +97 -0
- package/features/thursday/components/thursday-mark.tsx +4 -1
- package/features/thursday/components/thursday-setting.tsx +17 -47
- package/features/thursday/components/thursday.tsx +208 -95
- package/features/thursday/thursday.action.ts +12 -17
- package/features/thursday/thursday.query.ts +61 -70
- package/features/thursday/thursday.schema.ts +20 -7
- package/features/thursday/tool-line.ts +85 -11
- package/features/thursday/use-thursday.ts +86 -59
- package/features/workspace/components/file-view.tsx +4 -2
- package/features/workspace/workspace.ts +230 -13
- package/hooks/use-draft.ts +37 -0
- package/hooks/use-hotkey.ts +105 -0
- package/hooks/use-object-state.ts +51 -0
- package/hooks/use-on-visible.ts +72 -0
- package/hooks/use-theme.ts +106 -0
- package/hooks/use-wake-word.ts +262 -0
- package/lib/limits.ts +1 -1
- package/lib/oauth-page.ts +27 -0
- package/lib/realtime/openai.ts +2 -2
- package/lib/realtime/realtime.driver.ts +34 -1
- package/lib/realtime/realtime.protocol.ts +7 -1
- package/lib/realtime/realtime.tap.ts +122 -0
- package/lib/sandbox.ts +60 -29
- package/lib/tokens.ts +19 -0
- package/package.json +1 -1
- package/skills/browser/SKILL.md +55 -34
- package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/memory/tidy/route.js +0 -23
- package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
- package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__02_2sm-._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__02kt8yn._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__05luceg._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +0 -11
- package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0dupqt7._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0h59di-._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0j_n5l3._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__0nfuugi._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0qjqtwo._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0t_dqaj._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0yk6s3w._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0z3gelv._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__113fqg5._.js +0 -12
- package/.next/server/chunks/[root-of-the-server]__11w_7_3._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__16thabu._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1_0q_9w._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1_aoyr9._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1dow0bz._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1gmn1cs._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1mzp_el._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1u_qvc7._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
- package/.next/server/chunks/_012yhfm._.js +0 -39
- package/.next/server/chunks/_06mxlgn._.js +0 -3
- package/.next/server/chunks/_07rtlxv._.js +0 -3
- package/.next/server/chunks/_0a351g7._.js +0 -6
- package/.next/server/chunks/_0kjjmag._.js +0 -3
- package/.next/server/chunks/_0zmestz._.js +0 -55
- package/.next/server/chunks/_0zuzmt_._.js +0 -39
- package/.next/server/chunks/_12bbv5i._.js +0 -96
- package/.next/server/chunks/_15q1pig._.js +0 -3
- package/.next/server/chunks/_1drv2et._.js +0 -36
- package/.next/server/chunks/_1ic3bew._.js +0 -5
- package/.next/server/chunks/_1mxn_lb._.js +0 -3
- package/.next/server/chunks/_1uj4_io._.js +0 -10
- package/.next/server/chunks/_1x7p319._.js +0 -6
- package/.next/server/chunks/_1yldgo4._.js +0 -55
- package/.next/server/chunks/_2050gwn._.js +0 -255
- package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.js +0 -3
- package/.next/server/chunks/features_0scmr4i._.js +0 -3
- package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0vorg6w._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0ykklj0._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1ryg3wh._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vmaq9f._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vwbnq_._.js +0 -3
- package/.next/server/chunks/ssr/[externals]_node_crypto_1kn79-7._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__01id-v3._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0b-0i32._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0dfwvr4._.js +0 -4
- package/.next/server/chunks/ssr/[root-of-the-server]__0iuoa-r._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -90
- package/.next/server/chunks/ssr/[root-of-the-server]__185grso._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1minz8_._.js +0 -3
- package/.next/server/chunks/ssr/_0-mc7ro._.js +0 -3
- package/.next/server/chunks/ssr/_01p5r6-._.js +0 -3
- package/.next/server/chunks/ssr/_09-0k3z._.js +0 -3
- package/.next/server/chunks/ssr/_0ay6b01._.js +0 -3
- package/.next/server/chunks/ssr/_0cvjbuz._.js +0 -3
- package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
- package/.next/server/chunks/ssr/_0j4v8of._.js +0 -3
- package/.next/server/chunks/ssr/_0l81bzy._.js +0 -3
- package/.next/server/chunks/ssr/_0nso8tp._.js +0 -3
- package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
- package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
- package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
- package/.next/server/chunks/ssr/_10uvw3l._.js +0 -3
- package/.next/server/chunks/ssr/_1dcxzqe._.js +0 -3
- package/.next/server/chunks/ssr/_1e-dgz0._.js +0 -4
- package/.next/server/chunks/ssr/_1fq7zgb._.js +0 -3
- package/.next/server/chunks/ssr/_1jbv7m1._.js +0 -3
- package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +0 -4
- package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +0 -3
- package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +0 -4
- package/.next/server/chunks/ssr/lib_realtime_0aetuyp._.js +0 -24
- package/.next/server/chunks/ssr/lib_realtime_18k_aj-._.js +0 -24
- package/.next/server/chunks/ssr/node_modules_zod_v4_classic_errors_05xv7vp.js +0 -3
- package/.next/server/chunks/ssr/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
- package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
- package/.next/static/chunks/0tydnn2kybibk.js +0 -1
- package/.next/static/chunks/0yysv1gzin17t.js +0 -1
- package/.next/static/chunks/1fr5m75pwtyic.js +0 -1
- package/.next/static/chunks/1gy3bbv7usnjo.js +0 -1
- package/.next/static/chunks/1keib_gt066gc.js +0 -1
- package/.next/static/chunks/1ytnip8wdng93.js +0 -1
- package/.next/static/chunks/21rk8w7bda-su.js +0 -1
- package/.next/static/chunks/247d-r95j8xs0.js +0 -1
- package/.next/static/chunks/28g42tw8tsvhd.js +0 -1
- package/.next/static/chunks/29gyb3nubj51j.js +0 -53
- package/.next/static/chunks/2cwjgkt_hoyhj.js +0 -22
- package/.next/static/chunks/2fq_86jvq9ymq.js +0 -2
- package/.next/static/chunks/2rbf1fpd8n80-.js +0 -22
- package/.next/static/chunks/2ukj8k69y89um.js +0 -1
- package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
- package/.next/static/chunks/2wngeetfnzxkp.js +0 -2
- package/.next/static/chunks/388-t2v72jh39.css +0 -1
- package/.next/static/chunks/3fqvc0cg-atq_.js +0 -1
- package/.next/static/chunks/3p4egvfjxpatb.js +0 -1
- package/.next/static/chunks/3qe1w1ei-huhl.js +0 -1
- package/.next/static/chunks/3xxa2j56-f3nm.js +0 -3
- package/.next/static/chunks/3y40riycdip-i.js +0 -1
- package/.next/static/chunks/45a6zbtkelph9.js +0 -1
- package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
- package/features/ai/prompts/memory-tidy.prompt.ts +0 -123
- package/features/ai/tools/tidy.tool.ts +0 -22
- package/features/memory/components/memory-tidy.tsx +0 -395
- package/features/memory/memory.tidy.ts +0 -332
- package/features/memory/tidy.query.ts +0 -135
- package/features/thursday/use-call-title.ts +0 -31
- /package/.next/server/app/api/{memory/tidy → bot/memory/files}/route/build-manifest.json +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory/files}/route/server-reference-manifest.json +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory/files}/route.js.map +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_buildManifest.js +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → x1cCqY0kWpR_VcYx_lWO5}/_ssgManifest.js +0 -0
|
@@ -27,15 +27,15 @@ import { Bubble, BubbleContent } from "@/components/ui/bubble";
|
|
|
27
27
|
import { Button } from "@/components/ui/button";
|
|
28
28
|
import { FoldedText } from "@/components/ui/folded-text";
|
|
29
29
|
import { Markdown } from "@/components/ui/markdown";
|
|
30
|
-
import ShinyText from "@/components/ui/shiny-text";
|
|
30
|
+
import { ShinyText, type ShinyTone } from "@/components/ui/shiny-text";
|
|
31
31
|
import { Textarea } from "@/components/ui/textarea";
|
|
32
32
|
import { toast } from "@/components/ui/toast";
|
|
33
|
-
import {
|
|
34
|
-
import { markReportedAction, startTaskAction } from "@/features/bot/bot.action";
|
|
33
|
+
import { startTaskAction } from "@/features/bot/bot.action";
|
|
35
34
|
import {
|
|
36
35
|
type Bot,
|
|
36
|
+
type BotIcon,
|
|
37
37
|
DEFAULT_BOT,
|
|
38
|
-
|
|
38
|
+
isAppStop,
|
|
39
39
|
TASK_CONTINUE,
|
|
40
40
|
} from "@/features/bot/bot.schema";
|
|
41
41
|
import { BotMark } from "@/features/bot/components/bot-mark";
|
|
@@ -43,9 +43,9 @@ import { BotRoster } from "@/features/bot/components/bot-roster";
|
|
|
43
43
|
import { PathChips } from "@/features/bot/components/path-chips";
|
|
44
44
|
import { TaskReply, useAnswerTask } from "@/features/bot/components/task-reply";
|
|
45
45
|
import { openSettings } from "@/features/settings/settings.store";
|
|
46
|
+
import { ThursdayMark } from "@/features/thursday/components/thursday-mark";
|
|
46
47
|
import { FileViewer } from "@/features/workspace/components/file-view";
|
|
47
|
-
import { shortAgo
|
|
48
|
-
import { unwrapResult } from "@/lib/protocol/result";
|
|
48
|
+
import { shortAgo } from "@/lib/date-like";
|
|
49
49
|
import { useServerAction } from "@/lib/protocol/use-server-action";
|
|
50
50
|
import { revalidate, useServerRoute } from "@/lib/protocol/use-server-route";
|
|
51
51
|
import {
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
type TaskViewStatus,
|
|
70
70
|
threadItems,
|
|
71
71
|
useBotTasks,
|
|
72
|
+
useSeenOnDetail,
|
|
72
73
|
} from "../task.store";
|
|
73
74
|
import { BotTool } from "./bot-tool";
|
|
74
75
|
|
|
@@ -78,10 +79,11 @@ import { BotTool } from "./bot-tool";
|
|
|
78
79
|
* state.
|
|
79
80
|
*
|
|
80
81
|
* Folded, it is not a badge you have to open. Anything waiting on an answer is drawn
|
|
81
|
-
* on the chip itself, with its buttons
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* front of the one thing this
|
|
82
|
+
* on the chip itself, with its buttons; what a bot is doing right now sits beside its
|
|
83
|
+
* face, and what passed between two parties rides above one for a moment
|
|
84
|
+
* (useHandoffs). Opening the room is for reading a thread, never for answering — a
|
|
85
|
+
* chevron that reveals the rows would put a click in front of the one thing this
|
|
86
|
+
* corner exists to remove.
|
|
85
87
|
*
|
|
86
88
|
* memo: the parent re-renders per transcript chunk and this reads only its store.
|
|
87
89
|
*/
|
|
@@ -99,12 +101,13 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
99
101
|
? (newest.find((entry) => entry.id === picked) ?? null)
|
|
100
102
|
: null;
|
|
101
103
|
|
|
102
|
-
const [
|
|
104
|
+
const [bubbles, handoff] = useHandoffs();
|
|
105
|
+
const { crew, more } = useMemo(() => crewOf(bots, tasks), [bots, tasks]);
|
|
103
106
|
|
|
104
|
-
// What each task was, and
|
|
105
|
-
//
|
|
107
|
+
// What each task was, and which hand-offs had already landed, at the previous
|
|
108
|
+
// sync. Only what changed since then just happened.
|
|
106
109
|
const known = useRef<Map<string, TaskViewStatus> | null>(null);
|
|
107
|
-
const
|
|
110
|
+
const passed = useRef(new Set<string>());
|
|
108
111
|
|
|
109
112
|
useEffect(() => {
|
|
110
113
|
if (!botTasks.primed()) return;
|
|
@@ -112,89 +115,85 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
112
115
|
const was = known.current;
|
|
113
116
|
known.current = now;
|
|
114
117
|
|
|
115
|
-
const
|
|
116
|
-
for (const
|
|
117
|
-
|
|
118
|
+
const seen = new Set<string>();
|
|
119
|
+
for (const task of tasks) {
|
|
120
|
+
for (const line of task.lines) {
|
|
121
|
+
if (line.kind === "ask" && line.to) seen.add(line.id);
|
|
122
|
+
}
|
|
118
123
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
124
|
+
const had = passed.current;
|
|
125
|
+
passed.current = seen;
|
|
121
126
|
|
|
122
|
-
// The first list only seeds the two
|
|
127
|
+
// The first list only seeds the two: nothing on it just happened.
|
|
123
128
|
if (!was) return;
|
|
124
129
|
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
130
|
+
// Bot to bot. A giver handing work to several at once would put a bubble
|
|
131
|
+
// over each of them, 20px apart, so a whole round is drawn once — over the
|
|
132
|
+
// giver — and the faces that took the work are awake, which says who.
|
|
133
|
+
const rounds = new Map<
|
|
134
|
+
string,
|
|
135
|
+
{ from: BotRef; to: BotRef; text: string }[]
|
|
136
|
+
>();
|
|
137
|
+
for (const task of tasks) {
|
|
138
|
+
for (const line of task.lines) {
|
|
139
|
+
if (line.kind !== "ask" || !line.to || had.has(line.id)) continue;
|
|
140
|
+
const round = rounds.get(line.bot.name) ?? [];
|
|
141
|
+
round.push({ from: line.bot, to: line.to, text: line.text });
|
|
142
|
+
rounds.set(line.bot.name, round);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const [giver, round] of rounds) {
|
|
146
|
+
if (round.length === 1) {
|
|
147
|
+
handoff({
|
|
148
|
+
at: round[0].to.name,
|
|
149
|
+
from: round[0].from,
|
|
150
|
+
text: clipWord(round[0].text),
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
handoff({
|
|
155
|
+
at: giver,
|
|
156
|
+
from: round[0].from,
|
|
157
|
+
text: `sent ${round.length} parts out`,
|
|
137
158
|
});
|
|
138
159
|
}
|
|
139
160
|
|
|
140
161
|
for (const task of newest) {
|
|
141
|
-
|
|
142
|
-
if (before === task.status) continue;
|
|
143
|
-
// First seen already finished — polling skipped the whole run. Say it only
|
|
144
|
-
// if nobody has heard it yet.
|
|
145
|
-
if (before === undefined && task.status !== "working" && task.relayed) {
|
|
162
|
+
if (was.get(task.id) === task.status || task.status !== "working")
|
|
146
163
|
continue;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
164
|
+
// A job that was never seen is one arriving; one that was is you having
|
|
165
|
+
// answered it. The mark says who gave it away — and a job typed into the
|
|
166
|
+
// compose box is Thursday's here too, because a task row does not record
|
|
167
|
+
// which of the two started it.
|
|
168
|
+
handoff(
|
|
169
|
+
was.has(task.id)
|
|
170
|
+
? { at: task.bot.name, from: null, text: "picked it back up" }
|
|
171
|
+
: {
|
|
172
|
+
at: task.bot.name,
|
|
173
|
+
from: THURSDAY,
|
|
174
|
+
text: `took on \u201c${clipWord(task.label)}\u201d`,
|
|
175
|
+
},
|
|
176
|
+
);
|
|
153
177
|
}
|
|
154
|
-
}, [tasks,
|
|
178
|
+
}, [tasks, handoff]);
|
|
155
179
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
// move `updatedAt`, so the set only bridges the gap until the next poll.
|
|
159
|
-
const marked = useRef(new Set<string>());
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (!current || current.relayed) return;
|
|
162
|
-
if (current.status !== "done" && current.status !== "failed") return;
|
|
163
|
-
const key = `${current.id}@${toDate(current.updatedAt).getTime()}`;
|
|
164
|
-
if (marked.current.has(key)) return;
|
|
165
|
-
marked.current.add(key);
|
|
166
|
-
// unwrapResult folds Result failures and rejections into one path; on failure
|
|
167
|
-
// release the key so the task can be marked again.
|
|
168
|
-
void markReportedAction([current.id])
|
|
169
|
-
.then(unwrapResult)
|
|
170
|
-
.then(() => revalidate(queryKey.tasks))
|
|
171
|
-
.catch((cause) => {
|
|
172
|
-
marked.current.delete(key);
|
|
173
|
-
toast.add({
|
|
174
|
-
type: "error",
|
|
175
|
-
title: "Could not mark the task as read",
|
|
176
|
-
description: errorToString(cause),
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
}, [current]);
|
|
180
|
+
// Reading a thread is reading its ending; that is what clears its dot.
|
|
181
|
+
useSeenOnDetail(open ? current : null);
|
|
180
182
|
|
|
181
183
|
const busy = tasks.filter((entry) => entry.status === "working").length;
|
|
182
184
|
const attention = newest.filter(needsYou);
|
|
183
185
|
const pending = attention.length;
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
.map((entry) => entry.bot)
|
|
187
|
-
.filter(
|
|
188
|
-
(bot, at, all) =>
|
|
189
|
-
all.findIndex((other) => other.name === bot.name) === at,
|
|
190
|
-
);
|
|
186
|
+
const unread = newest.filter(isUnread);
|
|
187
|
+
const failed = unread.filter((entry) => entry.status === "failed").length;
|
|
191
188
|
|
|
192
189
|
const closeCompose = useCallback(() => setComposing(false), []);
|
|
193
190
|
|
|
194
191
|
return (
|
|
195
|
-
|
|
192
|
+
// As wide as the resting pill may grow: 80% of the window. The open room
|
|
193
|
+
// keeps its own 33rem inside it.
|
|
194
|
+
<div className="pointer-events-none absolute right-5 bottom-5 z-10 flex w-[min(80vw,calc(100vw-2.5rem))] flex-col items-end gap-2">
|
|
196
195
|
{open ? (
|
|
197
|
-
<div className="pointer-events-auto flex max-h-[min(44rem,78vh)] w-full animate-in flex-col overflow-hidden rounded-3xl bg-background/75 shadow-2xl shadow-black/6 ring-1 ring-border/50 backdrop-blur-xl fade-in slide-in-from-bottom-1 duration-200">
|
|
196
|
+
<div className="pointer-events-auto flex max-h-[min(44rem,78vh)] w-132 max-w-full animate-in flex-col overflow-hidden rounded-3xl bg-background/75 shadow-2xl shadow-black/6 ring-1 ring-border/50 backdrop-blur-xl fade-in slide-in-from-bottom-1 duration-200">
|
|
198
197
|
{current ? (
|
|
199
198
|
<>
|
|
200
199
|
<ThreadHeader
|
|
@@ -241,13 +240,16 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
241
240
|
</div>
|
|
242
241
|
) : (
|
|
243
242
|
<Chip
|
|
243
|
+
crew={crew}
|
|
244
|
+
more={more}
|
|
245
|
+
bubbles={bubbles}
|
|
244
246
|
bots={bots}
|
|
245
247
|
rows={attention}
|
|
246
|
-
waiting={waiting}
|
|
247
248
|
count={tasks.length}
|
|
248
249
|
busy={busy}
|
|
249
250
|
pending={pending}
|
|
250
|
-
|
|
251
|
+
unread={unread.length}
|
|
252
|
+
failed={failed}
|
|
251
253
|
composing={composing}
|
|
252
254
|
onCompose={() => setComposing(true)}
|
|
253
255
|
onCloseCompose={closeCompose}
|
|
@@ -256,8 +258,10 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
256
258
|
setOpen(true);
|
|
257
259
|
}}
|
|
258
260
|
onOpen={() => {
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
+
// One thing to look at opens straight into its thread: a single
|
|
262
|
+
// question, or with none waiting, a single answer nobody has read.
|
|
263
|
+
const only = attention.length ? attention : unread;
|
|
264
|
+
if (!picked && only.length === 1) setPicked(only[0].id);
|
|
261
265
|
setOpen(true);
|
|
262
266
|
}}
|
|
263
267
|
/>
|
|
@@ -266,106 +270,163 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
266
270
|
);
|
|
267
271
|
});
|
|
268
272
|
|
|
269
|
-
/**
|
|
270
|
-
const
|
|
271
|
-
/**
|
|
272
|
-
const
|
|
273
|
+
/** Faces the row draws before the count takes over. */
|
|
274
|
+
const CREW_MAX = 10;
|
|
275
|
+
/** Stable identity for a row that can never have one up. */
|
|
276
|
+
const EMPTY_BUBBLES: Map<string, Handoff> = new Map();
|
|
277
|
+
/** How long a hand-off stays above a face, ms. */
|
|
278
|
+
const HANDOFF_MS = 3400;
|
|
279
|
+
/** A step is a glance, not a sentence. */
|
|
280
|
+
const WORD_MAX = 28;
|
|
281
|
+
|
|
282
|
+
/** One face in the row, and what it is doing. */
|
|
283
|
+
type CrewFace = {
|
|
284
|
+
name: string;
|
|
285
|
+
icon?: BotIcon | null;
|
|
286
|
+
/** Working or waiting: both are awake. */
|
|
287
|
+
awake: boolean;
|
|
288
|
+
/** Waiting on an answer; carries the dot. */
|
|
289
|
+
waiting: boolean;
|
|
290
|
+
/** The step it is on, in the model's own words. Null when it is not working. */
|
|
291
|
+
word: string | null;
|
|
292
|
+
/** A stand-in for an install with no bots; dimmed with the rest. */
|
|
293
|
+
standIn?: boolean;
|
|
294
|
+
};
|
|
273
295
|
|
|
274
296
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* while the sentence is up, so it shines; "finished" does not.
|
|
297
|
+
* Something passed between two parties. This is what a bubble is for, and the
|
|
298
|
+
* only thing: a step is the bot working alone and belongs beside its face.
|
|
278
299
|
*/
|
|
279
|
-
type
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
300
|
+
type Handoff = {
|
|
301
|
+
/** Whose face it points at. */
|
|
302
|
+
at: string;
|
|
303
|
+
/** Who gave it away. Null when it was the user, who has no mark. */
|
|
304
|
+
from: BotRef | null;
|
|
305
|
+
text: string;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const clipWord = (text: string) => {
|
|
309
|
+
const one = plainText(text).replace(/\s+/g, " ").trim();
|
|
310
|
+
return one.length > WORD_MAX ? `${one.slice(0, WORD_MAX - 1)}…` : one;
|
|
285
311
|
};
|
|
286
312
|
|
|
287
313
|
/**
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* beat from the same bot overwrites its place instead of joining the back of the
|
|
293
|
-
* queue, so three bots stepping at once still take turns and the newest thing a
|
|
294
|
-
* given bot did is the one you see.
|
|
314
|
+
* Hand-offs currently up, one per face. A second one to the same face replaces
|
|
315
|
+
* it rather than queueing: the newest thing that landed there is the true one.
|
|
316
|
+
* Different faces hold their own at the same time — the fan-out that would make
|
|
317
|
+
* two of them collide is folded into one before it gets here (BotRoom).
|
|
295
318
|
*/
|
|
296
|
-
function
|
|
297
|
-
const [
|
|
298
|
-
const
|
|
299
|
-
const holding = useRef(false);
|
|
300
|
-
const timers = useRef<ReturnType<typeof setTimeout>[]>([]);
|
|
319
|
+
function useHandoffs(): [Map<string, Handoff>, (one: Handoff) => void] {
|
|
320
|
+
const [up, setUp] = useState<Map<string, Handoff>>(new Map());
|
|
321
|
+
const timers = useRef(new Map<string, ReturnType<typeof setTimeout>>());
|
|
301
322
|
|
|
302
323
|
useEffect(
|
|
303
324
|
() => () => {
|
|
304
|
-
for (const timer of timers.current) clearTimeout(timer);
|
|
325
|
+
for (const timer of timers.current.values()) clearTimeout(timer);
|
|
305
326
|
},
|
|
306
327
|
[],
|
|
307
328
|
);
|
|
308
329
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
holding.current = true;
|
|
316
|
-
setNow(next);
|
|
317
|
-
timers.current.push(
|
|
330
|
+
const show = useCallback((one: Handoff) => {
|
|
331
|
+
setUp((was) => new Map(was).set(one.at, one));
|
|
332
|
+
const held = timers.current.get(one.at);
|
|
333
|
+
if (held) clearTimeout(held);
|
|
334
|
+
timers.current.set(
|
|
335
|
+
one.at,
|
|
318
336
|
setTimeout(() => {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}, BEAT_MS),
|
|
337
|
+
timers.current.delete(one.at);
|
|
338
|
+
setUp((was) => {
|
|
339
|
+
const next = new Map(was);
|
|
340
|
+
next.delete(one.at);
|
|
341
|
+
return next;
|
|
342
|
+
});
|
|
343
|
+
}, HANDOFF_MS),
|
|
327
344
|
);
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
const announce = useCallback((beat: Beat) => {
|
|
331
|
-
const at = queue.current.findIndex((one) => one.bot === beat.bot);
|
|
332
|
-
if (at >= 0) queue.current[at] = beat;
|
|
333
|
-
else queue.current.push(beat);
|
|
334
|
-
pump.current();
|
|
335
345
|
}, []);
|
|
336
346
|
|
|
337
|
-
return [
|
|
347
|
+
return [up, show];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** The step a bot is on, as the model labelled it. */
|
|
351
|
+
function wordOf(line: Chatter | null): string | null {
|
|
352
|
+
if (!line) return null;
|
|
353
|
+
if (line.kind === "tool" && line.tool) {
|
|
354
|
+
// The model's own label when it wrote one, else the raw call.
|
|
355
|
+
return clipWord(line.tool.note ?? `${line.tool.name} · ${line.tool.input}`);
|
|
356
|
+
}
|
|
357
|
+
return line.kind === "say" ? clipWord(line.text) : null;
|
|
338
358
|
}
|
|
339
359
|
|
|
340
360
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
361
|
+
* Who is in the row, in the order it is drawn.
|
|
362
|
+
*
|
|
363
|
+
* Anyone moving sorts to the front, so the count at the tail only ever hides
|
|
364
|
+
* idle bots — a bot with something to say always has a face to say it from,
|
|
365
|
+
* which is what lets a hand-off point at one. A bot that spoke inside somebody
|
|
366
|
+
* else's job is in the room too, whether or not it owns a task (latestPerBot).
|
|
343
367
|
*/
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
):
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
368
|
+
function crewOf(
|
|
369
|
+
bots: Bot[] | undefined,
|
|
370
|
+
tasks: TaskView[],
|
|
371
|
+
): { crew: CrewFace[]; more: number } {
|
|
372
|
+
const live = new Map<string, { waiting: boolean; word: string | null }>();
|
|
373
|
+
for (const entry of latestPerBot(tasks)) {
|
|
374
|
+
const { task, bot, line } = entry;
|
|
375
|
+
// Waiting outranks working: a bot that stopped to ask is not on a step.
|
|
376
|
+
if (task.status === "waiting" && task.bot.name === bot.name) {
|
|
377
|
+
live.set(bot.name, { waiting: true, word: null });
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (task.status !== "working" || live.get(bot.name)?.waiting) continue;
|
|
381
|
+
live.set(bot.name, { waiting: false, word: wordOf(line) });
|
|
354
382
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
383
|
+
|
|
384
|
+
const named = new Map<string, CrewFace>();
|
|
385
|
+
for (const bot of bots ?? []) {
|
|
386
|
+
named.set(bot.name, {
|
|
387
|
+
name: bot.name,
|
|
388
|
+
icon: bot.icon,
|
|
389
|
+
awake: live.has(bot.name),
|
|
390
|
+
waiting: live.get(bot.name)?.waiting ?? false,
|
|
391
|
+
word: live.get(bot.name)?.word ?? null,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
// A bot that is working but not on the roster (the row-less default) still
|
|
395
|
+
// has a face: the row is who is here, not who is configured.
|
|
396
|
+
for (const [name, doing] of live) {
|
|
397
|
+
if (named.has(name)) continue;
|
|
398
|
+
named.set(name, {
|
|
399
|
+
name,
|
|
400
|
+
icon: null,
|
|
401
|
+
awake: true,
|
|
402
|
+
waiting: doing.waiting,
|
|
403
|
+
word: doing.word,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// A fresh install has one worker and one silhouette says "one bot", which is
|
|
408
|
+
// the wrong thing to say about a room. Stand-ins fill it out and are dimmed.
|
|
409
|
+
const roster = [...named.values()];
|
|
410
|
+
if (!roster.length) return { crew: GHOSTS.slice(0, FLOOR), more: 0 };
|
|
411
|
+
for (const ghost of GHOSTS) {
|
|
412
|
+
if (roster.length >= FLOOR) break;
|
|
413
|
+
if (!named.has(ghost.name)) roster.push(ghost);
|
|
362
414
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
:
|
|
415
|
+
|
|
416
|
+
const rank = (face: CrewFace) =>
|
|
417
|
+
face.waiting ? 0 : face.awake ? 1 : face.standIn ? 3 : 2;
|
|
418
|
+
const sorted = roster
|
|
419
|
+
.map((face, at) => ({ face, at }))
|
|
420
|
+
.sort((a, b) => rank(a.face) - rank(b.face) || a.at - b.at)
|
|
421
|
+
.map((one) => one.face);
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
crew: sorted.slice(0, CREW_MAX),
|
|
425
|
+
more: Math.max(0, sorted.length - CREW_MAX),
|
|
426
|
+
};
|
|
366
427
|
}
|
|
367
428
|
|
|
368
|
-
/** Blanks the question in the reply box when it already is the last thread line (
|
|
429
|
+
/** Blanks the question in the reply box when it already is the last thread line (stops the app made). */
|
|
369
430
|
function askFor(task: TaskView): TaskView["ask"] {
|
|
370
431
|
if (!task.ask?.question) return task.ask;
|
|
371
432
|
const last = task.lines.at(-1);
|
|
@@ -375,30 +436,59 @@ function askFor(task: TaskView): TaskView["ask"] {
|
|
|
375
436
|
}
|
|
376
437
|
|
|
377
438
|
/**
|
|
378
|
-
* A task that cannot move until the user answers: a question, or a
|
|
439
|
+
* A task that cannot move until the user answers: a question, or a stop the app made.
|
|
379
440
|
*
|
|
380
441
|
* A finished job is not on this list. It has nothing for the user to do, and
|
|
381
|
-
* counting it here made the chip hold itself open over
|
|
382
|
-
* act on.
|
|
442
|
+
* counting it here made the chip hold itself open over an answer nobody had to
|
|
443
|
+
* act on. An answer says itself once, in the sentence, and then it is a row in
|
|
383
444
|
* the room like every other one.
|
|
384
445
|
*/
|
|
385
446
|
const needsYou = (task: TaskView) => task.status === "waiting";
|
|
386
447
|
|
|
387
|
-
/**
|
|
448
|
+
/** An ending nobody has opened. It needs the user too, to read rather than to answer. */
|
|
449
|
+
const isUnread = (task: TaskView) =>
|
|
450
|
+
(task.status === "done" || task.status === "failed") && !task.seen;
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* What the room itself is doing, and nothing else — the right side of the pill.
|
|
454
|
+
*
|
|
455
|
+
* What is still moving shines, like every other running line: a wait in amber,
|
|
456
|
+
* running jobs in muted ink beside the spinner. An ending does not — an answer
|
|
457
|
+
* nobody has opened is something to read, not something happening.
|
|
458
|
+
*/
|
|
388
459
|
function restingState({
|
|
389
460
|
count,
|
|
390
461
|
busy,
|
|
391
462
|
pending,
|
|
463
|
+
unread,
|
|
464
|
+
failed,
|
|
392
465
|
}: {
|
|
393
466
|
count: number;
|
|
394
467
|
busy: number;
|
|
395
468
|
pending: number;
|
|
396
|
-
|
|
469
|
+
/** Endings nobody has opened, failures included. */
|
|
470
|
+
unread: number;
|
|
471
|
+
failed: number;
|
|
472
|
+
}): { text: string; tone: string; shine: ShinyTone | null } {
|
|
397
473
|
if (pending > 0)
|
|
398
474
|
return {
|
|
399
475
|
text: pending === 1 ? "waiting on you" : `${pending} waiting on you`,
|
|
400
476
|
tone: WAITING_INK,
|
|
401
|
-
shine: "
|
|
477
|
+
shine: "waiting",
|
|
478
|
+
};
|
|
479
|
+
// Unread outranks running: a job still going will say so again, and an answer
|
|
480
|
+
// left unopened will not.
|
|
481
|
+
if (failed > 0)
|
|
482
|
+
return {
|
|
483
|
+
text: failed === 1 ? "1 failed" : `${failed} failed`,
|
|
484
|
+
tone: "text-destructive",
|
|
485
|
+
shine: null,
|
|
486
|
+
};
|
|
487
|
+
if (unread > 0)
|
|
488
|
+
return {
|
|
489
|
+
text: unread === 1 ? "1 new answer" : `${unread} new answers`,
|
|
490
|
+
tone: WAITING_INK,
|
|
491
|
+
shine: null,
|
|
402
492
|
};
|
|
403
493
|
if (busy > 0)
|
|
404
494
|
return {
|
|
@@ -406,67 +496,83 @@ function restingState({
|
|
|
406
496
|
tone: "text-muted-foreground",
|
|
407
497
|
shine: "muted",
|
|
408
498
|
};
|
|
409
|
-
if (count > 0)
|
|
410
|
-
|
|
499
|
+
if (count > 0)
|
|
500
|
+
return { text: "all done", tone: "text-muted-foreground", shine: null };
|
|
501
|
+
return { text: "no jobs yet", tone: "text-muted-foreground", shine: null };
|
|
411
502
|
}
|
|
412
503
|
|
|
413
504
|
/**
|
|
414
505
|
* The room folded into one object in the corner.
|
|
415
506
|
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
507
|
+
* The row carries two facts and they never take each other's place. On the
|
|
508
|
+
* **left**, who is here and what each of them is doing: a face, and the shiny
|
|
509
|
+
* text beside it. On the **right**, what the room itself is doing, with its
|
|
510
|
+
* glyph 6px away — one fact, so nothing gets to come between them and nothing
|
|
511
|
+
* takes the glyph away. Anything that passes between two parties is neither, so
|
|
512
|
+
* it rides above a face in a bubble.
|
|
513
|
+
*
|
|
514
|
+
* While something is waiting on an answer its rows are grown in place above the
|
|
515
|
+
* row. Nothing here sits behind a disclosure: they appear because there is
|
|
516
|
+
* something to answer and leave when it is answered. The pill's own click opens
|
|
517
|
+
* the room, to read.
|
|
422
518
|
*
|
|
423
519
|
* The corner radius does not animate with the height: interpolating a pill radius
|
|
424
520
|
* down to a card radius while the box is also resizing warps the corners in flight.
|
|
425
521
|
*/
|
|
426
522
|
function Chip({
|
|
523
|
+
crew,
|
|
524
|
+
more,
|
|
525
|
+
bubbles,
|
|
427
526
|
bots,
|
|
428
527
|
rows,
|
|
429
|
-
waiting,
|
|
430
528
|
count,
|
|
431
529
|
busy,
|
|
432
530
|
pending,
|
|
433
|
-
|
|
531
|
+
unread,
|
|
532
|
+
failed,
|
|
434
533
|
composing,
|
|
435
534
|
onCompose,
|
|
436
535
|
onCloseCompose,
|
|
437
536
|
onPick,
|
|
438
537
|
onOpen,
|
|
439
538
|
}: {
|
|
539
|
+
crew: CrewFace[];
|
|
540
|
+
more: number;
|
|
541
|
+
bubbles: Map<string, Handoff>;
|
|
440
542
|
bots?: Bot[];
|
|
441
543
|
/** Everything waiting on an answer, newest first. */
|
|
442
544
|
rows: TaskView[];
|
|
443
|
-
waiting: BotRef[];
|
|
444
545
|
count: number;
|
|
445
546
|
busy: number;
|
|
446
547
|
pending: number;
|
|
447
|
-
|
|
548
|
+
unread: number;
|
|
549
|
+
failed: number;
|
|
448
550
|
composing: boolean;
|
|
449
551
|
onCompose: () => void;
|
|
450
552
|
onCloseCompose: () => void;
|
|
451
553
|
onPick: (id: string) => void;
|
|
452
554
|
onOpen: () => void;
|
|
453
555
|
}) {
|
|
454
|
-
const state = restingState({ count, busy, pending });
|
|
556
|
+
const state = restingState({ count, busy, pending, unread, failed });
|
|
455
557
|
const grown = composing || pending > 0;
|
|
456
558
|
|
|
457
559
|
return (
|
|
458
560
|
<div
|
|
459
561
|
className={cn(
|
|
460
|
-
|
|
562
|
+
// Not `overflow-hidden`: a hand-off bubble stands above the row, outside
|
|
563
|
+
// this box. The growing part clips itself instead.
|
|
564
|
+
"pointer-events-auto w-fit max-w-full bg-background/78 ring-1 ring-border/50 backdrop-blur-md transition-shadow duration-300",
|
|
565
|
+
// A card with questions in it keeps the room's width: only the resting
|
|
566
|
+
// row grows with the corner, and a reply's long lines would stretch it.
|
|
461
567
|
grown
|
|
462
|
-
? "min-w-96 rounded-3xl shadow-lg shadow-black/8"
|
|
568
|
+
? "max-w-[min(33rem,100%)] min-w-96 rounded-3xl shadow-lg shadow-black/8"
|
|
463
569
|
: "rounded-full shadow-sm shadow-black/3",
|
|
464
570
|
)}
|
|
465
571
|
>
|
|
466
572
|
{/* One box, two heights: the rows grow out of nothing rather than appearing. */}
|
|
467
573
|
<div
|
|
468
574
|
className={cn(
|
|
469
|
-
"grid transition-[grid-template-rows] duration-300 ease-out",
|
|
575
|
+
"grid overflow-hidden transition-[grid-template-rows] duration-300 ease-out",
|
|
470
576
|
grown ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
|
|
471
577
|
)}
|
|
472
578
|
>
|
|
@@ -501,79 +607,49 @@ function Chip({
|
|
|
501
607
|
{/* The pill row. Same geometry either way, so the card shrinks into it. */}
|
|
502
608
|
{/* px-3 is the chip's one rail: the faces here, the section line and every
|
|
503
609
|
row's mark all start at 12px, and the trailing glyph ends at 12px — which
|
|
504
|
-
is why
|
|
505
|
-
|
|
506
|
-
as their own 8px inside a 4px list. */}
|
|
507
|
-
<div className={cn("flex items-center gap-2 px-3 py-1.5")}>
|
|
610
|
+
is why the well carries no box of its own. */}
|
|
611
|
+
<div className="flex items-center gap-2 px-3 py-1.5">
|
|
508
612
|
<button
|
|
509
613
|
type="button"
|
|
510
614
|
onClick={onOpen}
|
|
511
615
|
aria-label={count ? `Tasks (${count})` : "Bots"}
|
|
512
616
|
className="flex min-w-0 flex-1 items-center gap-2 rounded-full text-left outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
|
|
513
617
|
>
|
|
514
|
-
<
|
|
515
|
-
|
|
516
|
-
{/*
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
{
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
<span
|
|
533
|
-
className={cn(
|
|
534
|
-
"min-w-0 truncate text-[14px] leading-5 tracking-[-0.15px]",
|
|
535
|
-
beat?.tone ?? "text-foreground",
|
|
536
|
-
)}
|
|
537
|
-
>
|
|
538
|
-
{beat?.what}
|
|
539
|
-
</span>
|
|
618
|
+
<Crew crew={crew} more={more} bubbles={bubbles} />
|
|
619
|
+
|
|
620
|
+
{/* The room's own state and its glyph: one group, and the only thing on
|
|
621
|
+
the right. `ml-auto` keeps it there when the crew says nothing. */}
|
|
622
|
+
<span className="ml-auto flex h-7 shrink-0 items-center gap-1.5">
|
|
623
|
+
{state.shine ? (
|
|
624
|
+
<ShinyText
|
|
625
|
+
text={state.text}
|
|
626
|
+
tone={state.shine}
|
|
627
|
+
speed={2.6}
|
|
628
|
+
className="block truncate text-[14px] leading-5 tracking-[-0.15px]"
|
|
629
|
+
/>
|
|
630
|
+
) : (
|
|
631
|
+
<span
|
|
632
|
+
key={state.text}
|
|
633
|
+
className={cn(
|
|
634
|
+
"block animate-in truncate text-[14px] leading-5 tracking-[-0.15px] fade-in duration-300",
|
|
635
|
+
state.tone,
|
|
540
636
|
)}
|
|
637
|
+
>
|
|
638
|
+
{state.text}
|
|
541
639
|
</span>
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
// theme, and ShinyText reads the variables they set.
|
|
549
|
-
<span
|
|
550
|
-
className={cn(
|
|
551
|
-
"block",
|
|
552
|
-
state.shine === "amber"
|
|
553
|
-
? "[--rest:var(--color-amber-700)] [--shine:var(--color-amber-400)] dark:[--rest:var(--color-amber-400)] dark:[--shine:var(--color-amber-100)]"
|
|
554
|
-
: "[--rest:var(--muted-foreground)] [--shine:var(--foreground)]",
|
|
555
|
-
)}
|
|
556
|
-
>
|
|
557
|
-
<ShinyText
|
|
558
|
-
text={state.text}
|
|
559
|
-
speed={2.6}
|
|
560
|
-
color="var(--rest)"
|
|
561
|
-
shineColor="var(--shine)"
|
|
562
|
-
className="truncate text-[14px] leading-5 tracking-[-0.15px]"
|
|
563
|
-
/>
|
|
564
|
-
</span>
|
|
565
|
-
) : (
|
|
566
|
-
<span
|
|
567
|
-
key={state.text}
|
|
568
|
-
className={cn(
|
|
569
|
-
"block animate-in truncate text-[14px] leading-5 tracking-[-0.15px] fade-in duration-300",
|
|
570
|
-
state.tone,
|
|
571
|
-
)}
|
|
572
|
-
>
|
|
573
|
-
{state.text}
|
|
574
|
-
</span>
|
|
640
|
+
)}
|
|
641
|
+
{/* Always 16px, empty or not: a glyph that comes and goes moves the
|
|
642
|
+
sentence's right end even when the sentence has not changed. */}
|
|
643
|
+
<span className="grid size-4 shrink-0 place-items-center">
|
|
644
|
+
{pending === 0 && unread === 0 && busy > 0 && (
|
|
645
|
+
<Loader2 className="size-4 animate-spin text-muted-foreground/70" />
|
|
575
646
|
)}
|
|
576
|
-
|
|
647
|
+
{pending === 0 &&
|
|
648
|
+
failed === 0 &&
|
|
649
|
+
(unread > 0 || (busy === 0 && count > 0)) && (
|
|
650
|
+
<Check className="size-4 text-muted-foreground/60" />
|
|
651
|
+
)}
|
|
652
|
+
</span>
|
|
577
653
|
</span>
|
|
578
654
|
</button>
|
|
579
655
|
|
|
@@ -582,36 +658,11 @@ function Chip({
|
|
|
582
658
|
<X className="size-3.5" />
|
|
583
659
|
</RoundButton>
|
|
584
660
|
)}
|
|
585
|
-
{!composing && pending === 0 && busy > 0 && (
|
|
586
|
-
<Loader2 className="size-4 shrink-0 animate-spin text-muted-foreground/70" />
|
|
587
|
-
)}
|
|
588
|
-
{!composing && pending === 0 && busy === 0 && count > 0 && (
|
|
589
|
-
<Check className="size-4 shrink-0 text-muted-foreground/60" />
|
|
590
|
-
)}
|
|
591
661
|
</div>
|
|
592
662
|
</div>
|
|
593
663
|
);
|
|
594
664
|
}
|
|
595
665
|
|
|
596
|
-
/**
|
|
597
|
-
* Two things sharing one line: the one that is on takes the width and the other
|
|
598
|
-
* folds to nothing. Both stay mounted, so the swap is a slide and not a jump.
|
|
599
|
-
*/
|
|
600
|
-
function Lane({ on, children }: { on: boolean; children: ReactNode }) {
|
|
601
|
-
return (
|
|
602
|
-
<span
|
|
603
|
-
className={cn(
|
|
604
|
-
// The width is the only thing that moves, and it moves fast: at half a
|
|
605
|
-
// second the letters arrive one by one and it reads as typing.
|
|
606
|
-
"grid min-w-0 transition-[grid-template-columns,opacity] duration-200 ease-out",
|
|
607
|
-
on ? "grid-cols-[1fr] opacity-100" : "grid-cols-[0fr] opacity-0",
|
|
608
|
-
)}
|
|
609
|
-
>
|
|
610
|
-
<span className="min-w-0 overflow-hidden">{children}</span>
|
|
611
|
-
</span>
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
666
|
/** Starts a job. Lives in the header of whichever list is on screen. */
|
|
616
667
|
function ComposeButton({ onClick }: { onClick: () => void }) {
|
|
617
668
|
return (
|
|
@@ -661,8 +712,13 @@ function Compose({ bots, onDone }: { bots?: Bot[]; onDone: () => void }) {
|
|
|
661
712
|
});
|
|
662
713
|
|
|
663
714
|
// A fresh install has no rows and still has a worker (bot.schema DEFAULT_BOT).
|
|
715
|
+
// Switched-off bots are left out for the same reason no model is shown one —
|
|
716
|
+
// but the fallback answers "no rows", not "every row off", so switching them
|
|
717
|
+
// all off leaves nothing to pick rather than conjuring a worker.
|
|
664
718
|
const roster: BotRef[] = bots?.length
|
|
665
|
-
? bots
|
|
719
|
+
? bots
|
|
720
|
+
.filter((bot) => !bot.disabled)
|
|
721
|
+
.map((bot) => ({ name: bot.name, icon: bot.icon }))
|
|
666
722
|
: [{ name: DEFAULT_BOT.name, icon: DEFAULT_BOT.icon }];
|
|
667
723
|
|
|
668
724
|
const send = () => {
|
|
@@ -775,94 +831,159 @@ function Compose({ bots, onDone }: { bots?: Bot[]; onDone: () => void }) {
|
|
|
775
831
|
}
|
|
776
832
|
|
|
777
833
|
/** Stand-in faces for an install with no bots. Fixed, not random: this renders on the server too and Math.random would break hydration. */
|
|
778
|
-
const GHOSTS = ["alto", "brio", "cinder", "delta"].map(
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
834
|
+
const GHOSTS: CrewFace[] = ["alto", "brio", "cinder", "delta"].map(
|
|
835
|
+
(seed, index) => ({
|
|
836
|
+
name: seed,
|
|
837
|
+
icon: {
|
|
838
|
+
color: MARK_PALETTE[Math.floor((index * MARK_PALETTE.length) / 4)],
|
|
839
|
+
shape: MARK_SHAPES[index % MARK_SHAPES.length],
|
|
840
|
+
},
|
|
841
|
+
awake: false,
|
|
842
|
+
waiting: false,
|
|
843
|
+
word: null,
|
|
844
|
+
standIn: true,
|
|
845
|
+
}),
|
|
846
|
+
);
|
|
785
847
|
|
|
786
848
|
/** A single face reads as one worker, not as a crew; below this the row is padded out. */
|
|
787
|
-
const
|
|
849
|
+
const FLOOR = 3;
|
|
788
850
|
|
|
789
|
-
/** The
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
851
|
+
/** The awake halo, in the mark's own view-box units: about 2px of blur at 28px. */
|
|
852
|
+
const AWAKE_GLOW = 20;
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* The crew, and what each of them is doing.
|
|
856
|
+
*
|
|
857
|
+
* Two things share this row and never take each other's place: a face says
|
|
858
|
+
* **who**, and the shiny text beside it says **what**, for as long as that bot
|
|
859
|
+
* is on that step. Both are derived — there is no queue and no cap, because
|
|
860
|
+
* every bot that is working is saying something true at the same time.
|
|
861
|
+
*
|
|
862
|
+
* A face that is moving is awake: lifted, swollen a little, and glowing in its
|
|
863
|
+
* own colour. That layer alone survives every collision — three bots at once
|
|
864
|
+
* are three awake faces, with no order to decide.
|
|
865
|
+
*/
|
|
866
|
+
function Crew({
|
|
867
|
+
crew,
|
|
868
|
+
more,
|
|
869
|
+
bubbles,
|
|
794
870
|
}: {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
|
|
871
|
+
crew: CrewFace[];
|
|
872
|
+
more: number;
|
|
873
|
+
/** Hand-offs currently up, keyed by the face they point at. */
|
|
874
|
+
bubbles: Map<string, Handoff>;
|
|
799
875
|
}) {
|
|
800
|
-
// Faces with a task waiting go in front and are the only ones with the dot.
|
|
801
|
-
const roster = bots?.length
|
|
802
|
-
? bots.map((bot) => ({
|
|
803
|
-
key: bot.name,
|
|
804
|
-
seed: bot.name,
|
|
805
|
-
color: bot.icon?.color,
|
|
806
|
-
shape: bot.icon?.shape,
|
|
807
|
-
outline: bot.icon?.outline,
|
|
808
|
-
notify: false,
|
|
809
|
-
}))
|
|
810
|
-
: GHOSTS.map((ghost) => ({ ...ghost, notify: false }));
|
|
811
|
-
|
|
812
|
-
const front = waiting.map((bot) => ({
|
|
813
|
-
key: bot.name,
|
|
814
|
-
seed: bot.name,
|
|
815
|
-
color: bot.icon?.color,
|
|
816
|
-
shape: bot.icon?.shape,
|
|
817
|
-
outline: bot.icon?.outline,
|
|
818
|
-
notify: true,
|
|
819
|
-
}));
|
|
820
|
-
|
|
821
|
-
// A fresh install has one worker (DEFAULT_BOT) and one silhouette says "one bot",
|
|
822
|
-
// which is the wrong thing to say about a room. Stand-ins fill the row out to CREW
|
|
823
|
-
// and are dimmed with it, so the corner reads as a crew before it holds one.
|
|
824
|
-
const named = [
|
|
825
|
-
...front,
|
|
826
|
-
...roster.filter((face) => !front.some((one) => one.key === face.key)),
|
|
827
|
-
].map((face) => ({ ...face, standIn: false }));
|
|
828
|
-
const filler = GHOSTS.filter(
|
|
829
|
-
(ghost) => !named.some((face) => face.key === ghost.key),
|
|
830
|
-
)
|
|
831
|
-
.slice(0, Math.max(0, CREW - named.length))
|
|
832
|
-
.map((ghost) => ({ ...ghost, notify: false, standIn: true }));
|
|
833
|
-
const crew = [...named, ...filler].slice(0, 4);
|
|
834
|
-
|
|
835
876
|
return (
|
|
836
|
-
<span
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
877
|
+
<span className="flex min-w-0 shrink items-center">
|
|
878
|
+
{crew.map((face, index) => {
|
|
879
|
+
const bubble = bubbles.get(face.name) ?? null;
|
|
880
|
+
return (
|
|
881
|
+
<Fragment key={face.name}>
|
|
882
|
+
<span
|
|
883
|
+
// Silhouettes overlapped, never ringed: a ring needs a circle and
|
|
884
|
+
// these are not circles. Earlier faces sit on top, so the dot on a
|
|
885
|
+
// waiting face is never covered by its neighbour.
|
|
886
|
+
className={cn(
|
|
887
|
+
"relative shrink-0 transition-[margin,transform] duration-500 ease-out",
|
|
888
|
+
// A word to the left has already broken the shingle.
|
|
889
|
+
index > 0 && !crew[index - 1].word && "-ml-2",
|
|
890
|
+
face.standIn && "opacity-35",
|
|
891
|
+
face.awake && "-translate-y-0.5 scale-110",
|
|
892
|
+
)}
|
|
893
|
+
style={{ zIndex: crew.length - index }}
|
|
894
|
+
>
|
|
895
|
+
{/* The halo is the mark's own (bot-mark `glow`), not a filter on
|
|
896
|
+
this box: a filter here also lands on the bubble above, which
|
|
897
|
+
came out tinted in the bot's colour and wearing its blur. */}
|
|
898
|
+
<CrewMark face={face} />
|
|
899
|
+
{bubble && <HandoffBubble handoff={bubble} />}
|
|
900
|
+
</span>
|
|
901
|
+
{face.word && (
|
|
902
|
+
// No box: the shine is what says this is happening right now, so a
|
|
903
|
+
// capsule around it was drawing a second time what the sweep says.
|
|
904
|
+
<span className="mx-2.5 min-w-0 shrink truncate">
|
|
905
|
+
<ShinyText
|
|
906
|
+
text={face.word}
|
|
907
|
+
speed={2.6}
|
|
908
|
+
className="truncate text-[13px] leading-5 tracking-[-0.1px]"
|
|
909
|
+
/>
|
|
910
|
+
</span>
|
|
911
|
+
)}
|
|
912
|
+
</Fragment>
|
|
913
|
+
);
|
|
914
|
+
})}
|
|
915
|
+
{more > 0 && (
|
|
916
|
+
// Past CREW_MAX the row stops growing. Only idle bots are ever behind it:
|
|
917
|
+
// anyone moving sorted to the front.
|
|
843
918
|
<span
|
|
844
|
-
key={face.key}
|
|
845
|
-
// Silhouettes overlapped, never ringed: a ring needs a circle and these
|
|
846
|
-
// are not circles. One speaking bot pulls the rest in behind it.
|
|
847
919
|
className={cn(
|
|
848
|
-
"
|
|
849
|
-
|
|
850
|
-
face.standIn && "opacity-35",
|
|
851
|
-
only && face.key !== only && "-ml-[28px] scale-50 opacity-0",
|
|
920
|
+
"grid size-7 shrink-0 place-items-center rounded-[9px] bg-muted font-mono text-[10px] text-muted-foreground ring-1 ring-border/50",
|
|
921
|
+
!crew.at(-1)?.word && "-ml-2",
|
|
852
922
|
)}
|
|
853
|
-
|
|
923
|
+
title={`${more} more`}
|
|
854
924
|
>
|
|
855
|
-
|
|
856
|
-
size={28}
|
|
857
|
-
seed={face.seed}
|
|
858
|
-
vary={face.seed}
|
|
859
|
-
color={face.color}
|
|
860
|
-
shape={face.shape}
|
|
861
|
-
outline={face.outline}
|
|
862
|
-
notify={face.notify}
|
|
863
|
-
/>
|
|
925
|
+
+{more}
|
|
864
926
|
</span>
|
|
865
|
-
)
|
|
927
|
+
)}
|
|
928
|
+
</span>
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* One crew face. Awake, it wears a halo in its own colour, so who is moving
|
|
934
|
+
* reads before any word does; the options object is memoized because a new one
|
|
935
|
+
* every render re-derives the silhouette behind it.
|
|
936
|
+
*/
|
|
937
|
+
function CrewMark({ face }: { face: CrewFace }) {
|
|
938
|
+
const glow = useMemo(
|
|
939
|
+
() => (face.awake ? { glow: AWAKE_GLOW } : undefined),
|
|
940
|
+
[face.awake],
|
|
941
|
+
);
|
|
942
|
+
return (
|
|
943
|
+
<BotMark
|
|
944
|
+
size={28}
|
|
945
|
+
seed={face.name}
|
|
946
|
+
vary={face.name}
|
|
947
|
+
color={face.icon?.color}
|
|
948
|
+
shape={face.icon?.shape}
|
|
949
|
+
outline={face.icon?.outline}
|
|
950
|
+
notify={face.waiting}
|
|
951
|
+
options={glow}
|
|
952
|
+
/>
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* A hand-off, over the face it landed on. The mark at its head is whoever gave
|
|
958
|
+
* it away — Thursday, another bot, or nobody at all when it was you.
|
|
959
|
+
*
|
|
960
|
+
* It sits outside the pill's box on purpose, in space the corner is not using;
|
|
961
|
+
* the chip cannot clip its own children while one is up.
|
|
962
|
+
*/
|
|
963
|
+
function HandoffBubble({ handoff }: { handoff: Handoff }) {
|
|
964
|
+
return (
|
|
965
|
+
<span className="pointer-events-none absolute bottom-[calc(100%+9px)] left-1/2 flex -translate-x-1/2 animate-in flex-col items-center whitespace-nowrap fade-in zoom-in-95 duration-200">
|
|
966
|
+
<span className="flex items-center gap-1.5 rounded-full bg-background px-2.5 py-1 text-[12.5px] leading-4 tracking-[-0.1px] shadow-lg shadow-black/10 ring-1 ring-border">
|
|
967
|
+
{handoff.from?.name === THURSDAY.name ? (
|
|
968
|
+
<ThursdayMark size={14} className="shrink-0 opacity-75" />
|
|
969
|
+
) : (
|
|
970
|
+
handoff.from && (
|
|
971
|
+
<BotMark
|
|
972
|
+
size={14}
|
|
973
|
+
seed={handoff.from.name}
|
|
974
|
+
vary={handoff.from.name}
|
|
975
|
+
color={handoff.from.icon?.color}
|
|
976
|
+
shape={handoff.from.icon?.shape}
|
|
977
|
+
outline={handoff.from.icon?.outline}
|
|
978
|
+
className="shrink-0 opacity-75"
|
|
979
|
+
/>
|
|
980
|
+
)
|
|
981
|
+
)}
|
|
982
|
+
{handoff.text}
|
|
983
|
+
</span>
|
|
984
|
+
{/* Two triangles: the ring's, then the fill's a pixel over it. */}
|
|
985
|
+
<span className="-mt-px size-0 border-x-[5px] border-t-[6px] border-x-transparent border-t-border" />
|
|
986
|
+
<span className="-mt-[6.5px] size-0 border-x-[5px] border-t-[6px] border-x-transparent border-t-background" />
|
|
866
987
|
</span>
|
|
867
988
|
);
|
|
868
989
|
}
|
|
@@ -1006,7 +1127,7 @@ const STATE_LOOK: Record<TaskViewStatus, string> = {
|
|
|
1006
1127
|
|
|
1007
1128
|
function State({ task }: { task: TaskView }) {
|
|
1008
1129
|
const look =
|
|
1009
|
-
task.status === "done" && task.
|
|
1130
|
+
task.status === "done" && task.seen
|
|
1010
1131
|
? "text-muted-foreground"
|
|
1011
1132
|
: STATE_LOOK[task.status];
|
|
1012
1133
|
|
|
@@ -1015,8 +1136,6 @@ function State({ task }: { task: TaskView }) {
|
|
|
1015
1136
|
<ShinyText
|
|
1016
1137
|
text="working"
|
|
1017
1138
|
speed={2.2}
|
|
1018
|
-
color="var(--muted-foreground)"
|
|
1019
|
-
shineColor="var(--foreground)"
|
|
1020
1139
|
className="shrink-0 font-mono text-[10px]"
|
|
1021
1140
|
/>
|
|
1022
1141
|
);
|
|
@@ -1035,9 +1154,10 @@ function State({ task }: { task: TaskView }) {
|
|
|
1035
1154
|
}
|
|
1036
1155
|
|
|
1037
1156
|
function Empty({ bots }: { bots?: Bot[] }) {
|
|
1157
|
+
const { crew, more } = crewOf(bots, []);
|
|
1038
1158
|
return (
|
|
1039
1159
|
<div className="flex flex-col items-center gap-3 px-6 pt-3 pb-4 text-center">
|
|
1040
|
-
<
|
|
1160
|
+
<Crew crew={crew} more={more} bubbles={EMPTY_BUBBLES} />
|
|
1041
1161
|
{bots?.length ? (
|
|
1042
1162
|
<p className="text-[12px] text-muted-foreground">
|
|
1043
1163
|
Nothing handed over yet — ask for something that takes a while.
|
|
@@ -1129,18 +1249,17 @@ function TaskList({
|
|
|
1129
1249
|
|
|
1130
1250
|
function TaskRow({ task, onPick }: { task: TaskView; onPick: () => void }) {
|
|
1131
1251
|
const attention = needsYou(task);
|
|
1132
|
-
// secondLine runs plainText over the whole
|
|
1252
|
+
// secondLine runs plainText over the whole answer. Every sync rebuilds each TaskView
|
|
1133
1253
|
// (task.store), so depend on the fields that change the line, not on `task`.
|
|
1134
1254
|
const last = task.lines.at(-1);
|
|
1135
1255
|
const line = useMemo(
|
|
1136
1256
|
() => secondLine(task),
|
|
1137
|
-
[task.status, task.outcome, task.
|
|
1257
|
+
[task.status, task.outcome, task.seen, task.ask, last?.id],
|
|
1138
1258
|
);
|
|
1139
1259
|
const [answer, answering] = useAnswerTask();
|
|
1140
1260
|
const [sending, setSending] = useState<string | null>(null);
|
|
1141
1261
|
// Options are answered inline, without opening the thread.
|
|
1142
1262
|
const options = task.status === "waiting" ? (task.ask?.options ?? []) : [];
|
|
1143
|
-
const budget = isBudgetAsk(task.ask);
|
|
1144
1263
|
|
|
1145
1264
|
return (
|
|
1146
1265
|
// The row is not itself a button: the option buttons cannot nest inside one.
|
|
@@ -1192,8 +1311,6 @@ function TaskRow({ task, onPick }: { task: TaskView; onPick: () => void }) {
|
|
|
1192
1311
|
<ShinyText
|
|
1193
1312
|
text={line.text}
|
|
1194
1313
|
speed={2.2}
|
|
1195
|
-
color="var(--muted-foreground)"
|
|
1196
|
-
shineColor="var(--foreground)"
|
|
1197
1314
|
className={cn(
|
|
1198
1315
|
"min-w-0 flex-1 truncate text-[12px] leading-4",
|
|
1199
1316
|
line.tone,
|
|
@@ -1227,21 +1344,12 @@ function TaskRow({ task, onPick }: { task: TaskView; onPick: () => void }) {
|
|
|
1227
1344
|
await answer(task, option);
|
|
1228
1345
|
setSending(null);
|
|
1229
1346
|
}}
|
|
1230
|
-
className=
|
|
1231
|
-
"h-7 gap-1.5 rounded-full bg-background px-3 text-[12px]",
|
|
1232
|
-
// A budget stop is not a question, so it does not take the waiting colour.
|
|
1233
|
-
!budget && "border-amber-500/35",
|
|
1234
|
-
)}
|
|
1347
|
+
className="h-7 gap-1.5 rounded-full border-amber-500/35 bg-background px-3 text-[12px]"
|
|
1235
1348
|
>
|
|
1236
|
-
{
|
|
1349
|
+
{option === TASK_CONTINUE && (
|
|
1237
1350
|
<ChevronsRight className="size-3.5 text-muted-foreground" />
|
|
1238
1351
|
)}
|
|
1239
1352
|
{option}
|
|
1240
|
-
{budget && option === TASK_CONTINUE && (
|
|
1241
|
-
<span className="font-mono text-[10px] text-muted-foreground">
|
|
1242
|
-
+{BOT_RUN.steps} steps
|
|
1243
|
-
</span>
|
|
1244
|
-
)}
|
|
1245
1353
|
</Button>
|
|
1246
1354
|
))}
|
|
1247
1355
|
</div>
|
|
@@ -1253,29 +1361,28 @@ function TaskRow({ task, onPick }: { task: TaskView; onPick: () => void }) {
|
|
|
1253
1361
|
/** Second row of a task line: the question, the outcome, or the bot's last step. */
|
|
1254
1362
|
function secondLine(task: TaskView): { text: string; tone: string } {
|
|
1255
1363
|
if (task.status === "waiting" && task.ask) {
|
|
1256
|
-
//
|
|
1257
|
-
|
|
1258
|
-
return {
|
|
1259
|
-
text: plainText(task.outcome ?? task.ask.question),
|
|
1260
|
-
tone: "text-muted-foreground",
|
|
1261
|
-
};
|
|
1262
|
-
}
|
|
1364
|
+
// A budget stop is not a question, but it waits on the user exactly as one
|
|
1365
|
+
// does, so it carries the waiting colour too; only the words differ.
|
|
1263
1366
|
return {
|
|
1264
|
-
text: task.ask
|
|
1367
|
+
text: isAppStop(task.ask)
|
|
1368
|
+
? plainText(task.outcome ?? task.ask.question)
|
|
1369
|
+
: task.ask.question,
|
|
1265
1370
|
tone: WAITING_INK,
|
|
1266
1371
|
};
|
|
1267
1372
|
}
|
|
1268
1373
|
// Reports are markdown; keep only the text.
|
|
1374
|
+
// An ending the user has opened steps back; red stays red, only quieter.
|
|
1375
|
+
const had = task.seen;
|
|
1269
1376
|
if (task.status === "failed") {
|
|
1270
1377
|
return {
|
|
1271
1378
|
text: plainText(task.outcome ?? "Failed"),
|
|
1272
|
-
tone: "text-destructive",
|
|
1379
|
+
tone: had ? "text-destructive/70" : "text-destructive",
|
|
1273
1380
|
};
|
|
1274
1381
|
}
|
|
1275
1382
|
if (task.status === "done") {
|
|
1276
1383
|
return {
|
|
1277
1384
|
text: plainText(task.outcome ?? "Done"),
|
|
1278
|
-
tone:
|
|
1385
|
+
tone: had ? "text-muted-foreground" : "text-foreground",
|
|
1279
1386
|
};
|
|
1280
1387
|
}
|
|
1281
1388
|
const last = lastSaid(task);
|
|
@@ -1339,20 +1446,37 @@ export function Conversation({
|
|
|
1339
1446
|
<Group key={item.key} group={item.group} task={task} />
|
|
1340
1447
|
),
|
|
1341
1448
|
)}
|
|
1342
|
-
{task.status === "working" && task
|
|
1343
|
-
<ShinyText
|
|
1344
|
-
text={`${task.bot.name} is taking it on…`}
|
|
1345
|
-
speed={2.2}
|
|
1346
|
-
color="var(--muted-foreground)"
|
|
1347
|
-
shineColor="var(--foreground)"
|
|
1348
|
-
className="block px-1 font-mono text-[10px]"
|
|
1349
|
-
/>
|
|
1350
|
-
)}
|
|
1449
|
+
{task.status === "working" && <NextMove task={task} />}
|
|
1351
1450
|
</div>
|
|
1352
1451
|
</FileViewer>
|
|
1353
1452
|
);
|
|
1354
1453
|
}
|
|
1355
1454
|
|
|
1455
|
+
/**
|
|
1456
|
+
* The foot of a running thread while nothing on it is moving: the bot is
|
|
1457
|
+
* writing its next step. A tool mid-call already shines on its own row, so this
|
|
1458
|
+
* steps aside for it.
|
|
1459
|
+
*/
|
|
1460
|
+
function NextMove({ task }: { task: TaskView }) {
|
|
1461
|
+
if (task.lines.some((line) => line.tool && line.tool.results === undefined)) {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
const last = task.lines.at(-1);
|
|
1465
|
+
// After a hand-off, in either direction, the move is the receiver's
|
|
1466
|
+
const who = last?.kind === "ask" && last.to ? last.to : last?.bot;
|
|
1467
|
+
return (
|
|
1468
|
+
<ShinyText
|
|
1469
|
+
text={
|
|
1470
|
+
who
|
|
1471
|
+
? `${who.name} is on the next step…`
|
|
1472
|
+
: `${task.bot.name} is taking it on…`
|
|
1473
|
+
}
|
|
1474
|
+
speed={2.2}
|
|
1475
|
+
className="block px-1 font-mono text-[10px]"
|
|
1476
|
+
/>
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1356
1480
|
/** The delegated request, folded to three lines (FoldedText). */
|
|
1357
1481
|
function Request({ task }: { task: TaskView }) {
|
|
1358
1482
|
return (
|
|
@@ -1369,7 +1493,11 @@ function Request({ task }: { task: TaskView }) {
|
|
|
1369
1493
|
);
|
|
1370
1494
|
}
|
|
1371
1495
|
|
|
1372
|
-
/**
|
|
1496
|
+
/**
|
|
1497
|
+
* Thursday's own face; she is not a bot and has no row to read one from. Also
|
|
1498
|
+
* who a hand-off came from when it came from the call — her mark is drawn by
|
|
1499
|
+
* her own domain (features/thursday), so this only has to name her.
|
|
1500
|
+
*/
|
|
1373
1501
|
const THURSDAY: BotRef = { name: "Thursday" };
|
|
1374
1502
|
|
|
1375
1503
|
/**
|
|
@@ -1445,7 +1573,7 @@ function Turn({
|
|
|
1445
1573
|
return (
|
|
1446
1574
|
<div className={cn("flex gap-2.5", side === "end" && "flex-row-reverse")}>
|
|
1447
1575
|
{mark}
|
|
1448
|
-
{/* One cap on the turn's column, not one per block inside it:
|
|
1576
|
+
{/* One cap on the turn's column, not one per block inside it: an answer and
|
|
1449
1577
|
a one-line remark from the same bot then end on the same edge. The
|
|
1450
1578
|
answering side hugs that edge, so the two sides face each other. */}
|
|
1451
1579
|
<div
|
|
@@ -1537,7 +1665,7 @@ function Line({ line, taskId }: { line: Chatter; taskId: string }) {
|
|
|
1537
1665
|
);
|
|
1538
1666
|
}
|
|
1539
1667
|
|
|
1540
|
-
// Passing remarks are muted; the
|
|
1668
|
+
// Passing remarks are muted; the answer is the one thing here at full weight.
|
|
1541
1669
|
if (!isOutcome(line)) {
|
|
1542
1670
|
return (
|
|
1543
1671
|
<p className="px-1 text-[12.5px] leading-relaxed break-keep text-muted-foreground">
|
|
@@ -1570,7 +1698,7 @@ function Line({ line, taskId }: { line: Chatter; taskId: string }) {
|
|
|
1570
1698
|
const failed = line.kind === "error";
|
|
1571
1699
|
|
|
1572
1700
|
return (
|
|
1573
|
-
// The
|
|
1701
|
+
// The answer is not a card. It sits in a thread that is already in a box,
|
|
1574
1702
|
// in a section that is another: a fourth border reads as a second chat
|
|
1575
1703
|
// window. What tells it from a passing remark is that it is the only prose
|
|
1576
1704
|
// here at foreground weight, plus the files it names — and its copy button
|
|
@@ -1591,7 +1719,7 @@ function Line({ line, taskId }: { line: Chatter; taskId: string }) {
|
|
|
1591
1719
|
{line.text}
|
|
1592
1720
|
</Markdown>
|
|
1593
1721
|
{/* The files it names, and the copy — one row, because the end of the
|
|
1594
|
-
|
|
1722
|
+
answer is where a reader is when they want either. */}
|
|
1595
1723
|
<div className="mt-2 flex items-center gap-2">
|
|
1596
1724
|
<PathChips text={line.text} className="min-w-0" />
|
|
1597
1725
|
{!failed && (
|
|
@@ -1604,7 +1732,7 @@ function Line({ line, taskId }: { line: Chatter; taskId: string }) {
|
|
|
1604
1732
|
);
|
|
1605
1733
|
}
|
|
1606
1734
|
|
|
1607
|
-
/** Copies the
|
|
1735
|
+
/** Copies the answer. The icon confirms; a toast only reports failure. */
|
|
1608
1736
|
function CopyReport({ text }: { text: string }) {
|
|
1609
1737
|
const [copied, setCopied] = useState(false);
|
|
1610
1738
|
const back = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -1619,7 +1747,7 @@ function CopyReport({ text }: { text: string }) {
|
|
|
1619
1747
|
return (
|
|
1620
1748
|
<button
|
|
1621
1749
|
type="button"
|
|
1622
|
-
aria-label="Copy the
|
|
1750
|
+
aria-label="Copy the answer"
|
|
1623
1751
|
onClick={async () => {
|
|
1624
1752
|
try {
|
|
1625
1753
|
await navigator.clipboard.writeText(text);
|
|
@@ -1629,7 +1757,7 @@ function CopyReport({ text }: { text: string }) {
|
|
|
1629
1757
|
} catch (cause) {
|
|
1630
1758
|
toast.add({
|
|
1631
1759
|
type: "error",
|
|
1632
|
-
title: "Could not copy the
|
|
1760
|
+
title: "Could not copy the answer",
|
|
1633
1761
|
description: errorToString(cause),
|
|
1634
1762
|
});
|
|
1635
1763
|
}
|