thursday-agent 0.2.0 → 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 -2
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +5 -5
- package/.next/routes-manifest.json +24 -6
- 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/page_client-reference-manifest.js +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.js +14 -0
- package/.next/server/app/api/bot/{notes → memory}/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/bot/route.js +6 -6
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/task/[id]/route.js +8 -8
- 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 +5 -5
- 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 +9 -8
- 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 +4 -4
- 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 +4 -4
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js +4 -4
- 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 +6 -7
- 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 +17 -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 +57 -90
- package/.next/server/app/page.js +10 -10
- 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 -2
- 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 +1 -1
- 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 +5 -5
- 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/_0-73of8._.js +5 -5
- package/.next/server/chunks/_0-fjql4._.js +26 -0
- package/.next/server/chunks/_00xh_q0._.js +27 -0
- package/.next/server/chunks/{_0pppbb5._.js → _03kp0jp._.js} +163 -36
- 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 +130 -0
- package/.next/server/chunks/{_0vnutv9._.js → _0ioz3nd._.js} +1 -1
- package/.next/server/chunks/_0iwfby5._.js +3 -0
- package/.next/server/chunks/_0mecv0f._.js +26 -0
- package/.next/server/chunks/_0mh8j4m._.js +3 -0
- package/.next/server/chunks/_0n74zsf._.js +4 -4
- package/.next/server/chunks/_0obojvv._.js +3 -0
- package/.next/server/chunks/_0recufk._.js +49 -0
- package/.next/server/chunks/_0rnf-6n._.js +4 -4
- package/.next/server/chunks/_0sb7tlz._.js +3 -0
- package/.next/server/chunks/_0u10wmw._.js +5 -5
- package/.next/server/chunks/_0u4aiy2._.js +3 -0
- 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/_18-1wcc._.js +5 -5
- package/.next/server/chunks/_1_5mnq4._.js +4 -4
- package/.next/server/chunks/_1_9mjx-._.js +3 -0
- package/.next/server/chunks/_1d0j961._.js +6 -0
- package/.next/server/chunks/_1d5zmy8._.js +1 -1
- package/.next/server/chunks/_1drgfx4._.js +4 -4
- 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/_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 +3 -0
- package/.next/server/chunks/features_memory_1uqvksn._.js +6 -0
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0a1rd09._.js → node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_06067w6._.js → node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_096ais_._.js → node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_02s3hjt._.js → node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js} +1 -1
- 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_0tvkfgh._.js → node_modules_consola_dist_chunks_prompt_mjs_1y49572._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__1qfqvmg._.js → [root-of-the-server]__0_69wqr._.js} +2 -2
- 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]__0m1b5ky._.js → [root-of-the-server]__1cyjywu._.js} +1 -1
- 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/{_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/{_040sk32._.js → _0u2jv_x._.js} +76 -82
- 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/{_1lip4th._.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/{_09ja_w1._.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/{_0x_nu5e._.js → _1vi_uo7._.js} +2 -2
- package/.next/server/chunks/ssr/_1vyd1ji._.js +1 -1
- package/.next/server/chunks/ssr/{_1m3bsfr._.js → _1xvgn77._.js} +2 -2
- 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/{node_modules_18_dk7_._.js → node_modules_063xwpp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_01qnv-a._.js → node_modules_0bvraul._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0n1kj4p._.js → node_modules_0eu1va6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0kl7-tn._.js → node_modules_0h50b8n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0_n241r._.js → node_modules_0h5hnr1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0lrjk5t._.js → node_modules_0h7u60n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ndvxub._.js → node_modules_0lm7_vm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_13r44jq._.js → node_modules_0mwym0g._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_17lrt3y._.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_004qlr4._.js → node_modules_0y_cded._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0kxz4gt._.js → node_modules_0z5tsu8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0iz1rzd._.js → node_modules_0zp9r92._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_19dp7ko._.js → node_modules_1-hc6uv._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1q0p85z._.js → node_modules_113tz0l._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1toafc-._.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_08d8bke._.js → node_modules_1_n3ztw._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1dwguw_._.js → node_modules_1bc7gu8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_20yebtj._.js → node_modules_1bgb6-f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1n5ygqe._.js → node_modules_1h4a4pp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_04n7mjs._.js → node_modules_1i7u1ia._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hn8z1f._.js → node_modules_1juq3hs._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1xyd7ov._.js → node_modules_1l0qtdu._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0m93zeg._.js → node_modules_1t9cyee._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_09xhur7._.js → node_modules_1uuj9x1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hd-5tv._.js → node_modules_1vqb6po._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1zu988w._.js → node_modules_1xx28h1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0m9a4y0._.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_13w_wy9._.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_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 +58 -91
- 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/{3_h9txt3tq8t8.js → 1dkjg87pxk0n9.js} +1 -1
- package/.next/static/chunks/1ekc7ul556kbl.js +1 -0
- package/.next/static/chunks/{3ard_noqapnw1.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/{18vhba44l1fc9.js → 280t0_e99i276.js} +1 -1
- 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/{07kl352c-lvcz.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.ts +3 -5
- package/app/api/llm-model/credits/route.ts +11 -0
- package/app/api/llm-model/route.ts +6 -1
- 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 +19 -4
- package/bin/thursday.mjs +3 -0
- package/components/ui/shiny-text.tsx +48 -134
- package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/migration.sql +24 -34
- package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/snapshot.json +71 -145
- package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
- package/database/migrations/{20260909063821_bitter_tenebrous → 20260911030320_opposite_jack_power}/snapshot.json +57 -184
- 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 +58 -42
- package/features/ai/model.schema.ts +232 -34
- package/features/ai/model.ts +169 -24
- package/features/ai/prompts/bot.prompt.ts +156 -65
- package/features/ai/prompts/memory-edit.prompt.ts +33 -0
- package/features/ai/prompts/prompt-helper.ts +45 -26
- package/features/ai/prompts/thursday.prompt.ts +40 -17
- package/features/ai/tools/bot.tool.ts +35 -52
- package/features/ai/tools/connected.ts +17 -4
- package/features/ai/tools/memory.tool.ts +133 -72
- package/features/ai/tools/search.tool.ts +136 -47
- package/features/ai/tools/tool-name.ts +2 -5
- package/features/ai/tools/workspace.tool.ts +2 -1
- package/features/bot/bot.action.ts +8 -18
- package/features/bot/bot.memory.ts +89 -0
- package/features/bot/bot.query.ts +37 -57
- package/features/bot/bot.run.ts +507 -241
- package/features/bot/bot.runner.ts +410 -114
- package/features/bot/bot.schema.ts +79 -19
- package/features/bot/bot.seed.ts +89 -16
- package/features/bot/components/bot-mark.tsx +122 -20
- package/features/bot/components/bot-room.tsx +159 -130
- package/features/bot/components/bot-setting.tsx +790 -402
- 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 +215 -28
- 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 +75 -77
- package/features/memory/memory.action.ts +3 -43
- package/features/memory/memory.edit.ts +87 -0
- package/features/memory/memory.query.ts +24 -42
- package/features/memory/memory.schema.ts +29 -106
- package/features/settings/components/settings.tsx +2 -0
- package/features/settings/settings.alert.ts +3 -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 +110 -26
- package/lib/limits.ts +1 -1
- package/lib/oauth-page.ts +27 -0
- package/lib/realtime/openai.ts +2 -2
- package/lib/realtime/realtime.protocol.ts +1 -1
- package/lib/realtime/realtime.tap.ts +122 -0
- package/lib/sandbox.ts +59 -36
- package/package.json +1 -1
- package/skills/browser/SKILL.md +55 -34
- package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/bot/notes/route.js +0 -14
- package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +0 -3
- package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/memory/tidy/route.js +0 -22
- package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
- package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0-npkh_._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__005var5._.js +0 -3
- 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]__04_8spk._.js +0 -3
- 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]__0cro2dz._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0dupqt7._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__0emr6go._.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]__0p32jfz._.js +0 -12
- package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1692zrv._.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_aoyr9._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +0 -10
- 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]__1e-ci13._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1fboj2j._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1oli73c._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
- package/.next/server/chunks/_00_584r._.js +0 -293
- 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/_0nn99o2._.js +0 -3
- package/.next/server/chunks/_0zuzmt_._.js +0 -39
- package/.next/server/chunks/_106co2o._.js +0 -3
- package/.next/server/chunks/_12bbv5i._.js +0 -100
- package/.next/server/chunks/_15q1pig._.js +0 -3
- package/.next/server/chunks/_19wg5yn._.js +0 -138
- package/.next/server/chunks/_1bt95p9._.js +0 -55
- package/.next/server/chunks/_1cbk7ms._.js +0 -26
- package/.next/server/chunks/_1drv2et._.js +0 -40
- package/.next/server/chunks/_1ic3bew._.js +0 -5
- package/.next/server/chunks/_1mxn_lb._.js +0 -3
- package/.next/server/chunks/_1rysr-r._.js +0 -10
- package/.next/server/chunks/_1u3rwbr._.js +0 -3
- package/.next/server/chunks/_1x7p319._.js +0 -6
- package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +0 -3
- package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.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_0gzvgfv._.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_1tpvamf._.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_1y9-wcr._.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]__0iuoa-r._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0un-erv._.js +0 -4
- package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -92
- package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.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/_03webdr._.js +0 -3
- package/.next/server/chunks/ssr/_09-0k3z._.js +0 -3
- package/.next/server/chunks/ssr/_0f_b_9s._.js +0 -3
- package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
- package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
- package/.next/server/chunks/ssr/_0su6_08._.js +0 -3
- package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
- package/.next/server/chunks/ssr/_0w5bgak._.js +0 -3
- package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
- package/.next/server/chunks/ssr/_1ciyoo3._.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/_1g6l4uj._.js +0 -3
- package/.next/server/chunks/ssr/_1ruzehw._.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/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
- package/.next/static/chunks/05b6nx_o3_f_n.js +0 -1
- package/.next/static/chunks/0dmns-gi0raa4.js +0 -1
- package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
- package/.next/static/chunks/0oc4_xoia77dg.css +0 -1
- package/.next/static/chunks/0pheo8m3a-rzi.js +0 -1
- package/.next/static/chunks/19i8h2t-5evjx.js +0 -1
- package/.next/static/chunks/1c5fcj473025q.js +0 -1
- package/.next/static/chunks/1pi_hbmjw-f-z.js +0 -1
- package/.next/static/chunks/1twt6k09vg4np.js +0 -1
- package/.next/static/chunks/1ytnip8wdng93.js +0 -1
- package/.next/static/chunks/28gotuisldmom.js +0 -1
- package/.next/static/chunks/2998p0y7k1c8z.js +0 -3
- package/.next/static/chunks/29gyb3nubj51j.js +0 -53
- package/.next/static/chunks/2l233xfycjpd8.js +0 -1
- package/.next/static/chunks/2p5n7pa3c40t-.js +0 -2
- package/.next/static/chunks/2ru1q132af7h7.js +0 -1
- package/.next/static/chunks/2t8wqycwg3y04.js +0 -2
- package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
- package/.next/static/chunks/32z_ypgtchw48.js +0 -1
- package/.next/static/chunks/33pyjb3xd1iez.js +0 -1
- package/.next/static/chunks/3omud5xunrk1f.js +0 -1
- package/.next/static/chunks/42dnrkbph9e-1.js +0 -1
- package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
- package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +0 -1
- package/features/ai/prompts/memory-tidy.prompt.ts +0 -129
- package/features/ai/tools/tidy.tool.ts +0 -22
- package/features/bot/bot.notes.ts +0 -120
- 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/bot/{notes → memory/files}/route/build-manifest.json +0 -0
- /package/.next/server/app/api/bot/{notes → memory/files}/route/server-reference-manifest.json +0 -0
- /package/.next/server/app/api/bot/{notes → memory/files}/route.js.map +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory}/route/build-manifest.json +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory}/route/server-reference-manifest.json +0 -0
- /package/.next/server/app/api/{memory/tidy → bot/memory}/route.js.map +0 -0
- /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_buildManifest.js +0 -0
- /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_ssgManifest.js +0 -0
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
Check,
|
|
5
|
+
ChevronRight,
|
|
6
|
+
CircleAlert,
|
|
7
|
+
FolderOpen,
|
|
5
8
|
History,
|
|
6
|
-
PencilLine,
|
|
7
9
|
Plus,
|
|
8
10
|
Trash2,
|
|
9
11
|
Wrench,
|
|
@@ -21,34 +23,57 @@ import {
|
|
|
21
23
|
CommandList,
|
|
22
24
|
} from "@/components/ui/command";
|
|
23
25
|
import { Input } from "@/components/ui/input";
|
|
26
|
+
import {
|
|
27
|
+
InputGroup,
|
|
28
|
+
InputGroupAddon,
|
|
29
|
+
InputGroupInput,
|
|
30
|
+
InputGroupText,
|
|
31
|
+
} from "@/components/ui/input-group";
|
|
24
32
|
import { notify } from "@/components/ui/notify";
|
|
25
|
-
import ShinyText from "@/components/ui/shiny-text";
|
|
26
|
-
import {
|
|
33
|
+
import { ShinyText } from "@/components/ui/shiny-text";
|
|
34
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
27
35
|
import { Switch } from "@/components/ui/switch";
|
|
28
36
|
import { Textarea } from "@/components/ui/textarea";
|
|
29
|
-
import {
|
|
37
|
+
import { APP_NAME, BOT_RUN, PROMPT_CROWDED } from "@/config";
|
|
30
38
|
import { ModelPicker } from "@/features/ai/components/model-picker";
|
|
31
|
-
import type { TextModelProviderId } from "@/features/ai/model.schema";
|
|
32
39
|
import {
|
|
33
|
-
|
|
40
|
+
compactAtFor,
|
|
41
|
+
contextWindowOf,
|
|
42
|
+
type GatewayModel,
|
|
43
|
+
type TextModelProviderId,
|
|
44
|
+
} from "@/features/ai/model.schema";
|
|
45
|
+
import {
|
|
34
46
|
createBotAction,
|
|
35
47
|
createSeedBotsAction,
|
|
36
48
|
deleteBotAction,
|
|
37
|
-
|
|
49
|
+
setBotMemoryOnAction,
|
|
38
50
|
updateBotAction,
|
|
39
51
|
} from "@/features/bot/bot.action";
|
|
40
52
|
import {
|
|
41
53
|
type Bot,
|
|
42
54
|
type BotForm,
|
|
43
55
|
type BotIcon,
|
|
44
|
-
|
|
56
|
+
type BotMemory,
|
|
57
|
+
type BotMemoryFile,
|
|
45
58
|
MAX_PINNED_TOOLS,
|
|
46
59
|
randomBotIcon,
|
|
47
60
|
type Task,
|
|
48
61
|
} from "@/features/bot/bot.schema";
|
|
49
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
BOT_SEEDS,
|
|
64
|
+
type BotSeed,
|
|
65
|
+
rollSeedColors,
|
|
66
|
+
} from "@/features/bot/bot.seed";
|
|
50
67
|
import { BotMark } from "@/features/bot/components/bot-mark";
|
|
68
|
+
import { MarkPalette } from "@/features/bot/components/mark-palette";
|
|
51
69
|
import {
|
|
70
|
+
type ConfigStatus,
|
|
71
|
+
isConfigSet,
|
|
72
|
+
MEDIA_MODEL_KEYS,
|
|
73
|
+
mediaModelWords,
|
|
74
|
+
} from "@/features/config/config.const";
|
|
75
|
+
import {
|
|
76
|
+
SettingDialogContent,
|
|
52
77
|
SettingError,
|
|
53
78
|
SettingNote,
|
|
54
79
|
SettingPanes,
|
|
@@ -56,13 +81,22 @@ import {
|
|
|
56
81
|
SettingRailNote,
|
|
57
82
|
} from "@/features/settings/components/setting-ui";
|
|
58
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";
|
|
59
93
|
import { useObjectState } from "@/hooks/use-object-state";
|
|
60
94
|
import { type DateLike, shortAgo, whenOf } from "@/lib/date-like";
|
|
61
95
|
import { COMMON_VALIDATE } from "@/lib/limits";
|
|
62
96
|
import { useServerAction } from "@/lib/protocol/use-server-action";
|
|
63
97
|
import { revalidate, useServerRoute } from "@/lib/protocol/use-server-route";
|
|
64
98
|
import { cn, formatCount, WAITING_INK } from "@/lib/utils";
|
|
65
|
-
import {
|
|
99
|
+
import { MARK_SHAPES, MARK_SYSTEM } from "../mark.const";
|
|
66
100
|
|
|
67
101
|
/**
|
|
68
102
|
* The bots Thursday hands background work to. Roster on the left, the picked bot's
|
|
@@ -77,35 +111,56 @@ export function BotSetting() {
|
|
|
77
111
|
/** Picked roster entry: a bot name, NEW, or null for the first bot. */
|
|
78
112
|
const [picked, setPicked] = useState<string | null>(null);
|
|
79
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
|
+
|
|
80
122
|
if (isLoading) return <SettingPanesSkeleton />;
|
|
81
123
|
if (error) return <SettingError message={error.message} />;
|
|
82
124
|
|
|
83
125
|
const drafting = picked === NEW;
|
|
126
|
+
const on = bots.filter((bot) => !bot.disabled).length;
|
|
84
127
|
const have = new Set(bots.map((bot) => bot.name));
|
|
85
128
|
const missing = BOT_SEEDS.filter((seed) => !have.has(seed.name));
|
|
86
|
-
const current =
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
: (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);
|
|
90
132
|
|
|
91
133
|
return (
|
|
92
134
|
<SettingPanes
|
|
93
|
-
footer={
|
|
135
|
+
footer={bots.length === 0 ? null : <BotRail bot={current} />}
|
|
94
136
|
left={
|
|
95
137
|
<div className="flex flex-col py-2">
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
/>
|
|
104
162
|
)}
|
|
105
|
-
>
|
|
106
|
-
<Plus className="size-3.5 shrink-0" />
|
|
107
|
-
New bot
|
|
108
|
-
</button>
|
|
163
|
+
</div>
|
|
109
164
|
|
|
110
165
|
{bots.map((bot) => (
|
|
111
166
|
<RosterRow
|
|
@@ -117,26 +172,17 @@ export function BotSetting() {
|
|
|
117
172
|
/>
|
|
118
173
|
))}
|
|
119
174
|
|
|
120
|
-
{
|
|
121
|
-
|
|
122
|
-
missing={missing}
|
|
123
|
-
active={picked === SEEDS}
|
|
124
|
-
onPick={() => setPicked(SEEDS)}
|
|
125
|
-
/>
|
|
126
|
-
)}
|
|
127
|
-
|
|
128
|
-
{bots.length > PROMPT_CROWDED.bots && (
|
|
175
|
+
{/* Only bots that are on: a switched-off one is in no prompt to crowd. */}
|
|
176
|
+
{on > PROMPT_CROWDED.bots && (
|
|
129
177
|
<SettingNote className="mx-3 mt-2 leading-relaxed">
|
|
130
|
-
{
|
|
131
|
-
|
|
178
|
+
{on} bots are on. Each is a line in every prompt, and one more for
|
|
179
|
+
Thursday to choose between.
|
|
132
180
|
</SettingNote>
|
|
133
181
|
)}
|
|
134
182
|
</div>
|
|
135
183
|
}
|
|
136
184
|
right={
|
|
137
|
-
|
|
138
|
-
<SeedPackage have={have} onDone={(name) => setPicked(name)} />
|
|
139
|
-
) : drafting ? (
|
|
185
|
+
drafting ? (
|
|
140
186
|
<BotPage
|
|
141
187
|
key="new"
|
|
142
188
|
jobs={[]}
|
|
@@ -153,6 +199,14 @@ export function BotSetting() {
|
|
|
153
199
|
.slice(0, RECENT)}
|
|
154
200
|
onDone={() => setPicked(null)}
|
|
155
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
|
+
/>
|
|
156
210
|
) : (
|
|
157
211
|
<div className="space-y-4 p-8">
|
|
158
212
|
<p className="text-sm leading-relaxed text-muted-foreground">
|
|
@@ -173,8 +227,6 @@ export function BotSetting() {
|
|
|
173
227
|
|
|
174
228
|
/** Roster selections that are not a bot; values no bot name can be. */
|
|
175
229
|
const NEW = " new";
|
|
176
|
-
const SEEDS = " seeds";
|
|
177
|
-
|
|
178
230
|
/** Jobs shown under Recent on a bot's page. */
|
|
179
231
|
const RECENT = 3;
|
|
180
232
|
|
|
@@ -191,7 +243,7 @@ function RosterRow({
|
|
|
191
243
|
active: boolean;
|
|
192
244
|
onPick: () => void;
|
|
193
245
|
}) {
|
|
194
|
-
const line = liveLine(job, bot.lastJobAt);
|
|
246
|
+
const line = liveLine(job, bot.lastJobAt, bot.disabled);
|
|
195
247
|
return (
|
|
196
248
|
<button
|
|
197
249
|
type="button"
|
|
@@ -206,9 +258,14 @@ function RosterRow({
|
|
|
206
258
|
{...markProps(bot.name, bot.icon)}
|
|
207
259
|
state={job?.status === "running" ? "thinking" : "idle"}
|
|
208
260
|
notify={job?.status === "waiting"}
|
|
209
|
-
className="shrink-0"
|
|
261
|
+
className={cn("shrink-0", bot.disabled && "opacity-45")}
|
|
210
262
|
/>
|
|
211
|
-
<span
|
|
263
|
+
<span
|
|
264
|
+
className={cn(
|
|
265
|
+
"min-w-0 flex-1 space-y-0.5",
|
|
266
|
+
bot.disabled && "opacity-45",
|
|
267
|
+
)}
|
|
268
|
+
>
|
|
212
269
|
<span
|
|
213
270
|
className={cn(
|
|
214
271
|
"block truncate text-[13px]",
|
|
@@ -218,23 +275,11 @@ function RosterRow({
|
|
|
218
275
|
{bot.name}
|
|
219
276
|
</span>
|
|
220
277
|
{line.shine ? (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
? "[--rest:var(--color-amber-700)] [--shine:var(--color-amber-400)] dark:[--rest:var(--color-amber-400)] dark:[--shine:var(--color-amber-100)]"
|
|
227
|
-
: "[--rest:var(--muted-foreground)] [--shine:var(--foreground)]",
|
|
228
|
-
)}
|
|
229
|
-
>
|
|
230
|
-
<ShinyText
|
|
231
|
-
text={line.text}
|
|
232
|
-
speed={2.4}
|
|
233
|
-
color="var(--rest)"
|
|
234
|
-
shineColor="var(--shine)"
|
|
235
|
-
className="truncate font-mono text-[11px] leading-4"
|
|
236
|
-
/>
|
|
237
|
-
</span>
|
|
278
|
+
<ShinyText
|
|
279
|
+
text={line.text}
|
|
280
|
+
tone={line.amber ? "waiting" : "muted"}
|
|
281
|
+
className="block truncate font-mono text-[11px] leading-4"
|
|
282
|
+
/>
|
|
238
283
|
) : (
|
|
239
284
|
<span className="block truncate font-mono text-[11px] leading-4 text-muted-foreground">
|
|
240
285
|
{line.text}
|
|
@@ -245,27 +290,35 @@ function RosterRow({
|
|
|
245
290
|
);
|
|
246
291
|
}
|
|
247
292
|
|
|
248
|
-
/**
|
|
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
|
+
*/
|
|
249
298
|
function liveLine(
|
|
250
299
|
job: Task | null,
|
|
251
300
|
lastJobAt: Bot["lastJobAt"],
|
|
301
|
+
disabled: boolean,
|
|
252
302
|
): {
|
|
253
303
|
text: string;
|
|
254
304
|
shine?: boolean;
|
|
255
305
|
amber?: boolean;
|
|
256
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" };
|
|
257
318
|
if (!job) {
|
|
258
319
|
if (!lastJobAt) return { text: "idle · no jobs yet" };
|
|
259
320
|
return { text: `idle · last job ${sinceWord(lastJobAt)}` };
|
|
260
321
|
}
|
|
261
|
-
if (job.status === "waiting") {
|
|
262
|
-
return isBudgetAsk(job.ask)
|
|
263
|
-
? { text: "out of steps" }
|
|
264
|
-
: { text: "waiting on you", shine: true, amber: true };
|
|
265
|
-
}
|
|
266
|
-
if (job.status === "running") {
|
|
267
|
-
return { text: `working · ${job.label}`, shine: true };
|
|
268
|
-
}
|
|
269
322
|
return { text: `idle · last job ${sinceWord(job.updatedAt)}` };
|
|
270
323
|
}
|
|
271
324
|
|
|
@@ -274,86 +327,205 @@ function sinceWord(at: DateLike): string {
|
|
|
274
327
|
const ago = shortAgo(at);
|
|
275
328
|
return ago === "now" ? "just now" : `${ago} ago`;
|
|
276
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
|
+
}
|
|
277
346
|
|
|
278
347
|
/**
|
|
279
|
-
* The
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
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.
|
|
285
354
|
*/
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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,
|
|
290
404
|
}: {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
405
|
+
have: Set<string>;
|
|
406
|
+
inks: string[];
|
|
407
|
+
picks: ReturnType<typeof useSeedPicks>;
|
|
294
408
|
}) {
|
|
295
409
|
return (
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
+
</>
|
|
327
510
|
);
|
|
328
511
|
}
|
|
329
512
|
|
|
330
513
|
/**
|
|
331
|
-
* The
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* "already exists", which is an error about something this screen already knows.
|
|
335
|
-
*
|
|
336
|
-
* 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.
|
|
337
517
|
*/
|
|
338
518
|
function SeedPackage({
|
|
339
519
|
have,
|
|
520
|
+
inks,
|
|
340
521
|
onDone,
|
|
341
522
|
}: {
|
|
342
523
|
/** Names already on the roster. */
|
|
343
524
|
have: Set<string>;
|
|
525
|
+
inks: string[];
|
|
344
526
|
onDone: (name: string | null) => void;
|
|
345
527
|
}) {
|
|
346
|
-
const
|
|
347
|
-
const [add, adding] = useServerAction(createSeedBotsAction, {
|
|
348
|
-
onOk: (made) => {
|
|
349
|
-
revalidate(queryKey.bot);
|
|
350
|
-
onDone(made.created[0] ?? null);
|
|
351
|
-
},
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
const wanted = BOT_SEEDS.filter(
|
|
355
|
-
(seed) => !have.has(seed.name) && !off.has(seed.name),
|
|
356
|
-
);
|
|
528
|
+
const picks = useSeedPicks(have, inks, onDone);
|
|
357
529
|
|
|
358
530
|
return (
|
|
359
531
|
<div className="flex min-h-full flex-col">
|
|
@@ -363,108 +535,115 @@ function SeedPackage({
|
|
|
363
535
|
|
|
364
536
|
<div className="px-8 pt-7 pb-1">
|
|
365
537
|
<h3 className="text-[17px] font-medium tracking-tight">
|
|
366
|
-
|
|
367
|
-
? "One bot comes with her"
|
|
368
|
-
: `${BOT_SEEDS.length === 2 ? "Two" : BOT_SEEDS.length} bots come with her`}
|
|
538
|
+
Bots you can add
|
|
369
539
|
</h3>
|
|
370
540
|
<p className="mt-1.5 max-w-lg text-[13px] leading-relaxed text-muted-foreground break-keep">
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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.
|
|
374
544
|
</p>
|
|
375
545
|
</div>
|
|
376
546
|
|
|
377
|
-
<div className="
|
|
378
|
-
{
|
|
379
|
-
const owned = have.has(seed.name);
|
|
380
|
-
const on = owned || !off.has(seed.name);
|
|
381
|
-
return (
|
|
382
|
-
<div
|
|
383
|
-
key={seed.name}
|
|
384
|
-
className={cn(
|
|
385
|
-
"rounded-2xl ring-1 transition-colors",
|
|
386
|
-
owned
|
|
387
|
-
? "opacity-45 ring-border/60"
|
|
388
|
-
: on
|
|
389
|
-
? "ring-foreground"
|
|
390
|
-
: "ring-border/60",
|
|
391
|
-
)}
|
|
392
|
-
>
|
|
393
|
-
<button
|
|
394
|
-
type="button"
|
|
395
|
-
disabled={owned || adding}
|
|
396
|
-
aria-pressed={on}
|
|
397
|
-
onClick={() =>
|
|
398
|
-
setOff((was) => {
|
|
399
|
-
const next = new Set(was);
|
|
400
|
-
if (next.has(seed.name)) next.delete(seed.name);
|
|
401
|
-
else next.add(seed.name);
|
|
402
|
-
return next;
|
|
403
|
-
})
|
|
404
|
-
}
|
|
405
|
-
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"
|
|
406
|
-
>
|
|
407
|
-
<BotMark
|
|
408
|
-
size={32}
|
|
409
|
-
{...markProps(seed.name, seed.icon)}
|
|
410
|
-
className={cn(
|
|
411
|
-
"shrink-0 transition-opacity",
|
|
412
|
-
!on && "opacity-35",
|
|
413
|
-
)}
|
|
414
|
-
/>
|
|
415
|
-
<span className="min-w-0 flex-1 space-y-0.5">
|
|
416
|
-
<span className="block text-[14px] font-medium">
|
|
417
|
-
{seed.name}
|
|
418
|
-
</span>
|
|
419
|
-
<span className="block text-[12px] leading-normal text-muted-foreground break-keep">
|
|
420
|
-
{seed.description}
|
|
421
|
-
</span>
|
|
422
|
-
</span>
|
|
423
|
-
{owned && (
|
|
424
|
-
<span className="shrink-0 font-mono text-[10px] text-muted-foreground">
|
|
425
|
-
already added
|
|
426
|
-
</span>
|
|
427
|
-
)}
|
|
428
|
-
<span
|
|
429
|
-
className={cn(
|
|
430
|
-
"grid size-6 shrink-0 place-items-center rounded-full transition-colors",
|
|
431
|
-
on
|
|
432
|
-
? "bg-foreground text-background"
|
|
433
|
-
: "ring-1 ring-border/60 ring-inset",
|
|
434
|
-
)}
|
|
435
|
-
>
|
|
436
|
-
{on && <Check className="size-3.5" />}
|
|
437
|
-
</span>
|
|
438
|
-
</button>
|
|
439
|
-
</div>
|
|
440
|
-
);
|
|
441
|
-
})}
|
|
547
|
+
<div className="px-8 pt-4">
|
|
548
|
+
<SeedRows have={have} inks={inks} picks={picks} />
|
|
442
549
|
</div>
|
|
443
550
|
|
|
444
551
|
<div className="mt-auto flex items-center gap-3 border-t border-border/60 px-8 py-4">
|
|
445
552
|
<span className="flex-1 font-mono text-[11px] text-muted-foreground">
|
|
446
|
-
{wanted.length === 0
|
|
553
|
+
{picks.wanted.length === 0
|
|
447
554
|
? "nothing ticked"
|
|
448
|
-
: `${wanted.length} of ${
|
|
555
|
+
: `${picks.wanted.length} of ${picks.addable.length} ticked`}
|
|
449
556
|
</span>
|
|
450
|
-
<
|
|
451
|
-
Cancel
|
|
452
|
-
</Button>
|
|
453
|
-
{/* Says what it will do, so the count is checkable before the click. */}
|
|
454
|
-
<Button
|
|
455
|
-
loading={adding}
|
|
456
|
-
disabled={wanted.length === 0}
|
|
457
|
-
onClick={() => add(wanted.map((seed) => ({ name: seed.name })))}
|
|
458
|
-
>
|
|
459
|
-
{wanted.length === 1
|
|
460
|
-
? `Add ${wanted[0].name}`
|
|
461
|
-
: `Add ${wanted.length} bots`}
|
|
462
|
-
</Button>
|
|
557
|
+
<SeedActions picks={picks} onCancel={() => onDone(null)} />
|
|
463
558
|
</div>
|
|
464
559
|
</div>
|
|
465
560
|
);
|
|
466
561
|
}
|
|
467
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
|
+
|
|
468
647
|
/**
|
|
469
648
|
* The mark seed is the name, not the row id, so a new bot's preview is the face the
|
|
470
649
|
* roster will show. An unpicked colour stays empty and means the theme ink.
|
|
@@ -506,10 +685,34 @@ function BotPage({
|
|
|
506
685
|
icon: bot?.icon ?? randomBotIcon(),
|
|
507
686
|
provider: (bot?.provider ?? null) as TextModelProviderId | null,
|
|
508
687
|
model: bot?.model ?? "",
|
|
688
|
+
// In thousands of tokens, the unit the field is typed in
|
|
689
|
+
compactAt: bot?.compactAt ? String(bot.compactAt / 1000) : "",
|
|
509
690
|
toolIds: bot?.tools.map((tool) => tool.id) ?? [],
|
|
510
691
|
}));
|
|
511
|
-
const {
|
|
512
|
-
|
|
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);
|
|
513
716
|
|
|
514
717
|
const [save] = useServerAction(updateBotAction, {
|
|
515
718
|
onOk: () => revalidate(queryKey.bot),
|
|
@@ -534,12 +737,6 @@ function BotPage({
|
|
|
534
737
|
onDone(null);
|
|
535
738
|
},
|
|
536
739
|
});
|
|
537
|
-
// Shared with the rail's switch; SWR dedups the read
|
|
538
|
-
const { data: notesOn } = useServerRoute<boolean>(queryKey.botNotes);
|
|
539
|
-
const [clearNote, clearingNote] = useServerAction(clearBotNoteAction, {
|
|
540
|
-
okMessage: "Notes cleared",
|
|
541
|
-
onOk: () => revalidate(queryKey.bot),
|
|
542
|
-
});
|
|
543
740
|
|
|
544
741
|
const ready =
|
|
545
742
|
name.trim() && description.trim() && provider && model.trim() && !creating;
|
|
@@ -553,23 +750,13 @@ function BotPage({
|
|
|
553
750
|
icon,
|
|
554
751
|
provider,
|
|
555
752
|
model: model.trim(),
|
|
753
|
+
compactAt: compactEdited
|
|
754
|
+
? tokensFromK(compactAt)
|
|
755
|
+
: filledTokens(pickedWindow),
|
|
556
756
|
toolIds,
|
|
557
757
|
});
|
|
558
758
|
};
|
|
559
759
|
|
|
560
|
-
/** The bot writes its own notes; the only thing to do to them from here is throw them away. */
|
|
561
|
-
const confirmClearNote = async () => {
|
|
562
|
-
if (!bot) return;
|
|
563
|
-
const confirmed = await notify.confirm({
|
|
564
|
-
title: `Clear ${bot.name}'s own prompt?`,
|
|
565
|
-
description:
|
|
566
|
-
"What it worked out about doing jobs here is gone, and it starts the next one without it.",
|
|
567
|
-
okText: "Clear",
|
|
568
|
-
destructive: true,
|
|
569
|
-
});
|
|
570
|
-
if (confirmed) clearNote(bot.name);
|
|
571
|
-
};
|
|
572
|
-
|
|
573
760
|
const confirmRemove = async () => {
|
|
574
761
|
if (!bot) return;
|
|
575
762
|
const confirmed = await notify.confirm({
|
|
@@ -588,97 +775,37 @@ function BotPage({
|
|
|
588
775
|
{bot ? bot.name : "New bot"}
|
|
589
776
|
</span>
|
|
590
777
|
{bot && (
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
className="text-muted-foreground hover:text-destructive"
|
|
598
|
-
>
|
|
599
|
-
<Trash2 />
|
|
600
|
-
</Button>
|
|
601
|
-
)}
|
|
602
|
-
</div>
|
|
603
|
-
|
|
604
|
-
<div className="flex-1 space-y-5 p-6">
|
|
605
|
-
<div className="flex items-start gap-4">
|
|
606
|
-
<BotMark size={72} {...markProps(name, icon)} className="shrink-0" />
|
|
607
|
-
<div className="min-w-0 flex-1 space-y-2.5">
|
|
608
|
-
{bot ? (
|
|
609
|
-
<p className="truncate text-lg leading-8 font-medium">
|
|
610
|
-
{bot.name}
|
|
611
|
-
</p>
|
|
612
|
-
) : (
|
|
613
|
-
<Input
|
|
614
|
-
value={name}
|
|
615
|
-
onChange={(event) => patch({ name: event.target.value })}
|
|
616
|
-
placeholder="researcher"
|
|
617
|
-
spellCheck={false}
|
|
618
|
-
maxLength={COMMON_VALIDATE.name.max}
|
|
619
|
-
autoFocus
|
|
620
|
-
/>
|
|
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>
|
|
621
784
|
)}
|
|
622
|
-
<
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
commit({ description: next });
|
|
629
|
-
}
|
|
630
|
-
}}
|
|
631
|
-
placeholder="Searches the web and reports back"
|
|
632
|
-
maxLength={COMMON_VALIDATE.description.max}
|
|
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"
|
|
633
791
|
/>
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
What {bot.name} has worked out
|
|
646
|
-
</span>
|
|
647
|
-
<span className="shrink-0 font-mono text-[10px] text-muted-foreground">
|
|
648
|
-
{bot.note.length}/{BOT_NOTES.chars}
|
|
649
|
-
</span>
|
|
650
|
-
</div>
|
|
651
|
-
<p className="px-4 py-3.5 text-[13px] leading-relaxed whitespace-pre-wrap">
|
|
652
|
-
{bot.note}
|
|
653
|
-
</p>
|
|
654
|
-
<div className="flex items-center gap-2 px-4 pb-3">
|
|
655
|
-
{/* No amber: switched off is the user's own choice, not something
|
|
656
|
-
waiting on them. The word carries it. */}
|
|
657
|
-
<span className="min-w-0 flex-1 truncate font-mono text-[10px] text-muted-foreground">
|
|
658
|
-
{notesOn === false
|
|
659
|
-
? "Paused — bots are not keeping their own prompt"
|
|
660
|
-
: "It writes this itself when a job ends"}
|
|
661
|
-
{bot.noteAt && (
|
|
662
|
-
<>
|
|
663
|
-
<span className="px-1.5 opacity-50">·</span>
|
|
664
|
-
{shortAgo(bot.noteAt)}
|
|
665
|
-
</>
|
|
666
|
-
)}
|
|
667
|
-
</span>
|
|
668
|
-
<Button
|
|
669
|
-
size="sm"
|
|
670
|
-
variant="ghost"
|
|
671
|
-
loading={clearingNote}
|
|
672
|
-
onClick={confirmClearNote}
|
|
673
|
-
className="shrink-0 text-muted-foreground hover:text-destructive"
|
|
674
|
-
>
|
|
675
|
-
Clear
|
|
676
|
-
</Button>
|
|
677
|
-
</div>
|
|
678
|
-
</div>
|
|
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
|
+
</>
|
|
679
803
|
)}
|
|
804
|
+
</div>
|
|
680
805
|
|
|
806
|
+
<div className="flex-1 space-y-5 p-6">
|
|
681
807
|
<MarkPicker
|
|
808
|
+
name={name}
|
|
682
809
|
icon={icon}
|
|
683
810
|
onChange={(next) => {
|
|
684
811
|
patch({ icon: next });
|
|
@@ -686,18 +813,100 @@ function BotPage({
|
|
|
686
813
|
}}
|
|
687
814
|
/>
|
|
688
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
|
+
|
|
689
856
|
<Row label="Runs on">
|
|
690
857
|
<ModelPicker
|
|
691
858
|
provider={provider}
|
|
692
859
|
model={model}
|
|
693
860
|
onChange={(next) => {
|
|
694
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);
|
|
695
864
|
// A provider without a model is not a model; save once both are picked.
|
|
696
|
-
if (next.model)
|
|
865
|
+
if (next.model) {
|
|
866
|
+
commit({
|
|
867
|
+
...next,
|
|
868
|
+
compactAt: filledTokens(windowOf(next.provider, next.model)),
|
|
869
|
+
});
|
|
870
|
+
}
|
|
697
871
|
}}
|
|
698
872
|
/>
|
|
699
873
|
</Row>
|
|
700
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
|
+
|
|
701
910
|
<Row label="Tools">
|
|
702
911
|
<ToolPicker
|
|
703
912
|
selected={toolIds}
|
|
@@ -730,6 +939,8 @@ function BotPage({
|
|
|
730
939
|
)}
|
|
731
940
|
</Row>
|
|
732
941
|
|
|
942
|
+
{bot && <Memory bot={bot.name} />}
|
|
943
|
+
|
|
733
944
|
{bot && <Recent jobs={jobs} />}
|
|
734
945
|
|
|
735
946
|
{!bot && (
|
|
@@ -751,14 +962,164 @@ function BotPage({
|
|
|
751
962
|
);
|
|
752
963
|
}
|
|
753
964
|
|
|
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) {
|
|
1088
|
+
return (
|
|
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>
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
const kind = viewKindOf(path);
|
|
1096
|
+
return (
|
|
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
|
+
|
|
754
1113
|
/**
|
|
755
1114
|
* What the roster's pick is, and the one setting that is the whole set's rather
|
|
756
|
-
* than any bot's: whether bots keep their own
|
|
1115
|
+
* than any bot's: whether bots keep their own memory at all.
|
|
757
1116
|
*/
|
|
758
1117
|
function BotRail({ bot }: { bot: Bot | null }) {
|
|
759
|
-
const { data:
|
|
760
|
-
|
|
761
|
-
|
|
1118
|
+
const { data: memoryOn, mutate } = useServerRoute<boolean>(
|
|
1119
|
+
queryKey.botMemory,
|
|
1120
|
+
);
|
|
1121
|
+
const [setMemoryOn] = useServerAction(setBotMemoryOnAction, {
|
|
1122
|
+
onOk: () => revalidate(queryKey.botMemory),
|
|
762
1123
|
});
|
|
763
1124
|
|
|
764
1125
|
const tokens = bot ? bot.tokens.input + bot.tokens.output : 0;
|
|
@@ -768,6 +1129,11 @@ function BotRail({ bot }: { bot: Bot | null }) {
|
|
|
768
1129
|
{bot ? (
|
|
769
1130
|
<span className="font-mono text-[11px]">
|
|
770
1131
|
{bot.name}
|
|
1132
|
+
{bot.disabled && (
|
|
1133
|
+
<>
|
|
1134
|
+
<span className="px-1.5 opacity-50">·</span>off
|
|
1135
|
+
</>
|
|
1136
|
+
)}
|
|
771
1137
|
<span className="px-1.5 opacity-50">·</span>
|
|
772
1138
|
<span
|
|
773
1139
|
title={`in ${formatCount(bot.tokens.input)} · out ${formatCount(bot.tokens.output)}`}
|
|
@@ -782,21 +1148,48 @@ function BotRail({ bot }: { bot: Bot | null }) {
|
|
|
782
1148
|
)}
|
|
783
1149
|
</SettingRailNote>
|
|
784
1150
|
<span className="shrink-0 text-xs text-muted-foreground">
|
|
785
|
-
Bots keep their own
|
|
1151
|
+
Bots keep their own memory
|
|
786
1152
|
</span>
|
|
787
1153
|
<Switch
|
|
788
|
-
checked={
|
|
789
|
-
disabled={
|
|
1154
|
+
checked={memoryOn ?? true}
|
|
1155
|
+
disabled={memoryOn === undefined}
|
|
790
1156
|
onCheckedChange={(on) => {
|
|
791
1157
|
void mutate(on, false);
|
|
792
|
-
|
|
1158
|
+
setMemoryOn(on);
|
|
793
1159
|
}}
|
|
794
|
-
aria-label="Bots keep their own
|
|
1160
|
+
aria-label="Bots keep their own memory"
|
|
795
1161
|
/>
|
|
796
1162
|
</>
|
|
797
1163
|
);
|
|
798
1164
|
}
|
|
799
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
|
+
|
|
800
1193
|
function Row({ label, children }: { label: string; children: ReactNode }) {
|
|
801
1194
|
return (
|
|
802
1195
|
<div className="flex gap-3">
|
|
@@ -837,11 +1230,18 @@ function Recent({ jobs }: { jobs: Task[] }) {
|
|
|
837
1230
|
className="flex h-9 items-center gap-3 border-t border-border/60 text-[13px]"
|
|
838
1231
|
>
|
|
839
1232
|
<span className="min-w-0 flex-1 truncate">{job.label}</span>
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
+
)}
|
|
845
1245
|
<span className="w-28 shrink-0 text-right font-mono text-[11px] text-muted-foreground tabular-nums">
|
|
846
1246
|
{whenOf(job.updatedAt)}
|
|
847
1247
|
</span>
|
|
@@ -856,12 +1256,7 @@ function Recent({ jobs }: { jobs: Task[] }) {
|
|
|
856
1256
|
function jobState(job: Task): { text: string; tone: string } {
|
|
857
1257
|
switch (job.status) {
|
|
858
1258
|
case "waiting":
|
|
859
|
-
return
|
|
860
|
-
? { text: "out of steps", tone: "text-muted-foreground" }
|
|
861
|
-
: {
|
|
862
|
-
text: "waiting on you",
|
|
863
|
-
tone: WAITING_INK,
|
|
864
|
-
};
|
|
1259
|
+
return { text: "waiting on you", tone: WAITING_INK };
|
|
865
1260
|
case "running":
|
|
866
1261
|
return { text: "working", tone: "text-muted-foreground" };
|
|
867
1262
|
case "failed":
|
|
@@ -871,81 +1266,74 @@ function jobState(job: Task): { text: string; tone: string } {
|
|
|
871
1266
|
}
|
|
872
1267
|
}
|
|
873
1268
|
|
|
874
|
-
/**
|
|
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
|
+
*/
|
|
875
1274
|
function MarkPicker({
|
|
1275
|
+
name,
|
|
876
1276
|
icon,
|
|
877
1277
|
onChange,
|
|
878
1278
|
}: {
|
|
1279
|
+
name: string;
|
|
879
1280
|
icon: BotIcon;
|
|
880
1281
|
onChange: (icon: BotIcon) => void;
|
|
881
1282
|
}) {
|
|
882
1283
|
return (
|
|
883
|
-
|
|
884
|
-
<
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
picked={icon.color === color}
|
|
897
|
-
onPick={() =>
|
|
898
|
-
onChange({
|
|
899
|
-
...icon,
|
|
900
|
-
color: icon.color === color ? undefined : color,
|
|
901
|
-
})
|
|
902
|
-
}
|
|
903
|
-
/>
|
|
904
|
-
))}
|
|
905
|
-
</div>
|
|
906
|
-
</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
|
+
/>
|
|
907
1297
|
|
|
908
1298
|
{/* Every chip is a toggle: unpicked means "whatever the name seeds", not a value */}
|
|
909
|
-
<
|
|
910
|
-
|
|
911
|
-
{MARK_SHAPES.map((shape) => (
|
|
912
|
-
<button
|
|
913
|
-
key={shape}
|
|
914
|
-
type="button"
|
|
915
|
-
onClick={() =>
|
|
916
|
-
onChange({
|
|
917
|
-
...icon,
|
|
918
|
-
shape: icon.shape === shape ? undefined : shape,
|
|
919
|
-
})
|
|
920
|
-
}
|
|
921
|
-
className={cn(
|
|
922
|
-
"rounded-md px-2 py-1 font-mono text-[11px] capitalize transition-colors",
|
|
923
|
-
icon.shape === shape
|
|
924
|
-
? "bg-foreground text-background"
|
|
925
|
-
: "bg-muted text-muted-foreground hover:text-foreground",
|
|
926
|
-
)}
|
|
927
|
-
>
|
|
928
|
-
{shape}
|
|
929
|
-
</button>
|
|
930
|
-
))}
|
|
931
|
-
<span className="mx-1 w-px self-stretch bg-border/60" />
|
|
1299
|
+
<div className="flex gap-1">
|
|
1300
|
+
{MARK_SHAPES.map((shape) => (
|
|
932
1301
|
<button
|
|
1302
|
+
key={shape}
|
|
933
1303
|
type="button"
|
|
934
1304
|
onClick={() =>
|
|
935
|
-
onChange({
|
|
1305
|
+
onChange({
|
|
1306
|
+
...icon,
|
|
1307
|
+
shape: icon.shape === shape ? undefined : shape,
|
|
1308
|
+
})
|
|
936
1309
|
}
|
|
937
1310
|
className={cn(
|
|
938
|
-
"rounded-md px-2 py-1 font-mono text-[11px] transition-colors",
|
|
939
|
-
icon.
|
|
1311
|
+
"rounded-md px-2 py-1 font-mono text-[11px] capitalize transition-colors",
|
|
1312
|
+
icon.shape === shape
|
|
940
1313
|
? "bg-foreground text-background"
|
|
941
1314
|
: "bg-muted text-muted-foreground hover:text-foreground",
|
|
942
1315
|
)}
|
|
943
1316
|
>
|
|
944
|
-
|
|
1317
|
+
{shape}
|
|
945
1318
|
</button>
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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>
|
|
949
1337
|
);
|
|
950
1338
|
}
|
|
951
1339
|
|