thursday-agent 0.10.0 → 0.12.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 +0 -1
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +0 -6
- 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 +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/keep-working/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/reach/route.js +13 -13
- package/.next/server/app/api/reach/route.js.nft.json +1 -1
- package/.next/server/app/api/routine/route.js.nft.json +1 -1
- package/.next/server/app/api/signins/route.js +1 -1
- package/.next/server/app/api/signins/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +14 -15
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/.next/server/app/page/server-reference-manifest.json +56 -56
- package/.next/server/app/page.js +4 -4
- 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 +0 -1
- package/.next/server/chunks/[root-of-the-server]__014ydvq._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0209ah4._.js +7 -5
- package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +4 -2
- package/.next/server/chunks/[root-of-the-server]__04wldw9._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0542v_d._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0cfszwn._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0gewdg9._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0npn_pn._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0nr-v8s._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0rqgb17._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1-swlkw._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__12iddfq._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1ai5lme._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1cjxw0-._.js +2 -2
- package/.next/server/chunks/{[root-of-the-server]__1fy2g82._.js → [root-of-the-server]__1k3j52n._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__1kyjt5m._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1tg5got._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
- package/.next/server/chunks/_05v8k21._.js +3 -1
- package/.next/server/chunks/_0awtznl._.js +1 -1
- package/.next/server/chunks/_0f6ts7z._.js +1 -1
- package/.next/server/chunks/_0na2fj1._.js +1 -1
- package/.next/server/chunks/_0x-ej3f._.js +17 -17
- package/.next/server/chunks/_10tkr26._.js +1 -1
- package/.next/server/chunks/_12-amdu._.js +2 -2
- package/.next/server/chunks/_15740dw._.js +60 -0
- package/.next/server/chunks/_1dc_d1q._.js +3 -3
- package/.next/server/chunks/_1i350l5._.js +3 -1
- package/.next/server/chunks/_1jc5g4p._.js +2 -2
- package/.next/server/chunks/_1jn-aep._.js +4 -4
- package/.next/server/chunks/_1m88lkh._.js +11 -11
- package/.next/server/chunks/_1ogqovh._.js +4 -4
- package/.next/server/chunks/_1taa4bs._.js +1 -1
- package/.next/server/chunks/_1tpr507._.js +1 -1
- package/.next/server/chunks/_1uuucy-._.js +1 -1
- package/.next/server/chunks/_1zxp-p6._.js +1 -1
- package/.next/server/chunks/features_0rfvely._.js +5 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0a967km._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__1se25x0._.js → [root-of-the-server]__0hpp3nw._.js} +29 -13
- package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +24 -60
- package/.next/server/chunks/ssr/{[root-of-the-server]__0tvavy9._.js → [root-of-the-server]__1iwyp1e._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1r77-88._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1w9hqq8._.js +1 -1
- package/.next/server/chunks/ssr/_026qnc9._.js +1 -1
- package/.next/server/chunks/ssr/{_173siic._.js → _02c7o30._.js} +2 -2
- package/.next/server/chunks/ssr/_097-ljd._.js +1 -1
- package/.next/server/chunks/ssr/_0euzzf8._.js +3 -0
- package/.next/server/chunks/ssr/{_1u2jw0i._.js → _0fbi6om._.js} +3 -3
- package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
- package/.next/server/chunks/ssr/_0igfcax._.js +20 -0
- package/.next/server/chunks/ssr/_0ld9_qq._.js +1 -1
- package/.next/server/chunks/ssr/_0nvh1h9._.js +3 -0
- package/.next/server/chunks/ssr/_0qmpw35._.js +3 -0
- package/.next/server/chunks/ssr/{_1yk6yak._.js → _0rmaf32._.js} +2 -2
- package/.next/server/chunks/ssr/_0uv-qs4._.js +3 -0
- package/.next/server/chunks/ssr/{_1doidrw._.js → _10f-jib._.js} +2 -2
- package/.next/server/chunks/ssr/{_05rxyf9._.js → _10mo72m._.js} +2 -2
- package/.next/server/chunks/ssr/_12mijol._.js +11 -11
- package/.next/server/chunks/ssr/_132qkfz._.js +1 -1
- package/.next/server/chunks/ssr/_15zqt-t._.js +1 -1
- package/.next/server/chunks/ssr/_16gfxpn._.js +3 -0
- package/.next/server/chunks/ssr/_184327j._.js +1 -1
- package/.next/server/chunks/ssr/{_1ng831c._.js → _18ujhhq._.js} +2 -2
- package/.next/server/chunks/ssr/_1d423aj._.js +7 -5
- package/.next/server/chunks/ssr/_1lx6w_o._.js +67 -0
- package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
- package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
- package/.next/server/chunks/ssr/{_19n08g5._.js → _1ntpny5._.js} +2 -2
- package/.next/server/chunks/ssr/{_0bp-_fi._.js → _1o1g4uk._.js} +3 -3
- package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
- package/.next/server/chunks/ssr/_1yh5bj5._.js +1 -1
- package/.next/server/chunks/ssr/{_0tg0vf8._.js → _2008vmi._.js} +8 -8
- package/.next/server/chunks/ssr/{features_bot_1yr-_pm._.js → features_bot_1n1s7bh._.js} +2 -2
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
- package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
- package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
- package/.next/server/chunks/ssr/features_workspace_00jz2hu._.js +1 -1
- package/.next/server/chunks/ssr/{node_modules_1n0h9sf._.js → node_modules_0-x0zxm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1xzo2wn._.js → node_modules_00nrzz_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0511ha-._.js → node_modules_01ed0yi._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_18woe20._.js → node_modules_01u4ea3._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1expacw._.js → node_modules_05b6e99._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0eb4rx7._.js → node_modules_07jp6tj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0piws2a._.js → node_modules_0azd56x._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0f2k5l0._.js → node_modules_0b0iy01._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0wq4-ts._.js → node_modules_0fpnetd._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_11hbkuj._.js → node_modules_0ky76fz._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_048wmmj._.js → node_modules_0lautv3._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1dcbvfw._.js → node_modules_0m3_5ge._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0pk6i-e._.js → node_modules_0operbz._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1zkpek5._.js → node_modules_0ou0all._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_04t200t._.js → node_modules_0ucridu._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_08g12f-._.js → node_modules_0v_-uht._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1m2ho33._.js → node_modules_0yzmxfq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ksh9gy._.js → node_modules_19b0bir._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1qehmwm._.js → node_modules_1c2ikr9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_afpz7._.js → node_modules_1hv-j1l._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0cdu6jq._.js → node_modules_1iau_ea._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1zf46lp._.js → node_modules_1ma_y68._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0s1lwc9._.js → node_modules_1omg9xr._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1cxc8kn._.js → node_modules_1wgnj8q._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0zjqwz7._.js → node_modules_1wvf3oo._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_ban4u._.js → node_modules_1z3sh11._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0avl8-1._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0tmjn9w._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_14oslro._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1_e8c3o._.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 +57 -57
- package/.next/static/chunks/09pkygbht7ki8.js +18 -0
- package/.next/static/chunks/0bm-nhblcmmj4.js +17 -0
- package/.next/static/chunks/{22shnca_6zfzs.js → 0hhza87hkbqaz.js} +1 -1
- package/.next/static/chunks/{19ojn4lomsk0r.js → 0mscosx0eh3ee.js} +1 -1
- package/.next/static/chunks/{3vynnr71ghvom.js → 0p50d-au6-3hx.js} +2 -2
- package/.next/static/chunks/{42890jqk049q1.js → 0p9jcyqkpq71f.js} +1 -1
- package/.next/static/chunks/{35kwn237o4wdn.js → 0qgn3u86jkbm7.js} +1 -1
- package/.next/static/chunks/{2d4z3er53ng8r.js → 0samf80yu2b17.js} +1 -1
- package/.next/static/chunks/{1pjygp7hqib6j.js → 1czfknd4luo6t.js} +1 -1
- package/.next/static/chunks/{09120tms63y21.js → 1jz8n0mhtdzr8.js} +1 -1
- package/.next/static/chunks/206laxm6maiz2.js +1 -0
- package/.next/static/chunks/{24dst4mgurbsh.js → 2_c7mu6x1lu49.js} +1 -1
- package/.next/static/chunks/{2ky81z-jgvasc.js → 2h-tlhb2ffhkk.js} +1 -1
- package/.next/static/chunks/{0oz3rs47yy3p8.js → 2i5_w8olnrvo0.js} +2 -2
- package/.next/static/chunks/{1mwee7xvitlp9.js → 2m8iqwc6sykln.js} +1 -1
- package/.next/static/chunks/{0o980477kfqez.js → 2r6xnyg7ezyxr.js} +1 -1
- package/.next/static/chunks/33fyp-byw48ju.js +65 -0
- package/.next/static/chunks/{2ndw4w4u2x51r.js → 35yd_27d9ylra.js} +1 -1
- package/.next/static/chunks/{3exjsi8pfdvzj.js → 3ag1i3__yl94f.js} +1 -1
- package/.next/static/chunks/{3obf9n3cxj99k.js → 3jvldk2wvwbge.js} +1 -1
- package/.next/static/chunks/3m_ejzg-xn21r.css +1 -0
- package/.next/static/chunks/{0w20wnnfrscq7.js → 3s-jpd3ky49-n.js} +1 -1
- package/.next/static/chunks/40ij3wsn3vt7h.js +1 -0
- package/README.md +1 -1
- package/app/api/thursday/tool-call/route.ts +1 -15
- package/docs/images/demo-still.jpg +0 -0
- package/docs/live-calls.md +19 -11
- package/features/ai/components/model-picker.tsx +8 -2
- package/features/ai/model.schema.ts +26 -0
- package/features/ai/prompts/live.prompt.ts +51 -26
- package/features/ai/prompts/prompt-helper.ts +1 -9
- package/features/ai/prompts/thursday.prompt.ts +8 -2
- package/features/bot/bot.run.ts +9 -3
- package/features/bot/bot.seed.ts +35 -91
- package/features/bot/components/bot-room.tsx +3 -12
- package/features/bot/components/room-list.tsx +1 -1
- package/features/bot/components/room-pill.tsx +44 -55
- package/features/bot/seed-bots.ts +2 -2
- package/features/config/components/config-badge.tsx +6 -0
- package/features/config/components/config-setting.tsx +20 -1
- package/features/config/components/models-badge.tsx +5 -7
- package/features/config/components/voice-key.tsx +24 -1
- package/features/config/config.const.ts +7 -0
- package/features/intro/components/intro.tsx +4 -19
- package/features/reach/reach.ts +2 -2
- package/features/settings/components/setting-ui.tsx +14 -5
- package/features/settings/components/settings.tsx +3 -1
- package/features/settings/settings.alert.ts +12 -11
- package/features/signins/signins.query.ts +66 -1
- package/features/thursday/components/side-captions.tsx +31 -8
- package/features/thursday/components/tab-state.tsx +5 -2
- package/features/thursday/components/thursday.tsx +158 -68
- package/features/thursday/components/write-line.tsx +37 -17
- package/features/thursday/thursday.action.ts +4 -20
- package/features/thursday/thursday.schema.ts +0 -9
- package/features/thursday/thursday.store.ts +11 -15
- package/features/thursday/thursday.text.ts +10 -8
- package/features/thursday/tool-line.ts +17 -6
- package/features/thursday/use-call-ring.ts +1 -5
- package/features/thursday/use-text-call.ts +37 -21
- package/features/thursday/use-thursday.ts +14 -44
- package/features/workspace/workspace.ts +4 -2
- package/guide/bots.md +12 -2
- package/guide/calls.md +9 -6
- package/guide/setup.md +4 -1
- package/lib/live/live.session.ts +0 -39
- package/package.json +1 -1
- package/scripts/live-session.test.mts +19 -26
- package/seed-skills/analyst/README.md +3 -4
- package/seed-skills/analyst/data-report/SKILL.md +5 -4
- package/seed-skills/insta/instagram/SKILL.md +5 -2
- package/seed-skills/insta/instagram/references/post.md +2 -2
- package/seed-skills/insta/instagram/scripts/compose.mjs +5 -1
- package/seed-skills/insta/instagram/scripts/inbox.mjs +5 -1
- package/seed-skills/insta/instagram/scripts/lib.mjs +12 -70
- package/seed-skills/insta/instagram/scripts/media.mjs +1 -1
- package/seed-skills/insta/instagram/scripts/profile.mjs +6 -2
- package/seed-skills/insta/instagram/scripts/read.mjs +5 -1
- package/seed-skills/insta/instagram/scripts/search.mjs +6 -2
- package/seed-skills/insta/instagram/scripts/sheet.mjs +31 -88
- package/seed-skills/insta/instagram/templates/carousel.html +1 -1
- package/seed-skills/tutor/picture-book/SKILL.md +16 -0
- package/seed-skills/tutor/picture-book/book/book.css +181 -0
- package/seed-skills/tutor/picture-book/book/book.html +33 -0
- package/seed-skills/tutor/picture-book/book/book.js +60 -0
- package/seed-skills/tutor/picture-book/references/picture-book.md +104 -0
- package/seed-skills/tutor/picture-book/scripts/book-video.mjs +177 -0
- package/seed-skills/tutor/picture-book/scripts/book.mjs +98 -0
- package/skills/browser/SKILL.md +13 -4
- package/skills/browser/references/scripts.md +55 -0
- package/{seed-skills/insta/instagram → skills/browser}/scripts/render.mjs +1 -1
- package/skills/browser/scripts/session.mjs +105 -0
- package/skills/browser/scripts/sheet.mjs +97 -0
- package/{seed-skills/insta/instagram → skills/browser}/scripts/webimage.mjs +1 -1
- package/skills/interactive-page/SKILL.md +4 -3
- package/{seed-skills/analyst/data-report → skills/interactive-page}/scripts/chart.mjs +3 -3
- package/skills/interactive-page/scripts/page.mjs +1 -0
- package/.next/server/app/api/probe/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/probe/route/build-manifest.json +0 -12
- package/.next/server/app/api/probe/route/server-reference-manifest.json +0 -4
- package/.next/server/app/api/probe/route.js +0 -10
- package/.next/server/app/api/probe/route.js.map +0 -5
- package/.next/server/app/api/probe/route.js.nft.json +0 -1
- package/.next/server/app/api/probe/route_client-reference-manifest.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__012s2o8._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0z0hc1f._.js +0 -4
- package/.next/server/chunks/[root-of-the-server]__1v7-zke._.js +0 -5
- package/.next/server/chunks/_0whlpyi._.js +0 -4
- package/.next/server/chunks/_next-internal_server_app_api_probe_route_actions_0cqd30j.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0cxqzfc._.js +0 -103
- package/.next/server/chunks/ssr/[root-of-the-server]__1-d2tgh._.js +0 -3
- package/.next/server/chunks/ssr/_070nv1_._.js +0 -3
- package/.next/server/chunks/ssr/_0hlxij-._.js +0 -3
- package/.next/server/chunks/ssr/_0p9pmxe._.js +0 -3
- package/.next/server/chunks/ssr/_0s41irp._.js +0 -3
- package/.next/server/chunks/ssr/_157yfrk._.js +0 -3
- package/.next/server/chunks/ssr/_17v-33v._.js +0 -20
- package/.next/server/chunks/ssr/_1flsjeo._.js +0 -3
- package/.next/static/chunks/03kgfyrli_0gz.js +0 -1
- package/.next/static/chunks/1p764d75_0lhv.js +0 -18
- package/.next/static/chunks/25zq8gzhbdsr7.js +0 -1
- package/.next/static/chunks/2qod5tqlpz8b7.js +0 -17
- package/.next/static/chunks/30skcjexa57zn.js +0 -101
- package/.next/static/chunks/3qq4o8w2rv_e9.css +0 -1
- package/lib/probe.server.ts +0 -17
- package/lib/probe.ts +0 -55
- package/seed-skills/mail/mail/SKILL.md +0 -37
- package/seed-skills/mail/mail/recipes/gmail.mjs +0 -448
- package/seed-skills/mail/mail/references/recipes.md +0 -55
- package/seed-skills/mail/mail/scripts/mail.mjs +0 -202
- /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → QSe0rTdXU5kh5BBzr2XBB}/_buildManifest.js +0 -0
- /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → QSe0rTdXU5kh5BBzr2XBB}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → QSe0rTdXU5kh5BBzr2XBB}/_ssgManifest.js +0 -0
- /package/{seed-skills/insta/instagram → skills/browser}/scripts/image-size.mjs +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Check, X } from "lucide-react";
|
|
3
|
+
import { ArrowUpRight, Check, X } from "lucide-react";
|
|
4
4
|
import { type Ref, useImperativeHandle, useState } from "react";
|
|
5
5
|
import { queryKey } from "@/app/api/query-key";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import { Input } from "@/components/ui/input";
|
|
8
8
|
import { ProviderIcon } from "@/features/ai/components/provider-icon";
|
|
9
9
|
import { LIVE_PROVIDER } from "@/features/ai/live.schema";
|
|
10
|
+
import { TEXT_MODEL_PROVIDERS } from "@/features/ai/model.schema";
|
|
10
11
|
import { useServerAction } from "@/lib/protocol/use-server-action";
|
|
11
12
|
import { revalidate, useServerRoute } from "@/lib/protocol/use-server-route";
|
|
12
13
|
import { cn, WAITING_INK } from "@/lib/utils";
|
|
@@ -27,6 +28,28 @@ export type VoiceKeysHandle = {
|
|
|
27
28
|
/** Shorter than this is not a key: no save button, no prompt on leaving. */
|
|
28
29
|
export const KEY_MIN = 8;
|
|
29
30
|
|
|
31
|
+
/** Where the voice key is made, beside its field: a pill that opens the page, and the page's address. */
|
|
32
|
+
export function GetKeyLink() {
|
|
33
|
+
const at = TEXT_MODEL_PROVIDERS[LIVE_PROVIDER.id].keysAt;
|
|
34
|
+
if (!at) return null;
|
|
35
|
+
return (
|
|
36
|
+
<a
|
|
37
|
+
href={at}
|
|
38
|
+
target="_blank"
|
|
39
|
+
rel="noreferrer"
|
|
40
|
+
className="flex items-center gap-2.5 text-[13px] font-medium text-foreground no-underline"
|
|
41
|
+
>
|
|
42
|
+
<span className="flex h-7.5 shrink-0 items-center gap-1.5 rounded-full bg-muted px-3.5 whitespace-nowrap transition-colors hover:bg-accent">
|
|
43
|
+
Get a key
|
|
44
|
+
<ArrowUpRight className="size-3.5" />
|
|
45
|
+
</span>
|
|
46
|
+
<span className="truncate font-mono text-[11px] font-normal text-muted-foreground/70">
|
|
47
|
+
{at.replace(/^https:\/\//, "")}
|
|
48
|
+
</span>
|
|
49
|
+
</a>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
30
53
|
/** The OpenAI key for Live voice and Responses delegation. Used by the intro and by the call screen while no key is set. */
|
|
31
54
|
export function VoiceKeys({
|
|
32
55
|
ref,
|
|
@@ -45,6 +45,10 @@ export type ConfigEntry = {
|
|
|
45
45
|
recommended?: true;
|
|
46
46
|
/** The service's own site, for a key that is no model provider's: its icon is the row's mark. */
|
|
47
47
|
site?: string;
|
|
48
|
+
/** Where the key is made: the link its dialog offers. */
|
|
49
|
+
keysAt?: string;
|
|
50
|
+
/** How the key begins, shown in its empty field in place of the setting's name. */
|
|
51
|
+
keyLooks?: string;
|
|
48
52
|
};
|
|
49
53
|
|
|
50
54
|
/**
|
|
@@ -179,6 +183,8 @@ const keyEntry = (
|
|
|
179
183
|
label: provider.label,
|
|
180
184
|
provider: provider.id,
|
|
181
185
|
signIn: provider.signIn,
|
|
186
|
+
keysAt: provider.keysAt,
|
|
187
|
+
keyLooks: provider.keyLooks,
|
|
182
188
|
});
|
|
183
189
|
|
|
184
190
|
export const CONFIG_GROUPS: ConfigGroup[] = [
|
|
@@ -227,6 +233,7 @@ export const CONFIG_GROUPS: ConfigGroup[] = [
|
|
|
227
233
|
label: "Exa",
|
|
228
234
|
hint: "web search in one call — dashboard.exa.ai",
|
|
229
235
|
site: "exa.ai",
|
|
236
|
+
keysAt: "https://dashboard.exa.ai/api-keys",
|
|
230
237
|
},
|
|
231
238
|
],
|
|
232
239
|
},
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
ArrowUpRight,
|
|
5
4
|
Check,
|
|
6
5
|
ChevronLeft,
|
|
7
6
|
ChevronRight,
|
|
@@ -31,7 +30,7 @@ import { BOT_SEEDS, type BotSeed } from "@/features/bot/bot.seed";
|
|
|
31
30
|
import { BotMark } from "@/features/bot/components/bot-mark";
|
|
32
31
|
import { installSeedBots } from "@/features/bot/seed-bots";
|
|
33
32
|
import { AccountsSetup } from "@/features/config/components/config-setting";
|
|
34
|
-
import { VoiceKeys } from "@/features/config/components/voice-key";
|
|
33
|
+
import { GetKeyLink, VoiceKeys } from "@/features/config/components/voice-key";
|
|
35
34
|
import {
|
|
36
35
|
removeConfigAction,
|
|
37
36
|
setConfigAction,
|
|
@@ -464,20 +463,7 @@ function KeyTurn({ keyed, onSaved }: { keyed: boolean; onSaved: () => void }) {
|
|
|
464
463
|
<>
|
|
465
464
|
<Mine>Paste your OpenAI API key</Mine>
|
|
466
465
|
<VoiceKeys dense plain autoFocus onSaved={onSaved} />
|
|
467
|
-
<
|
|
468
|
-
href="https://platform.openai.com/api-keys"
|
|
469
|
-
target="_blank"
|
|
470
|
-
rel="noreferrer"
|
|
471
|
-
className="flex items-center gap-2.5 text-[13px] font-medium text-foreground no-underline"
|
|
472
|
-
>
|
|
473
|
-
<span className="flex h-7.5 shrink-0 items-center gap-1.5 rounded-full bg-muted px-3.5 whitespace-nowrap transition-colors hover:bg-accent">
|
|
474
|
-
Get a key
|
|
475
|
-
<ArrowUpRight className="size-3.5" />
|
|
476
|
-
</span>
|
|
477
|
-
<span className="truncate font-mono text-[11px] font-normal text-muted-foreground/70">
|
|
478
|
-
platform.openai.com/api-keys
|
|
479
|
-
</span>
|
|
480
|
-
</a>
|
|
466
|
+
<GetKeyLink />
|
|
481
467
|
<ol className="flex flex-col gap-1.75 text-[13px] text-muted-foreground">
|
|
482
468
|
{[
|
|
483
469
|
"Sign in, or make an account",
|
|
@@ -823,7 +809,6 @@ function useDemo(playing: boolean) {
|
|
|
823
809
|
|
|
824
810
|
/** The pill and the corner where finished work lands, as they stand during the loop. */
|
|
825
811
|
function DemoCorners({ stage, icons }: { stage: DemoStage; icons: BotIcon[] }) {
|
|
826
|
-
const crew = BOT_SEEDS.filter((seed) => seed.recommended);
|
|
827
812
|
return (
|
|
828
813
|
<>
|
|
829
814
|
{/* The card finished work really lands as, drawn here with nothing behind it */}
|
|
@@ -843,12 +828,12 @@ function DemoCorners({ stage, icons }: { stage: DemoStage; icons: BotIcon[] }) {
|
|
|
843
828
|
)}
|
|
844
829
|
<div className="absolute right-5 bottom-5 flex h-10 items-center gap-2.5 rounded-full bg-background pr-3.5 pl-2.5 ring-1 ring-border">
|
|
845
830
|
<span className="flex">
|
|
846
|
-
{
|
|
831
|
+
{BOT_SEEDS.map((seed, index) => (
|
|
847
832
|
<BotMark
|
|
848
833
|
key={seed.name}
|
|
849
834
|
size={22}
|
|
850
835
|
seed={seed.name}
|
|
851
|
-
{...icons[
|
|
836
|
+
{...icons[index]}
|
|
852
837
|
state={
|
|
853
838
|
stage === "working" && seed.name === "Analyst"
|
|
854
839
|
? "thinking"
|
package/features/reach/reach.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
2
2
|
import type { ModelMessage } from "ai";
|
|
3
3
|
import { appEvents } from "@/app/api/events/app-event.server";
|
|
4
4
|
import { REACH } from "@/config";
|
|
5
|
+
import { LiveSettingsSchema } from "@/features/ai/live.schema";
|
|
5
6
|
import { modelErrorToString } from "@/features/ai/model";
|
|
6
7
|
import { answerThread } from "@/features/bot/bot.runner";
|
|
7
8
|
import type { Thread } from "@/features/bot/bot.schema";
|
|
@@ -14,7 +15,6 @@ import {
|
|
|
14
15
|
} from "@/features/config/config.query";
|
|
15
16
|
import { openWork } from "@/features/thursday/open-work";
|
|
16
17
|
import { endCall, isCallOpen } from "@/features/thursday/thursday.query";
|
|
17
|
-
import { ThursdaySettingsSchema } from "@/features/thursday/thursday.schema";
|
|
18
18
|
import {
|
|
19
19
|
answerInWriting,
|
|
20
20
|
openTextCall,
|
|
@@ -353,7 +353,7 @@ async function answer(
|
|
|
353
353
|
4_000,
|
|
354
354
|
);
|
|
355
355
|
try {
|
|
356
|
-
const settings =
|
|
356
|
+
const settings = LiveSettingsSchema.parse({});
|
|
357
357
|
// Quiet for long enough, or closed under it (the tab went, the server restarted): the
|
|
358
358
|
// next words open a new call, which reads the last one back under Earlier calls
|
|
359
359
|
const kept = live.line;
|
|
@@ -187,14 +187,15 @@ export function SettingFilter({
|
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* What a nav row reports about its section. Amber counts what waits on you, red
|
|
190
|
-
* marks what failed — the app's two colours
|
|
191
|
-
*
|
|
190
|
+
* marks what failed — the app's two status colours — and a blue dot says a
|
|
191
|
+
* section is worth setting up though nothing waits on it. The nav carries no
|
|
192
|
+
* others. A count of 0 draws nothing, so a section at rest stays quiet.
|
|
192
193
|
*/
|
|
193
194
|
export function NavBadge({
|
|
194
195
|
tone,
|
|
195
196
|
count,
|
|
196
197
|
}: {
|
|
197
|
-
tone: "amber" | "red";
|
|
198
|
+
tone: "amber" | "red" | "brand";
|
|
198
199
|
/** Omitted draws a dot: the section has something wrong, not a number of things. */
|
|
199
200
|
count?: number;
|
|
200
201
|
}) {
|
|
@@ -204,7 +205,11 @@ export function NavBadge({
|
|
|
204
205
|
<span
|
|
205
206
|
className={cn(
|
|
206
207
|
"size-1.5 shrink-0 rounded-full",
|
|
207
|
-
tone === "red"
|
|
208
|
+
tone === "red"
|
|
209
|
+
? "bg-destructive"
|
|
210
|
+
: tone === "brand"
|
|
211
|
+
? "bg-brand"
|
|
212
|
+
: "bg-amber-600 dark:bg-amber-400",
|
|
208
213
|
)}
|
|
209
214
|
/>
|
|
210
215
|
);
|
|
@@ -212,7 +217,11 @@ export function NavBadge({
|
|
|
212
217
|
<span
|
|
213
218
|
className={cn(
|
|
214
219
|
"shrink-0 font-mono text-[11px]",
|
|
215
|
-
tone === "red"
|
|
220
|
+
tone === "red"
|
|
221
|
+
? "text-destructive"
|
|
222
|
+
: tone === "brand"
|
|
223
|
+
? "text-brand"
|
|
224
|
+
: WAITING_INK,
|
|
216
225
|
)}
|
|
217
226
|
>
|
|
218
227
|
{count}
|
|
@@ -31,6 +31,7 @@ import { Segmented } from "@/components/ui/segmented";
|
|
|
31
31
|
import { BotBadge } from "@/features/bot/components/bot-badge";
|
|
32
32
|
import { BotsMark } from "@/features/bot/components/bot-mark";
|
|
33
33
|
import { ThreadBadge } from "@/features/bot/components/thread-badge";
|
|
34
|
+
import { ConfigBadge } from "@/features/config/components/config-badge";
|
|
34
35
|
import { ModelsBadge } from "@/features/config/components/models-badge";
|
|
35
36
|
import { McpBadge } from "@/features/connectors/components/mcp-badge";
|
|
36
37
|
import { McpMark } from "@/features/connectors/components/mcp-mark";
|
|
@@ -269,6 +270,7 @@ export const SECTIONS: readonly {
|
|
|
269
270
|
hint: "What bots think with, and what they draw, film and speak with",
|
|
270
271
|
icon: Aperture,
|
|
271
272
|
Component: ModelsSetting,
|
|
273
|
+
Badge: ModelsBadge,
|
|
272
274
|
},
|
|
273
275
|
{
|
|
274
276
|
id: "keys",
|
|
@@ -277,7 +279,7 @@ export const SECTIONS: readonly {
|
|
|
277
279
|
hint: "The accounts the app runs on",
|
|
278
280
|
icon: KeyRound,
|
|
279
281
|
Component: KeysSetting,
|
|
280
|
-
Badge:
|
|
282
|
+
Badge: ConfigBadge,
|
|
281
283
|
},
|
|
282
284
|
{
|
|
283
285
|
id: "phone",
|
|
@@ -8,17 +8,19 @@ import { useMcpAlert } from "@/features/connectors/components/mcp-badge";
|
|
|
8
8
|
import type { SettingSectionId } from "./settings.store";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* What a section is reporting
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* What a section is reporting: amber waits on the user and red is broken — the
|
|
12
|
+
* app's two status colours — and brand blue is worth setting up though nothing
|
|
13
|
+
* waits on it (the user's pick). A section at rest reports nothing. The fact
|
|
14
|
+
* lives here so both places that draw it stay in step — the nav row (which can
|
|
15
|
+
* afford a count) and the call screen's corner (which cannot).
|
|
15
16
|
*/
|
|
16
|
-
export type SectionAlert = "amber" | "red" | null;
|
|
17
|
+
export type SectionAlert = "amber" | "red" | "brand" | null;
|
|
17
18
|
|
|
18
|
-
/** Broken outranks waiting; one of them is the whole ladder. */
|
|
19
|
+
/** Broken outranks waiting, and waiting outranks a suggestion; one of them is the whole ladder. */
|
|
19
20
|
export function worstAlert(alerts: SectionAlert[]): SectionAlert {
|
|
20
21
|
if (alerts.includes("red")) return "red";
|
|
21
|
-
|
|
22
|
+
if (alerts.includes("amber")) return "amber";
|
|
23
|
+
return alerts.includes("brand") ? "brand" : null;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -32,8 +34,7 @@ export function useSectionAlerts(): Partial<
|
|
|
32
34
|
const bot = useBotAlert();
|
|
33
35
|
const threads = useThreadAlert();
|
|
34
36
|
const mcp = useMcpAlert();
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
return { bot, threads, mcp, models: worstAlert([config, studio]) };
|
|
37
|
+
const keys = useConfigAlert();
|
|
38
|
+
const models = useModelsAlert();
|
|
39
|
+
return { bot, threads, mcp, keys, models };
|
|
39
40
|
}
|
|
@@ -2,7 +2,11 @@ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { appEvents } from "@/app/api/events/app-event.server";
|
|
4
4
|
import { DATA_DIR, PATHS } from "@/config";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
jobShellEnv,
|
|
7
|
+
openWorkspace,
|
|
8
|
+
WORKSPACE,
|
|
9
|
+
} from "@/features/workspace/workspace";
|
|
6
10
|
import { logger } from "@/lib/logger";
|
|
7
11
|
import { type SignIn, siteOf } from "./signins.schema";
|
|
8
12
|
|
|
@@ -100,6 +104,67 @@ export async function borrowSignIn(
|
|
|
100
104
|
return { kind: "state", signIn: record(used), state: kept.state };
|
|
101
105
|
}
|
|
102
106
|
|
|
107
|
+
type Cookie = { name: string; domain: string; path: string; value: string };
|
|
108
|
+
type State = { cookies?: Cookie[] };
|
|
109
|
+
|
|
110
|
+
const cookieKey = (cookie: Cookie) =>
|
|
111
|
+
`${cookie.name}\n${cookie.domain}\n${cookie.path}`;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A site renews its session cookies while the session is used, and the copy kept here goes
|
|
115
|
+
* stale: lent again, it can be refused, or end the session it was copied from. So after a
|
|
116
|
+
* bot's turn its browser's cookies go back into every kept sign-in — only the ones a sign-in
|
|
117
|
+
* already holds (same name, domain and path), so a browser that signed out, or never signed
|
|
118
|
+
* in, changes nothing. `session` is the participant's (workspace botBrowserSession); one
|
|
119
|
+
* attached to the user's own Chrome is theirs and is not read.
|
|
120
|
+
*/
|
|
121
|
+
export async function renewSignIns(session: string): Promise<void> {
|
|
122
|
+
const names = await readdir(VAULT).catch(() => [] as string[]);
|
|
123
|
+
if (!names.some((name) => name.endsWith(".json"))) return;
|
|
124
|
+
|
|
125
|
+
const sandbox = await openWorkspace();
|
|
126
|
+
const env = jobShellEnv(session);
|
|
127
|
+
const listed = await sandbox.exec("playwright-cli list --json", {
|
|
128
|
+
env,
|
|
129
|
+
timeoutMs: 15_000,
|
|
130
|
+
});
|
|
131
|
+
const { browsers } = JSON.parse(listed.stdout || "{}") as {
|
|
132
|
+
browsers?: { name: string; attached?: boolean }[];
|
|
133
|
+
};
|
|
134
|
+
const open = browsers?.find((b) => b.name === env.PLAYWRIGHT_CLI_SESSION);
|
|
135
|
+
if (!open || open.attached) return;
|
|
136
|
+
|
|
137
|
+
const path = `.playwright-cli/state-${crypto.randomUUID()}.json`;
|
|
138
|
+
let now: Map<string, Cookie>;
|
|
139
|
+
try {
|
|
140
|
+
const saved = await sandbox.exec(
|
|
141
|
+
`mkdir -p .playwright-cli && playwright-cli state-save ${path}`,
|
|
142
|
+
{ env, timeoutMs: 30_000 },
|
|
143
|
+
);
|
|
144
|
+
if (saved.exitCode !== 0) return;
|
|
145
|
+
const state = JSON.parse(await sandbox.readFile(path, "utf-8")) as State;
|
|
146
|
+
now = new Map((state.cookies ?? []).map((c) => [cookieKey(c), c]));
|
|
147
|
+
} finally {
|
|
148
|
+
await sandbox.exec(`rm -f ${path}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const name of names) {
|
|
152
|
+
if (!name.endsWith(".json")) continue;
|
|
153
|
+
const kept = await read(decodeURIComponent(name.slice(0, -5)));
|
|
154
|
+
const state = kept?.state as State | undefined;
|
|
155
|
+
if (!kept || !state?.cookies) continue;
|
|
156
|
+
let renewed = false;
|
|
157
|
+
const cookies = state.cookies.map((cookie) => {
|
|
158
|
+
const fresh = now.get(cookieKey(cookie));
|
|
159
|
+
if (!fresh || JSON.stringify(fresh) === JSON.stringify(cookie))
|
|
160
|
+
return cookie;
|
|
161
|
+
renewed = true;
|
|
162
|
+
return fresh;
|
|
163
|
+
});
|
|
164
|
+
if (renewed) await write({ ...kept, state: { ...state, cookies } });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
103
168
|
/** The user's say on one bot: let in, or not any more. Only the screen calls this. */
|
|
104
169
|
export async function setSignInBot(site: string, bot: string, on: boolean) {
|
|
105
170
|
const kept = await read(site);
|
|
@@ -123,6 +123,7 @@ export function SideCaptions({
|
|
|
123
123
|
under = null,
|
|
124
124
|
ahead = false,
|
|
125
125
|
workOf,
|
|
126
|
+
typed = false,
|
|
126
127
|
}: {
|
|
127
128
|
turns: Turn[];
|
|
128
129
|
pinned: Pinned;
|
|
@@ -139,6 +140,8 @@ export function SideCaptions({
|
|
|
139
140
|
ahead?: boolean;
|
|
140
141
|
/** The work behind one of her turns, drawn under it while it is level; null for none. */
|
|
141
142
|
workOf?: (turn: string) => ReactNode;
|
|
143
|
+
/** A call in writing: their words were typed whole, so they arrive whole. */
|
|
144
|
+
typed?: boolean;
|
|
142
145
|
}) {
|
|
143
146
|
const last = turns.at(-1);
|
|
144
147
|
return (
|
|
@@ -154,6 +157,7 @@ export function SideCaptions({
|
|
|
154
157
|
under={role === "assistant" ? under : null}
|
|
155
158
|
ahead={role === "assistant" && ahead}
|
|
156
159
|
workOf={role === "assistant" ? workOf : undefined}
|
|
160
|
+
whole={typed && role === "user"}
|
|
157
161
|
/>
|
|
158
162
|
))}
|
|
159
163
|
</>
|
|
@@ -169,6 +173,7 @@ function SideColumn({
|
|
|
169
173
|
under,
|
|
170
174
|
ahead,
|
|
171
175
|
workOf,
|
|
176
|
+
whole,
|
|
172
177
|
}: {
|
|
173
178
|
role: Role;
|
|
174
179
|
/** This side's turns only. */
|
|
@@ -180,28 +185,38 @@ function SideColumn({
|
|
|
180
185
|
under: ReactNode;
|
|
181
186
|
ahead: boolean;
|
|
182
187
|
workOf: ((turn: string) => ReactNode) | undefined;
|
|
188
|
+
/** The latest turn is drawn at once rather than letter by letter. */
|
|
189
|
+
whole: boolean;
|
|
183
190
|
}) {
|
|
184
191
|
const mine = role === "user";
|
|
185
192
|
// Heights as laid out, before any transform: text grows while it is said, and
|
|
186
193
|
// receding clamps a turn to four lines
|
|
187
194
|
const [heights, setHeights] = useState<Record<string, number>>({});
|
|
195
|
+
// Widths too: what stands under her level words is as wide as they are
|
|
196
|
+
const [widths, setWidths] = useState<Record<string, number>>({});
|
|
188
197
|
const watch = useRef<ResizeObserver | null>(null);
|
|
189
198
|
useEffect(() => () => watch.current?.disconnect(), []);
|
|
190
199
|
const measure = useCallback((node: HTMLDivElement | null) => {
|
|
191
200
|
if (!node) return;
|
|
192
|
-
|
|
193
|
-
|
|
201
|
+
const sizes =
|
|
202
|
+
(read: (box: HTMLElement) => number) =>
|
|
203
|
+
(was: Record<string, number>, entries: ResizeObserverEntry[]) => {
|
|
194
204
|
let next = was;
|
|
195
205
|
for (const entry of entries) {
|
|
196
206
|
const box = entry.target as HTMLElement;
|
|
197
207
|
const id = box.dataset.turn;
|
|
198
|
-
if (!id || was[id] === box
|
|
208
|
+
if (!id || was[id] === read(box)) continue;
|
|
199
209
|
if (next === was) next = { ...was };
|
|
200
|
-
next[id] = box
|
|
210
|
+
next[id] = read(box);
|
|
201
211
|
}
|
|
202
212
|
return next;
|
|
203
|
-
}
|
|
204
|
-
);
|
|
213
|
+
};
|
|
214
|
+
const tall = sizes((box) => box.offsetHeight);
|
|
215
|
+
const wide = sizes((box) => box.offsetWidth);
|
|
216
|
+
watch.current ??= new ResizeObserver((entries) => {
|
|
217
|
+
setHeights((was) => tall(was, entries));
|
|
218
|
+
setWidths((was) => wide(was, entries));
|
|
219
|
+
});
|
|
205
220
|
watch.current.observe(node);
|
|
206
221
|
return () => watch.current?.unobserve(node);
|
|
207
222
|
}, []);
|
|
@@ -273,6 +288,9 @@ function SideColumn({
|
|
|
273
288
|
}
|
|
274
289
|
className={cn(
|
|
275
290
|
"group/turn absolute top-0 w-max max-w-full text-[17px] leading-[1.675] tracking-[0.3px] text-foreground transition-transform duration-[520ms] ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
|
|
291
|
+
whole &&
|
|
292
|
+
k === turns.length - 1 &&
|
|
293
|
+
"animate-in fade-in duration-200 motion-reduce:animate-none",
|
|
276
294
|
mine ? "left-0 origin-top-left" : "right-0 origin-top-right",
|
|
277
295
|
!shown && "invisible",
|
|
278
296
|
pickable && "pointer-events-auto cursor-pointer",
|
|
@@ -299,7 +317,7 @@ function SideColumn({
|
|
|
299
317
|
/>
|
|
300
318
|
)}
|
|
301
319
|
{/* Each side's latest turn keeps its letters wherever it sits, so going back and returning never draws it again */}
|
|
302
|
-
{k === turns.length - 1 ? (
|
|
320
|
+
{k === turns.length - 1 && !whole ? (
|
|
303
321
|
<Letters text={turn.text} />
|
|
304
322
|
) : (
|
|
305
323
|
turn.text
|
|
@@ -312,7 +330,12 @@ function SideColumn({
|
|
|
312
330
|
<div
|
|
313
331
|
ref={measure}
|
|
314
332
|
data-turn={UNDER}
|
|
315
|
-
style={{
|
|
333
|
+
style={{
|
|
334
|
+
transform: `translateY(${(underTop ?? 0).toFixed(1)}px)`,
|
|
335
|
+
// under her words it takes their width, so its lines start where they do;
|
|
336
|
+
// before she has said any, the side's
|
|
337
|
+
width: at < turns.length ? widths[turns[at].id] : undefined,
|
|
338
|
+
}}
|
|
316
339
|
className={cn(
|
|
317
340
|
"absolute top-0 flex w-full transition-transform duration-[520ms] ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none",
|
|
318
341
|
mine ? "left-0 justify-start" : "right-0 justify-end",
|
|
@@ -50,7 +50,8 @@ function tabIconSvg(alert: SectionAlert): string {
|
|
|
50
50
|
* The tab, for when the app is not the window in front. The title leads with
|
|
51
51
|
* what is owed — the Threads badge's count — and a live call keeps its waveform at
|
|
52
52
|
* the end, so neither takes the other's place. The icon wears the settings
|
|
53
|
-
* corner's dot: amber waits on the user, red is broken
|
|
53
|
+
* corner's dot when something is owed: amber waits on the user, red is broken
|
|
54
|
+
* (a blue suggestion stays in the corner), and a section with
|
|
54
55
|
* nothing to count still gets the dot.
|
|
55
56
|
*
|
|
56
57
|
* Draws nothing. A component rather than a hook in the call screen, so a report
|
|
@@ -66,7 +67,9 @@ export function TabState({
|
|
|
66
67
|
}) {
|
|
67
68
|
const { owed } = useThreadReport();
|
|
68
69
|
const alerts = useSectionAlerts();
|
|
69
|
-
const
|
|
70
|
+
const worst = worstAlert(Object.values(alerts).map((each) => each ?? null));
|
|
71
|
+
// A suggestion is for the settings corner, not the tab: from another window nothing is owed
|
|
72
|
+
const alert = worst === "brand" ? null : worst;
|
|
70
73
|
|
|
71
74
|
useEffect(() => {
|
|
72
75
|
const name = ringing ? `${APP_NAME} is calling` : APP_NAME;
|