thursday-agent 0.1.2 → 0.2.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 +1 -0
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +6 -0
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- 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 +13 -13
- package/.next/server/app/_not-found.segments/_full.segment.rsc +13 -13
- 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 +1 -1
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/bot/notes/route/build-manifest.json +12 -0
- package/.next/server/app/api/bot/notes/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/bot/notes/route.js +14 -0
- package/.next/server/app/api/bot/notes/route.js.map +5 -0
- package/.next/server/app/api/bot/notes/route.js.nft.json +1 -0
- package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/route.js +4 -4
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/[id]/route.js +6 -6
- package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/route.js +6 -6
- package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +3 -3
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js +1 -1
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js +1 -1
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js +6 -6
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js +3 -3
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js +3 -3
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js +3 -3
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js +3 -3
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js +7 -7
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/tidy/route.js +10 -11
- package/.next/server/app/api/memory/tidy/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js +8 -8
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js +8 -8
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +11 -12
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js +1 -1
- 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 +3 -3
- 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.nft.json +1 -1
- package/.next/server/app/page/server-reference-manifest.json +63 -41
- package/.next/server/app/page.js +7 -7
- 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 +1 -0
- package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__0t_dqaj._.js → [root-of-the-server]__0-npkh_._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__005var5._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__1mzp_el._.js → [root-of-the-server]__04_8spk._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__0cro2dz._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__0emr6go._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +5 -5
- package/.next/server/chunks/{[root-of-the-server]__113fqg5._.js → [root-of-the-server]__0p32jfz._.js} +3 -3
- package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +5 -0
- package/.next/server/chunks/{[root-of-the-server]__0j_n5l3._.js → [root-of-the-server]__1692zrv._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__16thabu._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +10 -0
- package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +10 -0
- package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +1 -1
- package/.next/server/chunks/{[root-of-the-server]__1u_qvc7._.js → [root-of-the-server]__1fboj2j._.js} +2 -2
- package/.next/server/chunks/{[root-of-the-server]__1dow0bz._.js → [root-of-the-server]__1oli73c._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +4 -4
- package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +5 -0
- package/.next/server/chunks/_0-73of8._.js +55 -0
- package/.next/server/chunks/{_2050gwn._.js → _00_584r._.js} +66 -28
- package/.next/server/chunks/{_0y8is-s._.js → _03a0897._.js} +1 -1
- package/.next/server/chunks/_05h2cii._.js +1 -1
- package/.next/server/chunks/_06mxlgn._.js +1 -1
- package/.next/server/chunks/_0a351g7._.js +1 -1
- package/.next/server/chunks/{_0hin38_._.js → _0khihjk._.js} +5 -5
- package/.next/server/chunks/_0kjjmag._.js +1 -1
- package/.next/server/chunks/{_1t8fv7b._.js → _0n74zsf._.js} +5 -5
- package/.next/server/chunks/_0nn99o2._.js +3 -0
- package/.next/server/chunks/_0pppbb5._.js +10 -6
- package/.next/server/chunks/{_1icgbi8._.js → _0rnf-6n._.js} +5 -5
- package/.next/server/chunks/_0u10wmw._.js +55 -0
- package/.next/server/chunks/{_0kl37zl._.js → _0vnutv9._.js} +1 -1
- package/.next/server/chunks/_0wp4le3._.js +1 -1
- package/.next/server/chunks/_0y5fg-x._.js +1 -1
- package/.next/server/chunks/_106co2o._.js +3 -0
- package/.next/server/chunks/_12bbv5i._.js +10 -6
- package/.next/server/chunks/_15hq89o._.js +1 -1
- package/.next/server/chunks/_15q1pig._.js +1 -1
- package/.next/server/chunks/_18-1wcc._.js +55 -0
- package/.next/server/chunks/_19wg5yn._.js +138 -0
- package/.next/server/chunks/_1_5mnq4._.js +18 -0
- package/.next/server/chunks/_1bt95p9._.js +55 -0
- package/.next/server/chunks/_1cbk7ms._.js +9 -9
- package/.next/server/chunks/_1drgfx4._.js +18 -0
- package/.next/server/chunks/_1drv2et._.js +10 -6
- package/.next/server/chunks/{_10qam6o._.js → _1e48czp._.js} +5 -5
- package/.next/server/chunks/{_1skgy7b._.js → _1kg70wc._.js} +5 -5
- package/.next/server/chunks/_1php4eq._.js +1 -1
- package/.next/server/chunks/_1rysr-r._.js +10 -0
- package/.next/server/chunks/_1u3rwbr._.js +3 -0
- package/.next/server/chunks/_1x7p319._.js +1 -1
- package/.next/server/chunks/_1yuv4vp._.js +1 -1
- package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +3 -0
- package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
- package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0228f43._.js → node_modules_consola_dist_chunks_prompt_mjs_02s3hjt._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0ir-vo2._.js → node_modules_consola_dist_chunks_prompt_mjs_06067w6._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0otw8ef._.js → node_modules_consola_dist_chunks_prompt_mjs_096ais_._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tf290y._.js → node_modules_consola_dist_chunks_prompt_mjs_0a1rd09._.js} +1 -1
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0j61-ma._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0tvkfgh._.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_1tpvamf._.js +3 -0
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y9-wcr._.js +3 -0
- package/.next/server/chunks/ssr/[externals]__0dizgek._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0f6s48x._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0m1b5ky._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0dfwvr4._.js → [root-of-the-server]__0un-erv._.js} +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +5 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1qfqvmg._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.js +3 -0
- package/.next/server/chunks/ssr/_03webdr._.js +3 -0
- package/.next/server/chunks/ssr/{_0gd4vaa._.js → _040sk32._.js} +71 -29
- package/.next/server/chunks/ssr/_077-g6s._.js +1 -1
- package/.next/server/chunks/ssr/{_0lc_7c8._.js → _09ja_w1._.js} +2 -2
- package/.next/server/chunks/ssr/{_1dcxzqe._.js → _0f_b_9s._.js} +2 -2
- package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
- package/.next/server/chunks/ssr/_0lu1n_x._.js +3 -0
- package/.next/server/chunks/ssr/_0sf1isi._.js +1 -1
- package/.next/server/chunks/ssr/_0su6_08._.js +3 -0
- package/.next/server/chunks/ssr/{_0nso8tp._.js → _0w5bgak._.js} +2 -2
- package/.next/server/chunks/ssr/{_19_o9d-._.js → _0x_nu5e._.js} +2 -2
- package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
- package/.next/server/chunks/ssr/_13punpm._.js +1 -1
- package/.next/server/chunks/ssr/{_0ay6b01._.js → _1ciyoo3._.js} +2 -2
- package/.next/server/chunks/ssr/_1e-dgz0._.js +1 -1
- package/.next/server/chunks/ssr/_1fq7zgb._.js +1 -1
- package/.next/server/chunks/ssr/{_0cvjbuz._.js → _1g6l4uj._.js} +2 -2
- package/.next/server/chunks/ssr/{_0q_2f2x._.js → _1lip4th._.js} +2 -2
- package/.next/server/chunks/ssr/_1m3bsfr._.js +3 -0
- package/.next/server/chunks/ssr/_1mznrxk._.js +1 -1
- package/.next/server/chunks/ssr/_1ruzehw._.js +3 -0
- package/.next/server/chunks/ssr/_1vyd1ji._.js +3 -0
- package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +2 -2
- package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +1 -1
- 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/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
- 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_108wd2o._.js → node_modules_004qlr4._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ub40gk._.js → node_modules_01qnv-a._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1y8_3vg._.js → node_modules_04n7mjs._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_j7-g4._.js → node_modules_08d8bke._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0y2-av4._.js → node_modules_09xhur7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_043-236._.js → node_modules_0_n241r._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ls96je._.js → node_modules_0iz1rzd._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0xwx4u-._.js → node_modules_0kl7-tn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1jatz7w._.js → node_modules_0kxz4gt._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1t0y5jt._.js → node_modules_0lrjk5t._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_13f2md6._.js → node_modules_0m93zeg._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1nhw9qg._.js → node_modules_0n1kj4p._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1k29j1j._.js → node_modules_0ndvxub._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_16i4o_3._.js → node_modules_13r44jq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0fbw5-u._.js → node_modules_17lrt3y._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1d-is1o._.js → node_modules_18_dk7_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hn_sd5._.js → node_modules_19dp7ko._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_20ynonm._.js → node_modules_1dwguw_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00d6675._.js → node_modules_1hd-5tv._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ee8gav._.js → node_modules_1hn8z1f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0bue6gy._.js → node_modules_1n5ygqe._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_588_4._.js → node_modules_1q0p85z._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ipn1ho._.js → node_modules_1toafc-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_fvxnt._.js → node_modules_1xyd7ov._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1v0077l._.js → node_modules_1zu988w._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_06kvvuo._.js → node_modules_20yebtj._.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_0m9a4y0._.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_13w_wy9._.js} +1 -1
- package/.next/server/chunks/ssr/node_modules_consola_dist_chunks_prompt_mjs_05cwqv2._.js +47 -0
- 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 +64 -42
- package/.next/static/chunks/05b6nx_o3_f_n.js +1 -0
- package/.next/static/chunks/{3aquirz5h71da.js → 07kl352c-lvcz.js} +1 -1
- package/.next/static/chunks/0dmns-gi0raa4.js +1 -0
- package/.next/static/chunks/0oc4_xoia77dg.css +1 -0
- package/.next/static/chunks/0pheo8m3a-rzi.js +1 -0
- package/.next/static/chunks/{2ihkf6l2jowo9.js → 18vhba44l1fc9.js} +3 -3
- package/.next/static/chunks/19i8h2t-5evjx.js +1 -0
- package/.next/static/chunks/{1gy3bbv7usnjo.js → 1c5fcj473025q.js} +1 -1
- package/.next/static/chunks/1pi_hbmjw-f-z.js +1 -0
- package/.next/static/chunks/{0tydnn2kybibk.js → 1twt6k09vg4np.js} +1 -1
- package/.next/static/chunks/{21rk8w7bda-su.js → 28gotuisldmom.js} +1 -1
- package/.next/static/chunks/{3xxa2j56-f3nm.js → 2998p0y7k1c8z.js} +1 -1
- package/.next/static/chunks/2l233xfycjpd8.js +1 -0
- package/.next/static/chunks/2p5n7pa3c40t-.js +2 -0
- package/.next/static/chunks/2ru1q132af7h7.js +1 -0
- package/.next/static/chunks/{2fq_86jvq9ymq.js → 2t8wqycwg3y04.js} +2 -2
- package/.next/static/chunks/32z_ypgtchw48.js +1 -0
- package/.next/static/chunks/{247d-r95j8xs0.js → 33pyjb3xd1iez.js} +1 -1
- package/.next/static/chunks/3786h5vayt80-.js +28 -0
- package/.next/static/chunks/3_h9txt3tq8t8.js +28 -0
- package/.next/static/chunks/{1khakdu_iuedc.js → 3ard_noqapnw1.js} +1 -1
- package/.next/static/chunks/{1fr5m75pwtyic.js → 3omud5xunrk1f.js} +1 -1
- package/.next/static/chunks/42dnrkbph9e-1.js +1 -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 +44 -0
- package/app/api/llm-model/catalog/route.ts +12 -0
- package/app/api/llm-model/route.ts +21 -0
- package/app/api/query-key.ts +144 -0
- package/app/api/thursday/tool-call/route.ts +67 -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 +141 -0
- package/components/ui/text-type.tsx +213 -0
- package/components/ui/theme-sync.tsx +9 -0
- package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260909060813_optimal_sabretooth}/migration.sql +7 -0
- package/database/migrations/{20260908030742_rainy_marvel_zombies → 20260909060813_optimal_sabretooth}/snapshot.json +54 -1
- package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +1 -0
- package/database/migrations/20260909063821_bitter_tenebrous/snapshot.json +1285 -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/load-tools.ts +55 -5
- package/features/ai/prompts/bot.prompt.ts +121 -26
- package/features/ai/prompts/memory-tidy.prompt.ts +7 -1
- package/features/ai/prompts/prompt-helper.ts +31 -5
- package/features/ai/prompts/thursday.prompt.ts +2 -0
- package/features/ai/tools/bot.tool.ts +37 -18
- package/features/ai/tools/mcp.tool.ts +5 -5
- package/features/ai/tools/memory.tool.ts +17 -10
- package/features/ai/tools/studio.tool.ts +5 -5
- package/features/ai/tools/tool-name.ts +2 -0
- package/features/ai/tools/workspace.tool.ts +33 -5
- package/features/bot/bot.action.ts +31 -4
- package/features/bot/bot.notes.ts +120 -0
- package/features/bot/bot.query.ts +56 -1
- package/features/bot/bot.run.ts +43 -3
- package/features/bot/bot.runner.ts +94 -20
- package/features/bot/bot.schema.ts +17 -0
- package/features/bot/components/bot-room.tsx +405 -306
- package/features/bot/components/bot-setting.tsx +123 -20
- package/features/bot/task.query.ts +18 -5
- package/features/memory/components/memory-setting.tsx +11 -2
- package/features/memory/memory.query.ts +9 -2
- package/features/memory/memory.schema.ts +30 -0
- package/features/skills/components/skills-setting.tsx +9 -1
- package/features/workspace/workspace.ts +136 -3
- 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/realtime/realtime.driver.ts +34 -1
- package/lib/realtime/realtime.protocol.ts +6 -0
- package/lib/sandbox.ts +9 -1
- package/lib/tokens.ts +19 -0
- package/package.json +1 -1
- package/.next/server/chunks/[root-of-the-server]__02kt8yn._.js +0 -10
- package/.next/server/chunks/[root-of-the-server]__0h59di-._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0qjqtwo._.js +0 -5
- 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]__11w_7_3._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1_0q_9w._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1gmn1cs._.js +0 -10
- package/.next/server/chunks/_0fzqt2y._.js +0 -100
- package/.next/server/chunks/_0jgm3b8._.js +0 -55
- package/.next/server/chunks/_0ks6_sg._.js +0 -55
- package/.next/server/chunks/_0tzfesa._.js +0 -3
- package/.next/server/chunks/_0zmestz._.js +0 -55
- package/.next/server/chunks/_12nr4xc._.js +0 -3
- package/.next/server/chunks/_1hzrsvg._.js +0 -55
- package/.next/server/chunks/_1uj4_io._.js +0 -10
- package/.next/server/chunks/_1yldgo4._.js +0 -55
- package/.next/server/chunks/features_0scmr4i._.js +0 -3
- package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.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_1vwbnq_._.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]__185grso._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1minz8_._.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/_10uvw3l._.js +0 -3
- package/.next/server/chunks/ssr/_1jbv7m1._.js +0 -3
- 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/static/chunks/0yysv1gzin17t.js +0 -1
- package/.next/static/chunks/1keib_gt066gc.js +0 -1
- package/.next/static/chunks/28g42tw8tsvhd.js +0 -1
- package/.next/static/chunks/2cwjgkt_hoyhj.js +0 -22
- package/.next/static/chunks/2rbf1fpd8n80-.js +0 -22
- package/.next/static/chunks/2ukj8k69y89um.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/3y40riycdip-i.js +0 -1
- package/.next/static/chunks/45a6zbtkelph9.js +0 -1
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_buildManifest.js +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{X34BUd9bfdH27ripuwwBm → YR4NWcDDuzwLbTTU7U7Vr}/_ssgManifest.js +0 -0
|
@@ -34,6 +34,7 @@ import { BOT_RUN } from "@/config";
|
|
|
34
34
|
import { markReportedAction, startTaskAction } from "@/features/bot/bot.action";
|
|
35
35
|
import {
|
|
36
36
|
type Bot,
|
|
37
|
+
type BotIcon,
|
|
37
38
|
DEFAULT_BOT,
|
|
38
39
|
isBudgetAsk,
|
|
39
40
|
TASK_CONTINUE,
|
|
@@ -43,6 +44,7 @@ import { BotRoster } from "@/features/bot/components/bot-roster";
|
|
|
43
44
|
import { PathChips } from "@/features/bot/components/path-chips";
|
|
44
45
|
import { TaskReply, useAnswerTask } from "@/features/bot/components/task-reply";
|
|
45
46
|
import { openSettings } from "@/features/settings/settings.store";
|
|
47
|
+
import { ThursdayMark } from "@/features/thursday/components/thursday-mark";
|
|
46
48
|
import { FileViewer } from "@/features/workspace/components/file-view";
|
|
47
49
|
import { shortAgo, toDate } from "@/lib/date-like";
|
|
48
50
|
import { unwrapResult } from "@/lib/protocol/result";
|
|
@@ -78,10 +80,11 @@ import { BotTool } from "./bot-tool";
|
|
|
78
80
|
* state.
|
|
79
81
|
*
|
|
80
82
|
* 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
|
|
83
|
+
* on the chip itself, with its buttons; what a bot is doing right now sits beside its
|
|
84
|
+
* face, and what passed between two parties rides above one for a moment
|
|
85
|
+
* (useHandoffs). Opening the room is for reading a thread, never for answering — a
|
|
86
|
+
* chevron that reveals the rows would put a click in front of the one thing this
|
|
87
|
+
* corner exists to remove.
|
|
85
88
|
*
|
|
86
89
|
* memo: the parent re-renders per transcript chunk and this reads only its store.
|
|
87
90
|
*/
|
|
@@ -99,12 +102,13 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
99
102
|
? (newest.find((entry) => entry.id === picked) ?? null)
|
|
100
103
|
: null;
|
|
101
104
|
|
|
102
|
-
const [
|
|
105
|
+
const [bubbles, handoff] = useHandoffs();
|
|
106
|
+
const { crew, more } = useMemo(() => crewOf(bots, tasks), [bots, tasks]);
|
|
103
107
|
|
|
104
|
-
// What each task was, and
|
|
105
|
-
//
|
|
108
|
+
// What each task was, and which hand-offs had already landed, at the previous
|
|
109
|
+
// sync. Only what changed since then just happened.
|
|
106
110
|
const known = useRef<Map<string, TaskViewStatus> | null>(null);
|
|
107
|
-
const
|
|
111
|
+
const passed = useRef(new Set<string>());
|
|
108
112
|
|
|
109
113
|
useEffect(() => {
|
|
110
114
|
if (!botTasks.primed()) return;
|
|
@@ -112,46 +116,67 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
112
116
|
const was = known.current;
|
|
113
117
|
known.current = now;
|
|
114
118
|
|
|
115
|
-
const
|
|
116
|
-
for (const
|
|
117
|
-
|
|
119
|
+
const seen = new Set<string>();
|
|
120
|
+
for (const task of tasks) {
|
|
121
|
+
for (const line of task.lines) {
|
|
122
|
+
if (line.kind === "ask" && line.to) seen.add(line.id);
|
|
123
|
+
}
|
|
118
124
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
125
|
+
const had = passed.current;
|
|
126
|
+
passed.current = seen;
|
|
121
127
|
|
|
122
|
-
// The first list only seeds the two
|
|
128
|
+
// The first list only seeds the two: nothing on it just happened.
|
|
123
129
|
if (!was) return;
|
|
124
130
|
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
// Bot to bot. A giver handing work to several at once would put a bubble
|
|
132
|
+
// over each of them, 20px apart, so a whole round is drawn once — over the
|
|
133
|
+
// giver — and the faces that took the work are awake, which says who.
|
|
134
|
+
const rounds = new Map<
|
|
135
|
+
string,
|
|
136
|
+
{ from: BotRef; to: BotRef; text: string }[]
|
|
137
|
+
>();
|
|
138
|
+
for (const task of tasks) {
|
|
139
|
+
for (const line of task.lines) {
|
|
140
|
+
if (line.kind !== "ask" || !line.to || had.has(line.id)) continue;
|
|
141
|
+
const round = rounds.get(line.bot.name) ?? [];
|
|
142
|
+
round.push({ from: line.bot, to: line.to, text: line.text });
|
|
143
|
+
rounds.set(line.bot.name, round);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const [giver, round] of rounds) {
|
|
147
|
+
if (round.length === 1) {
|
|
148
|
+
handoff({
|
|
149
|
+
at: round[0].to.name,
|
|
150
|
+
from: round[0].from,
|
|
151
|
+
text: clipWord(round[0].text),
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
handoff({
|
|
156
|
+
at: giver,
|
|
157
|
+
from: round[0].from,
|
|
158
|
+
text: `sent ${round.length} parts out`,
|
|
137
159
|
});
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
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) {
|
|
163
|
+
if (was.get(task.id) === task.status || task.status !== "working")
|
|
146
164
|
continue;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
165
|
+
// A job that was never seen is one arriving; one that was is you having
|
|
166
|
+
// answered it. The mark says who gave it away — and a job typed into the
|
|
167
|
+
// compose box is Thursday's here too, because a task row does not record
|
|
168
|
+
// which of the two started it.
|
|
169
|
+
handoff(
|
|
170
|
+
was.has(task.id)
|
|
171
|
+
? { at: task.bot.name, from: null, text: "picked it back up" }
|
|
172
|
+
: {
|
|
173
|
+
at: task.bot.name,
|
|
174
|
+
from: THURSDAY,
|
|
175
|
+
text: `took on \u201c${clipWord(task.label)}\u201d`,
|
|
176
|
+
},
|
|
177
|
+
);
|
|
153
178
|
}
|
|
154
|
-
}, [tasks,
|
|
179
|
+
}, [tasks, handoff]);
|
|
155
180
|
|
|
156
181
|
// Opening a finished thread marks it reported. The key includes updatedAt: a
|
|
157
182
|
// follow-up request finishes again and that result is unread. markReported does not
|
|
@@ -181,13 +206,6 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
181
206
|
const busy = tasks.filter((entry) => entry.status === "working").length;
|
|
182
207
|
const attention = newest.filter(needsYou);
|
|
183
208
|
const pending = attention.length;
|
|
184
|
-
// Bots with a task waiting on the user; the chip shows them in front.
|
|
185
|
-
const waiting = attention
|
|
186
|
-
.map((entry) => entry.bot)
|
|
187
|
-
.filter(
|
|
188
|
-
(bot, at, all) =>
|
|
189
|
-
all.findIndex((other) => other.name === bot.name) === at,
|
|
190
|
-
);
|
|
191
209
|
|
|
192
210
|
const closeCompose = useCallback(() => setComposing(false), []);
|
|
193
211
|
|
|
@@ -241,13 +259,14 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
241
259
|
</div>
|
|
242
260
|
) : (
|
|
243
261
|
<Chip
|
|
262
|
+
crew={crew}
|
|
263
|
+
more={more}
|
|
264
|
+
bubbles={bubbles}
|
|
244
265
|
bots={bots}
|
|
245
266
|
rows={attention}
|
|
246
|
-
waiting={waiting}
|
|
247
267
|
count={tasks.length}
|
|
248
268
|
busy={busy}
|
|
249
269
|
pending={pending}
|
|
250
|
-
beat={beat}
|
|
251
270
|
composing={composing}
|
|
252
271
|
onCompose={() => setComposing(true)}
|
|
253
272
|
onCloseCompose={closeCompose}
|
|
@@ -266,103 +285,160 @@ export const BotRoom = memo(function BotRoom() {
|
|
|
266
285
|
);
|
|
267
286
|
});
|
|
268
287
|
|
|
269
|
-
/**
|
|
270
|
-
const
|
|
271
|
-
/**
|
|
272
|
-
const
|
|
288
|
+
/** Faces the row draws before the count takes over. */
|
|
289
|
+
const CREW_MAX = 10;
|
|
290
|
+
/** Stable identity for a row that can never have one up. */
|
|
291
|
+
const EMPTY_BUBBLES: Map<string, Handoff> = new Map();
|
|
292
|
+
/** How long a hand-off stays above a face, ms. */
|
|
293
|
+
const HANDOFF_MS = 3400;
|
|
294
|
+
/** A step is a glance, not a sentence. */
|
|
295
|
+
const WORD_MAX = 28;
|
|
296
|
+
|
|
297
|
+
/** One face in the row, and what it is doing. */
|
|
298
|
+
type CrewFace = {
|
|
299
|
+
name: string;
|
|
300
|
+
icon?: BotIcon | null;
|
|
301
|
+
/** Working or waiting: both are awake. */
|
|
302
|
+
awake: boolean;
|
|
303
|
+
/** Waiting on an answer; carries the dot. */
|
|
304
|
+
waiting: boolean;
|
|
305
|
+
/** The step it is on, in the model's own words. Null when it is not working. */
|
|
306
|
+
word: string | null;
|
|
307
|
+
/** A stand-in for an install with no bots; dimmed with the rest. */
|
|
308
|
+
standIn?: boolean;
|
|
309
|
+
};
|
|
273
310
|
|
|
274
311
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* while the sentence is up, so it shines; "finished" does not.
|
|
312
|
+
* Something passed between two parties. This is what a bubble is for, and the
|
|
313
|
+
* only thing: a step is the bot working alone and belongs beside its face.
|
|
278
314
|
*/
|
|
279
|
-
type
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
315
|
+
type Handoff = {
|
|
316
|
+
/** Whose face it points at. */
|
|
317
|
+
at: string;
|
|
318
|
+
/** Who gave it away. Null when it was the user, who has no mark. */
|
|
319
|
+
from: BotRef | null;
|
|
320
|
+
text: string;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const clipWord = (text: string) => {
|
|
324
|
+
const one = plainText(text).replace(/\s+/g, " ").trim();
|
|
325
|
+
return one.length > WORD_MAX ? `${one.slice(0, WORD_MAX - 1)}…` : one;
|
|
285
326
|
};
|
|
286
327
|
|
|
287
328
|
/**
|
|
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.
|
|
329
|
+
* Hand-offs currently up, one per face. A second one to the same face replaces
|
|
330
|
+
* it rather than queueing: the newest thing that landed there is the true one.
|
|
331
|
+
* Different faces hold their own at the same time — the fan-out that would make
|
|
332
|
+
* two of them collide is folded into one before it gets here (BotRoom).
|
|
295
333
|
*/
|
|
296
|
-
function
|
|
297
|
-
const [
|
|
298
|
-
const
|
|
299
|
-
const holding = useRef(false);
|
|
300
|
-
const timers = useRef<ReturnType<typeof setTimeout>[]>([]);
|
|
334
|
+
function useHandoffs(): [Map<string, Handoff>, (one: Handoff) => void] {
|
|
335
|
+
const [up, setUp] = useState<Map<string, Handoff>>(new Map());
|
|
336
|
+
const timers = useRef(new Map<string, ReturnType<typeof setTimeout>>());
|
|
301
337
|
|
|
302
338
|
useEffect(
|
|
303
339
|
() => () => {
|
|
304
|
-
for (const timer of timers.current) clearTimeout(timer);
|
|
340
|
+
for (const timer of timers.current.values()) clearTimeout(timer);
|
|
305
341
|
},
|
|
306
342
|
[],
|
|
307
343
|
);
|
|
308
344
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
holding.current = true;
|
|
316
|
-
setNow(next);
|
|
317
|
-
timers.current.push(
|
|
345
|
+
const show = useCallback((one: Handoff) => {
|
|
346
|
+
setUp((was) => new Map(was).set(one.at, one));
|
|
347
|
+
const held = timers.current.get(one.at);
|
|
348
|
+
if (held) clearTimeout(held);
|
|
349
|
+
timers.current.set(
|
|
350
|
+
one.at,
|
|
318
351
|
setTimeout(() => {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}, BEAT_MS),
|
|
352
|
+
timers.current.delete(one.at);
|
|
353
|
+
setUp((was) => {
|
|
354
|
+
const next = new Map(was);
|
|
355
|
+
next.delete(one.at);
|
|
356
|
+
return next;
|
|
357
|
+
});
|
|
358
|
+
}, HANDOFF_MS),
|
|
327
359
|
);
|
|
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
360
|
}, []);
|
|
336
361
|
|
|
337
|
-
return [
|
|
362
|
+
return [up, show];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** The step a bot is on, as the model labelled it. */
|
|
366
|
+
function wordOf(line: Chatter | null): string | null {
|
|
367
|
+
if (!line) return null;
|
|
368
|
+
if (line.kind === "tool" && line.tool) {
|
|
369
|
+
// The model's own label when it wrote one, else the raw call.
|
|
370
|
+
return clipWord(line.tool.note ?? `${line.tool.name} · ${line.tool.input}`);
|
|
371
|
+
}
|
|
372
|
+
return line.kind === "say" ? clipWord(line.text) : null;
|
|
338
373
|
}
|
|
339
374
|
|
|
340
375
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
376
|
+
* Who is in the row, in the order it is drawn.
|
|
377
|
+
*
|
|
378
|
+
* Anyone moving sorts to the front, so the count at the tail only ever hides
|
|
379
|
+
* idle bots — a bot with something to say always has a face to say it from,
|
|
380
|
+
* which is what lets a hand-off point at one. A bot that spoke inside somebody
|
|
381
|
+
* else's job is in the room too, whether or not it owns a task (latestPerBot).
|
|
343
382
|
*/
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
):
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
383
|
+
function crewOf(
|
|
384
|
+
bots: Bot[] | undefined,
|
|
385
|
+
tasks: TaskView[],
|
|
386
|
+
): { crew: CrewFace[]; more: number } {
|
|
387
|
+
const live = new Map<string, { waiting: boolean; word: string | null }>();
|
|
388
|
+
for (const entry of latestPerBot(tasks)) {
|
|
389
|
+
const { task, bot, line } = entry;
|
|
390
|
+
// Waiting outranks working: a bot that stopped to ask is not on a step.
|
|
391
|
+
if (task.status === "waiting" && task.bot.name === bot.name) {
|
|
392
|
+
live.set(bot.name, { waiting: true, word: null });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (task.status !== "working" || live.get(bot.name)?.waiting) continue;
|
|
396
|
+
live.set(bot.name, { waiting: false, word: wordOf(line) });
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const named = new Map<string, CrewFace>();
|
|
400
|
+
for (const bot of bots ?? []) {
|
|
401
|
+
named.set(bot.name, {
|
|
402
|
+
name: bot.name,
|
|
403
|
+
icon: bot.icon,
|
|
404
|
+
awake: live.has(bot.name),
|
|
405
|
+
waiting: live.get(bot.name)?.waiting ?? false,
|
|
406
|
+
word: live.get(bot.name)?.word ?? null,
|
|
407
|
+
});
|
|
354
408
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
409
|
+
// A bot that is working but not on the roster (the row-less default) still
|
|
410
|
+
// has a face: the row is who is here, not who is configured.
|
|
411
|
+
for (const [name, doing] of live) {
|
|
412
|
+
if (named.has(name)) continue;
|
|
413
|
+
named.set(name, {
|
|
414
|
+
name,
|
|
415
|
+
icon: null,
|
|
416
|
+
awake: true,
|
|
417
|
+
waiting: doing.waiting,
|
|
418
|
+
word: doing.word,
|
|
419
|
+
});
|
|
362
420
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
421
|
+
|
|
422
|
+
// A fresh install has one worker and one silhouette says "one bot", which is
|
|
423
|
+
// the wrong thing to say about a room. Stand-ins fill it out and are dimmed.
|
|
424
|
+
const roster = [...named.values()];
|
|
425
|
+
if (!roster.length) return { crew: GHOSTS.slice(0, FLOOR), more: 0 };
|
|
426
|
+
for (const ghost of GHOSTS) {
|
|
427
|
+
if (roster.length >= FLOOR) break;
|
|
428
|
+
if (!named.has(ghost.name)) roster.push(ghost);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const rank = (face: CrewFace) =>
|
|
432
|
+
face.waiting ? 0 : face.awake ? 1 : face.standIn ? 3 : 2;
|
|
433
|
+
const sorted = roster
|
|
434
|
+
.map((face, at) => ({ face, at }))
|
|
435
|
+
.sort((a, b) => rank(a.face) - rank(b.face) || a.at - b.at)
|
|
436
|
+
.map((one) => one.face);
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
crew: sorted.slice(0, CREW_MAX),
|
|
440
|
+
more: Math.max(0, sorted.length - CREW_MAX),
|
|
441
|
+
};
|
|
366
442
|
}
|
|
367
443
|
|
|
368
444
|
/** Blanks the question in the reply box when it already is the last thread line (budget stops). */
|
|
@@ -384,7 +460,13 @@ function askFor(task: TaskView): TaskView["ask"] {
|
|
|
384
460
|
*/
|
|
385
461
|
const needsYou = (task: TaskView) => task.status === "waiting";
|
|
386
462
|
|
|
387
|
-
/**
|
|
463
|
+
/**
|
|
464
|
+
* What the room itself is doing, and nothing else — the right side of the pill.
|
|
465
|
+
*
|
|
466
|
+
* Only `waiting on you` sweeps. A shine means something is happening right now,
|
|
467
|
+
* which is what the words beside the faces are for; the spinner already says a
|
|
468
|
+
* job is running, and two things sweeping in one pill read as a loading screen.
|
|
469
|
+
*/
|
|
388
470
|
function restingState({
|
|
389
471
|
count,
|
|
390
472
|
busy,
|
|
@@ -393,58 +475,66 @@ function restingState({
|
|
|
393
475
|
count: number;
|
|
394
476
|
busy: number;
|
|
395
477
|
pending: number;
|
|
396
|
-
}): { text: string; tone
|
|
478
|
+
}): { text: string; tone: string; shine: boolean } {
|
|
397
479
|
if (pending > 0)
|
|
398
480
|
return {
|
|
399
481
|
text: pending === 1 ? "waiting on you" : `${pending} waiting on you`,
|
|
400
482
|
tone: WAITING_INK,
|
|
401
|
-
shine:
|
|
483
|
+
shine: true,
|
|
402
484
|
};
|
|
403
485
|
if (busy > 0)
|
|
404
486
|
return {
|
|
405
487
|
text: busy === 1 ? "working" : `${busy} running`,
|
|
406
488
|
tone: "text-muted-foreground",
|
|
407
|
-
shine:
|
|
489
|
+
shine: false,
|
|
408
490
|
};
|
|
409
|
-
if (count > 0)
|
|
410
|
-
|
|
491
|
+
if (count > 0)
|
|
492
|
+
return { text: "all done", tone: "text-muted-foreground", shine: false };
|
|
493
|
+
return { text: "no jobs yet", tone: "text-muted-foreground", shine: false };
|
|
411
494
|
}
|
|
412
495
|
|
|
413
496
|
/**
|
|
414
497
|
* The room folded into one object in the corner.
|
|
415
498
|
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
499
|
+
* The row carries two facts and they never take each other's place. On the
|
|
500
|
+
* **left**, who is here and what each of them is doing: a face, and the shiny
|
|
501
|
+
* text beside it. On the **right**, what the room itself is doing, with its
|
|
502
|
+
* glyph 6px away — one fact, so nothing gets to come between them and nothing
|
|
503
|
+
* takes the glyph away. Anything that passes between two parties is neither, so
|
|
504
|
+
* it rides above a face in a bubble.
|
|
505
|
+
*
|
|
506
|
+
* While something is waiting on an answer its rows are grown in place above the
|
|
507
|
+
* row. Nothing here sits behind a disclosure: they appear because there is
|
|
508
|
+
* something to answer and leave when it is answered. The pill's own click opens
|
|
509
|
+
* the room, to read.
|
|
422
510
|
*
|
|
423
511
|
* The corner radius does not animate with the height: interpolating a pill radius
|
|
424
512
|
* down to a card radius while the box is also resizing warps the corners in flight.
|
|
425
513
|
*/
|
|
426
514
|
function Chip({
|
|
515
|
+
crew,
|
|
516
|
+
more,
|
|
517
|
+
bubbles,
|
|
427
518
|
bots,
|
|
428
519
|
rows,
|
|
429
|
-
waiting,
|
|
430
520
|
count,
|
|
431
521
|
busy,
|
|
432
522
|
pending,
|
|
433
|
-
beat,
|
|
434
523
|
composing,
|
|
435
524
|
onCompose,
|
|
436
525
|
onCloseCompose,
|
|
437
526
|
onPick,
|
|
438
527
|
onOpen,
|
|
439
528
|
}: {
|
|
529
|
+
crew: CrewFace[];
|
|
530
|
+
more: number;
|
|
531
|
+
bubbles: Map<string, Handoff>;
|
|
440
532
|
bots?: Bot[];
|
|
441
533
|
/** Everything waiting on an answer, newest first. */
|
|
442
534
|
rows: TaskView[];
|
|
443
|
-
waiting: BotRef[];
|
|
444
535
|
count: number;
|
|
445
536
|
busy: number;
|
|
446
537
|
pending: number;
|
|
447
|
-
beat: Beat | null;
|
|
448
538
|
composing: boolean;
|
|
449
539
|
onCompose: () => void;
|
|
450
540
|
onCloseCompose: () => void;
|
|
@@ -457,7 +547,9 @@ function Chip({
|
|
|
457
547
|
return (
|
|
458
548
|
<div
|
|
459
549
|
className={cn(
|
|
460
|
-
|
|
550
|
+
// Not `overflow-hidden`: a hand-off bubble stands above the row, outside
|
|
551
|
+
// this box. The growing part clips itself instead.
|
|
552
|
+
"pointer-events-auto w-fit max-w-full bg-background/78 ring-1 ring-border/50 backdrop-blur-md transition-shadow duration-300",
|
|
461
553
|
grown
|
|
462
554
|
? "min-w-96 rounded-3xl shadow-lg shadow-black/8"
|
|
463
555
|
: "rounded-full shadow-sm shadow-black/3",
|
|
@@ -466,7 +558,7 @@ function Chip({
|
|
|
466
558
|
{/* One box, two heights: the rows grow out of nothing rather than appearing. */}
|
|
467
559
|
<div
|
|
468
560
|
className={cn(
|
|
469
|
-
"grid transition-[grid-template-rows] duration-300 ease-out",
|
|
561
|
+
"grid overflow-hidden transition-[grid-template-rows] duration-300 ease-out",
|
|
470
562
|
grown ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
|
|
471
563
|
)}
|
|
472
564
|
>
|
|
@@ -501,79 +593,53 @@ function Chip({
|
|
|
501
593
|
{/* The pill row. Same geometry either way, so the card shrinks into it. */}
|
|
502
594
|
{/* px-3 is the chip's one rail: the faces here, the section line and every
|
|
503
595
|
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")}>
|
|
596
|
+
is why the well carries no box of its own. */}
|
|
597
|
+
<div className="flex items-center gap-2 px-3 py-1.5">
|
|
508
598
|
<button
|
|
509
599
|
type="button"
|
|
510
600
|
onClick={onOpen}
|
|
511
601
|
aria-label={count ? `Tasks (${count})` : "Bots"}
|
|
512
602
|
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
603
|
>
|
|
514
|
-
<
|
|
515
|
-
|
|
516
|
-
{/*
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
{beat?.what}
|
|
539
|
-
</span>
|
|
604
|
+
<Crew crew={crew} more={more} bubbles={bubbles} />
|
|
605
|
+
|
|
606
|
+
{/* The room's own state and its glyph: one group, and the only thing on
|
|
607
|
+
the right. `ml-auto` keeps it there when the crew says nothing. */}
|
|
608
|
+
<span className="ml-auto flex h-7 shrink-0 items-center gap-1.5">
|
|
609
|
+
{state.shine ? (
|
|
610
|
+
// Neither waiting nor running is a state at rest, but only one of
|
|
611
|
+
// them needs you. The colours are classes because they differ per
|
|
612
|
+
// theme, and ShinyText reads the variables they set.
|
|
613
|
+
<span className="block [--rest:var(--color-amber-700)] [--shine:var(--color-amber-400)] dark:[--rest:var(--color-amber-400)] dark:[--shine:var(--color-amber-100)]">
|
|
614
|
+
<ShinyText
|
|
615
|
+
text={state.text}
|
|
616
|
+
speed={2.6}
|
|
617
|
+
color="var(--rest)"
|
|
618
|
+
shineColor="var(--shine)"
|
|
619
|
+
className="truncate text-[14px] leading-5 tracking-[-0.15px]"
|
|
620
|
+
/>
|
|
621
|
+
</span>
|
|
622
|
+
) : (
|
|
623
|
+
<span
|
|
624
|
+
key={state.text}
|
|
625
|
+
className={cn(
|
|
626
|
+
"block animate-in truncate text-[14px] leading-5 tracking-[-0.15px] fade-in duration-300",
|
|
627
|
+
state.tone,
|
|
540
628
|
)}
|
|
629
|
+
>
|
|
630
|
+
{state.text}
|
|
541
631
|
</span>
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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>
|
|
632
|
+
)}
|
|
633
|
+
{/* Always 16px, empty or not: a glyph that comes and goes moves the
|
|
634
|
+
sentence's right end even when the sentence has not changed. */}
|
|
635
|
+
<span className="grid size-4 shrink-0 place-items-center">
|
|
636
|
+
{pending === 0 && busy > 0 && (
|
|
637
|
+
<Loader2 className="size-4 animate-spin text-muted-foreground/70" />
|
|
638
|
+
)}
|
|
639
|
+
{pending === 0 && busy === 0 && count > 0 && (
|
|
640
|
+
<Check className="size-4 text-muted-foreground/60" />
|
|
575
641
|
)}
|
|
576
|
-
</
|
|
642
|
+
</span>
|
|
577
643
|
</span>
|
|
578
644
|
</button>
|
|
579
645
|
|
|
@@ -582,36 +648,11 @@ function Chip({
|
|
|
582
648
|
<X className="size-3.5" />
|
|
583
649
|
</RoundButton>
|
|
584
650
|
)}
|
|
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
651
|
</div>
|
|
592
652
|
</div>
|
|
593
653
|
);
|
|
594
654
|
}
|
|
595
655
|
|
|
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
656
|
/** Starts a job. Lives in the header of whichever list is on screen. */
|
|
616
657
|
function ComposeButton({ onClick }: { onClick: () => void }) {
|
|
617
658
|
return (
|
|
@@ -775,94 +816,147 @@ function Compose({ bots, onDone }: { bots?: Bot[]; onDone: () => void }) {
|
|
|
775
816
|
}
|
|
776
817
|
|
|
777
818
|
/** 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
|
-
}
|
|
819
|
+
const GHOSTS: CrewFace[] = ["alto", "brio", "cinder", "delta"].map(
|
|
820
|
+
(seed, index) => ({
|
|
821
|
+
name: seed,
|
|
822
|
+
icon: {
|
|
823
|
+
color: MARK_PALETTE[Math.floor((index * MARK_PALETTE.length) / 4)],
|
|
824
|
+
shape: MARK_SHAPES[index % MARK_SHAPES.length],
|
|
825
|
+
},
|
|
826
|
+
awake: false,
|
|
827
|
+
waiting: false,
|
|
828
|
+
word: null,
|
|
829
|
+
standIn: true,
|
|
830
|
+
}),
|
|
831
|
+
);
|
|
785
832
|
|
|
786
833
|
/** A single face reads as one worker, not as a crew; below this the row is padded out. */
|
|
787
|
-
const
|
|
834
|
+
const FLOOR = 3;
|
|
788
835
|
|
|
789
|
-
/**
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
836
|
+
/**
|
|
837
|
+
* The crew, and what each of them is doing.
|
|
838
|
+
*
|
|
839
|
+
* Two things share this row and never take each other's place: a face says
|
|
840
|
+
* **who**, and the shiny text beside it says **what**, for as long as that bot
|
|
841
|
+
* is on that step. Both are derived — there is no queue and no cap, because
|
|
842
|
+
* every bot that is working is saying something true at the same time.
|
|
843
|
+
*
|
|
844
|
+
* A face that is moving is awake: lifted, swollen a little, and glowing in its
|
|
845
|
+
* own colour. That layer alone survives every collision — three bots at once
|
|
846
|
+
* are three awake faces, with no order to decide.
|
|
847
|
+
*/
|
|
848
|
+
function Crew({
|
|
849
|
+
crew,
|
|
850
|
+
more,
|
|
851
|
+
bubbles,
|
|
794
852
|
}: {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
|
|
853
|
+
crew: CrewFace[];
|
|
854
|
+
more: number;
|
|
855
|
+
/** Hand-offs currently up, keyed by the face they point at. */
|
|
856
|
+
bubbles: Map<string, Handoff>;
|
|
799
857
|
}) {
|
|
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
858
|
return (
|
|
836
|
-
<span
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
859
|
+
<span className="flex min-w-0 shrink items-center">
|
|
860
|
+
{crew.map((face, index) => {
|
|
861
|
+
const bubble = bubbles.get(face.name) ?? null;
|
|
862
|
+
return (
|
|
863
|
+
<Fragment key={face.name}>
|
|
864
|
+
<span
|
|
865
|
+
// Silhouettes overlapped, never ringed: a ring needs a circle and
|
|
866
|
+
// these are not circles. Earlier faces sit on top, so the dot on a
|
|
867
|
+
// waiting face is never covered by its neighbour.
|
|
868
|
+
className={cn(
|
|
869
|
+
"relative shrink-0 transition-[margin,transform] duration-500 ease-out",
|
|
870
|
+
// A word to the left has already broken the shingle.
|
|
871
|
+
index > 0 && !crew[index - 1].word && "-ml-2",
|
|
872
|
+
face.standIn && "opacity-35",
|
|
873
|
+
face.awake && "-translate-y-0.5 scale-110",
|
|
874
|
+
)}
|
|
875
|
+
style={{
|
|
876
|
+
zIndex: crew.length - index,
|
|
877
|
+
color: face.icon?.color ?? undefined,
|
|
878
|
+
// The glow is the bot's own colour, so who is moving reads before
|
|
879
|
+
// any word does. currentColor keeps it right when nobody chose one.
|
|
880
|
+
filter: face.awake
|
|
881
|
+
? "drop-shadow(0 0 5px color-mix(in srgb, currentColor 55%, transparent))"
|
|
882
|
+
: undefined,
|
|
883
|
+
}}
|
|
884
|
+
>
|
|
885
|
+
<BotMark
|
|
886
|
+
size={28}
|
|
887
|
+
seed={face.name}
|
|
888
|
+
vary={face.name}
|
|
889
|
+
color={face.icon?.color}
|
|
890
|
+
shape={face.icon?.shape}
|
|
891
|
+
outline={face.icon?.outline}
|
|
892
|
+
notify={face.waiting}
|
|
893
|
+
/>
|
|
894
|
+
{bubble && <HandoffBubble handoff={bubble} />}
|
|
895
|
+
</span>
|
|
896
|
+
{face.word && (
|
|
897
|
+
// No box: the shine is what says this is happening right now, so a
|
|
898
|
+
// capsule around it was drawing a second time what the sweep says.
|
|
899
|
+
<span className="mx-2.5 min-w-0 shrink truncate">
|
|
900
|
+
<ShinyText
|
|
901
|
+
text={face.word}
|
|
902
|
+
speed={2.6}
|
|
903
|
+
color="var(--muted-foreground)"
|
|
904
|
+
shineColor="var(--foreground)"
|
|
905
|
+
className="truncate text-[13px] leading-5 tracking-[-0.1px]"
|
|
906
|
+
/>
|
|
907
|
+
</span>
|
|
908
|
+
)}
|
|
909
|
+
</Fragment>
|
|
910
|
+
);
|
|
911
|
+
})}
|
|
912
|
+
{more > 0 && (
|
|
913
|
+
// Past CREW_MAX the row stops growing. Only idle bots are ever behind it:
|
|
914
|
+
// anyone moving sorted to the front.
|
|
843
915
|
<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
916
|
className={cn(
|
|
848
|
-
"
|
|
849
|
-
|
|
850
|
-
face.standIn && "opacity-35",
|
|
851
|
-
only && face.key !== only && "-ml-[28px] scale-50 opacity-0",
|
|
917
|
+
"grid size-7 shrink-0 place-items-center rounded-[9px] bg-muted font-mono text-[10px] text-muted-foreground ring-1 ring-border/50",
|
|
918
|
+
!crew.at(-1)?.word && "-ml-2",
|
|
852
919
|
)}
|
|
853
|
-
|
|
920
|
+
title={`${more} more`}
|
|
854
921
|
>
|
|
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
|
-
/>
|
|
922
|
+
+{more}
|
|
864
923
|
</span>
|
|
865
|
-
)
|
|
924
|
+
)}
|
|
925
|
+
</span>
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* A hand-off, over the face it landed on. The mark at its head is whoever gave
|
|
931
|
+
* it away — Thursday, another bot, or nobody at all when it was you.
|
|
932
|
+
*
|
|
933
|
+
* It sits outside the pill's box on purpose, in space the corner is not using;
|
|
934
|
+
* the chip cannot clip its own children while one is up.
|
|
935
|
+
*/
|
|
936
|
+
function HandoffBubble({ handoff }: { handoff: Handoff }) {
|
|
937
|
+
return (
|
|
938
|
+
<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">
|
|
939
|
+
<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">
|
|
940
|
+
{handoff.from?.name === THURSDAY.name ? (
|
|
941
|
+
<ThursdayMark size={14} className="shrink-0 opacity-75" />
|
|
942
|
+
) : (
|
|
943
|
+
handoff.from && (
|
|
944
|
+
<BotMark
|
|
945
|
+
size={14}
|
|
946
|
+
seed={handoff.from.name}
|
|
947
|
+
vary={handoff.from.name}
|
|
948
|
+
color={handoff.from.icon?.color}
|
|
949
|
+
shape={handoff.from.icon?.shape}
|
|
950
|
+
outline={handoff.from.icon?.outline}
|
|
951
|
+
className="shrink-0 opacity-75"
|
|
952
|
+
/>
|
|
953
|
+
)
|
|
954
|
+
)}
|
|
955
|
+
{handoff.text}
|
|
956
|
+
</span>
|
|
957
|
+
{/* Two triangles: the ring's, then the fill's a pixel over it. */}
|
|
958
|
+
<span className="-mt-px size-0 border-x-[5px] border-t-[6px] border-x-transparent border-t-border" />
|
|
959
|
+
<span className="-mt-[6.5px] size-0 border-x-[5px] border-t-[6px] border-x-transparent border-t-background" />
|
|
866
960
|
</span>
|
|
867
961
|
);
|
|
868
962
|
}
|
|
@@ -1035,9 +1129,10 @@ function State({ task }: { task: TaskView }) {
|
|
|
1035
1129
|
}
|
|
1036
1130
|
|
|
1037
1131
|
function Empty({ bots }: { bots?: Bot[] }) {
|
|
1132
|
+
const { crew, more } = crewOf(bots, []);
|
|
1038
1133
|
return (
|
|
1039
1134
|
<div className="flex flex-col items-center gap-3 px-6 pt-3 pb-4 text-center">
|
|
1040
|
-
<
|
|
1135
|
+
<Crew crew={crew} more={more} bubbles={EMPTY_BUBBLES} />
|
|
1041
1136
|
{bots?.length ? (
|
|
1042
1137
|
<p className="text-[12px] text-muted-foreground">
|
|
1043
1138
|
Nothing handed over yet — ask for something that takes a while.
|
|
@@ -1369,7 +1464,11 @@ function Request({ task }: { task: TaskView }) {
|
|
|
1369
1464
|
);
|
|
1370
1465
|
}
|
|
1371
1466
|
|
|
1372
|
-
/**
|
|
1467
|
+
/**
|
|
1468
|
+
* Thursday's own face; she is not a bot and has no row to read one from. Also
|
|
1469
|
+
* who a hand-off came from when it came from the call — her mark is drawn by
|
|
1470
|
+
* her own domain (features/thursday), so this only has to name her.
|
|
1471
|
+
*/
|
|
1373
1472
|
const THURSDAY: BotRef = { name: "Thursday" };
|
|
1374
1473
|
|
|
1375
1474
|
/**
|