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
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Check,
|
|
5
|
+
ChevronRight,
|
|
6
|
+
CircleAlert,
|
|
7
|
+
FolderOpen,
|
|
8
|
+
History,
|
|
9
|
+
Plus,
|
|
10
|
+
Trash2,
|
|
11
|
+
Wrench,
|
|
12
|
+
X,
|
|
13
|
+
} from "lucide-react";
|
|
4
14
|
import { type ReactNode, useState } from "react";
|
|
5
15
|
import { queryKey } from "@/app/api/query-key";
|
|
6
16
|
import { Button } from "@/components/ui/button";
|
|
@@ -13,43 +23,80 @@ import {
|
|
|
13
23
|
CommandList,
|
|
14
24
|
} from "@/components/ui/command";
|
|
15
25
|
import { Input } from "@/components/ui/input";
|
|
26
|
+
import {
|
|
27
|
+
InputGroup,
|
|
28
|
+
InputGroupAddon,
|
|
29
|
+
InputGroupInput,
|
|
30
|
+
InputGroupText,
|
|
31
|
+
} from "@/components/ui/input-group";
|
|
16
32
|
import { notify } from "@/components/ui/notify";
|
|
17
|
-
import ShinyText from "@/components/ui/shiny-text";
|
|
18
|
-
import {
|
|
33
|
+
import { ShinyText } from "@/components/ui/shiny-text";
|
|
34
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
35
|
+
import { Switch } from "@/components/ui/switch";
|
|
19
36
|
import { Textarea } from "@/components/ui/textarea";
|
|
37
|
+
import { APP_NAME, BOT_RUN, PROMPT_CROWDED } from "@/config";
|
|
20
38
|
import { ModelPicker } from "@/features/ai/components/model-picker";
|
|
21
|
-
import
|
|
39
|
+
import {
|
|
40
|
+
compactAtFor,
|
|
41
|
+
contextWindowOf,
|
|
42
|
+
type GatewayModel,
|
|
43
|
+
type TextModelProviderId,
|
|
44
|
+
} from "@/features/ai/model.schema";
|
|
22
45
|
import {
|
|
23
46
|
createBotAction,
|
|
24
47
|
createSeedBotsAction,
|
|
25
48
|
deleteBotAction,
|
|
49
|
+
setBotMemoryOnAction,
|
|
26
50
|
updateBotAction,
|
|
27
51
|
} from "@/features/bot/bot.action";
|
|
28
52
|
import {
|
|
29
53
|
type Bot,
|
|
30
54
|
type BotForm,
|
|
31
55
|
type BotIcon,
|
|
32
|
-
|
|
56
|
+
type BotMemory,
|
|
57
|
+
type BotMemoryFile,
|
|
33
58
|
MAX_PINNED_TOOLS,
|
|
34
59
|
randomBotIcon,
|
|
35
60
|
type Task,
|
|
36
61
|
} from "@/features/bot/bot.schema";
|
|
37
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
BOT_SEEDS,
|
|
64
|
+
type BotSeed,
|
|
65
|
+
rollSeedColors,
|
|
66
|
+
} from "@/features/bot/bot.seed";
|
|
38
67
|
import { BotMark } from "@/features/bot/components/bot-mark";
|
|
68
|
+
import { MarkPalette } from "@/features/bot/components/mark-palette";
|
|
69
|
+
import {
|
|
70
|
+
type ConfigStatus,
|
|
71
|
+
isConfigSet,
|
|
72
|
+
MEDIA_MODEL_KEYS,
|
|
73
|
+
mediaModelWords,
|
|
74
|
+
} from "@/features/config/config.const";
|
|
39
75
|
import {
|
|
76
|
+
SettingDialogContent,
|
|
40
77
|
SettingError,
|
|
78
|
+
SettingNote,
|
|
41
79
|
SettingPanes,
|
|
42
80
|
SettingPanesSkeleton,
|
|
43
81
|
SettingRailNote,
|
|
44
82
|
} from "@/features/settings/components/setting-ui";
|
|
45
83
|
import { openSettings } from "@/features/settings/settings.store";
|
|
84
|
+
import {
|
|
85
|
+
FileBody,
|
|
86
|
+
useFileText,
|
|
87
|
+
} from "@/features/workspace/components/file-view";
|
|
88
|
+
import { viewKindOf } from "@/features/workspace/file-kind";
|
|
89
|
+
import {
|
|
90
|
+
deleteWorkspaceFileAction,
|
|
91
|
+
openFileAction,
|
|
92
|
+
} from "@/features/workspace/workspace.action";
|
|
46
93
|
import { useObjectState } from "@/hooks/use-object-state";
|
|
47
94
|
import { type DateLike, shortAgo, whenOf } from "@/lib/date-like";
|
|
48
95
|
import { COMMON_VALIDATE } from "@/lib/limits";
|
|
49
96
|
import { useServerAction } from "@/lib/protocol/use-server-action";
|
|
50
97
|
import { revalidate, useServerRoute } from "@/lib/protocol/use-server-route";
|
|
51
98
|
import { cn, formatCount, WAITING_INK } from "@/lib/utils";
|
|
52
|
-
import {
|
|
99
|
+
import { MARK_SHAPES, MARK_SYSTEM } from "../mark.const";
|
|
53
100
|
|
|
54
101
|
/**
|
|
55
102
|
* The bots Thursday hands background work to. Roster on the left, the picked bot's
|
|
@@ -64,35 +111,56 @@ export function BotSetting() {
|
|
|
64
111
|
/** Picked roster entry: a bot name, NEW, or null for the first bot. */
|
|
65
112
|
const [picked, setPicked] = useState<string | null>(null);
|
|
66
113
|
|
|
114
|
+
/**
|
|
115
|
+
* One roll for this screen, in BOT_SEEDS order. The faces on the invite, in the
|
|
116
|
+
* picker and on the bot that gets created are then the same colour, because
|
|
117
|
+
* `createSeedBotsAction` takes it (bot.seed rollSeedColors rolls per install so
|
|
118
|
+
* no two rosters look alike; a seed itself carries none).
|
|
119
|
+
*/
|
|
120
|
+
const [inks] = useState(rollSeedColors);
|
|
121
|
+
|
|
67
122
|
if (isLoading) return <SettingPanesSkeleton />;
|
|
68
123
|
if (error) return <SettingError message={error.message} />;
|
|
69
124
|
|
|
70
125
|
const drafting = picked === NEW;
|
|
126
|
+
const on = bots.filter((bot) => !bot.disabled).length;
|
|
71
127
|
const have = new Set(bots.map((bot) => bot.name));
|
|
72
128
|
const missing = BOT_SEEDS.filter((seed) => !have.has(seed.name));
|
|
73
|
-
const current =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
: (bots.find((bot) => bot.name === picked) ?? bots[0] ?? null);
|
|
129
|
+
const current = drafting
|
|
130
|
+
? null
|
|
131
|
+
: (bots.find((bot) => bot.name === picked) ?? bots[0] ?? null);
|
|
77
132
|
|
|
78
133
|
return (
|
|
79
134
|
<SettingPanes
|
|
80
|
-
footer={
|
|
135
|
+
footer={bots.length === 0 ? null : <BotRail bot={current} />}
|
|
81
136
|
left={
|
|
82
137
|
<div className="flex flex-col py-2">
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
138
|
+
{/* The two ways to get a bot, on one line and apart from the roster
|
|
139
|
+
under it: a seed is not a bot you have */}
|
|
140
|
+
<div className="mx-2 mb-1 flex items-center gap-1">
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
onClick={() => setPicked(NEW)}
|
|
144
|
+
className={cn(
|
|
145
|
+
"flex flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-left text-[13px] outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-inset",
|
|
146
|
+
drafting
|
|
147
|
+
? "bg-secondary text-foreground"
|
|
148
|
+
: "text-muted-foreground hover:bg-muted/60 hover:text-foreground",
|
|
149
|
+
)}
|
|
150
|
+
>
|
|
151
|
+
<Plus className="size-3.5 shrink-0" />
|
|
152
|
+
New bot
|
|
153
|
+
</button>
|
|
154
|
+
|
|
155
|
+
{bots.length > 0 && missing.length > 0 && (
|
|
156
|
+
<SeedInvite
|
|
157
|
+
missing={missing}
|
|
158
|
+
inks={inks}
|
|
159
|
+
have={have}
|
|
160
|
+
onDone={(name) => setPicked(name)}
|
|
161
|
+
/>
|
|
91
162
|
)}
|
|
92
|
-
>
|
|
93
|
-
<Plus className="size-3.5 shrink-0" />
|
|
94
|
-
New bot
|
|
95
|
-
</button>
|
|
163
|
+
</div>
|
|
96
164
|
|
|
97
165
|
{bots.map((bot) => (
|
|
98
166
|
<RosterRow
|
|
@@ -104,19 +172,17 @@ export function BotSetting() {
|
|
|
104
172
|
/>
|
|
105
173
|
))}
|
|
106
174
|
|
|
107
|
-
{
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
175
|
+
{/* Only bots that are on: a switched-off one is in no prompt to crowd. */}
|
|
176
|
+
{on > PROMPT_CROWDED.bots && (
|
|
177
|
+
<SettingNote className="mx-3 mt-2 leading-relaxed">
|
|
178
|
+
{on} bots are on. Each is a line in every prompt, and one more for
|
|
179
|
+
Thursday to choose between.
|
|
180
|
+
</SettingNote>
|
|
113
181
|
)}
|
|
114
182
|
</div>
|
|
115
183
|
}
|
|
116
184
|
right={
|
|
117
|
-
|
|
118
|
-
<SeedPackage have={have} onDone={(name) => setPicked(name)} />
|
|
119
|
-
) : drafting ? (
|
|
185
|
+
drafting ? (
|
|
120
186
|
<BotPage
|
|
121
187
|
key="new"
|
|
122
188
|
jobs={[]}
|
|
@@ -133,6 +199,14 @@ export function BotSetting() {
|
|
|
133
199
|
.slice(0, RECENT)}
|
|
134
200
|
onDone={() => setPicked(null)}
|
|
135
201
|
/>
|
|
202
|
+
) : missing.length > 0 ? (
|
|
203
|
+
// Nothing on the roster: the offer is the only thing this pane could
|
|
204
|
+
// hold, so it fills it rather than sitting in a dialog nobody opened
|
|
205
|
+
<SeedPackage
|
|
206
|
+
have={have}
|
|
207
|
+
inks={inks}
|
|
208
|
+
onDone={(name) => setPicked(name)}
|
|
209
|
+
/>
|
|
136
210
|
) : (
|
|
137
211
|
<div className="space-y-4 p-8">
|
|
138
212
|
<p className="text-sm leading-relaxed text-muted-foreground">
|
|
@@ -153,8 +227,6 @@ export function BotSetting() {
|
|
|
153
227
|
|
|
154
228
|
/** Roster selections that are not a bot; values no bot name can be. */
|
|
155
229
|
const NEW = " new";
|
|
156
|
-
const SEEDS = " seeds";
|
|
157
|
-
|
|
158
230
|
/** Jobs shown under Recent on a bot's page. */
|
|
159
231
|
const RECENT = 3;
|
|
160
232
|
|
|
@@ -171,7 +243,7 @@ function RosterRow({
|
|
|
171
243
|
active: boolean;
|
|
172
244
|
onPick: () => void;
|
|
173
245
|
}) {
|
|
174
|
-
const line = liveLine(job, bot.lastJobAt);
|
|
246
|
+
const line = liveLine(job, bot.lastJobAt, bot.disabled);
|
|
175
247
|
return (
|
|
176
248
|
<button
|
|
177
249
|
type="button"
|
|
@@ -186,9 +258,14 @@ function RosterRow({
|
|
|
186
258
|
{...markProps(bot.name, bot.icon)}
|
|
187
259
|
state={job?.status === "running" ? "thinking" : "idle"}
|
|
188
260
|
notify={job?.status === "waiting"}
|
|
189
|
-
className="shrink-0"
|
|
261
|
+
className={cn("shrink-0", bot.disabled && "opacity-45")}
|
|
190
262
|
/>
|
|
191
|
-
<span
|
|
263
|
+
<span
|
|
264
|
+
className={cn(
|
|
265
|
+
"min-w-0 flex-1 space-y-0.5",
|
|
266
|
+
bot.disabled && "opacity-45",
|
|
267
|
+
)}
|
|
268
|
+
>
|
|
192
269
|
<span
|
|
193
270
|
className={cn(
|
|
194
271
|
"block truncate text-[13px]",
|
|
@@ -198,23 +275,11 @@ function RosterRow({
|
|
|
198
275
|
{bot.name}
|
|
199
276
|
</span>
|
|
200
277
|
{line.shine ? (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
? "[--rest:var(--color-amber-700)] [--shine:var(--color-amber-400)] dark:[--rest:var(--color-amber-400)] dark:[--shine:var(--color-amber-100)]"
|
|
207
|
-
: "[--rest:var(--muted-foreground)] [--shine:var(--foreground)]",
|
|
208
|
-
)}
|
|
209
|
-
>
|
|
210
|
-
<ShinyText
|
|
211
|
-
text={line.text}
|
|
212
|
-
speed={2.4}
|
|
213
|
-
color="var(--rest)"
|
|
214
|
-
shineColor="var(--shine)"
|
|
215
|
-
className="truncate font-mono text-[11px] leading-4"
|
|
216
|
-
/>
|
|
217
|
-
</span>
|
|
278
|
+
<ShinyText
|
|
279
|
+
text={line.text}
|
|
280
|
+
tone={line.amber ? "waiting" : "muted"}
|
|
281
|
+
className="block truncate font-mono text-[11px] leading-4"
|
|
282
|
+
/>
|
|
218
283
|
) : (
|
|
219
284
|
<span className="block truncate font-mono text-[11px] leading-4 text-muted-foreground">
|
|
220
285
|
{line.text}
|
|
@@ -225,27 +290,35 @@ function RosterRow({
|
|
|
225
290
|
);
|
|
226
291
|
}
|
|
227
292
|
|
|
228
|
-
/**
|
|
293
|
+
/**
|
|
294
|
+
* What the bot is doing, most urgent first. Every wait is amber: the job moves
|
|
295
|
+
* again only when the user answers, whatever stopped it. Which kind of stop it
|
|
296
|
+
* was is the words' job, not the colour's.
|
|
297
|
+
*/
|
|
229
298
|
function liveLine(
|
|
230
299
|
job: Task | null,
|
|
231
300
|
lastJobAt: Bot["lastJobAt"],
|
|
301
|
+
disabled: boolean,
|
|
232
302
|
): {
|
|
233
303
|
text: string;
|
|
234
304
|
shine?: boolean;
|
|
235
305
|
amber?: boolean;
|
|
236
306
|
} {
|
|
307
|
+
if (job?.status === "waiting") {
|
|
308
|
+
return { text: "waiting on you", shine: true, amber: true };
|
|
309
|
+
}
|
|
310
|
+
if (job?.status === "running") {
|
|
311
|
+
return { text: `working · ${job.label}`, shine: true };
|
|
312
|
+
}
|
|
313
|
+
// Switched off, and no colour: off is the user's own choice, not something
|
|
314
|
+
// waiting on them, so the word carries it. It stands where the idle line
|
|
315
|
+
// would — the two cases above are a job it already had, which off never
|
|
316
|
+
// stopped, and hiding an ask behind "off" is how one goes unanswered.
|
|
317
|
+
if (disabled) return { text: "off" };
|
|
237
318
|
if (!job) {
|
|
238
319
|
if (!lastJobAt) return { text: "idle · no jobs yet" };
|
|
239
320
|
return { text: `idle · last job ${sinceWord(lastJobAt)}` };
|
|
240
321
|
}
|
|
241
|
-
if (job.status === "waiting") {
|
|
242
|
-
return isBudgetAsk(job.ask)
|
|
243
|
-
? { text: "out of steps" }
|
|
244
|
-
: { text: "waiting on you", shine: true, amber: true };
|
|
245
|
-
}
|
|
246
|
-
if (job.status === "running") {
|
|
247
|
-
return { text: `working · ${job.label}`, shine: true };
|
|
248
|
-
}
|
|
249
322
|
return { text: `idle · last job ${sinceWord(job.updatedAt)}` };
|
|
250
323
|
}
|
|
251
324
|
|
|
@@ -254,86 +327,205 @@ function sinceWord(at: DateLike): string {
|
|
|
254
327
|
const ago = shortAgo(at);
|
|
255
328
|
return ago === "now" ? "just now" : `${ago} ago`;
|
|
256
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* What a seed still needs before it can work, as one line, or null when it needs
|
|
332
|
+
* nothing. A missing studio model is not a fallback — the tool is absent — so the
|
|
333
|
+
* row says so before the bot finds out mid-job. Ticking is never blocked: the list
|
|
334
|
+
* states the cost, it does not cap it.
|
|
335
|
+
*/
|
|
336
|
+
function unmetLine(
|
|
337
|
+
seed: BotSeed,
|
|
338
|
+
isSet: (key: string) => boolean,
|
|
339
|
+
): string | null {
|
|
340
|
+
const unmet = (seed.requires ?? []).filter(
|
|
341
|
+
(kind) => !isSet(MEDIA_MODEL_KEYS[kind]),
|
|
342
|
+
);
|
|
343
|
+
if (!unmet.length) return null;
|
|
344
|
+
return `needs ${unmet.map(mediaModelWords).join(" and ")}`;
|
|
345
|
+
}
|
|
257
346
|
|
|
258
347
|
/**
|
|
259
|
-
* The
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
348
|
+
* The picker's state, shared by the two places the offer appears: the pane an
|
|
349
|
+
* empty roster opens on, and the dialog the roster's invite opens. Seeds are
|
|
350
|
+
* ticked already, because the set is the recommendation and unticking is the
|
|
351
|
+
* decision; one already on the roster is locked on and says so, since `createBot`
|
|
352
|
+
* would only answer "already exists", which is an error about something this
|
|
353
|
+
* screen already knows. One call creates all of them.
|
|
265
354
|
*/
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
355
|
+
function useSeedPicks(
|
|
356
|
+
have: Set<string>,
|
|
357
|
+
inks: string[],
|
|
358
|
+
onDone: (name: string | null) => void,
|
|
359
|
+
) {
|
|
360
|
+
const [off, setOff] = useState<Set<string>>(new Set());
|
|
361
|
+
const [add, adding] = useServerAction(createSeedBotsAction, {
|
|
362
|
+
onOk: (made) => {
|
|
363
|
+
revalidate(queryKey.bot);
|
|
364
|
+
onDone(made.created[0] ?? null);
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
// Same key the Models section and its badge read, so one fetch answers all three.
|
|
368
|
+
const { data: config } = useServerRoute<ConfigStatus[]>(queryKey.config);
|
|
369
|
+
const isSet = (key: string) => isConfigSet(config, key);
|
|
370
|
+
|
|
371
|
+
// Seeds not on the roster yet. `have` is the whole roster, so counting against
|
|
372
|
+
// its size goes negative the moment a bot nobody seeded is on it.
|
|
373
|
+
const addable = BOT_SEEDS.filter((seed) => !have.has(seed.name));
|
|
374
|
+
const wanted = addable.filter((seed) => !off.has(seed.name));
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
isSet,
|
|
378
|
+
adding,
|
|
379
|
+
addable,
|
|
380
|
+
wanted,
|
|
381
|
+
ticked: (seed: BotSeed) => have.has(seed.name) || !off.has(seed.name),
|
|
382
|
+
toggle: (name: string) =>
|
|
383
|
+
setOff((was) => {
|
|
384
|
+
const next = new Set(was);
|
|
385
|
+
if (next.has(name)) next.delete(name);
|
|
386
|
+
else next.add(name);
|
|
387
|
+
return next;
|
|
388
|
+
}),
|
|
389
|
+
submit: () =>
|
|
390
|
+
add(
|
|
391
|
+
wanted.map((seed) => ({
|
|
392
|
+
name: seed.name,
|
|
393
|
+
color: inks[BOT_SEEDS.indexOf(seed)],
|
|
394
|
+
})),
|
|
395
|
+
),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** The rows themselves. Both wrappers draw these and supply their own chrome. */
|
|
400
|
+
function SeedRows({
|
|
401
|
+
have,
|
|
402
|
+
inks,
|
|
403
|
+
picks,
|
|
270
404
|
}: {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
405
|
+
have: Set<string>;
|
|
406
|
+
inks: string[];
|
|
407
|
+
picks: ReturnType<typeof useSeedPicks>;
|
|
274
408
|
}) {
|
|
275
409
|
return (
|
|
276
|
-
<
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
410
|
+
<div className="flex flex-col gap-2">
|
|
411
|
+
{BOT_SEEDS.map((seed, at) => {
|
|
412
|
+
const owned = have.has(seed.name);
|
|
413
|
+
const on = picks.ticked(seed);
|
|
414
|
+
const needs = unmetLine(seed, picks.isSet);
|
|
415
|
+
return (
|
|
416
|
+
<div
|
|
417
|
+
key={seed.name}
|
|
418
|
+
className={cn(
|
|
419
|
+
"rounded-xl ring-1 transition-colors",
|
|
420
|
+
owned
|
|
421
|
+
? "opacity-45 ring-border/60"
|
|
422
|
+
: on
|
|
423
|
+
? "ring-foreground"
|
|
424
|
+
: "ring-border/60",
|
|
425
|
+
)}
|
|
426
|
+
>
|
|
427
|
+
<button
|
|
428
|
+
type="button"
|
|
429
|
+
disabled={owned || picks.adding}
|
|
430
|
+
aria-pressed={on}
|
|
431
|
+
onClick={() => picks.toggle(seed.name)}
|
|
432
|
+
className="flex w-full items-center gap-3 rounded-xl px-3.5 py-2.5 text-left outline-none focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-default"
|
|
433
|
+
>
|
|
434
|
+
<BotMark
|
|
435
|
+
size={28}
|
|
436
|
+
{...markProps(seed.name, seed.icon)}
|
|
437
|
+
color={inks[at]}
|
|
438
|
+
className={cn(
|
|
439
|
+
"shrink-0 transition-opacity",
|
|
440
|
+
!on && "opacity-35",
|
|
441
|
+
)}
|
|
442
|
+
/>
|
|
443
|
+
{/* The hint stays on one line: the list grows, and a wrapping
|
|
444
|
+
sentence per row is what made this read as a page of prose */}
|
|
445
|
+
<span className="min-w-0 flex-1 space-y-px">
|
|
446
|
+
<span className="block text-[14px] leading-[18px] font-medium">
|
|
447
|
+
{seed.name}
|
|
448
|
+
</span>
|
|
449
|
+
<span className="block truncate text-[12px] leading-[17px] text-muted-foreground">
|
|
450
|
+
{seed.hint}
|
|
451
|
+
</span>
|
|
452
|
+
</span>
|
|
453
|
+
{owned ? (
|
|
454
|
+
<span className="shrink-0 font-mono text-[11px] text-muted-foreground">
|
|
455
|
+
already added
|
|
456
|
+
</span>
|
|
457
|
+
) : needs ? (
|
|
458
|
+
<span
|
|
459
|
+
className={cn(
|
|
460
|
+
"flex shrink-0 items-center gap-1.5 font-mono text-[11px]",
|
|
461
|
+
WAITING_INK,
|
|
462
|
+
)}
|
|
463
|
+
>
|
|
464
|
+
<CircleAlert className="size-3" />
|
|
465
|
+
{needs}
|
|
466
|
+
</span>
|
|
467
|
+
) : null}
|
|
468
|
+
<span
|
|
469
|
+
className={cn(
|
|
470
|
+
"grid size-5 shrink-0 place-items-center rounded-full transition-colors",
|
|
471
|
+
on
|
|
472
|
+
? "bg-foreground text-background"
|
|
473
|
+
: "ring-1 ring-border/60 ring-inset",
|
|
474
|
+
)}
|
|
475
|
+
>
|
|
476
|
+
{on && <Check className="size-3" />}
|
|
477
|
+
</span>
|
|
478
|
+
</button>
|
|
479
|
+
</div>
|
|
480
|
+
);
|
|
481
|
+
})}
|
|
482
|
+
</div>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** Says what Add will do, so the count is checkable before the click. */
|
|
487
|
+
function SeedActions({
|
|
488
|
+
picks,
|
|
489
|
+
onCancel,
|
|
490
|
+
}: {
|
|
491
|
+
picks: ReturnType<typeof useSeedPicks>;
|
|
492
|
+
onCancel: () => void;
|
|
493
|
+
}) {
|
|
494
|
+
return (
|
|
495
|
+
<>
|
|
496
|
+
<Button variant="ghost" onClick={onCancel}>
|
|
497
|
+
Cancel
|
|
498
|
+
</Button>
|
|
499
|
+
<Button
|
|
500
|
+
loading={picks.adding}
|
|
501
|
+
disabled={picks.wanted.length === 0}
|
|
502
|
+
onClick={picks.submit}
|
|
503
|
+
>
|
|
504
|
+
<Plus />
|
|
505
|
+
{picks.wanted.length === 1
|
|
506
|
+
? `Add ${picks.wanted[0].name}`
|
|
507
|
+
: `Add ${picks.wanted.length} bots`}
|
|
508
|
+
</Button>
|
|
509
|
+
</>
|
|
307
510
|
);
|
|
308
511
|
}
|
|
309
512
|
|
|
310
513
|
/**
|
|
311
|
-
* The
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
* "already exists", which is an error about something this screen already knows.
|
|
315
|
-
*
|
|
316
|
-
* One call creates all of them; `createSeedBotsAction` has always taken an array.
|
|
514
|
+
* The offer as a pane: what an empty roster opens on, because nothing else could
|
|
515
|
+
* be there. A roster with bots on it gets `SeedInvite` and a dialog instead — a
|
|
516
|
+
* row among the bots read as a bot the user already had.
|
|
317
517
|
*/
|
|
318
518
|
function SeedPackage({
|
|
319
519
|
have,
|
|
520
|
+
inks,
|
|
320
521
|
onDone,
|
|
321
522
|
}: {
|
|
322
523
|
/** Names already on the roster. */
|
|
323
524
|
have: Set<string>;
|
|
525
|
+
inks: string[];
|
|
324
526
|
onDone: (name: string | null) => void;
|
|
325
527
|
}) {
|
|
326
|
-
const
|
|
327
|
-
const [add, adding] = useServerAction(createSeedBotsAction, {
|
|
328
|
-
onOk: (made) => {
|
|
329
|
-
revalidate(queryKey.bot);
|
|
330
|
-
onDone(made.created[0] ?? null);
|
|
331
|
-
},
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
const wanted = BOT_SEEDS.filter(
|
|
335
|
-
(seed) => !have.has(seed.name) && !off.has(seed.name),
|
|
336
|
-
);
|
|
528
|
+
const picks = useSeedPicks(have, inks, onDone);
|
|
337
529
|
|
|
338
530
|
return (
|
|
339
531
|
<div className="flex min-h-full flex-col">
|
|
@@ -343,108 +535,115 @@ function SeedPackage({
|
|
|
343
535
|
|
|
344
536
|
<div className="px-8 pt-7 pb-1">
|
|
345
537
|
<h3 className="text-[17px] font-medium tracking-tight">
|
|
346
|
-
|
|
347
|
-
? "One bot comes with her"
|
|
348
|
-
: `${BOT_SEEDS.length === 2 ? "Two" : BOT_SEEDS.length} bots come with her`}
|
|
538
|
+
Bots you can add
|
|
349
539
|
</h3>
|
|
350
540
|
<p className="mt-1.5 max-w-lg text-[13px] leading-relaxed text-muted-foreground break-keep">
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
541
|
+
Each one is a starting point — rename it, re-prompt it, give it a
|
|
542
|
+
model of its own. What a bot needs before it can work stands on its
|
|
543
|
+
row; until then it runs on the app default model.
|
|
354
544
|
</p>
|
|
355
545
|
</div>
|
|
356
546
|
|
|
357
|
-
<div className="
|
|
358
|
-
{
|
|
359
|
-
const owned = have.has(seed.name);
|
|
360
|
-
const on = owned || !off.has(seed.name);
|
|
361
|
-
return (
|
|
362
|
-
<div
|
|
363
|
-
key={seed.name}
|
|
364
|
-
className={cn(
|
|
365
|
-
"rounded-2xl ring-1 transition-colors",
|
|
366
|
-
owned
|
|
367
|
-
? "opacity-45 ring-border/60"
|
|
368
|
-
: on
|
|
369
|
-
? "ring-foreground"
|
|
370
|
-
: "ring-border/60",
|
|
371
|
-
)}
|
|
372
|
-
>
|
|
373
|
-
<button
|
|
374
|
-
type="button"
|
|
375
|
-
disabled={owned || adding}
|
|
376
|
-
aria-pressed={on}
|
|
377
|
-
onClick={() =>
|
|
378
|
-
setOff((was) => {
|
|
379
|
-
const next = new Set(was);
|
|
380
|
-
if (next.has(seed.name)) next.delete(seed.name);
|
|
381
|
-
else next.add(seed.name);
|
|
382
|
-
return next;
|
|
383
|
-
})
|
|
384
|
-
}
|
|
385
|
-
className="flex w-full items-center gap-3.5 rounded-2xl px-4 py-3 text-left outline-none focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-default"
|
|
386
|
-
>
|
|
387
|
-
<BotMark
|
|
388
|
-
size={32}
|
|
389
|
-
{...markProps(seed.name, seed.icon)}
|
|
390
|
-
className={cn(
|
|
391
|
-
"shrink-0 transition-opacity",
|
|
392
|
-
!on && "opacity-35",
|
|
393
|
-
)}
|
|
394
|
-
/>
|
|
395
|
-
<span className="min-w-0 flex-1 space-y-0.5">
|
|
396
|
-
<span className="block text-[14px] font-medium">
|
|
397
|
-
{seed.name}
|
|
398
|
-
</span>
|
|
399
|
-
<span className="block text-[12px] leading-normal text-muted-foreground break-keep">
|
|
400
|
-
{seed.description}
|
|
401
|
-
</span>
|
|
402
|
-
</span>
|
|
403
|
-
{owned && (
|
|
404
|
-
<span className="shrink-0 font-mono text-[10px] text-muted-foreground">
|
|
405
|
-
already added
|
|
406
|
-
</span>
|
|
407
|
-
)}
|
|
408
|
-
<span
|
|
409
|
-
className={cn(
|
|
410
|
-
"grid size-6 shrink-0 place-items-center rounded-full transition-colors",
|
|
411
|
-
on
|
|
412
|
-
? "bg-foreground text-background"
|
|
413
|
-
: "ring-1 ring-border/60 ring-inset",
|
|
414
|
-
)}
|
|
415
|
-
>
|
|
416
|
-
{on && <Check className="size-3.5" />}
|
|
417
|
-
</span>
|
|
418
|
-
</button>
|
|
419
|
-
</div>
|
|
420
|
-
);
|
|
421
|
-
})}
|
|
547
|
+
<div className="px-8 pt-4">
|
|
548
|
+
<SeedRows have={have} inks={inks} picks={picks} />
|
|
422
549
|
</div>
|
|
423
550
|
|
|
424
551
|
<div className="mt-auto flex items-center gap-3 border-t border-border/60 px-8 py-4">
|
|
425
552
|
<span className="flex-1 font-mono text-[11px] text-muted-foreground">
|
|
426
|
-
{wanted.length === 0
|
|
553
|
+
{picks.wanted.length === 0
|
|
427
554
|
? "nothing ticked"
|
|
428
|
-
: `${wanted.length} of ${
|
|
555
|
+
: `${picks.wanted.length} of ${picks.addable.length} ticked`}
|
|
429
556
|
</span>
|
|
430
|
-
<
|
|
431
|
-
Cancel
|
|
432
|
-
</Button>
|
|
433
|
-
{/* Says what it will do, so the count is checkable before the click. */}
|
|
434
|
-
<Button
|
|
435
|
-
loading={adding}
|
|
436
|
-
disabled={wanted.length === 0}
|
|
437
|
-
onClick={() => add(wanted.map((seed) => ({ name: seed.name })))}
|
|
438
|
-
>
|
|
439
|
-
{wanted.length === 1
|
|
440
|
-
? `Add ${wanted[0].name}`
|
|
441
|
-
: `Add ${wanted.length} bots`}
|
|
442
|
-
</Button>
|
|
557
|
+
<SeedActions picks={picks} onCancel={() => onDone(null)} />
|
|
443
558
|
</div>
|
|
444
559
|
</div>
|
|
445
560
|
);
|
|
446
561
|
}
|
|
447
562
|
|
|
563
|
+
/** The same offer in a dialog, which brings its own padding and its own footer. */
|
|
564
|
+
function SeedDialog({
|
|
565
|
+
have,
|
|
566
|
+
inks,
|
|
567
|
+
onDone,
|
|
568
|
+
}: {
|
|
569
|
+
have: Set<string>;
|
|
570
|
+
inks: string[];
|
|
571
|
+
onDone: (name: string | null) => void;
|
|
572
|
+
}) {
|
|
573
|
+
const picks = useSeedPicks(have, inks, onDone);
|
|
574
|
+
|
|
575
|
+
return (
|
|
576
|
+
<SettingDialogContent
|
|
577
|
+
title="Bots you can add"
|
|
578
|
+
description="Each one is a starting point — rename it, re-prompt it, give it a model of its own. What a bot needs before it can work stands on its row."
|
|
579
|
+
footer={<SeedActions picks={picks} onCancel={() => onDone(null)} />}
|
|
580
|
+
>
|
|
581
|
+
<SeedRows have={have} inks={inks} picks={picks} />
|
|
582
|
+
</SettingDialogContent>
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* The invite, on the New bot line rather than in the roster: both are ways to get
|
|
588
|
+
* a bot, and neither is a bot you have. It carries the faces of the ones still on
|
|
589
|
+
* offer, in the colours they would be created with, because a row of names would
|
|
590
|
+
* read as one more list and the faces are what says these are bots.
|
|
591
|
+
*/
|
|
592
|
+
function SeedInvite({
|
|
593
|
+
missing,
|
|
594
|
+
inks,
|
|
595
|
+
have,
|
|
596
|
+
onDone,
|
|
597
|
+
}: {
|
|
598
|
+
missing: BotSeed[];
|
|
599
|
+
inks: string[];
|
|
600
|
+
have: Set<string>;
|
|
601
|
+
onDone: (name: string | null) => void;
|
|
602
|
+
}) {
|
|
603
|
+
return (
|
|
604
|
+
<button
|
|
605
|
+
type="button"
|
|
606
|
+
title="Ready-made bots"
|
|
607
|
+
aria-label={`Ready-made bots, ${missing.length} on offer`}
|
|
608
|
+
onClick={() =>
|
|
609
|
+
notify.component({
|
|
610
|
+
className: "sm:max-w-xl",
|
|
611
|
+
renderer: ({ close }) => (
|
|
612
|
+
<SeedDialog
|
|
613
|
+
have={have}
|
|
614
|
+
inks={inks}
|
|
615
|
+
onDone={(name) => {
|
|
616
|
+
close();
|
|
617
|
+
onDone(name);
|
|
618
|
+
}}
|
|
619
|
+
/>
|
|
620
|
+
),
|
|
621
|
+
})
|
|
622
|
+
}
|
|
623
|
+
className="flex shrink-0 items-center gap-1 rounded-md px-1.5 py-1 text-muted-foreground outline-none transition-colors hover:bg-muted/60 hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-inset"
|
|
624
|
+
>
|
|
625
|
+
{/* Says the same thing the words next to it do, about a different set: these
|
|
626
|
+
are bots to add. It carries the affordance on its own when one seed is
|
|
627
|
+
left and the faces are a single dot */}
|
|
628
|
+
<Plus className="size-3 shrink-0" />
|
|
629
|
+
{missing.map((seed, at) => (
|
|
630
|
+
<span
|
|
631
|
+
key={seed.name}
|
|
632
|
+
className={cn("block", at > 0 && "-ml-1.5")}
|
|
633
|
+
style={{ zIndex: missing.length - at }}
|
|
634
|
+
>
|
|
635
|
+
<BotMark
|
|
636
|
+
size={20}
|
|
637
|
+
{...markProps(seed.name, seed.icon)}
|
|
638
|
+
color={inks[BOT_SEEDS.indexOf(seed)]}
|
|
639
|
+
notify={false}
|
|
640
|
+
/>
|
|
641
|
+
</span>
|
|
642
|
+
))}
|
|
643
|
+
</button>
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
448
647
|
/**
|
|
449
648
|
* The mark seed is the name, not the row id, so a new bot's preview is the face the
|
|
450
649
|
* roster will show. An unpicked colour stays empty and means the theme ink.
|
|
@@ -486,10 +685,34 @@ function BotPage({
|
|
|
486
685
|
icon: bot?.icon ?? randomBotIcon(),
|
|
487
686
|
provider: (bot?.provider ?? null) as TextModelProviderId | null,
|
|
488
687
|
model: bot?.model ?? "",
|
|
688
|
+
// In thousands of tokens, the unit the field is typed in
|
|
689
|
+
compactAt: bot?.compactAt ? String(bot.compactAt / 1000) : "",
|
|
489
690
|
toolIds: bot?.tools.map((tool) => tool.id) ?? [],
|
|
490
691
|
}));
|
|
491
|
-
const {
|
|
492
|
-
|
|
692
|
+
const {
|
|
693
|
+
name,
|
|
694
|
+
description,
|
|
695
|
+
systemPrompt,
|
|
696
|
+
icon,
|
|
697
|
+
provider,
|
|
698
|
+
model,
|
|
699
|
+
compactAt,
|
|
700
|
+
toolIds,
|
|
701
|
+
} = fields;
|
|
702
|
+
// Typed by hand; otherwise the field shows what the picked model fills in
|
|
703
|
+
const [compactEdited, setCompactEdited] = useState(Boolean(bot?.compactAt));
|
|
704
|
+
// Read here as well as in the picker: a pick has to fill in from it the moment it happens
|
|
705
|
+
const catalog = useServerRoute<GatewayModel[]>(queryKey.modelCatalog);
|
|
706
|
+
const windowOf = (of: TextModelProviderId | null, id: string) =>
|
|
707
|
+
of && id.trim() ? contextWindowOf(of, id.trim(), catalog.data) : null;
|
|
708
|
+
const pickedWindow = windowOf(provider, model);
|
|
709
|
+
const filledK =
|
|
710
|
+
provider && model.trim() ? filledTokens(pickedWindow) / 1000 : null;
|
|
711
|
+
const shownK = compactEdited
|
|
712
|
+
? compactAt
|
|
713
|
+
: filledK === null
|
|
714
|
+
? ""
|
|
715
|
+
: String(filledK);
|
|
493
716
|
|
|
494
717
|
const [save] = useServerAction(updateBotAction, {
|
|
495
718
|
onOk: () => revalidate(queryKey.bot),
|
|
@@ -527,6 +750,9 @@ function BotPage({
|
|
|
527
750
|
icon,
|
|
528
751
|
provider,
|
|
529
752
|
model: model.trim(),
|
|
753
|
+
compactAt: compactEdited
|
|
754
|
+
? tokensFromK(compactAt)
|
|
755
|
+
: filledTokens(pickedWindow),
|
|
530
756
|
toolIds,
|
|
531
757
|
});
|
|
532
758
|
};
|
|
@@ -549,53 +775,37 @@ function BotPage({
|
|
|
549
775
|
{bot ? bot.name : "New bot"}
|
|
550
776
|
</span>
|
|
551
777
|
{bot && (
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
778
|
+
<>
|
|
779
|
+
{/* Only when off: on is the resting state and needs no word. */}
|
|
780
|
+
{bot.disabled && (
|
|
781
|
+
<span className="font-mono text-[10px] text-muted-foreground">
|
|
782
|
+
off
|
|
783
|
+
</span>
|
|
784
|
+
)}
|
|
785
|
+
<Switch
|
|
786
|
+
checked={!bot.disabled}
|
|
787
|
+
onCheckedChange={(on) => commit({ disabled: !on })}
|
|
788
|
+
aria-label={`${bot.name} on or off`}
|
|
789
|
+
/* mr-1 keeps it off Delete: one is a setting, the other is not. */
|
|
790
|
+
className="mr-1 shrink-0"
|
|
791
|
+
/>
|
|
792
|
+
<Button
|
|
793
|
+
size="icon-sm"
|
|
794
|
+
variant="ghost"
|
|
795
|
+
loading={removing}
|
|
796
|
+
aria-label="Delete this bot"
|
|
797
|
+
onClick={confirmRemove}
|
|
798
|
+
className="text-muted-foreground hover:text-destructive"
|
|
799
|
+
>
|
|
800
|
+
<Trash2 />
|
|
801
|
+
</Button>
|
|
802
|
+
</>
|
|
562
803
|
)}
|
|
563
804
|
</div>
|
|
564
805
|
|
|
565
806
|
<div className="flex-1 space-y-5 p-6">
|
|
566
|
-
<div className="flex items-start gap-4">
|
|
567
|
-
<BotMark size={72} {...markProps(name, icon)} className="shrink-0" />
|
|
568
|
-
<div className="min-w-0 flex-1 space-y-2.5">
|
|
569
|
-
{bot ? (
|
|
570
|
-
<p className="truncate text-lg leading-8 font-medium">
|
|
571
|
-
{bot.name}
|
|
572
|
-
</p>
|
|
573
|
-
) : (
|
|
574
|
-
<Input
|
|
575
|
-
value={name}
|
|
576
|
-
onChange={(event) => patch({ name: event.target.value })}
|
|
577
|
-
placeholder="researcher"
|
|
578
|
-
spellCheck={false}
|
|
579
|
-
maxLength={COMMON_VALIDATE.name.max}
|
|
580
|
-
autoFocus
|
|
581
|
-
/>
|
|
582
|
-
)}
|
|
583
|
-
<Input
|
|
584
|
-
value={description}
|
|
585
|
-
onChange={(event) => patch({ description: event.target.value })}
|
|
586
|
-
onBlur={() => {
|
|
587
|
-
const next = description.trim();
|
|
588
|
-
if (bot && next && next !== bot.description) {
|
|
589
|
-
commit({ description: next });
|
|
590
|
-
}
|
|
591
|
-
}}
|
|
592
|
-
placeholder="Searches the web and reports back"
|
|
593
|
-
maxLength={COMMON_VALIDATE.description.max}
|
|
594
|
-
/>
|
|
595
|
-
</div>
|
|
596
|
-
</div>
|
|
597
|
-
|
|
598
807
|
<MarkPicker
|
|
808
|
+
name={name}
|
|
599
809
|
icon={icon}
|
|
600
810
|
onChange={(next) => {
|
|
601
811
|
patch({ icon: next });
|
|
@@ -603,18 +813,100 @@ function BotPage({
|
|
|
603
813
|
}}
|
|
604
814
|
/>
|
|
605
815
|
|
|
816
|
+
<Row label="Name">
|
|
817
|
+
{bot ? (
|
|
818
|
+
<p className="truncate text-sm leading-8 font-medium">{bot.name}</p>
|
|
819
|
+
) : (
|
|
820
|
+
<Input
|
|
821
|
+
value={name}
|
|
822
|
+
onChange={(event) => patch({ name: event.target.value })}
|
|
823
|
+
placeholder="researcher"
|
|
824
|
+
spellCheck={false}
|
|
825
|
+
maxLength={COMMON_VALIDATE.name.max}
|
|
826
|
+
autoFocus
|
|
827
|
+
/>
|
|
828
|
+
)}
|
|
829
|
+
{/* Renaming is delete and recreate (bot.query updateBot), so say it before the name is typed */}
|
|
830
|
+
<p className="text-xs text-muted-foreground">
|
|
831
|
+
{bot
|
|
832
|
+
? `What ${APP_NAME} calls it when she hands it work. Fixed once the bot is made.`
|
|
833
|
+
: `What ${APP_NAME} calls it when she hands it work. Up to ${COMMON_VALIDATE.name.max} characters, and it can’t be renamed later.`}
|
|
834
|
+
</p>
|
|
835
|
+
</Row>
|
|
836
|
+
|
|
837
|
+
<Row label="Description">
|
|
838
|
+
<Input
|
|
839
|
+
value={description}
|
|
840
|
+
onChange={(event) => patch({ description: event.target.value })}
|
|
841
|
+
onBlur={() => {
|
|
842
|
+
const next = description.trim();
|
|
843
|
+
if (bot && next && next !== bot.description) {
|
|
844
|
+
commit({ description: next });
|
|
845
|
+
}
|
|
846
|
+
}}
|
|
847
|
+
placeholder="Searches the web and answers"
|
|
848
|
+
maxLength={COMMON_VALIDATE.description.max}
|
|
849
|
+
/>
|
|
850
|
+
<p className="text-xs text-muted-foreground">
|
|
851
|
+
The one line {APP_NAME} and the other bots read when they decide who
|
|
852
|
+
gets a job.
|
|
853
|
+
</p>
|
|
854
|
+
</Row>
|
|
855
|
+
|
|
606
856
|
<Row label="Runs on">
|
|
607
857
|
<ModelPicker
|
|
608
858
|
provider={provider}
|
|
609
859
|
model={model}
|
|
610
860
|
onChange={(next) => {
|
|
611
861
|
patch({ provider: next.provider, model: next.model });
|
|
862
|
+
// A new model fills in its own point; a number typed for the old one no longer fits
|
|
863
|
+
setCompactEdited(false);
|
|
612
864
|
// A provider without a model is not a model; save once both are picked.
|
|
613
|
-
if (next.model)
|
|
865
|
+
if (next.model) {
|
|
866
|
+
commit({
|
|
867
|
+
...next,
|
|
868
|
+
compactAt: filledTokens(windowOf(next.provider, next.model)),
|
|
869
|
+
});
|
|
870
|
+
}
|
|
614
871
|
}}
|
|
615
872
|
/>
|
|
616
873
|
</Row>
|
|
617
874
|
|
|
875
|
+
<Row label="Compacts at">
|
|
876
|
+
<InputGroup>
|
|
877
|
+
<InputGroupInput
|
|
878
|
+
inputMode="decimal"
|
|
879
|
+
value={shownK}
|
|
880
|
+
placeholder={model.trim() ? "" : "Pick a model first"}
|
|
881
|
+
onChange={(event) => {
|
|
882
|
+
setCompactEdited(true);
|
|
883
|
+
patch({ compactAt: event.target.value });
|
|
884
|
+
}}
|
|
885
|
+
onBlur={() => {
|
|
886
|
+
if (!compactEdited) return;
|
|
887
|
+
// Typing is a draft; only a settled field is a value. Emptied, the field goes back to the model's own
|
|
888
|
+
const typed = tokensFromK(compactAt);
|
|
889
|
+
if (typed === null) setCompactEdited(false);
|
|
890
|
+
else patch({ compactAt: String(typed / 1000) });
|
|
891
|
+
commit({
|
|
892
|
+
compactAt:
|
|
893
|
+
typed ?? (model.trim() ? filledTokens(pickedWindow) : null),
|
|
894
|
+
});
|
|
895
|
+
}}
|
|
896
|
+
/>
|
|
897
|
+
<InputGroupAddon align="inline-end">
|
|
898
|
+
<InputGroupText>k tokens</InputGroupText>
|
|
899
|
+
</InputGroupAddon>
|
|
900
|
+
</InputGroup>
|
|
901
|
+
<p className="text-xs text-muted-foreground">
|
|
902
|
+
{compactNote({
|
|
903
|
+
model: model.trim(),
|
|
904
|
+
window: pickedWindow,
|
|
905
|
+
shown: tokensFromK(shownK),
|
|
906
|
+
})}
|
|
907
|
+
</p>
|
|
908
|
+
</Row>
|
|
909
|
+
|
|
618
910
|
<Row label="Tools">
|
|
619
911
|
<ToolPicker
|
|
620
912
|
selected={toolIds}
|
|
@@ -647,6 +939,8 @@ function BotPage({
|
|
|
647
939
|
)}
|
|
648
940
|
</Row>
|
|
649
941
|
|
|
942
|
+
{bot && <Memory bot={bot.name} />}
|
|
943
|
+
|
|
650
944
|
{bot && <Recent jobs={jobs} />}
|
|
651
945
|
|
|
652
946
|
{!bot && (
|
|
@@ -668,32 +962,234 @@ function BotPage({
|
|
|
668
962
|
);
|
|
669
963
|
}
|
|
670
964
|
|
|
671
|
-
/**
|
|
672
|
-
|
|
673
|
-
|
|
965
|
+
/**
|
|
966
|
+
* What this bot keeps for its next jobs (bot.memory), drawn the way Recent is: one
|
|
967
|
+
* line per file, newest first. A row opens in place. Deleting is the one thing done
|
|
968
|
+
* to a file from here — writing one is the bot's.
|
|
969
|
+
*/
|
|
970
|
+
function Memory({ bot }: { bot: string }) {
|
|
971
|
+
const key = queryKey.botMemoryFiles(bot);
|
|
972
|
+
const { data, isLoading } = useServerRoute<BotMemory>(key);
|
|
973
|
+
const [open, setOpen] = useState<string | null>(null);
|
|
974
|
+
const [reveal] = useServerAction(openFileAction);
|
|
975
|
+
const [remove, removing] = useServerAction(deleteWorkspaceFileAction, {
|
|
976
|
+
okMessage: "File deleted",
|
|
977
|
+
onOk: () => {
|
|
978
|
+
setOpen(null);
|
|
979
|
+
revalidate(key);
|
|
980
|
+
revalidate(queryKey.workspace);
|
|
981
|
+
},
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
const confirmRemove = async (file: BotMemoryFile) => {
|
|
985
|
+
const confirmed = await notify.confirm({
|
|
986
|
+
title: `Delete ${file.file}?`,
|
|
987
|
+
description: `It is deleted from disk for good, and ${bot}'s next job starts without it.`,
|
|
988
|
+
okText: "Delete",
|
|
989
|
+
destructive: true,
|
|
990
|
+
});
|
|
991
|
+
if (confirmed) remove(file.path);
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
return (
|
|
995
|
+
<div className="pt-1">
|
|
996
|
+
<div className="flex h-6 items-center">
|
|
997
|
+
<span className="font-mono text-xs text-muted-foreground">Memory</span>
|
|
998
|
+
<span className="flex-1" />
|
|
999
|
+
{data && data.total > 0 && (
|
|
1000
|
+
<>
|
|
1001
|
+
<span className="pr-1 font-mono text-[11px] text-muted-foreground tabular-nums">
|
|
1002
|
+
{data.total === 1 ? "1 file" : `${data.total} files`}
|
|
1003
|
+
</span>
|
|
1004
|
+
<button
|
|
1005
|
+
type="button"
|
|
1006
|
+
title="Show in the file manager"
|
|
1007
|
+
onClick={() => reveal(data.folder)}
|
|
1008
|
+
className="flex items-center gap-1 rounded-md px-1.5 py-1 font-mono text-[11px] text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
|
|
1009
|
+
>
|
|
1010
|
+
<FolderOpen className="size-3" />
|
|
1011
|
+
Folder
|
|
1012
|
+
</button>
|
|
1013
|
+
</>
|
|
1014
|
+
)}
|
|
1015
|
+
</div>
|
|
1016
|
+
{isLoading ? (
|
|
1017
|
+
<div className="flex h-9 items-center border-t border-border/60">
|
|
1018
|
+
<Skeleton className="h-3 w-56" />
|
|
1019
|
+
</div>
|
|
1020
|
+
) : !data?.entries.length ? (
|
|
1021
|
+
<p className="py-2 text-[13px] text-muted-foreground">
|
|
1022
|
+
Nothing kept yet.
|
|
1023
|
+
</p>
|
|
1024
|
+
) : (
|
|
1025
|
+
data.entries.map((file) => {
|
|
1026
|
+
const isOpen = open === file.path;
|
|
1027
|
+
return (
|
|
1028
|
+
<div key={file.path} className="border-t border-border/60">
|
|
1029
|
+
<button
|
|
1030
|
+
type="button"
|
|
1031
|
+
aria-expanded={isOpen}
|
|
1032
|
+
onClick={() => setOpen(isOpen ? null : file.path)}
|
|
1033
|
+
className="flex h-9 w-full items-center gap-3 text-left text-[13px] outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
|
|
1034
|
+
>
|
|
1035
|
+
<span
|
|
1036
|
+
className={cn(
|
|
1037
|
+
"min-w-0 flex-1 truncate",
|
|
1038
|
+
isOpen && "font-medium",
|
|
1039
|
+
)}
|
|
1040
|
+
>
|
|
1041
|
+
{file.line || file.file}
|
|
1042
|
+
</span>
|
|
1043
|
+
<span className="w-36 shrink-0 truncate font-mono text-[11px] text-muted-foreground">
|
|
1044
|
+
{file.file}
|
|
1045
|
+
</span>
|
|
1046
|
+
<span className="w-28 shrink-0 text-right font-mono text-[11px] text-muted-foreground tabular-nums">
|
|
1047
|
+
{whenOf(file.at)}
|
|
1048
|
+
</span>
|
|
1049
|
+
<ChevronRight
|
|
1050
|
+
className={cn(
|
|
1051
|
+
"size-3 shrink-0 text-muted-foreground transition-transform",
|
|
1052
|
+
isOpen && "rotate-90",
|
|
1053
|
+
)}
|
|
1054
|
+
/>
|
|
1055
|
+
</button>
|
|
1056
|
+
{isOpen && (
|
|
1057
|
+
<div className="flex items-start gap-3">
|
|
1058
|
+
<div className="max-h-96 min-w-0 flex-1 overflow-y-auto">
|
|
1059
|
+
<MemoryText path={file.path} />
|
|
1060
|
+
</div>
|
|
1061
|
+
<Button
|
|
1062
|
+
size="icon-sm"
|
|
1063
|
+
variant="ghost"
|
|
1064
|
+
aria-label={`Delete ${file.file}`}
|
|
1065
|
+
loading={removing}
|
|
1066
|
+
onClick={() => confirmRemove(file)}
|
|
1067
|
+
className="text-muted-foreground hover:text-destructive"
|
|
1068
|
+
>
|
|
1069
|
+
<Trash2 />
|
|
1070
|
+
</Button>
|
|
1071
|
+
</div>
|
|
1072
|
+
)}
|
|
1073
|
+
</div>
|
|
1074
|
+
);
|
|
1075
|
+
})
|
|
1076
|
+
)}
|
|
1077
|
+
</div>
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/** An opened memory file, read and capped the way Workspace reads one (file-view useFileText). */
|
|
1082
|
+
function MemoryText({ path }: { path: string }) {
|
|
1083
|
+
const { content, failure, truncated } = useFileText(path);
|
|
1084
|
+
if (failure) {
|
|
1085
|
+
return <p className="pb-3 font-mono text-xs text-destructive">{failure}</p>;
|
|
1086
|
+
}
|
|
1087
|
+
if (content === null) {
|
|
674
1088
|
return (
|
|
675
|
-
<
|
|
676
|
-
|
|
677
|
-
|
|
1089
|
+
<div className="space-y-2 pb-3">
|
|
1090
|
+
<Skeleton className="h-4 w-5/6" />
|
|
1091
|
+
<Skeleton className="h-4 w-2/3" />
|
|
1092
|
+
</div>
|
|
678
1093
|
);
|
|
679
|
-
|
|
1094
|
+
}
|
|
1095
|
+
const kind = viewKindOf(path);
|
|
680
1096
|
return (
|
|
681
|
-
<
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1097
|
+
<FileBody
|
|
1098
|
+
kind={kind}
|
|
1099
|
+
content={kind === "markdown" ? underItsRow(content) : content}
|
|
1100
|
+
truncated={truncated}
|
|
1101
|
+
where="inline"
|
|
1102
|
+
/>
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* A markdown memory file as it reads under its row: the row already carries the
|
|
1108
|
+
* file's first line, so frontmatter and a leading heading are not drawn again.
|
|
1109
|
+
*/
|
|
1110
|
+
const underItsRow = (content: string) =>
|
|
1111
|
+
content.replace(/^---\n[\s\S]*?\n---\n/, "").replace(/^\s*#[^\n]*\n?/, "");
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* What the roster's pick is, and the one setting that is the whole set's rather
|
|
1115
|
+
* than any bot's: whether bots keep their own memory at all.
|
|
1116
|
+
*/
|
|
1117
|
+
function BotRail({ bot }: { bot: Bot | null }) {
|
|
1118
|
+
const { data: memoryOn, mutate } = useServerRoute<boolean>(
|
|
1119
|
+
queryKey.botMemory,
|
|
1120
|
+
);
|
|
1121
|
+
const [setMemoryOn] = useServerAction(setBotMemoryOnAction, {
|
|
1122
|
+
onOk: () => revalidate(queryKey.botMemory),
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
const tokens = bot ? bot.tokens.input + bot.tokens.output : 0;
|
|
1126
|
+
return (
|
|
1127
|
+
<>
|
|
1128
|
+
<SettingRailNote>
|
|
1129
|
+
{bot ? (
|
|
1130
|
+
<span className="font-mono text-[11px]">
|
|
1131
|
+
{bot.name}
|
|
1132
|
+
{bot.disabled && (
|
|
1133
|
+
<>
|
|
1134
|
+
<span className="px-1.5 opacity-50">·</span>off
|
|
1135
|
+
</>
|
|
1136
|
+
)}
|
|
1137
|
+
<span className="px-1.5 opacity-50">·</span>
|
|
1138
|
+
<span
|
|
1139
|
+
title={`in ${formatCount(bot.tokens.input)} · out ${formatCount(bot.tokens.output)}`}
|
|
1140
|
+
>
|
|
1141
|
+
{tokens > 0 ? `${formatCount(tokens)} tokens` : "no tokens yet"}
|
|
1142
|
+
</span>
|
|
1143
|
+
<span className="px-1.5 opacity-50">·</span>
|
|
1144
|
+
since {whenOf(bot.createdAt)}
|
|
1145
|
+
</span>
|
|
1146
|
+
) : (
|
|
1147
|
+
"Becomes a bot once it has a name and a model"
|
|
1148
|
+
)}
|
|
1149
|
+
</SettingRailNote>
|
|
1150
|
+
<span className="shrink-0 text-xs text-muted-foreground">
|
|
1151
|
+
Bots keep their own memory
|
|
692
1152
|
</span>
|
|
693
|
-
|
|
1153
|
+
<Switch
|
|
1154
|
+
checked={memoryOn ?? true}
|
|
1155
|
+
disabled={memoryOn === undefined}
|
|
1156
|
+
onCheckedChange={(on) => {
|
|
1157
|
+
void mutate(on, false);
|
|
1158
|
+
setMemoryOn(on);
|
|
1159
|
+
}}
|
|
1160
|
+
aria-label="Bots keep their own memory"
|
|
1161
|
+
/>
|
|
1162
|
+
</>
|
|
694
1163
|
);
|
|
695
1164
|
}
|
|
696
1165
|
|
|
1166
|
+
/** "800", "800k" or "1.2" → tokens (800,000 / 1,200); null when there is no number. */
|
|
1167
|
+
function tokensFromK(text: string): number | null {
|
|
1168
|
+
const k = Number(text.replace(/[^0-9.]/g, ""));
|
|
1169
|
+
return Number.isFinite(k) && k > 0 ? Math.round(k * 1000) : null;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/** What a model fills in, rounded to the field's unit so the saved number is the one shown (model.schema compactAtFor). */
|
|
1173
|
+
const filledTokens = (window: number | null) =>
|
|
1174
|
+
Math.round(compactAtFor(window) / 1000) * 1000;
|
|
1175
|
+
|
|
1176
|
+
/** The line under the field: where the number came from. */
|
|
1177
|
+
function compactNote(input: {
|
|
1178
|
+
model: string;
|
|
1179
|
+
window: number | null;
|
|
1180
|
+
shown: number | null;
|
|
1181
|
+
}): string {
|
|
1182
|
+
if (!input.model) return "Filled in from the model once one is picked.";
|
|
1183
|
+
const filled = filledTokens(input.window);
|
|
1184
|
+
const summarize = "A job summarizes itself here and carries on.";
|
|
1185
|
+
if (input.shown !== null && input.shown !== filled) {
|
|
1186
|
+
return `Set by hand. Picking a model fills in its own again. ${summarize}`;
|
|
1187
|
+
}
|
|
1188
|
+
return input.window
|
|
1189
|
+
? `${Math.round(BOT_RUN.compactHeadroom * 100)}% of this model's ${(input.window / 1000).toLocaleString()}k context window. ${summarize}`
|
|
1190
|
+
: `This model's context window is unknown, so the default is filled in. ${summarize}`;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
697
1193
|
function Row({ label, children }: { label: string; children: ReactNode }) {
|
|
698
1194
|
return (
|
|
699
1195
|
<div className="flex gap-3">
|
|
@@ -734,11 +1230,18 @@ function Recent({ jobs }: { jobs: Task[] }) {
|
|
|
734
1230
|
className="flex h-9 items-center gap-3 border-t border-border/60 text-[13px]"
|
|
735
1231
|
>
|
|
736
1232
|
<span className="min-w-0 flex-1 truncate">{job.label}</span>
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1233
|
+
{job.status === "running" ? (
|
|
1234
|
+
<ShinyText
|
|
1235
|
+
text={state.text}
|
|
1236
|
+
className="shrink-0 font-mono text-[11px]"
|
|
1237
|
+
/>
|
|
1238
|
+
) : (
|
|
1239
|
+
<span
|
|
1240
|
+
className={cn("shrink-0 font-mono text-[11px]", state.tone)}
|
|
1241
|
+
>
|
|
1242
|
+
{state.text}
|
|
1243
|
+
</span>
|
|
1244
|
+
)}
|
|
742
1245
|
<span className="w-28 shrink-0 text-right font-mono text-[11px] text-muted-foreground tabular-nums">
|
|
743
1246
|
{whenOf(job.updatedAt)}
|
|
744
1247
|
</span>
|
|
@@ -753,12 +1256,7 @@ function Recent({ jobs }: { jobs: Task[] }) {
|
|
|
753
1256
|
function jobState(job: Task): { text: string; tone: string } {
|
|
754
1257
|
switch (job.status) {
|
|
755
1258
|
case "waiting":
|
|
756
|
-
return
|
|
757
|
-
? { text: "out of steps", tone: "text-muted-foreground" }
|
|
758
|
-
: {
|
|
759
|
-
text: "waiting on you",
|
|
760
|
-
tone: WAITING_INK,
|
|
761
|
-
};
|
|
1259
|
+
return { text: "waiting on you", tone: WAITING_INK };
|
|
762
1260
|
case "running":
|
|
763
1261
|
return { text: "working", tone: "text-muted-foreground" };
|
|
764
1262
|
case "failed":
|
|
@@ -768,81 +1266,74 @@ function jobState(job: Task): { text: string; tone: string } {
|
|
|
768
1266
|
}
|
|
769
1267
|
}
|
|
770
1268
|
|
|
771
|
-
/**
|
|
1269
|
+
/**
|
|
1270
|
+
* The face, with what paints it right under it: the palette, then the
|
|
1271
|
+
* silhouette. Laid out like Thursday's own face picker, so both faces are
|
|
1272
|
+
* chosen the same way.
|
|
1273
|
+
*/
|
|
772
1274
|
function MarkPicker({
|
|
1275
|
+
name,
|
|
773
1276
|
icon,
|
|
774
1277
|
onChange,
|
|
775
1278
|
}: {
|
|
1279
|
+
name: string;
|
|
776
1280
|
icon: BotIcon;
|
|
777
1281
|
onChange: (icon: BotIcon) => void;
|
|
778
1282
|
}) {
|
|
779
1283
|
return (
|
|
780
|
-
|
|
781
|
-
<
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
picked={icon.color === color}
|
|
794
|
-
onPick={() =>
|
|
795
|
-
onChange({
|
|
796
|
-
...icon,
|
|
797
|
-
color: icon.color === color ? undefined : color,
|
|
798
|
-
})
|
|
799
|
-
}
|
|
800
|
-
/>
|
|
801
|
-
))}
|
|
802
|
-
</div>
|
|
803
|
-
</Row>
|
|
1284
|
+
<div className="flex flex-col items-center gap-4 pt-3 pb-2">
|
|
1285
|
+
<BotMark size={112} {...markProps(name, icon)} className="shrink-0" />
|
|
1286
|
+
|
|
1287
|
+
{/* MARK_SYSTEM is an explicit "follow the theme ink", distinct from no
|
|
1288
|
+
colour; picking the colour already on it goes back to none. */}
|
|
1289
|
+
<MarkPalette
|
|
1290
|
+
color={icon.color}
|
|
1291
|
+
themePicked={icon.color === MARK_SYSTEM}
|
|
1292
|
+
onTheme={() => onChange({ ...icon, color: MARK_SYSTEM })}
|
|
1293
|
+
onPick={(color) =>
|
|
1294
|
+
onChange({ ...icon, color: icon.color === color ? undefined : color })
|
|
1295
|
+
}
|
|
1296
|
+
/>
|
|
804
1297
|
|
|
805
1298
|
{/* Every chip is a toggle: unpicked means "whatever the name seeds", not a value */}
|
|
806
|
-
<
|
|
807
|
-
|
|
808
|
-
{MARK_SHAPES.map((shape) => (
|
|
809
|
-
<button
|
|
810
|
-
key={shape}
|
|
811
|
-
type="button"
|
|
812
|
-
onClick={() =>
|
|
813
|
-
onChange({
|
|
814
|
-
...icon,
|
|
815
|
-
shape: icon.shape === shape ? undefined : shape,
|
|
816
|
-
})
|
|
817
|
-
}
|
|
818
|
-
className={cn(
|
|
819
|
-
"rounded-md px-2 py-1 font-mono text-[11px] capitalize transition-colors",
|
|
820
|
-
icon.shape === shape
|
|
821
|
-
? "bg-foreground text-background"
|
|
822
|
-
: "bg-muted text-muted-foreground hover:text-foreground",
|
|
823
|
-
)}
|
|
824
|
-
>
|
|
825
|
-
{shape}
|
|
826
|
-
</button>
|
|
827
|
-
))}
|
|
828
|
-
<span className="mx-1 w-px self-stretch bg-border/60" />
|
|
1299
|
+
<div className="flex gap-1">
|
|
1300
|
+
{MARK_SHAPES.map((shape) => (
|
|
829
1301
|
<button
|
|
1302
|
+
key={shape}
|
|
830
1303
|
type="button"
|
|
831
1304
|
onClick={() =>
|
|
832
|
-
onChange({
|
|
1305
|
+
onChange({
|
|
1306
|
+
...icon,
|
|
1307
|
+
shape: icon.shape === shape ? undefined : shape,
|
|
1308
|
+
})
|
|
833
1309
|
}
|
|
834
1310
|
className={cn(
|
|
835
|
-
"rounded-md px-2 py-1 font-mono text-[11px] transition-colors",
|
|
836
|
-
icon.
|
|
1311
|
+
"rounded-md px-2 py-1 font-mono text-[11px] capitalize transition-colors",
|
|
1312
|
+
icon.shape === shape
|
|
837
1313
|
? "bg-foreground text-background"
|
|
838
1314
|
: "bg-muted text-muted-foreground hover:text-foreground",
|
|
839
1315
|
)}
|
|
840
1316
|
>
|
|
841
|
-
|
|
1317
|
+
{shape}
|
|
842
1318
|
</button>
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1319
|
+
))}
|
|
1320
|
+
<span className="mx-1 w-px self-stretch bg-border/60" />
|
|
1321
|
+
<button
|
|
1322
|
+
type="button"
|
|
1323
|
+
onClick={() =>
|
|
1324
|
+
onChange({ ...icon, outline: icon.outline ? undefined : true })
|
|
1325
|
+
}
|
|
1326
|
+
className={cn(
|
|
1327
|
+
"rounded-md px-2 py-1 font-mono text-[11px] transition-colors",
|
|
1328
|
+
icon.outline
|
|
1329
|
+
? "bg-foreground text-background"
|
|
1330
|
+
: "bg-muted text-muted-foreground hover:text-foreground",
|
|
1331
|
+
)}
|
|
1332
|
+
>
|
|
1333
|
+
outline
|
|
1334
|
+
</button>
|
|
1335
|
+
</div>
|
|
1336
|
+
</div>
|
|
846
1337
|
);
|
|
847
1338
|
}
|
|
848
1339
|
|