@hienlh/ppm 0.17.15 → 0.17.17
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/CHANGELOG.md +23 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +5 -1
- package/dist/web/assets/{ai-resource-editor-DfCN7LjR.js → ai-resource-editor-37UxM2lK.js} +1 -1
- package/dist/web/assets/{angular-html-DkR4iCth.js → angular-html-2g8YGiJY.js} +1 -1
- package/dist/web/assets/{angular-ts-DcyL4Chx.js → angular-ts-BwSmoLfh.js} +1 -1
- package/dist/web/assets/{api-settings-DAk7D-NP.js → api-settings-BJTjIG4U.js} +1 -1
- package/dist/web/assets/{api-themes-BdQCjXQk.js → api-themes-DuLpbQFV.js} +1 -1
- package/dist/web/assets/{apl-BVlrVtjt.js → apl-B44SMKHp.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-gLlhsjM9.js +1 -0
- package/dist/web/assets/{astro-MjJlozLj.js → astro-E5620wek.js} +1 -1
- package/dist/web/assets/{audio-preview-DbFUkIzm.js → audio-preview-BG8uEB67.js} +1 -1
- package/dist/web/assets/{blade-DnJWi1fz.js → blade-D-VQWAHB.js} +1 -1
- package/dist/web/assets/{bsl-DCmJxSIz.js → bsl-Bq5qFzpU.js} +1 -1
- package/dist/web/assets/{cairo-C8dDenub.js → cairo-Cn9-ZKRa.js} +1 -1
- package/dist/web/assets/chat-tab-6G5LulPM.js +13 -0
- package/dist/web/assets/{cobol-DPx_nifl.js → cobol-IM3E3iCH.js} +1 -1
- package/dist/web/assets/code-editor-BlBFI3jn.js +10 -0
- package/dist/web/assets/{coffee-DvFzqhgN.js → coffee-24wG7ekn.js} +1 -1
- package/dist/web/assets/{conflict-editor-BAAffNdj.js → conflict-editor-CYZjqn46.js} +1 -1
- package/dist/web/assets/{cpp-BLJN0umi.js → cpp-DH9Cqt4g.js} +1 -1
- package/dist/web/assets/{crystal-BXR-9fGw.js → crystal-4nZYHhdi.js} +1 -1
- package/dist/web/assets/{csv-preview-DzhSC0fd.js → csv-preview-YxR9adj_.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-cOzDcGkl.js → data-grid-overlay-editor-DxdOKRAY.js} +1 -1
- package/dist/web/assets/{database-viewer-DxmL2veb.js → database-viewer-CrdTyPIN.js} +1 -1
- package/dist/web/assets/dialog-BpkAWOy8.js +1 -0
- package/dist/web/assets/{diff-viewer-DvXxlk47.js → diff-viewer-DiYj_kRC.js} +1 -1
- package/dist/web/assets/{docx-preview-DfjH-dIr.js → docx-preview-Dsrm2Hgu.js} +1 -1
- package/dist/web/assets/{edge-rWOlCmXT.js → edge-DTI9d2Qi.js} +1 -1
- package/dist/web/assets/{elixir-E7Y82okW.js → elixir-Ys0wioho.js} +1 -1
- package/dist/web/assets/{elm-DTPGbc-w.js → elm-Cihc9TAz.js} +1 -1
- package/dist/web/assets/{erb-DHqLHrE6.js → erb-BwYBWK2-.js} +1 -1
- package/dist/web/assets/{erlang-Byg0nNiV.js → erlang-CzlD_Joo.js} +1 -1
- package/dist/web/assets/{esm-DU7axMjq.js → esm-JLkfN0k-.js} +1 -1
- package/dist/web/assets/{extension-webview-wuMQ8pD7.js → extension-webview-B8ph2gI9.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CY1n0Xf6.js → fortran-fixed-form-CT90EtSp.js} +1 -1
- package/dist/web/assets/{fsharp-_vEDSCJz.js → fsharp-LasxezlU.js} +1 -1
- package/dist/web/assets/{gdresource-B6XT0wAC.js → gdresource-XqgPdfiE.js} +1 -1
- package/dist/web/assets/{git-commit-Wk9ovq59.js → git-commit-CZBgPolw.js} +1 -1
- package/dist/web/assets/{git-log-panel-SPPYmbCr.js → git-log-panel-qIsOpM57.js} +1 -1
- package/dist/web/assets/{git-rebase-DSarGPvK.js → git-rebase-C2VzKDB1.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BKGq--Jw.js +1 -0
- package/dist/web/assets/{glide-data-grid-BC0AHzMn.js → glide-data-grid-BrQskOnP.js} +8 -8
- package/dist/web/assets/{glimmer-js-CkYI35_P.js → glimmer-js-DfjWcOLD.js} +1 -1
- package/dist/web/assets/{glimmer-ts-BqmIZcds.js → glimmer-ts-DBldQATc.js} +1 -1
- package/dist/web/assets/{glsl-Be-yBCF5.js → glsl-CzNv1NPP.js} +1 -1
- package/dist/web/assets/{graphql-a5DrENHY.js → graphql-DDyLtQb4.js} +1 -1
- package/dist/web/assets/group-chat-tab-DYgRs0zS.js +1 -0
- package/dist/web/assets/{hack-BLAg20aD.js → hack-CtoYNYkC.js} +1 -1
- package/dist/web/assets/{haml-Dj6bGOQH.js → haml-CnBVap7G.js} +1 -1
- package/dist/web/assets/{handlebars-dmEa36Oh.js → handlebars-Dg51esUH.js} +1 -1
- package/dist/web/assets/{html-yacZYzSE.js → html-Ec2_evSy.js} +1 -1
- package/dist/web/assets/{html-derivative-lLi3-RNT.js → html-derivative-Cy-F7ZqL.js} +1 -1
- package/dist/web/assets/{http-1KaxcE4e.js → http-C236v8Tg.js} +1 -1
- package/dist/web/assets/{hurl-ST-dEEZR.js → hurl-0rMD2gPU.js} +1 -1
- package/dist/web/assets/{hxml-CpDtuq__.js → hxml-e5uS5jTD.js} +1 -1
- package/dist/web/assets/{image-preview-D_8o6OLx.js → image-preview-B9EWGEFv.js} +1 -1
- package/dist/web/assets/index-BYFXiFsa.css +2 -0
- package/dist/web/assets/index-BiHrB3KU.js +50 -0
- package/dist/web/assets/info-3K5VOQVL-BOhGriBF.js +1 -0
- package/dist/web/assets/{jinja-Cjw-evAn.js → jinja-DybFmhfq.js} +1 -1
- package/dist/web/assets/{jison-DfATMYoY.js → jison-hkHbixdZ.js} +1 -1
- package/dist/web/assets/{julia-DDCaxiqT.js → julia-QnxY6sRE.js} +1 -1
- package/dist/web/assets/{just-CJL529u6.js → just-1Rs-nm8g.js} +1 -1
- package/dist/web/assets/keybindings-store-CWi8IGEx.js +1 -0
- package/dist/web/assets/{latex-Zc4EpLpW.js → latex-jEBQqA7u.js} +1 -1
- package/dist/web/assets/{liquid-B_6KMNYS.js → liquid-DfJaZpxG.js} +1 -1
- package/dist/web/assets/{lua-BtS2idK7.js → lua-ti0Jr6-C.js} +1 -1
- package/dist/web/assets/{markdown-renderer-DVVx3WCU.js → markdown-renderer-B4rtk2Hq.js} +3 -3
- package/dist/web/assets/markdown-renderer-XIhxiUQq.js +1 -0
- package/dist/web/assets/{marko-Wk6LBnck.js → marko-DNj4AhPg.js} +1 -1
- package/dist/web/assets/{mdc-cRaXBrzL.js → mdc-DPFwQFe4.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet--5_YEewV.js +1 -0
- package/dist/web/assets/{nextflow-Bos2wvDV.js → nextflow-BUrFV5aB.js} +1 -1
- package/dist/web/assets/{nginx-DMlOwzdr.js → nginx-BrW9NdOb.js} +1 -1
- package/dist/web/assets/{nim-D2dZQA88.js → nim-DS_6PF2p.js} +1 -1
- package/dist/web/assets/notification-store-DEIj7OrE.js +1 -0
- package/dist/web/assets/{number-overlay-editor-Du_UgiD3.js → number-overlay-editor-CReXWiQ1.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-CdLFnL8a.js +1 -0
- package/dist/web/assets/{panel-store-D6RV_ztx.js → panel-store-DnbT1c8L.js} +1 -1
- package/dist/web/assets/{pdf-preview-HTC73FyK.js → pdf-preview-Zl3KICd5.js} +1 -1
- package/dist/web/assets/{perl-C5-Z6fPn.js → perl-ytm9vKO_.js} +1 -1
- package/dist/web/assets/{php-BXNNhkMH.js → php-DWTD1Usw.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-COC-h0zz.js +1 -0
- package/dist/web/assets/{trash-2-CbbD5QF1.js → plus-DcuJuDC3.js} +1 -1
- package/dist/web/assets/{postgres-viewer-pWjwP3GD.js → postgres-viewer-BTupZTmP.js} +3 -3
- package/dist/web/assets/{project-store-Cekcjcom.js → project-store-Sbnqc41N.js} +1 -1
- package/dist/web/assets/{pug-C1zf1yWy.js → pug-Bt7KL3_r.js} +1 -1
- package/dist/web/assets/{qml-OW58jGpD.js → qml-Dexdon5J.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-CiCfdJNL.js +1 -0
- package/dist/web/assets/{razor-DzjSaobM.js → razor-DnYCSl0G.js} +1 -1
- package/dist/web/assets/{rst-RfuihOUW.js → rst-BEFYzwF8.js} +1 -1
- package/dist/web/assets/{ruby-CNO3pli1.js → ruby-BPaXFEjN.js} +1 -1
- package/dist/web/assets/{sas-DZY5BtXx.js → sas-CywiNBp8.js} +1 -1
- package/dist/web/assets/{scss--4g-REcN.js → scss-CGk0L6hN.js} +1 -1
- package/dist/web/assets/{settings-store-mWnY4Kt0.js → settings-store-DkRZhj-g.js} +2 -2
- package/dist/web/assets/settings-tab-BF069B1K.js +1 -0
- package/dist/web/assets/{shaderlab-Cu4sBiVn.js → shaderlab-Byjk0kH7.js} +1 -1
- package/dist/web/assets/{shellsession-DWzJfEv7.js → shellsession-DbA70QB6.js} +1 -1
- package/dist/web/assets/{soy-BnqE-5Ff.js → soy-Bm97kWiA.js} +1 -1
- package/dist/web/assets/sparkles-B3DmG8PJ.js +1 -0
- package/dist/web/assets/{sparql-Ctg__NhZ.js → sparql-CawLnniT.js} +1 -1
- package/dist/web/assets/{sql-query-editor-CJdpO95g.js → sql-query-editor-RwHY1p-a.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-BgewX3tM.js → sqlite-viewer-3UxftoM7.js} +1 -1
- package/dist/web/assets/{stata-Qk2h66XW.js → stata-RifEDtNO.js} +1 -1
- package/dist/web/assets/{surrealql-8QEX7EfH.js → surrealql-DGRQa5Lm.js} +1 -1
- package/dist/web/assets/{svelte-D_mubC5-.js → svelte-w9Hb12aM.js} +1 -1
- package/dist/web/assets/system-monitor-tab-Ck1cIP7g.js +1 -0
- package/dist/web/assets/{tab-store-CcLQfu8H.js → tab-store-vn0viMOP.js} +1 -1
- package/dist/web/assets/{templ-C1jV0fsO.js → templ-BpUwjJXV.js} +1 -1
- package/dist/web/assets/{terminal-tab-DWncjHEA.js → terminal-tab-B9opPxft.js} +2 -2
- package/dist/web/assets/{tex-D_StFXy_.js → tex-DJFSsuTD.js} +1 -1
- package/dist/web/assets/tool-cards-C7iLwehG.js +7 -0
- package/dist/web/assets/treemap-KZPCXAKY-CZwuSCtA.js +1 -0
- package/dist/web/assets/{ts-tags-B5m47rEm.js → ts-tags-vp9Qvx2O.js} +1 -1
- package/dist/web/assets/{twig-C_br6GIl.js → twig-CoazxjRB.js} +1 -1
- package/dist/web/assets/{use-blob-url-DpDVcTeK.js → use-blob-url-DdXgVqfz.js} +1 -1
- package/dist/web/assets/use-is-mobile-CE6AamG6.js +1 -0
- package/dist/web/assets/{use-monaco-theme-Ch2jRiGj.js → use-monaco-theme-jtYkroys.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-DP1CEcGn.js → vendor-mermaid-DPpYkrMf.js} +1 -1
- package/dist/web/assets/{video-preview-rWrNCfZX.js → video-preview-Ced_VIvq.js} +1 -1
- package/dist/web/assets/{vue-DQwCnLdr.js → vue-CnshsiXd.js} +1 -1
- package/dist/web/assets/{vue-html-T_EoGpPJ.js → vue-html-CZshynFp.js} +1 -1
- package/dist/web/assets/{vue-vine-DmxLqX50.js → vue-vine-C5gtp_T1.js} +1 -1
- package/dist/web/assets/{xml-Bs6Bw8is.js → xml-BEGJasgj.js} +1 -1
- package/dist/web/assets/{xsl-f5ZNhRCc.js → xsl-DXpRLjYk.js} +1 -1
- package/dist/web/index.html +21 -19
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/schemas/ppm-config.schema.json +3 -3
- package/src/cli/commands/init.ts +8 -5
- package/src/providers/claude-agent-sdk-query-options.ts +57 -0
- package/src/providers/claude-agent-sdk.ts +21 -9
- package/src/server/routes/group-chat.ts +118 -9
- package/src/server/ws/chat.ts +91 -10
- package/src/services/account.service.ts +13 -7
- package/src/services/db.service.ts +31 -1
- package/src/services/group-chat/agent-runner.ts +15 -2
- package/src/services/group-chat/context-window.ts +15 -0
- package/src/services/group-chat/group-chat.service.ts +178 -79
- package/src/services/group-chat/group-chat.store.ts +30 -0
- package/src/services/group-chat/responder-router.ts +110 -0
- package/src/services/group-chat/transcript-archive.ts +18 -0
- package/src/services/group-chat/turn-engine.ts +183 -105
- package/src/services/jsonl-transcript-parser.ts +36 -0
- package/src/types/api.ts +4 -2
- package/src/types/chat.ts +8 -0
- package/src/types/config.ts +4 -9
- package/src/types/group-chat-ws.ts +2 -2
- package/src/types/group-chat.ts +33 -4
- package/src/web/components/chat/chat-tab.tsx +8 -0
- package/src/web/components/chat/message-input.tsx +27 -7
- package/src/web/components/chat/message-list.tsx +48 -3
- package/src/web/components/chat/model-thinking-selector-helpers.ts +33 -0
- package/src/web/components/chat/model-thinking-selector.tsx +189 -0
- package/src/web/components/group-chat/group-chat-tab.tsx +109 -14
- package/src/web/components/group-chat/group-create-dialog.tsx +10 -3
- package/src/web/components/group-chat/group-full-transcript-view.tsx +9 -8
- package/src/web/components/group-chat/group-member-edit-dialog.tsx +108 -0
- package/src/web/components/group-chat/group-member-roster.tsx +51 -23
- package/src/web/components/group-chat/group-message-item.tsx +45 -26
- package/src/web/components/group-chat/group-transcript-messages.tsx +126 -0
- package/src/web/components/layout/mobile-drawer-tab-bar.tsx +10 -0
- package/src/web/components/layout/nav-section-rail.tsx +8 -2
- package/src/web/components/layout/upgrade-button.tsx +33 -11
- package/src/web/components/settings/ai-settings-section.tsx +2 -0
- package/src/web/components/settings/proxy-test-section.tsx +3 -1
- package/src/web/hooks/use-chat.ts +45 -0
- package/src/web/hooks/use-group-chat.ts +8 -1
- package/src/web/lib/api-group-chat.ts +47 -4
- package/src/web/lib/sidebar-tabs/tab-registry.ts +3 -1
- package/dist/web/assets/architecture-PBZL5I3N-CqTF9Xn6.js +0 -1
- package/dist/web/assets/chat-tab-C6nMDb9l.js +0 -18
- package/dist/web/assets/code-editor-C38IIs_y.js +0 -10
- package/dist/web/assets/gitGraph-HDMCJU4V-X12vfQ3i.js +0 -1
- package/dist/web/assets/group-chat-tab-D20bF8q5.js +0 -1
- package/dist/web/assets/index-C1Q59prB.js +0 -50
- package/dist/web/assets/index-CMJ6whqq.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-Bcz-Qhua.js +0 -1
- package/dist/web/assets/input-24Nf_edC.js +0 -1
- package/dist/web/assets/keybindings-store-TINaziU9.js +0 -1
- package/dist/web/assets/notification-store-DN8oDV7o.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-BTwjB70W.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-Cn-lrQYZ.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-Cmppm_Ia.js +0 -1
- package/dist/web/assets/settings-tab-BoRuss3O.js +0 -1
- package/dist/web/assets/sparkles-2ii4lR7b.js +0 -1
- package/dist/web/assets/system-monitor-tab-BA9gqJRW.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-BLWus1wm.js +0 -1
- package/dist/web/assets/use-websocket-MvxbuTdy.js +0 -1
- /package/dist/web/assets/{abap-tkptwpsK.js → abap-wT6fgqwv.js} +0 -0
- /package/dist/web/assets/{actionscript-3-Di9TMGwg.js → actionscript-3-CqcHpPYj.js} +0 -0
- /package/dist/web/assets/{ada-CB9F079D.js → ada-DWE3Hn4E.js} +0 -0
- /package/dist/web/assets/{andromeeda-Dcl8jpb7.js → andromeeda-DpQ-y-Jt.js} +0 -0
- /package/dist/web/assets/{apache-D0Nk0qsH.js → apache-LRTUP4r3.js} +0 -0
- /package/dist/web/assets/{apex-mQIoWncD.js → apex-DwVeH5_h.js} +0 -0
- /package/dist/web/assets/{api-client-Dvzcc_EO.js → api-client-BK4NPNoY.js} +0 -0
- /package/dist/web/assets/{applescript-C6ljbcsI.js → applescript-DyQiO9JU.js} +0 -0
- /package/dist/web/assets/{apply-theme-ORrEdHKT.js → apply-theme-DxKsrDLA.js} +0 -0
- /package/dist/web/assets/{ara-WFqRPGPe.js → ara-Cf-OWd2S.js} +0 -0
- /package/dist/web/assets/{asciidoc-bNd8w4nf.js → asciidoc-gocbKcVK.js} +0 -0
- /package/dist/web/assets/{asm-BQ1_F580.js → asm-C3meb5HY.js} +0 -0
- /package/dist/web/assets/{aurora-x-Ciqs_Dbu.js → aurora-x-BYcXaCIg.js} +0 -0
- /package/dist/web/assets/{awk-GCR-6KZV.js → awk-BcT6nIdN.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DxJXuj99.js → ayu-dark-CLZPoccg.js} +0 -0
- /package/dist/web/assets/{ayu-light-jvSyJWDV.js → ayu-light-CTyRQdS2.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-Cg3BPO6H.js → ayu-mirage-DjqfqZdo.js} +0 -0
- /package/dist/web/assets/{ballerina-BPz4CrX6.js → ballerina-BCARR-zK.js} +0 -0
- /package/dist/web/assets/{bat-CSTs2hIG.js → bat-_yaRjWOb.js} +0 -0
- /package/dist/web/assets/{beancount-DMateAZX.js → beancount-BPESBMS9.js} +0 -0
- /package/dist/web/assets/{berry-9lXg0GRM.js → berry-BSLE-Sf8.js} +0 -0
- /package/dist/web/assets/{bibtex-DIbqmvFg.js → bibtex-CvUWWCVx.js} +0 -0
- /package/dist/web/assets/{bicep-02WGsoJ8.js → bicep--vcqqALZ.js} +0 -0
- /package/dist/web/assets/{bird2-BY0tbu3k.js → bird2-Clclqgh6.js} +0 -0
- /package/dist/web/assets/{c-jMBX_9Je.js → c-CSC2LwTp.js} +0 -0
- /package/dist/web/assets/{c3-B8JVbNjW.js → c3-Cl1_jUnm.js} +0 -0
- /package/dist/web/assets/{cadence-DUIf1tvL.js → cadence-BwAD7vSC.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-FhUCVxex.js → catppuccin-frappe-hr0HKoFX.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CzfrtmgL.js → catppuccin-latte-DXEIYlFO.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-6xL0lOtj.js → catppuccin-macchiato-B2zST7sA.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-D39-xS3P.js → catppuccin-mocha-CvrdnYv9.js} +0 -0
- /package/dist/web/assets/{chevron-right-BBc6ATtm.js → chevron-right-DeUue2aK.js} +0 -0
- /package/dist/web/assets/{circle-question-mark-CveWaCux.js → circle-question-mark-PaJgx-3d.js} +0 -0
- /package/dist/web/assets/{clarity-D8TeGvoT.js → clarity-BeQd5TQD.js} +0 -0
- /package/dist/web/assets/{clojure-Dx_acSX7.js → clojure-Csdo0vb_.js} +0 -0
- /package/dist/web/assets/{cmake-CyeYPq8o.js → cmake-Cf0q7ddg.js} +0 -0
- /package/dist/web/assets/{code-7ZAlTvsm.js → code-TekEnDFM.js} +0 -0
- /package/dist/web/assets/{codeowners-DrTI0lmI.js → codeowners-CUEzlC7R.js} +0 -0
- /package/dist/web/assets/{codeql-CPkFxVW-.js → codeql-DxHyogQt.js} +0 -0
- /package/dist/web/assets/{common-lisp-B0k-3LTj.js → common-lisp-WLb-tAda.js} +0 -0
- /package/dist/web/assets/{coq-uygpo7to.js → coq-DFtMD6Wn.js} +0 -0
- /package/dist/web/assets/{csharp-DKUYGfSp.js → csharp-DEnvXVjs.js} +0 -0
- /package/dist/web/assets/{css-DoOUp7v4.js → css-DMPpCfkZ.js} +0 -0
- /package/dist/web/assets/{csv-B3YE13gi.js → csv-DTSgNSaP.js} +0 -0
- /package/dist/web/assets/{csv-parser-CB6bK93y.js → csv-parser-B_xNn07y.js} +0 -0
- /package/dist/web/assets/{cue-DViO2wvn.js → cue-BEmVIZmd.js} +0 -0
- /package/dist/web/assets/{cypher-4BMCIW5N.js → cypher-CncQVhvU.js} +0 -0
- /package/dist/web/assets/{d-BT_sTHas.js → d-BzUSBVex.js} +0 -0
- /package/dist/web/assets/{dark-plus-DVVXu-1N.js → dark-plus-mysFNRq2.js} +0 -0
- /package/dist/web/assets/{dart-DHz6f6_6.js → dart-BBu3nkcE.js} +0 -0
- /package/dist/web/assets/{data-grid-types-CWHnFOc-.js → data-grid-types-CzEQU9PJ.js} +0 -0
- /package/dist/web/assets/{database-BLht7BVM.js → database-COYAMZaw.js} +0 -0
- /package/dist/web/assets/{dax-ChYCjweK.js → dax-DcLVz9_9.js} +0 -0
- /package/dist/web/assets/{desktop-D6Lt4p04.js → desktop-DS2PdvoU.js} +0 -0
- /package/dist/web/assets/{diff-D8TDe4hP.js → diff-aUYk5Qys.js} +0 -0
- /package/dist/web/assets/{dist-B5ErTtYs.js → dist-Bn3E6I4R.js} +0 -0
- /package/dist/web/assets/{dist-D1CjMuoa.js → dist-Dxl3dqhC.js} +0 -0
- /package/dist/web/assets/{docker-Bsp0YbPx.js → docker-12Lj1QUD.js} +0 -0
- /package/dist/web/assets/{dotenv-B_7giSY_.js → dotenv-D-P4bzvl.js} +0 -0
- /package/dist/web/assets/{dracula-TrQL2iB5.js → dracula-C-eRNn4o.js} +0 -0
- /package/dist/web/assets/{dracula-soft-n2XOlgh-.js → dracula-soft-CBB1BI7O.js} +0 -0
- /package/dist/web/assets/{dream-maker-CdIEdhpT.js → dream-maker-C_j5A0r7.js} +0 -0
- /package/dist/web/assets/{edit-diff-preview-Cj6-iplb.js → edit-diff-preview-CRMu-6r1.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CSbixs6u.js → emacs-lisp-W54LT7rh.js} +0 -0
- /package/dist/web/assets/{everforest-dark-BuZahZYp.js → everforest-dark-B_ISUDeh.js} +0 -0
- /package/dist/web/assets/{everforest-light-DPH53z5H.js → everforest-light-CNUYa354.js} +0 -0
- /package/dist/web/assets/{fennel-B1-K_hlJ.js → fennel-DK82-oso.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-C-lL0g_E.js → file-exclamation-point-DBEC-Kst.js} +0 -0
- /package/dist/web/assets/{fish-Cy12Dbua.js → fish-CbJVJzQj.js} +0 -0
- /package/dist/web/assets/{fluent-B_82ys8E.js → fluent-Cug5cOL1.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BRQdNzZF.js → fortran-free-form-amHz32Zw.js} +0 -0
- /package/dist/web/assets/{gdscript-XBDmLUm9.js → gdscript-t_G6VYpv.js} +0 -0
- /package/dist/web/assets/{gdshader-DhVfB3u4.js → gdshader-DqZaHh4E.js} +0 -0
- /package/dist/web/assets/{genie-X3K29Y49.js → genie-CIJtFlZK.js} +0 -0
- /package/dist/web/assets/{gherkin-DKIx-6cX.js → gherkin-CalidqLY.js} +0 -0
- /package/dist/web/assets/{github-dark-Bf7zdp5s.js → github-dark-BG2xX0wa.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DqsBBV11.js → github-dark-default-tDp_EOBs.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-X2gqRz6w.js → github-dark-dimmed-DlSfPwW2.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-Z7tvbGX4.js → github-dark-high-contrast-qZM7j5KB.js} +0 -0
- /package/dist/web/assets/{github-light-BKxt-7zZ.js → github-light-CnvWTiyb.js} +0 -0
- /package/dist/web/assets/{github-light-default-B0liF4d4.js → github-light-default-Dy6OAt2Q.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-F4encJwr.js → github-light-high-contrast-1BHQCOPl.js} +0 -0
- /package/dist/web/assets/{gleam-Pz2bHgBB.js → gleam-D-2cpXVX.js} +0 -0
- /package/dist/web/assets/{gn-CgmdNsPU.js → gn-C1cC4xLc.js} +0 -0
- /package/dist/web/assets/{gnuplot-qLNF5yZ9.js → gnuplot-2Gp459mV.js} +0 -0
- /package/dist/web/assets/{go-CSDUNhE7.js → go-BA1q0q8h.js} +0 -0
- /package/dist/web/assets/{groovy-B3NEnOHF.js → groovy-BlIa-VfI.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DcoftiUX.js → gruvbox-dark-hard-BMIwVwaJ.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-D1wVSuD8.js → gruvbox-dark-medium-DxfuBW3n.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DWpunymH.js → gruvbox-dark-soft-_NSslDDi.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-rHMvyUdS.js → gruvbox-light-hard-CP5MIsL9.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-3O2KiXO7.js → gruvbox-light-medium-BTMursH0.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-lNlXJPwH.js → gruvbox-light-soft-Bv6zFoVX.js} +0 -0
- /package/dist/web/assets/{haskell-aaG3aC0h.js → haskell-Da8UeSv-.js} +0 -0
- /package/dist/web/assets/{haxe-CPcHhUDd.js → haxe-mcKlcxBa.js} +0 -0
- /package/dist/web/assets/{hcl-CMQNlR_4.js → hcl-BdZcx5JX.js} +0 -0
- /package/dist/web/assets/{hjson-DeuklPFj.js → hjson-pFXYbN4t.js} +0 -0
- /package/dist/web/assets/{hlsl-BI3hlsaM.js → hlsl-Z0II0KR8.js} +0 -0
- /package/dist/web/assets/{horizon-C7-gOcjs.js → horizon-CufN8DyP.js} +0 -0
- /package/dist/web/assets/{horizon-bright-Cxt9bRW7.js → horizon-bright-5qoQVyIY.js} +0 -0
- /package/dist/web/assets/{houston-DbF2G7iD.js → houston-DOFkQwep.js} +0 -0
- /package/dist/web/assets/{hy-DRG6Hs2I.js → hy-BVbDMDcG.js} +0 -0
- /package/dist/web/assets/{imba-CxKv_2TW.js → imba-DRM6LLS5.js} +0 -0
- /package/dist/web/assets/{ini-BiHsyCdO.js → ini-2KCnNvmz.js} +0 -0
- /package/dist/web/assets/{java-D3RZzgDB.js → java-BmvH-Ou1.js} +0 -0
- /package/dist/web/assets/{javascript-4FmSqG-T.js → javascript-B7VhU1Yc.js} +0 -0
- /package/dist/web/assets/{json-CjB236_E.js → json-X1pNk0n7.js} +0 -0
- /package/dist/web/assets/{json5-bkfO-Rco.js → json5-CJeEJPTg.js} +0 -0
- /package/dist/web/assets/{jsonc-BwaTeLyv.js → jsonc-C1haTBsf.js} +0 -0
- /package/dist/web/assets/{jsonl-CfxNSwGd.js → jsonl-wQ8-AJgs.js} +0 -0
- /package/dist/web/assets/{jsonnet-CQETCYrp.js → jsonnet-BrEjY3Xh.js} +0 -0
- /package/dist/web/assets/{jssm-D5o3Zzdz.js → jssm-C6LgYbOs.js} +0 -0
- /package/dist/web/assets/{jsx-BAMILPLo.js → jsx-B2cfD_Fb.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BhGVxu3R.js → kanagawa-dragon-6UCcJpLw.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-FH4Y59ro.js → kanagawa-lotus-DOmfpsRg.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-eilvP9MB.js → kanagawa-wave-CpxmPE7N.js} +0 -0
- /package/dist/web/assets/{kdl-BQ-yvXUK.js → kdl-BrLEivmb.js} +0 -0
- /package/dist/web/assets/{kotlin-BuCx0-VT.js → kotlin-BO2FUnDB.js} +0 -0
- /package/dist/web/assets/{kusto-DAN_n_Cy.js → kusto-Cev1yiq9.js} +0 -0
- /package/dist/web/assets/{laserwave-Cq2BlxrT.js → laserwave-BlEkf-eH.js} +0 -0
- /package/dist/web/assets/{lean-Dhq7uXKw.js → lean-BLS3Xk2w.js} +0 -0
- /package/dist/web/assets/{less-CxaMQb8j.js → less-BXmcsqcq.js} +0 -0
- /package/dist/web/assets/{lib-MIEszzkI.js → lib-CxSBdrd7.js} +0 -0
- /package/dist/web/assets/{light-plus-B4sMAend.js → light-plus-DS6zriYl.js} +0 -0
- /package/dist/web/assets/{llvm-DQ6-B_6Y.js → llvm-C_wLnJgX.js} +0 -0
- /package/dist/web/assets/{log-Bb_vqvu7.js → log-Cg8pvmWi.js} +0 -0
- /package/dist/web/assets/{logo-BzfYv3A_.js → logo-C9-kEvPW.js} +0 -0
- /package/dist/web/assets/{luau-BnAeNR77.js → luau-NwaGugfZ.js} +0 -0
- /package/dist/web/assets/{make-B6IFuxpC.js → make-DBpJE-Z8.js} +0 -0
- /package/dist/web/assets/{markdown-Z0xi77Fn.js → markdown-B7ZTAu7B.js} +0 -0
- /package/dist/web/assets/{material-theme-1scy1SYn.js → material-theme-BsXH-Mss.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-G-JykXQP.js → material-theme-darker-CW9OlIeY.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BtKkCMVq.js → material-theme-lighter-D2PAh0Kw.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-irrDC89L.js → material-theme-ocean-Vz6DBVi3.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-Eq5_K4LM.js → material-theme-palenight-D-GleP3G.js} +0 -0
- /package/dist/web/assets/{matlab-BE3oCXZC.js → matlab-C7MYlMSn.js} +0 -0
- /package/dist/web/assets/{mdx-DYiuFTGw.js → mdx-FkOOX3Dq.js} +0 -0
- /package/dist/web/assets/{mermaid-DVlaEvtw.js → mermaid-C_9qw5it.js} +0 -0
- /package/dist/web/assets/{min-dark-DSkeTpQa.js → min-dark-BowNvxk2.js} +0 -0
- /package/dist/web/assets/{min-light-CFv2Q0uF.js → min-light-x8oWUmbL.js} +0 -0
- /package/dist/web/assets/{mipsasm-CiAFuZQ2.js → mipsasm-Dmi1AZOT.js} +0 -0
- /package/dist/web/assets/{mojo-ARshcaSQ.js → mojo-C6z7mry2.js} +0 -0
- /package/dist/web/assets/{monokai-CPTV-SOg.js → monokai-BQ4e_Hn0.js} +0 -0
- /package/dist/web/assets/{moonbit-D57B0dZr.js → moonbit-CEeNIoB4.js} +0 -0
- /package/dist/web/assets/{move-qgy3G_o9.js → move-bALfKUa7.js} +0 -0
- /package/dist/web/assets/{narrat-CZ3rC-oc.js → narrat-DX4JlAWT.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-BxxIOcI4.js → nextflow-groovy-CKQWtRkO.js} +0 -0
- /package/dist/web/assets/{night-owl-B1ryGCda.js → night-owl-D4PQd726.js} +0 -0
- /package/dist/web/assets/{night-owl-light-BZvNnvMF.js → night-owl-light-BQo_YHll.js} +0 -0
- /package/dist/web/assets/{nix-BVBQi8yv.js → nix-B1jThkGj.js} +0 -0
- /package/dist/web/assets/{nord-3NXw613h.js → nord-35quHUKo.js} +0 -0
- /package/dist/web/assets/{nushell-BMOLRalb.js → nushell-L6DbJTuL.js} +0 -0
- /package/dist/web/assets/{objective-c-AxruEUgh.js → objective-c-CTZ4bR2U.js} +0 -0
- /package/dist/web/assets/{objective-cpp-cFT0qRsD.js → objective-cpp-Dm1YDIdA.js} +0 -0
- /package/dist/web/assets/{ocaml-_8JH6O8J.js → ocaml-B3n__LGy.js} +0 -0
- /package/dist/web/assets/{odin-nwYvR0O6.js → odin-DmqZ8n3i.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-C1LaYJaH.js → one-dark-pro-16_UaUig.js} +0 -0
- /package/dist/web/assets/{one-light-B7rMc__c.js → one-light-B-GGjWn5.js} +0 -0
- /package/dist/web/assets/{openscad-Cwes8bKO.js → openscad-DTFB9Sj6.js} +0 -0
- /package/dist/web/assets/{pascal-DMt-6tdZ.js → pascal-RcT1dQYN.js} +0 -0
- /package/dist/web/assets/{pkl-QBTyYQmB.js → pkl-Dr4yLQ_o.js} +0 -0
- /package/dist/web/assets/{plastic-BTSUp8Fk.js → plastic-hp2Nry0R.js} +0 -0
- /package/dist/web/assets/{plsql-DQv3IFXP.js → plsql-pFcO4WZ0.js} +0 -0
- /package/dist/web/assets/{po-CNwZx4tW.js → po-BkZ0_HvY.js} +0 -0
- /package/dist/web/assets/{poimandres-8Os7Qequ.js → poimandres-BcTrxj7f.js} +0 -0
- /package/dist/web/assets/{polar-b4yK8oD8.js → polar-DHK3q8FF.js} +0 -0
- /package/dist/web/assets/{postcss-sFctwpxx.js → postcss-Bv7WzijA.js} +0 -0
- /package/dist/web/assets/{powerquery-Cg_WrtuN.js → powerquery-DpaU_hW9.js} +0 -0
- /package/dist/web/assets/{powershell-C7Ifz95h.js → powershell-_Rqc0MGP.js} +0 -0
- /package/dist/web/assets/{prisma-Z52HUJzQ.js → prisma-wyegE8us.js} +0 -0
- /package/dist/web/assets/{prolog-DLqW-zR1.js → prolog-DspHCURv.js} +0 -0
- /package/dist/web/assets/{proto-BOIzxCCJ.js → proto-BOBiNGf5.js} +0 -0
- /package/dist/web/assets/{puppet-De7TjZIf.js → puppet-DfLvV8h2.js} +0 -0
- /package/dist/web/assets/{purescript-R76ou1WT.js → purescript-BY5ZSOZy.js} +0 -0
- /package/dist/web/assets/{python-Dprm0tNP.js → python-B758mMeg.js} +0 -0
- /package/dist/web/assets/{qmldir-Clp_jpNw.js → qmldir-C75bpHvy.js} +0 -0
- /package/dist/web/assets/{qss-eQPYYzSo.js → qss-DcLTlbLd.js} +0 -0
- /package/dist/web/assets/{r-CCcRHIf5.js → r-D0G_Y1P5.js} +0 -0
- /package/dist/web/assets/{racket-CzE472yp.js → racket-DTye_5qn.js} +0 -0
- /package/dist/web/assets/{raku-DQwXrTSi.js → raku-uJXBXkV0.js} +0 -0
- /package/dist/web/assets/{react-J9g6e3Rq.js → react-CorNPdie.js} +0 -0
- /package/dist/web/assets/{red-KCS6wyop.js → red-rs3HE3re.js} +0 -0
- /package/dist/web/assets/{reg-CcrAddUh.js → reg-DkTIaX7a.js} +0 -0
- /package/dist/web/assets/{regexp-DDbud-Ri.js → regexp-4TLSs4_m.js} +0 -0
- /package/dist/web/assets/{rel-99GDgaHp.js → rel-B0jumCjt.js} +0 -0
- /package/dist/web/assets/{resolve-theme-DJORYzKu.js → resolve-theme-CCoakF2E.js} +0 -0
- /package/dist/web/assets/{riscv-Dm2eNv0R.js → riscv-DtaWMlo0.js} +0 -0
- /package/dist/web/assets/{ron-CzFt0ygH.js → ron-BCPxwJD6.js} +0 -0
- /package/dist/web/assets/{rose-pine-Ck6lCrCP.js → rose-pine-CNryoqGi.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-vtALRJNj.js → rose-pine-dawn-C-gJOQVP.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-DPto8qXw.js → rose-pine-moon-BdXSXpZh.js} +0 -0
- /package/dist/web/assets/{rosmsg-NtS6429H.js → rosmsg-CPG8X-D5.js} +0 -0
- /package/dist/web/assets/{rust-BjIXvF9h.js → rust-CxfW3ePx.js} +0 -0
- /package/dist/web/assets/{sass-CQ23wf5E.js → sass-D-7bxo_L.js} +0 -0
- /package/dist/web/assets/{save-DO5xjW95.js → save-BM4p9_9n.js} +0 -0
- /package/dist/web/assets/{scala-LfKLM143.js → scala-DyUsfHNc.js} +0 -0
- /package/dist/web/assets/{scheme-D3SLBXlF.js → scheme-ROgrwiO-.js} +0 -0
- /package/dist/web/assets/{sdbl-ODRd2rGN.js → sdbl-B1EAQWpp.js} +0 -0
- /package/dist/web/assets/{shellscript-3KB1G_oA.js → shellscript-HhmB_Ufy.js} +0 -0
- /package/dist/web/assets/{shield-check-m2QzZTuZ.js → shield-check-C3qYSU8a.js} +0 -0
- /package/dist/web/assets/{shield-off-DNn7VzJe.js → shield-off-D5tU7gQT.js} +0 -0
- /package/dist/web/assets/{slack-dark-DTtyWmIy.js → slack-dark-DnmSY72G.js} +0 -0
- /package/dist/web/assets/{slack-ochin-Bu0PApk3.js → slack-ochin-q97gHjcd.js} +0 -0
- /package/dist/web/assets/{smalltalk-haMT-Sbe.js → smalltalk-B-1waXvA.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DXJzefE9.js → snazzy-light-C8yWDN2g.js} +0 -0
- /package/dist/web/assets/{solarized-dark-B6LyYa18.js → solarized-dark-ClVDEMJZ.js} +0 -0
- /package/dist/web/assets/{solarized-light-QLADebkv.js → solarized-light-Bts6CUIP.js} +0 -0
- /package/dist/web/assets/{solidity-mzfq1aDc.js → solidity-DYxwp0om.js} +0 -0
- /package/dist/web/assets/{splunk-CyH4RWBp.js → splunk-jK6-Sr06.js} +0 -0
- /package/dist/web/assets/{sql-CWVlT5D3.js → sql-DPpd8aUS.js} +0 -0
- /package/dist/web/assets/{ssh-config-DNvQJ1Qx.js → ssh-config-Bimb9LVr.js} +0 -0
- /package/dist/web/assets/{stylus-ByQQdtmf.js → stylus-B2DgJRJw.js} +0 -0
- /package/dist/web/assets/{swift-D1cpY4ow.js → swift-ChEH2nFL.js} +0 -0
- /package/dist/web/assets/{synthwave-84-BDF_-QiW.js → synthwave-84-DwSLbgAm.js} +0 -0
- /package/dist/web/assets/{system-verilog-BYrkMydC.js → system-verilog-DoVBMhJ8.js} +0 -0
- /package/dist/web/assets/{systemd-CQm3zyK0.js → systemd--Ux4VAYD.js} +0 -0
- /package/dist/web/assets/{table-CAVmgQMT.js → table-Di8pv2Y_.js} +0 -0
- /package/dist/web/assets/{talonscript-DMW5CzE1.js → talonscript-DQNx9nak.js} +0 -0
- /package/dist/web/assets/{tasl-BhFzMCr2.js → tasl-Bwy_Y9uD.js} +0 -0
- /package/dist/web/assets/{tcl-D7VEAF-9.js → tcl-Drea-2jA.js} +0 -0
- /package/dist/web/assets/{terraform-DdT5A0Vy.js → terraform-4rS5HoFk.js} +0 -0
- /package/dist/web/assets/{tokyo-night-WGLMM6Q9.js → tokyo-night-C9MqaxIn.js} +0 -0
- /package/dist/web/assets/{toml-CZ7ipXHZ.js → toml-tSgOSv42.js} +0 -0
- /package/dist/web/assets/{tsv-B1zVvoTr.js → tsv-BmL4O1Zx.js} +0 -0
- /package/dist/web/assets/{tsx-D4HwRUBq.js → tsx-BwzeA880.js} +0 -0
- /package/dist/web/assets/{turtle-B4r8KEJH.js → turtle-2DYvssFL.js} +0 -0
- /package/dist/web/assets/{typescript-BnHhxMtp.js → typescript-ZC-vonSh.js} +0 -0
- /package/dist/web/assets/{typespec-DP2q46GT.js → typespec-DkCEO45F.js} +0 -0
- /package/dist/web/assets/{typst-DFsD_yaD.js → typst-DAZdb2n6.js} +0 -0
- /package/dist/web/assets/{utils-CTg5uAYR.js → utils-CSCvNZxE.js} +0 -0
- /package/dist/web/assets/{v-Bji8hSVT.js → v-B_wsb4C1.js} +0 -0
- /package/dist/web/assets/{vala-D3TIgGmU.js → vala-BDBNS784.js} +0 -0
- /package/dist/web/assets/{vb-DvcxFS2q.js → vb-CuOoJSSa.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-DfNMj6xt.js → vendor-xterm-CImj6i6Z.js} +0 -0
- /package/dist/web/assets/{verilog-Dy7cF35d.js → verilog-BCcOvOcc.js} +0 -0
- /package/dist/web/assets/{vesper-BzjwUv9J.js → vesper-vN8CREK5.js} +0 -0
- /package/dist/web/assets/{vhdl-DuFnFNmW.js → vhdl-CI8PoqeM.js} +0 -0
- /package/dist/web/assets/{viml-yAPgKlbz.js → viml-B66dNP1B.js} +0 -0
- /package/dist/web/assets/{vitesse-black-oSSQdyIZ.js → vitesse-black-0bGIaCfD.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BZxwPihB.js → vitesse-dark-Dk-RSXq-.js} +0 -0
- /package/dist/web/assets/{vitesse-light-uIigycvt.js → vitesse-light-BUsA5RhG.js} +0 -0
- /package/dist/web/assets/{vyper-CX3NAxm5.js → vyper-dNodzEa8.js} +0 -0
- /package/dist/web/assets/{wasm-Coen1MOg.js → wasm-BDxaNPCj.js} +0 -0
- /package/dist/web/assets/{wasm-D9td37qO.js → wasm-d6SE2_qm.js} +0 -0
- /package/dist/web/assets/{wenyan-lwNIHgk6.js → wenyan-D0h8Uz55.js} +0 -0
- /package/dist/web/assets/{wgsl-DQAzCfog.js → wgsl-vKs_vsvQ.js} +0 -0
- /package/dist/web/assets/{wikitext-DTXmt5NE.js → wikitext-BIWsPJit.js} +0 -0
- /package/dist/web/assets/{wit-Bdw_7WDH.js → wit-6vmKi2H3.js} +0 -0
- /package/dist/web/assets/{wolfram-DqyHDZ07.js → wolfram-zoNg6F1a.js} +0 -0
- /package/dist/web/assets/{x-C2VSAp7O.js → x-BsFLg-T8.js} +0 -0
- /package/dist/web/assets/{yaml-CPXhdByd.js → yaml-W95qP6-B.js} +0 -0
- /package/dist/web/assets/{zenscript-6s_i5zek.js → zenscript-BxircxDx.js} +0 -0
- /package/dist/web/assets/{zig-B2htezJ_.js → zig-DD79WQtz.js} +0 -0
|
@@ -637,12 +637,15 @@ function parseIdeOpenedFile(text: string): { idePath: string | null; cleanText:
|
|
|
637
637
|
/** Parse user message content, extracting attached file paths and the actual text */
|
|
638
638
|
function parseUserAttachments(content: string): { files: string[]; text: string } {
|
|
639
639
|
// Match: [Attached file: /path] or [Attached files:\n/path1\n/path2\n]
|
|
640
|
-
|
|
640
|
+
// Trailing newlines are optional — an attachment-only message has the marker
|
|
641
|
+
// trimmed to the end of the string (extractTerminalBlocks trims), so the
|
|
642
|
+
// separator newlines may be absent.
|
|
643
|
+
const singleMatch = content.match(/^\[Attached file: (.+?)\]\n*/);
|
|
641
644
|
if (singleMatch) {
|
|
642
645
|
return { files: [singleMatch[1]!], text: content.slice(singleMatch[0].length) };
|
|
643
646
|
}
|
|
644
647
|
|
|
645
|
-
const multiMatch = content.match(/^\[Attached files:\n([\s\S]+?)\]\n
|
|
648
|
+
const multiMatch = content.match(/^\[Attached files:\n([\s\S]+?)\]\n*/);
|
|
646
649
|
if (multiMatch) {
|
|
647
650
|
const files = multiMatch[1]!.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
648
651
|
return { files, text: content.slice(multiMatch[0].length) };
|
|
@@ -815,7 +818,7 @@ function UserBubble({ content, messageId, timestamp, projectName, onFork, onEdit
|
|
|
815
818
|
expanded && "max-h-[50vh] overflow-y-auto",
|
|
816
819
|
)}
|
|
817
820
|
>
|
|
818
|
-
{isSystemContext ? <TextWithFilePaths text={text} projectName={projectName} /> : text}
|
|
821
|
+
{isSystemContext ? <TextWithFilePaths text={text} projectName={projectName} /> : <TextWithLinks text={text} />}
|
|
819
822
|
</div>
|
|
820
823
|
)}
|
|
821
824
|
{(isOverflowing || expanded) && (
|
|
@@ -1002,6 +1005,48 @@ function FilePathChip({ path, projectName }: { path: string; projectName?: strin
|
|
|
1002
1005
|
);
|
|
1003
1006
|
}
|
|
1004
1007
|
|
|
1008
|
+
/** Render plain text with http(s) URLs turned into clickable links. Preserves
|
|
1009
|
+
* whitespace/newlines via the surrounding `whitespace-pre-wrap` container. */
|
|
1010
|
+
function TextWithLinks({ text }: { text: string }) {
|
|
1011
|
+
const parts = useMemo(() => {
|
|
1012
|
+
const re = /(https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/g;
|
|
1013
|
+
const result: { kind: "text" | "url"; value: string }[] = [];
|
|
1014
|
+
let last = 0;
|
|
1015
|
+
let m;
|
|
1016
|
+
while ((m = re.exec(text)) !== null) {
|
|
1017
|
+
if (m.index > last) result.push({ kind: "text", value: text.slice(last, m.index) });
|
|
1018
|
+
result.push({ kind: "url", value: m[1]! });
|
|
1019
|
+
last = m.index + m[0].length;
|
|
1020
|
+
}
|
|
1021
|
+
if (last < text.length) result.push({ kind: "text", value: text.slice(last) });
|
|
1022
|
+
return result;
|
|
1023
|
+
}, [text]);
|
|
1024
|
+
|
|
1025
|
+
// No URLs — return the raw string so nothing about the layout changes.
|
|
1026
|
+
if (parts.every((p) => p.kind === "text")) return <>{text}</>;
|
|
1027
|
+
|
|
1028
|
+
return (
|
|
1029
|
+
<>
|
|
1030
|
+
{parts.map((p, i) =>
|
|
1031
|
+
p.kind === "url" ? (
|
|
1032
|
+
<a
|
|
1033
|
+
key={i}
|
|
1034
|
+
href={p.value}
|
|
1035
|
+
target="_blank"
|
|
1036
|
+
rel="noopener noreferrer"
|
|
1037
|
+
className="text-primary underline underline-offset-2 hover:text-primary/80 break-all"
|
|
1038
|
+
onClick={(e) => e.stopPropagation()}
|
|
1039
|
+
>
|
|
1040
|
+
{p.value}
|
|
1041
|
+
</a>
|
|
1042
|
+
) : (
|
|
1043
|
+
<span key={i}>{p.value}</span>
|
|
1044
|
+
),
|
|
1045
|
+
)}
|
|
1046
|
+
</>
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1005
1050
|
/** Render text with absolute file paths detected and turned into clickable chips */
|
|
1006
1051
|
function TextWithFilePaths({ text, projectName }: { text: string; projectName?: string }) {
|
|
1007
1052
|
const parts = useMemo(() => {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Pure helpers for the unified model + thinking picker. Kept DOM-free so the
|
|
2
|
+
// label↔value mapping (the crash-prone "Extra"→"xhigh" contract) is unit-testable
|
|
3
|
+
// without a browser harness.
|
|
4
|
+
//
|
|
5
|
+
// SDK effort enum = low | medium | high | xhigh | max. The app UI shows "Extra"
|
|
6
|
+
// for the SDK value "xhigh"; the literal "extra" would crash the CLI subprocess,
|
|
7
|
+
// so it must NEVER appear as a value here.
|
|
8
|
+
|
|
9
|
+
export interface EffortOption {
|
|
10
|
+
label: string;
|
|
11
|
+
value: "low" | "medium" | "high" | "xhigh" | "max";
|
|
12
|
+
default?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const EFFORT_OPTIONS: EffortOption[] = [
|
|
16
|
+
{ label: "Low", value: "low" },
|
|
17
|
+
{ label: "Medium", value: "medium" },
|
|
18
|
+
{ label: "High", value: "high", default: true },
|
|
19
|
+
{ label: "Extra", value: "xhigh" },
|
|
20
|
+
{ label: "Max", value: "max" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/** UI label for an effort value; falls back to the raw value when unknown. */
|
|
24
|
+
export function effortLabel(value: string | null | undefined): string {
|
|
25
|
+
if (!value) return "";
|
|
26
|
+
return EFFORT_OPTIONS.find((o) => o.value === value)?.label ?? value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Chip text: model display, plus " · <Effort>" when an effort is set. */
|
|
30
|
+
export function chipLabel(modelDisplay: string, effortValue: string | null | undefined): string {
|
|
31
|
+
const eff = effortLabel(effortValue);
|
|
32
|
+
return eff ? `${modelDisplay} · ${eff}` : modelDisplay;
|
|
33
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, type ReactNode } from "react";
|
|
2
|
+
import { Check, Sparkles, Brain } from "lucide-react";
|
|
3
|
+
import { api, projectUrl } from "@/lib/api-client";
|
|
4
|
+
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
5
|
+
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
6
|
+
import { EFFORT_OPTIONS, chipLabel } from "./model-thinking-selector-helpers";
|
|
7
|
+
|
|
8
|
+
interface ModelOption {
|
|
9
|
+
value: string;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ModelThinkingSelectorProps {
|
|
14
|
+
model: string | null;
|
|
15
|
+
effort: string | null;
|
|
16
|
+
thinking: boolean;
|
|
17
|
+
onModelChange: (model: string) => void;
|
|
18
|
+
onEffortChange: (effort: string) => void;
|
|
19
|
+
onThinkingChange: (enabled: boolean) => void;
|
|
20
|
+
projectName: string;
|
|
21
|
+
providerId: string;
|
|
22
|
+
/** Locked while a turn is streaming (model/effort can't change mid-turn). */
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Strip the leading "Claude " so the chip stays compact: "Claude Opus 5" → "Opus 5" */
|
|
27
|
+
function shortLabel(label: string): string {
|
|
28
|
+
return label.replace(/^Claude\s+/i, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DEFAULT_EFFORT = EFFORT_OPTIONS.find((o) => o.default)!.value;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Unified model + thinking picker — one chip, one popup. Mirrors Anthropic's native
|
|
35
|
+
* picker: model list, effort levels (Extra→SDK "xhigh"), and a thinking on/off toggle.
|
|
36
|
+
* Desktop → absolute popover; mobile (<md) → bottom sheet with 44px targets.
|
|
37
|
+
*/
|
|
38
|
+
export function ModelThinkingSelector({
|
|
39
|
+
model,
|
|
40
|
+
effort,
|
|
41
|
+
thinking,
|
|
42
|
+
onModelChange,
|
|
43
|
+
onEffortChange,
|
|
44
|
+
onThinkingChange,
|
|
45
|
+
projectName,
|
|
46
|
+
providerId,
|
|
47
|
+
disabled,
|
|
48
|
+
}: ModelThinkingSelectorProps) {
|
|
49
|
+
const [models, setModels] = useState<ModelOption[]>([]);
|
|
50
|
+
const [open, setOpen] = useState(false);
|
|
51
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
52
|
+
const isMobile = useIsMobile();
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!projectName || !providerId) return;
|
|
56
|
+
api
|
|
57
|
+
.get<ModelOption[]>(`${projectUrl(projectName)}/chat/providers/${providerId}/models`)
|
|
58
|
+
.then(setModels)
|
|
59
|
+
.catch(() => {});
|
|
60
|
+
}, [projectName, providerId]);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (disabled) setOpen(false);
|
|
64
|
+
}, [disabled]);
|
|
65
|
+
|
|
66
|
+
// Desktop popover: close on click outside (mobile bottom sheet handles its own backdrop).
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!open || isMobile) return;
|
|
69
|
+
const handler = (e: MouseEvent) => {
|
|
70
|
+
if (panelRef.current && !panelRef.current.contains(e.target as Node)) setOpen(false);
|
|
71
|
+
};
|
|
72
|
+
document.addEventListener("mousedown", handler);
|
|
73
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
74
|
+
}, [open, isMobile]);
|
|
75
|
+
|
|
76
|
+
const current = models.find((m) => m.value === model);
|
|
77
|
+
const modelDisplay = current ? shortLabel(current.label) : model ? shortLabel(model) : "Model";
|
|
78
|
+
const effortValue = effort ?? DEFAULT_EFFORT;
|
|
79
|
+
const showModelList = models.length > 1;
|
|
80
|
+
const chipText = chipLabel(modelDisplay, effortValue);
|
|
81
|
+
|
|
82
|
+
const pick = (fn: () => void) => {
|
|
83
|
+
fn();
|
|
84
|
+
if (!isMobile) setOpen(false);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const content = (
|
|
88
|
+
<div className="flex divide-x divide-border">
|
|
89
|
+
{/* Left column: model list */}
|
|
90
|
+
{showModelList && (
|
|
91
|
+
<div className="flex-1 min-w-0 py-1">
|
|
92
|
+
<SectionLabel>Model</SectionLabel>
|
|
93
|
+
{models.map((m) => (
|
|
94
|
+
<OptionRow key={m.value} active={m.value === model} onClick={() => pick(() => onModelChange(m.value))}>
|
|
95
|
+
<span className="flex-1 truncate">{shortLabel(m.label)}</span>
|
|
96
|
+
{m.value === model && <Check className="size-4 shrink-0 text-primary" />}
|
|
97
|
+
</OptionRow>
|
|
98
|
+
))}
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
{/* Right column: effort + thinking toggle */}
|
|
102
|
+
<div className="flex-1 min-w-0 py-1">
|
|
103
|
+
<SectionLabel>Effort</SectionLabel>
|
|
104
|
+
{EFFORT_OPTIONS.map((o) => (
|
|
105
|
+
<OptionRow key={o.value} active={o.value === effortValue} onClick={() => pick(() => onEffortChange(o.value))}>
|
|
106
|
+
<span className="flex-1 truncate">
|
|
107
|
+
{o.label}
|
|
108
|
+
{o.default && <span className="ml-2 text-xs text-text-subtle">Default</span>}
|
|
109
|
+
</span>
|
|
110
|
+
{o.value === effortValue && <Check className="size-4 shrink-0 text-primary" />}
|
|
111
|
+
</OptionRow>
|
|
112
|
+
))}
|
|
113
|
+
<Separator />
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => onThinkingChange(!thinking)}
|
|
117
|
+
className="w-full flex items-center gap-3 px-3 min-h-[44px] text-left text-sm hover:bg-surface-elevated"
|
|
118
|
+
role="switch"
|
|
119
|
+
aria-checked={thinking}
|
|
120
|
+
>
|
|
121
|
+
<Brain className="size-4 shrink-0" />
|
|
122
|
+
<span className="flex-1">Thinking</span>
|
|
123
|
+
<span className={`inline-flex h-5 w-9 items-center rounded-full transition-colors ${thinking ? "bg-primary" : "bg-border"}`}>
|
|
124
|
+
<span className={`h-4 w-4 rounded-full bg-white transition-transform ${thinking ? "translate-x-4" : "translate-x-0.5"}`} />
|
|
125
|
+
</span>
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div className="relative">
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
disabled={disabled}
|
|
136
|
+
onClick={(e) => {
|
|
137
|
+
e.stopPropagation();
|
|
138
|
+
if (!disabled) setOpen((v) => !v);
|
|
139
|
+
}}
|
|
140
|
+
className="inline-flex items-center gap-1.5 px-[9px] py-1 rounded-full text-[11.5px] text-primary bg-accent-wash border border-accent-wash-border hover:brightness-110 transition-[filter] disabled:opacity-50 disabled:cursor-default"
|
|
141
|
+
aria-label={`Model ${modelDisplay}, effort ${effortValue}${thinking ? ", thinking on" : ""}`}
|
|
142
|
+
title={disabled ? "Can't change while running" : chipText}
|
|
143
|
+
>
|
|
144
|
+
<Sparkles className="h-3.5 w-3.5 shrink-0" />
|
|
145
|
+
<span className="max-w-[140px] truncate">{chipText}</span>
|
|
146
|
+
{thinking && <Brain className="h-3 w-3 shrink-0" />}
|
|
147
|
+
</button>
|
|
148
|
+
|
|
149
|
+
{open && !disabled && (
|
|
150
|
+
isMobile ? (
|
|
151
|
+
<BottomSheet open={open} onClose={() => setOpen(false)}>
|
|
152
|
+
<div className="px-1 pb-2">{content}</div>
|
|
153
|
+
</BottomSheet>
|
|
154
|
+
) : (
|
|
155
|
+
<div
|
|
156
|
+
ref={panelRef}
|
|
157
|
+
role="listbox"
|
|
158
|
+
aria-label="Model and thinking"
|
|
159
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
160
|
+
onClick={(e) => e.stopPropagation()}
|
|
161
|
+
className="absolute bottom-full left-0 mb-1 z-50 w-80 rounded-lg border border-border popover-solid shadow-[var(--shadow-panel)] max-h-[70vh] overflow-y-auto"
|
|
162
|
+
>
|
|
163
|
+
{content}
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function SectionLabel({ children }: { children: ReactNode }) {
|
|
172
|
+
return <div className="px-3 py-1.5 text-xs font-medium text-text-secondary">{children}</div>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function Separator() {
|
|
176
|
+
return <div className="my-1 border-t border-border" />;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function OptionRow({ active, onClick, children }: { active: boolean; onClick: () => void; children: ReactNode }) {
|
|
180
|
+
return (
|
|
181
|
+
<button
|
|
182
|
+
type="button"
|
|
183
|
+
onClick={onClick}
|
|
184
|
+
className={`w-full flex items-center gap-3 px-3 min-h-[44px] text-left text-sm text-text-primary transition-colors hover:bg-surface-elevated ${active ? "bg-surface-elevated" : ""}`}
|
|
185
|
+
>
|
|
186
|
+
{children}
|
|
187
|
+
</button>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
@@ -5,11 +5,14 @@ import { useProjectStore } from "@/stores/project-store";
|
|
|
5
5
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
6
6
|
import { useGroupChat } from "@/hooks/use-group-chat";
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
8
9
|
import { cn } from "@/lib/utils";
|
|
9
10
|
import { GroupMessageItem } from "./group-message-item";
|
|
10
11
|
import { GroupMemberRoster } from "./group-member-roster";
|
|
12
|
+
import { GroupMemberEditDialog, type MemberFormValues } from "./group-member-edit-dialog";
|
|
11
13
|
import { GroupFullTranscriptView } from "./group-full-transcript-view";
|
|
12
|
-
import
|
|
14
|
+
import { getGroup, addGroupMember, updateGroupMember, removeGroupMember, updateGroupSettings } from "@/lib/api-group-chat";
|
|
15
|
+
import type { GroupMessage, GroupMember } from "../../../types/group-chat";
|
|
13
16
|
|
|
14
17
|
interface GroupChatTabProps {
|
|
15
18
|
metadata?: Record<string, unknown>;
|
|
@@ -31,20 +34,64 @@ export function GroupChatTab({ metadata }: GroupChatTabProps) {
|
|
|
31
34
|
const [rosterOpen, setRosterOpen] = useState(false);
|
|
32
35
|
const feedEndRef = useRef<HTMLDivElement>(null);
|
|
33
36
|
|
|
37
|
+
// Full roster (with persona/model) for management — the WS `members` only carry
|
|
38
|
+
// live status/color, so we fetch the editable detail separately and refetch on change.
|
|
39
|
+
const [roster, setRoster] = useState<GroupMember[]>([]);
|
|
40
|
+
const [cap, setCap] = useState<number | null>(null); // per-group reply-burst cap
|
|
41
|
+
const [rosterBusy, setRosterBusy] = useState(false);
|
|
42
|
+
const [rosterError, setRosterError] = useState<string | null>(null);
|
|
43
|
+
// `null` = dialog closed; `{ member: null }` = add; `{ member }` = edit.
|
|
44
|
+
const [editing, setEditing] = useState<{ member: GroupMember | null } | null>(null);
|
|
45
|
+
|
|
46
|
+
const reloadRoster = useCallback(async () => {
|
|
47
|
+
if (!groupId) return;
|
|
48
|
+
try { const g = await getGroup(groupId); setRoster(g.members); setCap(g.maxTurns); } catch { /* keep last */ }
|
|
49
|
+
}, [groupId]);
|
|
50
|
+
|
|
51
|
+
const handleCapChange = useCallback(async (n: number) => {
|
|
52
|
+
if (!groupId) return;
|
|
53
|
+
const clamped = Math.max(1, Math.min(50, Math.floor(n) || 1));
|
|
54
|
+
setCap(clamped);
|
|
55
|
+
try { await updateGroupSettings(groupId, { maxTurns: clamped }); }
|
|
56
|
+
catch { void reloadRoster(); }
|
|
57
|
+
}, [groupId, reloadRoster]);
|
|
58
|
+
|
|
59
|
+
useEffect(() => { setRoster([]); void reloadRoster(); }, [groupId, reloadRoster]);
|
|
60
|
+
|
|
34
61
|
// Stick to bottom on new messages.
|
|
35
62
|
useEffect(() => {
|
|
36
63
|
feedEndRef.current?.scrollIntoView({ block: "end" });
|
|
37
64
|
}, [messages.length, typing]);
|
|
38
65
|
|
|
66
|
+
// memberId → live status from the running burst (overlays the fetched roster).
|
|
67
|
+
const liveStatus = useMemo(() => new Map(members.map((m) => [m.id, m.status] as const)), [members]);
|
|
68
|
+
|
|
69
|
+
// Prefer the fetched roster (fresh after edits); fall back to WS members before load.
|
|
70
|
+
const metaSource = roster.length ? roster : members;
|
|
39
71
|
const colorFor = useMemo(() => {
|
|
40
|
-
const map = new Map(
|
|
72
|
+
const map = new Map(metaSource.map((m) => [m.name, m.color]));
|
|
41
73
|
return (name: string) => map.get(name) ?? null;
|
|
42
|
-
}, [
|
|
74
|
+
}, [metaSource]);
|
|
43
75
|
|
|
44
76
|
const isLeader = useMemo(() => {
|
|
45
|
-
const leaders = new Set(
|
|
77
|
+
const leaders = new Set(metaSource.filter((m) => m.role === "leader").map((m) => m.name));
|
|
46
78
|
return (name: string) => leaders.has(name);
|
|
47
|
-
}, [
|
|
79
|
+
}, [metaSource]);
|
|
80
|
+
|
|
81
|
+
const handleSubmitMember = useCallback(async (values: MemberFormValues) => {
|
|
82
|
+
if (!groupId) return;
|
|
83
|
+
if (editing?.member) await updateGroupMember(groupId, editing.member.id, values);
|
|
84
|
+
else await addGroupMember(groupId, values);
|
|
85
|
+
await reloadRoster();
|
|
86
|
+
}, [groupId, editing, reloadRoster]);
|
|
87
|
+
|
|
88
|
+
const handleRemoveMember = useCallback(async (m: GroupMember) => {
|
|
89
|
+
if (!groupId) return;
|
|
90
|
+
setRosterBusy(true); setRosterError(null);
|
|
91
|
+
try { await removeGroupMember(groupId, m.id); await reloadRoster(); }
|
|
92
|
+
catch (e) { setRosterError(e instanceof Error ? e.message : "Failed to remove member"); }
|
|
93
|
+
finally { setRosterBusy(false); }
|
|
94
|
+
}, [groupId, reloadRoster]);
|
|
48
95
|
|
|
49
96
|
const handleSend = useCallback(() => {
|
|
50
97
|
const content = draft.trim();
|
|
@@ -156,17 +203,65 @@ export function GroupChatTab({ metadata }: GroupChatTabProps) {
|
|
|
156
203
|
</div>
|
|
157
204
|
</div>
|
|
158
205
|
|
|
159
|
-
{/* Roster — sidebar on desktop
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
)}>
|
|
165
|
-
<div className="flex h-11 items-center border-b border-border px-3 text-sm font-medium text-foreground">
|
|
206
|
+
{/* Roster — inline sidebar on desktop; bottom sheet on mobile (tap backdrop
|
|
207
|
+
or swipe down to dismiss, since the header toggle sits under an overlay). */}
|
|
208
|
+
{isMobile ? (
|
|
209
|
+
<BottomSheet open={rosterOpen} onClose={() => setRosterOpen(false)} className="flex max-h-[72vh] flex-col">
|
|
210
|
+
<div className="flex h-11 shrink-0 items-center px-4 text-sm font-medium text-foreground">
|
|
166
211
|
Members
|
|
167
212
|
</div>
|
|
168
|
-
<
|
|
169
|
-
|
|
213
|
+
<div className="min-h-0 overflow-y-auto">
|
|
214
|
+
{cap !== null && (
|
|
215
|
+
<label className="flex items-center gap-2 px-4 py-2 text-xs text-text-secondary">
|
|
216
|
+
<span>Reply cap</span>
|
|
217
|
+
<input type="number" min={1} max={50} value={cap}
|
|
218
|
+
onChange={(e) => handleCapChange(Number(e.target.value))}
|
|
219
|
+
className="w-14 rounded border border-border bg-background px-1.5 py-1 text-xs text-foreground" />
|
|
220
|
+
<span className="text-text-subtle">turns/msg</span>
|
|
221
|
+
</label>
|
|
222
|
+
)}
|
|
223
|
+
{rosterError && <p className="px-3 py-1 text-xs text-destructive">{rosterError}</p>}
|
|
224
|
+
<GroupMemberRoster
|
|
225
|
+
members={roster} liveStatus={liveStatus} typing={typing} busy={rosterBusy}
|
|
226
|
+
onAdd={() => setEditing({ member: null })} onEdit={(m) => setEditing({ member: m })} onRemove={handleRemoveMember}
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
</BottomSheet>
|
|
230
|
+
) : (
|
|
231
|
+
rosterOpen && (
|
|
232
|
+
<div className="flex w-60 shrink-0 flex-col border-l border-border bg-panel">
|
|
233
|
+
<div className="flex h-11 shrink-0 items-center border-b border-border px-3 text-sm font-medium text-foreground">
|
|
234
|
+
Members
|
|
235
|
+
</div>
|
|
236
|
+
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
237
|
+
{cap !== null && (
|
|
238
|
+
<label className="flex items-center gap-2 px-3 py-2 text-xs text-text-secondary">
|
|
239
|
+
<span>Reply cap</span>
|
|
240
|
+
<input type="number" min={1} max={50} value={cap}
|
|
241
|
+
onChange={(e) => handleCapChange(Number(e.target.value))}
|
|
242
|
+
className="w-14 rounded border border-border bg-background px-1.5 py-1 text-xs text-foreground" />
|
|
243
|
+
<span className="text-text-subtle">turns/msg</span>
|
|
244
|
+
</label>
|
|
245
|
+
)}
|
|
246
|
+
{rosterError && <p className="px-3 py-1 text-xs text-destructive">{rosterError}</p>}
|
|
247
|
+
<GroupMemberRoster
|
|
248
|
+
members={roster} liveStatus={liveStatus} typing={typing} busy={rosterBusy}
|
|
249
|
+
onAdd={() => setEditing({ member: null })} onEdit={(m) => setEditing({ member: m })} onRemove={handleRemoveMember}
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
)
|
|
254
|
+
)}
|
|
255
|
+
|
|
256
|
+
{editing && (
|
|
257
|
+
<GroupMemberEditDialog
|
|
258
|
+
key={editing.member?.id ?? "add"}
|
|
259
|
+
open
|
|
260
|
+
member={editing.member}
|
|
261
|
+
projectName={projectName}
|
|
262
|
+
onSubmit={handleSubmitMember}
|
|
263
|
+
onClose={() => setEditing(null)}
|
|
264
|
+
/>
|
|
170
265
|
)}
|
|
171
266
|
|
|
172
267
|
<GroupFullTranscriptView groupId={groupId} message={transcriptMsg} onClose={() => setTranscriptMsg(null)} />
|
|
@@ -35,6 +35,7 @@ export function GroupCreateDialog({
|
|
|
35
35
|
}: GroupCreateDialogProps) {
|
|
36
36
|
const isMobile = useIsMobile();
|
|
37
37
|
const [name, setName] = useState("");
|
|
38
|
+
const [cap, setCap] = useState(10); // reply-burst cap (max AI turns per message)
|
|
38
39
|
const [leaderName, setLeaderName] = useState("Leader");
|
|
39
40
|
const [leaderPersona, setLeaderPersona] = useState("");
|
|
40
41
|
const [leaderModel, setLeaderModel] = useState<string | null>(null);
|
|
@@ -43,7 +44,7 @@ export function GroupCreateDialog({
|
|
|
43
44
|
const [error, setError] = useState<string | null>(null);
|
|
44
45
|
|
|
45
46
|
const reset = useCallback(() => {
|
|
46
|
-
setName(""); setLeaderName("Leader"); setLeaderPersona(""); setLeaderModel(null);
|
|
47
|
+
setName(""); setCap(10); setLeaderName("Leader"); setLeaderPersona(""); setLeaderModel(null);
|
|
47
48
|
setMembers([newMember()]); setError(null); setSubmitting(false);
|
|
48
49
|
}, []);
|
|
49
50
|
|
|
@@ -79,14 +80,14 @@ export function GroupCreateDialog({
|
|
|
79
80
|
setSubmitting(true);
|
|
80
81
|
setError(null);
|
|
81
82
|
try {
|
|
82
|
-
const group = await createGroup({ projectName, projectPath, name: gName, members: roster });
|
|
83
|
+
const group = await createGroup({ projectName, projectPath, name: gName, maxTurns: cap, members: roster });
|
|
83
84
|
onCreated(group);
|
|
84
85
|
close();
|
|
85
86
|
} catch (e) {
|
|
86
87
|
setError(e instanceof Error ? e.message : "Failed to create group");
|
|
87
88
|
setSubmitting(false);
|
|
88
89
|
}
|
|
89
|
-
}, [name, leaderName, leaderPersona, leaderModel, members, projectName, projectPath, onCreated, close]);
|
|
90
|
+
}, [name, cap, leaderName, leaderPersona, leaderModel, members, projectName, projectPath, onCreated, close]);
|
|
90
91
|
|
|
91
92
|
const inputCls =
|
|
92
93
|
"w-full min-h-[40px] rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-text-subtle focus:outline-none focus:ring-1 focus:ring-primary";
|
|
@@ -99,6 +100,12 @@ export function GroupCreateDialog({
|
|
|
99
100
|
placeholder="e.g. Design Review" autoFocus />
|
|
100
101
|
</div>
|
|
101
102
|
|
|
103
|
+
<div>
|
|
104
|
+
<label className="mb-1 block text-xs font-medium text-text-secondary">Reply cap (max AI turns per message, 1–50)</label>
|
|
105
|
+
<input type="number" min={1} max={50} className={inputCls} value={cap}
|
|
106
|
+
onChange={(e) => setCap(Math.max(1, Math.min(50, Number(e.target.value) || 1)))} />
|
|
107
|
+
</div>
|
|
108
|
+
|
|
102
109
|
<div className="rounded-lg border border-border p-3">
|
|
103
110
|
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-primary">Leader</p>
|
|
104
111
|
<div className="flex flex-col gap-2">
|
|
@@ -3,7 +3,8 @@ import { Loader2 } from "lucide-react";
|
|
|
3
3
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
4
4
|
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
5
5
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
6
|
-
import { getTranscript } from "@/lib/api-group-chat";
|
|
6
|
+
import { getTranscript, type TranscriptResult } from "@/lib/api-group-chat";
|
|
7
|
+
import { GroupTranscriptMessages } from "./group-transcript-messages";
|
|
7
8
|
import type { GroupMessage } from "../../../types/group-chat";
|
|
8
9
|
|
|
9
10
|
interface GroupFullTranscriptViewProps {
|
|
@@ -16,7 +17,7 @@ interface GroupFullTranscriptViewProps {
|
|
|
16
17
|
* Adaptive: centered dialog on desktop, bottom sheet on mobile. */
|
|
17
18
|
export function GroupFullTranscriptView({ groupId, message, onClose }: GroupFullTranscriptViewProps) {
|
|
18
19
|
const isMobile = useIsMobile();
|
|
19
|
-
const [
|
|
20
|
+
const [data, setData] = useState<TranscriptResult | null>(null);
|
|
20
21
|
const [loading, setLoading] = useState(false);
|
|
21
22
|
const [error, setError] = useState<string | null>(null);
|
|
22
23
|
|
|
@@ -25,11 +26,11 @@ export function GroupFullTranscriptView({ groupId, message, onClose }: GroupFull
|
|
|
25
26
|
useEffect(() => {
|
|
26
27
|
if (!message || !sessionRef) return;
|
|
27
28
|
let cancelled = false;
|
|
28
|
-
|
|
29
|
+
setData(null);
|
|
29
30
|
setError(null);
|
|
30
31
|
setLoading(true);
|
|
31
32
|
getTranscript(groupId, sessionRef)
|
|
32
|
-
.then((res) => { if (!cancelled)
|
|
33
|
+
.then((res) => { if (!cancelled) setData(res); })
|
|
33
34
|
.catch((e) => { if (!cancelled) setError(e instanceof Error ? e.message : "Failed to load transcript"); })
|
|
34
35
|
.finally(() => { if (!cancelled) setLoading(false); });
|
|
35
36
|
return () => { cancelled = true; };
|
|
@@ -55,10 +56,10 @@ export function GroupFullTranscriptView({ groupId, message, onClose }: GroupFull
|
|
|
55
56
|
)}
|
|
56
57
|
</div>
|
|
57
58
|
)}
|
|
58
|
-
{!loading && !error &&
|
|
59
|
-
<
|
|
60
|
-
{
|
|
61
|
-
</
|
|
59
|
+
{!loading && !error && data != null && (
|
|
60
|
+
<div className="px-1 py-1">
|
|
61
|
+
<GroupTranscriptMessages messages={data.messages} config={data.config} />
|
|
62
|
+
</div>
|
|
62
63
|
)}
|
|
63
64
|
</div>
|
|
64
65
|
);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import { Loader2 } from "lucide-react";
|
|
3
|
+
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
4
|
+
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
5
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { ModelSelector } from "@/components/chat/model-selector";
|
|
8
|
+
import type { GroupMember, MemberRole } from "../../../types/group-chat";
|
|
9
|
+
|
|
10
|
+
export interface MemberFormValues {
|
|
11
|
+
name: string;
|
|
12
|
+
persona: string | null;
|
|
13
|
+
model: string | null;
|
|
14
|
+
role: MemberRole;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
open: boolean;
|
|
19
|
+
/** Editing an existing member, or null when adding a new one. */
|
|
20
|
+
member: GroupMember | null;
|
|
21
|
+
projectName: string;
|
|
22
|
+
providerId?: string;
|
|
23
|
+
onSubmit: (values: MemberFormValues) => Promise<void>;
|
|
24
|
+
onClose: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const inputCls =
|
|
28
|
+
"w-full min-h-[40px] rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-text-subtle focus:outline-none focus:ring-1 focus:ring-primary";
|
|
29
|
+
|
|
30
|
+
export function GroupMemberEditDialog({ open, member, projectName, providerId = "claude", onSubmit, onClose }: Props) {
|
|
31
|
+
const isMobile = useIsMobile();
|
|
32
|
+
const [name, setName] = useState(member?.name ?? "");
|
|
33
|
+
const [persona, setPersona] = useState(member?.persona ?? "");
|
|
34
|
+
const [model, setModel] = useState<string | null>(member?.model ?? null);
|
|
35
|
+
const [isLeader, setIsLeader] = useState(member?.role === "leader");
|
|
36
|
+
const [submitting, setSubmitting] = useState(false);
|
|
37
|
+
const [error, setError] = useState<string | null>(null);
|
|
38
|
+
|
|
39
|
+
const handleSubmit = useCallback(async () => {
|
|
40
|
+
const n = name.trim();
|
|
41
|
+
if (!n) { setError("Name is required"); return; }
|
|
42
|
+
setSubmitting(true);
|
|
43
|
+
setError(null);
|
|
44
|
+
try {
|
|
45
|
+
await onSubmit({ name: n, persona: persona.trim() || null, model, role: isLeader ? "leader" : "member" });
|
|
46
|
+
onClose();
|
|
47
|
+
} catch (e) {
|
|
48
|
+
setError(e instanceof Error ? e.message : "Failed to save member");
|
|
49
|
+
setSubmitting(false);
|
|
50
|
+
}
|
|
51
|
+
}, [name, persona, model, isLeader, onSubmit, onClose]);
|
|
52
|
+
|
|
53
|
+
const alreadyLeader = member?.role === "leader";
|
|
54
|
+
|
|
55
|
+
const form = (
|
|
56
|
+
<div className="flex flex-col gap-3 px-1 py-1">
|
|
57
|
+
<input className={inputCls} value={name} onChange={(e) => setName(e.target.value)} placeholder="Member name" autoFocus />
|
|
58
|
+
<input className={inputCls} value={persona} onChange={(e) => setPersona(e.target.value)} placeholder="Persona (optional)" />
|
|
59
|
+
<ModelSelector value={model} onChange={setModel} projectName={projectName} providerId={providerId} />
|
|
60
|
+
<label className={cnLeader(alreadyLeader)}>
|
|
61
|
+
<input
|
|
62
|
+
type="checkbox"
|
|
63
|
+
className="size-4 accent-[var(--color-primary)]"
|
|
64
|
+
checked={isLeader}
|
|
65
|
+
disabled={alreadyLeader}
|
|
66
|
+
onChange={(e) => setIsLeader(e.target.checked)}
|
|
67
|
+
/>
|
|
68
|
+
<span>{alreadyLeader ? "Leader (reassign from another member)" : "Set as leader"}</span>
|
|
69
|
+
</label>
|
|
70
|
+
{error && <p className="text-xs text-destructive">{error}</p>}
|
|
71
|
+
<div className="flex justify-end gap-2 pt-1">
|
|
72
|
+
<Button variant="outline" onClick={onClose} disabled={submitting}>Cancel</Button>
|
|
73
|
+
<Button onClick={handleSubmit} disabled={submitting}>
|
|
74
|
+
{submitting && <Loader2 className="mr-1.5 size-4 animate-spin" />}
|
|
75
|
+
{member ? "Save" : "Add member"}
|
|
76
|
+
</Button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!open) return null;
|
|
82
|
+
const title = member ? "Edit member" : "Add member";
|
|
83
|
+
|
|
84
|
+
if (isMobile) {
|
|
85
|
+
return (
|
|
86
|
+
<BottomSheet open onClose={onClose} className="flex max-h-[90vh] flex-col">
|
|
87
|
+
<div className="flex min-h-0 flex-col px-4 pb-2">
|
|
88
|
+
<h2 className="mb-2 text-base font-semibold text-foreground">{title}</h2>
|
|
89
|
+
{form}
|
|
90
|
+
</div>
|
|
91
|
+
</BottomSheet>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<Dialog open onOpenChange={(o) => { if (!o) onClose(); }}>
|
|
97
|
+
<DialogContent className="flex flex-col sm:max-w-md">
|
|
98
|
+
<DialogHeader><DialogTitle>{title}</DialogTitle></DialogHeader>
|
|
99
|
+
{form}
|
|
100
|
+
</DialogContent>
|
|
101
|
+
</Dialog>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Label styling for the leader checkbox row (dimmed when already leader). */
|
|
106
|
+
function cnLeader(disabled: boolean): string {
|
|
107
|
+
return `flex items-center gap-2 text-sm text-text-secondary ${disabled ? "opacity-60" : ""}`;
|
|
108
|
+
}
|