@hienlh/ppm 0.19.0 → 0.19.2
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 +13 -1
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{adaptive-context-menu-JfuUSqbe.js → adaptive-context-menu-BbuRVTLn.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-MfdJKVM5.js → ai-resource-editor-BCCOUzox.js} +1 -1
- package/dist/web/assets/{ai-settings-section-CJE7jFsj.js → ai-settings-section-BEM1z7kp.js} +1 -1
- package/dist/web/assets/{angular-html-VzxSzTCy.js → angular-html-BlcrLjUx.js} +1 -1
- package/dist/web/assets/{angular-ts-CmJ4bmzM.js → angular-ts-BUjtceAJ.js} +1 -1
- package/dist/web/assets/{api-client-qRWu6_s5.js → api-client-BzWM7XPR.js} +1 -1
- package/dist/web/assets/api-client-D1CePgQ4.js +1 -0
- package/dist/web/assets/api-settings-CNtKkHqW.js +1 -0
- package/dist/web/assets/{api-settings-CPg4yGvS.js → api-settings-Cubuqjsu.js} +1 -1
- package/dist/web/assets/{api-themes-DsAp8m7m.js → api-themes-YsZgf6Xj.js} +1 -1
- package/dist/web/assets/{apl-do17bnVx.js → apl-B3g0v1pk.js} +1 -1
- package/dist/web/assets/{astro-ClY0e0OA.js → astro-FRnyn7cH.js} +1 -1
- package/dist/web/assets/{audio-preview-Fw935Mok.js → audio-preview-DWTRm4HC.js} +1 -1
- package/dist/web/assets/{blade-BXSdXDGq.js → blade-NLdOqUKw.js} +1 -1
- package/dist/web/assets/{bot-CWZAuloY.js → bot-D6sx11y0.js} +1 -1
- package/dist/web/assets/{bsl-MxsMKDUZ.js → bsl-CenHZfaU.js} +1 -1
- package/dist/web/assets/{button-CrwgGd1y.js → button-Cq8yixjf.js} +1 -1
- package/dist/web/assets/{cairo-4CIrzX_p.js → cairo-B_WR0aiQ.js} +1 -1
- package/dist/web/assets/chat-tab-CmXGsJGc.js +15 -0
- package/dist/web/assets/{chevron-down-D7CbB5f6.js → chevron-down-Dp0rpHb8.js} +1 -1
- package/dist/web/assets/chevron-right-De-Wz5Lz.js +1 -0
- package/dist/web/assets/circle-BCy0AUCo.js +1 -0
- package/dist/web/assets/{circle-question-mark-BBzFZrNh.js → circle-question-mark-CzR7YRc-.js} +1 -1
- package/dist/web/assets/clock-C1CQUQPZ.js +1 -0
- package/dist/web/assets/{cobol-phPOE89d.js → cobol-DOynhWYj.js} +1 -1
- package/dist/web/assets/code-DJgYrFzm.js +1 -0
- package/dist/web/assets/code-editor-DtHXzNZ1.js +7 -0
- package/dist/web/assets/{coffee-Cjuukfz3.js → coffee-Bn6dbw4P.js} +1 -1
- package/dist/web/assets/{conflict-editor-Ni0PPsKu.js → conflict-editor-Cgb-z12D.js} +1 -1
- package/dist/web/assets/{context-menu-BEAa1qhd.js → context-menu-DfWZAxQy.js} +1 -1
- package/dist/web/assets/{copy-CbPdNp7E.js → copy-BewHj66E.js} +1 -1
- package/dist/web/assets/{cpp-CT7tGBC3.js → cpp-CoZR9t8r.js} +1 -1
- package/dist/web/assets/{createLucideIcon-ConhwJnb.js → createLucideIcon-D8pyde1A.js} +1 -1
- package/dist/web/assets/{crystal-D8U1Qnqh.js → crystal-BSheONSg.js} +1 -1
- package/dist/web/assets/{csv-preview-Coc2oed1.js → csv-preview-BZMHfFH3.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-B8WQkZDB.js → data-grid-overlay-editor-gwPoVSCU.js} +1 -1
- package/dist/web/assets/{data-grid-types-KpjaKAci.js → data-grid-types-Dq_H1IPa.js} +1 -1
- package/dist/web/assets/{database-BqyeGogo.js → database-Y3ESDK4-.js} +1 -1
- package/dist/web/assets/database-viewer-GVaQVMdb.js +1 -0
- package/dist/web/assets/{dialog-DrL3WEXd.js → dialog-BSfuiTkJ.js} +1 -1
- package/dist/web/assets/{diff-viewer-CT0lmDc9.js → diff-viewer-BG5_fH1E.js} +1 -1
- package/dist/web/assets/{dist-C2_dKAa_.js → dist-aDSxTR_v.js} +1 -1
- package/dist/web/assets/{docx-preview-76KDw1eE.js → docx-preview-DqVDOMmv.js} +1 -1
- package/dist/web/assets/{download-DAF7SXcf.js → download-DG3_9xXy.js} +1 -1
- package/dist/web/assets/{edge-Db4ze6bw.js → edge-DTtoslQu.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-DyC_-4My.js → edit-diff-preview-CXSbSUBX.js} +1 -1
- package/dist/web/assets/{elixir-RZPZ0472.js → elixir-CIBCZ3Zi.js} +1 -1
- package/dist/web/assets/{elm-DNZcQt_7.js → elm-BLmb7K1H.js} +1 -1
- package/dist/web/assets/{erb-DRONsZXs.js → erb-BBY8ApRd.js} +1 -1
- package/dist/web/assets/{erlang-DeG0-QwJ.js → erlang-D5Zb5_P0.js} +1 -1
- package/dist/web/assets/{esm-CZy-sSoK.js → esm-6F8v1jLL.js} +1 -1
- package/dist/web/assets/explorer-body-BPFnjxYj.js +2 -0
- package/dist/web/assets/explorer-window-content-Da1T9uM4.js +1 -0
- package/dist/web/assets/{extension-store-ClQpnxQr.js → extension-store-Bhu--fPX.js} +1 -1
- package/dist/web/assets/{extension-webview-BW5GuBnH.js → extension-webview-BSh6KGd7.js} +1 -1
- package/dist/web/assets/{external-link-ojDHcQxQ.js → external-link-B6Hl0U4X.js} +1 -1
- package/dist/web/assets/{eye-off-Rq1n2-oe.js → eye-off-CUdzgmX8.js} +1 -1
- package/dist/web/assets/{file-exclamation-point-Bpg-zDPP.js → file-exclamation-point-JzhTU2TX.js} +1 -1
- package/dist/web/assets/{file-store-28e44KP3.js → file-store-6HW2Lfl4.js} +1 -1
- package/dist/web/assets/{folder-symlink-LD-mr54V.js → folder-symlink-Dx1hOk8X.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CK5VRJNd.js → fortran-fixed-form-CBdwkEUJ.js} +1 -1
- package/dist/web/assets/{fsharp-TVVgzHWp.js → fsharp-AT4XdVYH.js} +1 -1
- package/dist/web/assets/{gdresource-DiUcyewI.js → gdresource-D852fWBR.js} +1 -1
- package/dist/web/assets/{git-commit-CfOruFrg.js → git-commit-DWfWAyxy.js} +1 -1
- package/dist/web/assets/{git-log-panel-D1-YXZ0O.js → git-log-panel-DNC9fV0M.js} +1 -1
- package/dist/web/assets/{git-rebase-Bxyr24oc.js → git-rebase-C6opk8gr.js} +1 -1
- package/dist/web/assets/glide-data-grid-CICFjJon.js +141 -0
- package/dist/web/assets/{glimmer-js-CT5aJxz_.js → glimmer-js-DLi5mWYO.js} +1 -1
- package/dist/web/assets/{glimmer-ts-CRjP9Yey.js → glimmer-ts-D-scFX30.js} +1 -1
- package/dist/web/assets/{glsl-BX2--oI_.js → glsl-dbQzF4iA.js} +1 -1
- package/dist/web/assets/{graphql-BIQsvBxQ.js → graphql-D1m5Wen2.js} +1 -1
- package/dist/web/assets/group-chat-tab-DbUkcOaJ.js +1 -0
- package/dist/web/assets/{hack-CTWk0k1m.js → hack-CqYmpO7u.js} +1 -1
- package/dist/web/assets/{haml-DGh27hXc.js → haml-BXzKWEag.js} +1 -1
- package/dist/web/assets/{hand-DKSxDa5c.js → hand-x0ejq3fS.js} +1 -1
- package/dist/web/assets/{handlebars-BJL1jNrG.js → handlebars-DXnrtHtq.js} +1 -1
- package/dist/web/assets/{html-DALyDNqU.js → html-DhgQJ-xL.js} +1 -1
- package/dist/web/assets/{html-derivative-B4XcU57e.js → html-derivative-xZRft-YE.js} +1 -1
- package/dist/web/assets/{http-DfMo7nNu.js → http-B53mSj14.js} +1 -1
- package/dist/web/assets/{hurl-CGc_n2Qf.js → hurl-CsomVy04.js} +1 -1
- package/dist/web/assets/{hxml-40zZ1t4x.js → hxml-LffjWwKA.js} +1 -1
- package/dist/web/assets/{image-preview-Cp3XB8UO.js → image-preview-CNA6XMNS.js} +1 -1
- package/dist/web/assets/index-DTqbMiVc.js +52 -0
- package/dist/web/assets/{input-DTmI80l2.js → input-D0ol5jW0.js} +1 -1
- package/dist/web/assets/{jinja-D5zuIn9u.js → jinja-CCHURl7V.js} +1 -1
- package/dist/web/assets/{jison-Dxut9QwA.js → jison-7qBd8dnR.js} +1 -1
- package/dist/web/assets/{julia-Cu85tTPo.js → julia-BY9pnTcy.js} +1 -1
- package/dist/web/assets/{just-CctM4uLa.js → just-D7wRSNRr.js} +1 -1
- package/dist/web/assets/{keybindings-store-Bi0q9x4Q.js → keybindings-store-BGkB72gM.js} +1 -1
- package/dist/web/assets/keybindings-store-Bq6VWl5X.js +1 -0
- package/dist/web/assets/{keyboard-AdwmYDSu.js → keyboard-DBBaoQSu.js} +1 -1
- package/dist/web/assets/{latex-kpwS_CC7.js → latex-RgsCae55.js} +1 -1
- package/dist/web/assets/{lib-Ca7ai8xa.js → lib-v9x_qXgI.js} +1 -1
- package/dist/web/assets/{liquid-B8Rr0xue.js → liquid-CnDFzvOZ.js} +1 -1
- package/dist/web/assets/loader-circle-DouKgIXy.js +1 -0
- package/dist/web/assets/{lock-BEtt_eTO.js → lock-CNxUGpTk.js} +1 -1
- package/dist/web/assets/{lua-C_3gixwM.js → lua-0lj5W8sm.js} +1 -1
- package/dist/web/assets/markdown-renderer-DEAVpo0C.js +1 -0
- package/dist/web/assets/markdown-renderer-dbLIvU2M.js +3 -0
- package/dist/web/assets/{marko-B28rvNL-.js → marko-CQgqAhbV.js} +1 -1
- package/dist/web/assets/{mdc-DQ6-STFO.js → mdc-BkIcTiIy.js} +1 -1
- package/dist/web/assets/{media-url-BjvbfTTl.js → media-url-DDciwvLU.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-DXn5g2LB.js → mobile-bottom-sheet-pjcHKorA.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CD_kl-jA.js +1 -0
- package/dist/web/assets/{monitor-CXI_XpWJ.js → monitor-CxPV_fQI.js} +1 -1
- package/dist/web/assets/{monitor-smartphone-C1wUPOSg.js → monitor-smartphone-DLZ6Q05s.js} +1 -1
- package/dist/web/assets/{nextflow-B1xq2O8L.js → nextflow-DvIW68DS.js} +1 -1
- package/dist/web/assets/{nginx-xsLxRGxO.js → nginx-O-hft0gG.js} +1 -1
- package/dist/web/assets/{nim-wqcek3WO.js → nim-BxrR4HwQ.js} +1 -1
- package/dist/web/assets/notification-store-Dgbsw9p9.js +1 -0
- package/dist/web/assets/{number-overlay-editor-1c4sgZJt.js → number-overlay-editor-BoLRYuZT.js} +1 -1
- package/dist/web/assets/{overview-panel-Cr2xsHc6.js → overview-panel-l3g1gT5e.js} +1 -1
- package/dist/web/assets/{panel-store-Dv28vz3L.js → panel-store-UOV8wrfR.js} +1 -1
- package/dist/web/assets/{pdf-preview-DKeIIJTv.js → pdf-preview-B2CJ_EKv.js} +1 -1
- package/dist/web/assets/{pencil-CHKLOgLs.js → pencil-BlIIg6RH.js} +1 -1
- package/dist/web/assets/{perl-BH54gN9q.js → perl-Da-uNqmL.js} +1 -1
- package/dist/web/assets/{php-Br5ZrEZm.js → php-JMu45mEx.js} +1 -1
- package/dist/web/assets/play-DpEEBJTT.js +1 -0
- package/dist/web/assets/{plus-BxqD2FIZ.js → plus-BR0qewJI.js} +1 -1
- package/dist/web/assets/portal-container-context-D-495m5C.js +1 -0
- package/dist/web/assets/{postgres-viewer-BJPr_FYL.js → postgres-viewer-DstZwUai.js} +3 -3
- package/dist/web/assets/{process-table-BqTcAJ-h.js → process-table-kmQ2-8qJ.js} +1 -1
- package/dist/web/assets/{project-store-KHjFTCxf.js → project-store-DTUCS65m.js} +1 -1
- package/dist/web/assets/{pug-8VCubZ3H.js → pug-CLzyQhia.js} +1 -1
- package/dist/web/assets/{qml-D8AF1RZ6.js → qml-BHXfGB1l.js} +1 -1
- package/dist/web/assets/{razor-6v042IJW.js → razor-BqetJLN3.js} +1 -1
- package/dist/web/assets/{react-DHs6efqI.js → react-B549sK_T.js} +1 -1
- package/dist/web/assets/{refresh-cw-BALzw7L3.js → refresh-cw-BQU5g2CY.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-KKTRNjOL.js +2 -0
- package/dist/web/assets/{remote-desktop-mobile-view-DaoB49k4.js → remote-desktop-mobile-view-C1ST4-Fm.js} +1 -1
- package/dist/web/assets/{remote-desktop-readiness-gate--LJgsuoj.js → remote-desktop-readiness-gate-BH3BE5wA.js} +1 -1
- package/dist/web/assets/{remote-desktop-window-content-Cihx4PNZ.js → remote-desktop-window-content-BaZJQt_N.js} +1 -1
- package/dist/web/assets/{rotate-ccw-CuvNBM80.js → rotate-ccw-CpjRo90J.js} +1 -1
- package/dist/web/assets/{rst-mTGFo4AQ.js → rst-iQdJZ0BP.js} +1 -1
- package/dist/web/assets/{ruby-BBaOP636.js → ruby-CNwzZGdH.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Zl5iIx-B.js → run-in-terminal-CBXmVGWa.js} +1 -1
- package/dist/web/assets/{sas-D5QEkIH2.js → sas-BpBIT2eR.js} +1 -1
- package/dist/web/assets/{save-Dxq-Zepc.js → save-DYLiVzU8.js} +1 -1
- package/dist/web/assets/{scroll-area-B5yhPpZo.js → scroll-area-lc0ZHORr.js} +1 -1
- package/dist/web/assets/{scss-C5Mu6y2u.js → scss-D1MotuEp.js} +1 -1
- package/dist/web/assets/{send-Cits8q0l.js → send-C_sLFt56.js} +1 -1
- package/dist/web/assets/{send-to-chat-Cv7ikFUC.js → send-to-chat-DJAdlvJ8.js} +1 -1
- package/dist/web/assets/{settings-store-Dp4i8zeA.js → settings-store-zbPkG7o-.js} +2 -2
- package/dist/web/assets/settings-tab-Cn-AqUP_.js +1 -0
- package/dist/web/assets/{settings-tab-BtMvvKAF.js → settings-tab-xyRpt2gq.js} +1 -1
- package/dist/web/assets/{shaderlab-mLnpMpN3.js → shaderlab-BNqmcPQi.js} +1 -1
- package/dist/web/assets/{shallow-BBJ_qoEF.js → shallow-CmPMAPte.js} +1 -1
- package/dist/web/assets/{shellsession-qdcDfUuk.js → shellsession-BBLOVOO-.js} +1 -1
- package/dist/web/assets/{shield-check-CH7F1P7m.js → shield-check-CUI36aSc.js} +1 -1
- package/dist/web/assets/{shield-off-C9HiF0-6.js → shield-off-GdkkVhb3.js} +1 -1
- package/dist/web/assets/{soy-CGGf1G43.js → soy-BpTGo9uy.js} +1 -1
- package/dist/web/assets/{sparkles-BnuLH6Ck.js → sparkles-Dh-_J2Rd.js} +1 -1
- package/dist/web/assets/{sparql-tQHB50VD.js → sparql-D7A3yKob.js} +1 -1
- package/dist/web/assets/{sql-query-editor-DZqnYIB7.js → sql-query-editor-B86NiZHI.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DXutos0e.js → sqlite-viewer-DB775F6C.js} +1 -1
- package/dist/web/assets/{stata-DVQNLMcR.js → stata-B7N56ptS.js} +1 -1
- package/dist/web/assets/{surrealql-lu576tBi.js → surrealql-CTmnD-lv.js} +1 -1
- package/dist/web/assets/{svelte-CSCrOBjT.js → svelte-BppwY9TR.js} +1 -1
- package/dist/web/assets/{switch-BPol3BFC.js → switch-BmjKoIpR.js} +1 -1
- package/dist/web/assets/{system-monitor-body-Ct8_GX_f.js → system-monitor-body-BDxHQvy9.js} +2 -2
- package/dist/web/assets/system-monitor-tab-BoTHLx6B.js +1 -0
- package/dist/web/assets/system-monitor-window-content-B9Y98UW6.js +1 -0
- package/dist/web/assets/{tab-host-window-content-C5kYP0-z.js → tab-host-window-content-C1j8zr68.js} +1 -1
- package/dist/web/assets/{tab-store-DS3YlUwR.js → tab-store-DzYSsw5a.js} +1 -1
- package/dist/web/assets/{table-BXDyImqk.js → table-D5Q4ap4W.js} +1 -1
- package/dist/web/assets/team-member-sheet-Cxz6h5Ne.js +2 -0
- package/dist/web/assets/{team-member-window-content-BJ_XFw8-.js → team-member-window-content-CUSuRHCB.js} +1 -1
- package/dist/web/assets/{templ-CPt8AS4Q.js → templ-CvTMKKra.js} +1 -1
- package/dist/web/assets/{terminal-tab-8bKQ3CWR.js → terminal-tab-B9I_HlES.js} +3 -3
- package/dist/web/assets/{tex-BrTByT7i.js → tex--c4POEUE.js} +1 -1
- package/dist/web/assets/{text-wrap-CzMjB6GD.js → text-wrap-DBvQD3kc.js} +1 -1
- package/dist/web/assets/{tool-cards-pIu_cmEL.js → tool-cards-D3KSzd2K.js} +4 -4
- package/dist/web/assets/{trash-2-BjShZ7if.js → trash-2-By4VMbC4.js} +1 -1
- package/dist/web/assets/{triangle-alert-qQ6Plydp.js → triangle-alert-CUTURFbG.js} +1 -1
- package/dist/web/assets/{ts-tags-DSqEZ7A8.js → ts-tags--kx7D-Hw.js} +1 -1
- package/dist/web/assets/{twig-DW9qc1bf.js → twig-UG9uMHGd.js} +1 -1
- package/dist/web/assets/{upload-7Zs6oaK5.js → upload-BNlX8D07.js} +1 -1
- package/dist/web/assets/{use-blob-url-DKbV7PM_.js → use-blob-url-Bdxopwwc.js} +1 -1
- package/dist/web/assets/{use-coarse-long-press-DGyC48NF.js → use-coarse-long-press-BmI_GVW5.js} +1 -1
- package/dist/web/assets/{use-is-mobile-ClE-UcPF.js → use-is-mobile-B9UZNwSm.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DaxAk3gB.js → use-monaco-theme-CVCye2kq.js} +1 -1
- package/dist/web/assets/use-open-team-member-D9EOB8-i.js +1 -0
- package/dist/web/assets/{use-remote-desktop-display-choice-BUA_1eaH.js → use-remote-desktop-display-choice-neUqyE3p.js} +1 -1
- package/dist/web/assets/{use-visual-viewport-CCX21c9w.js → use-visual-viewport-Sfb6CZ80.js} +1 -1
- package/dist/web/assets/{use-websocket-CWpG7PWN.js → use-websocket-xOOwBdwP.js} +1 -1
- package/dist/web/assets/vendor-markdown-Ls5AcU91.js +291 -0
- package/dist/web/assets/{vendor-ui-CXJtBULr.js → vendor-ui-CaeeFO0B.js} +1 -1
- package/dist/web/assets/video-preview-DvpgBDhM.js +1 -0
- package/dist/web/assets/{vue-html-CG5BKtq1.js → vue-html-8KEW6-I9.js} +1 -1
- package/dist/web/assets/{vue-CJNDL1g-.js → vue-jjTomlbt.js} +1 -1
- package/dist/web/assets/{vue-vine-Dd5HZ7T5.js → vue-vine-C5tohCkU.js} +1 -1
- package/dist/web/assets/{window-pip-registry-9H6Y-HqR.js → window-pip-registry-FdZdGF7h.js} +1 -1
- package/dist/web/assets/{window-store-CNzkeUyO.js → window-store-D0x_fxr0.js} +1 -1
- package/dist/web/assets/x-DiOUR_mp.js +1 -0
- package/dist/web/assets/{xml-DTOm5niz.js → xml-ByFFosqL.js} +1 -1
- package/dist/web/assets/{xsl-BD_kBC04.js → xsl-CZUwiLkj.js} +1 -1
- package/dist/web/index.html +59 -59
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/scripts/check-ppm-dir-usage.sh +0 -0
- package/scripts/install.sh +0 -0
- package/scripts/release.sh +0 -0
- package/src/providers/claude-agent-sdk.ts +51 -43
- package/src/providers/codex-app-server/codex-provider.ts +7 -1
- package/src/server/routes/chat.ts +28 -3
- package/src/server/ws/chat.ts +9 -4
- package/src/server/ws/terminal.ts +9 -3
- package/src/services/media-transcode/ffmpeg-capabilities.ts +48 -7
- package/src/services/named-tunnel/named-tunnel-retry.ts +26 -0
- package/src/services/named-tunnel/network-ready.ts +63 -0
- package/src/services/supervisor.ts +98 -0
- package/src/services/transcript-images.ts +14 -7
- package/src/services/upgrade.service.ts +7 -2
- package/src/shared/image-limits.ts +37 -0
- package/src/types/api.ts +3 -1
- package/src/types/chat.ts +6 -0
- package/src/types/extension-messages.ts +2 -0
- package/src/web/components/chat/attachment-chips.tsx +10 -0
- package/src/web/components/chat/chat-tab.tsx +37 -13
- package/src/web/components/chat/message-input.tsx +102 -11
- package/src/web/components/chat/session-debug-dialog.tsx +2 -1
- package/src/web/components/chat/user-message-parse.ts +33 -11
- package/src/web/components/database/database-viewer.tsx +34 -11
- package/src/web/components/database/extract-query-table.ts +24 -0
- package/src/web/components/database/glide-data-grid.tsx +11 -4
- package/src/web/components/database/glide-grid-types.ts +4 -0
- package/src/web/components/database/split-sql-statements.ts +116 -0
- package/src/web/components/database/sql-completion-provider.ts +1 -26
- package/src/web/components/database/sql-query-editor.tsx +2 -1
- package/src/web/components/database/use-database.ts +16 -1
- package/src/web/components/database/use-glide-cell-content.ts +11 -6
- package/src/web/components/editor/code-editor.tsx +30 -56
- package/src/web/hooks/use-chat.ts +6 -3
- package/src/web/lib/attachment-marker-split.ts +44 -0
- package/src/web/lib/image-animation.ts +51 -0
- package/src/web/lib/image-resize-limits.ts +47 -0
- package/src/web/lib/image-resize.ts +133 -0
- package/dist/web/assets/api-client-DLWwuUkJ.js +0 -1
- package/dist/web/assets/api-settings-BFuaZrNC.js +0 -1
- package/dist/web/assets/chat-tab-bSOVoC5N.js +0 -15
- package/dist/web/assets/chevron-right-D7Pn5agw.js +0 -1
- package/dist/web/assets/circle-4g2coECH.js +0 -1
- package/dist/web/assets/clock-DbiNiwCG.js +0 -1
- package/dist/web/assets/code-DdKXqtf_.js +0 -1
- package/dist/web/assets/code-editor-COTa37oe.js +0 -10
- package/dist/web/assets/database-viewer-Dta7lSSk.js +0 -1
- package/dist/web/assets/explorer-body-BGjR2Gbt.js +0 -2
- package/dist/web/assets/explorer-window-content-DAhHh5EQ.js +0 -1
- package/dist/web/assets/glide-data-grid-BSsqxe2v.js +0 -138
- package/dist/web/assets/group-chat-tab-CMtzoaFc.js +0 -1
- package/dist/web/assets/index-BkHlMfPD.js +0 -52
- package/dist/web/assets/keybindings-store-_KfovSz7.js +0 -1
- package/dist/web/assets/loader-circle-DD65e9-7.js +0 -1
- package/dist/web/assets/markdown-renderer-DroTmKbJ.js +0 -1
- package/dist/web/assets/markdown-renderer-ntDi3Tx-.js +0 -3
- package/dist/web/assets/mobile-explorer-sheet-BP6iU4ma.js +0 -1
- package/dist/web/assets/notification-store-II5Kw9rD.js +0 -1
- package/dist/web/assets/play-CPbVzGgq.js +0 -1
- package/dist/web/assets/portal-container-context-BmG4--I-.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-CYZUjYTO.js +0 -2
- package/dist/web/assets/settings-tab-UPPycGRF.js +0 -1
- package/dist/web/assets/system-monitor-tab-DC_EerdI.js +0 -1
- package/dist/web/assets/system-monitor-window-content-D25DMNpw.js +0 -1
- package/dist/web/assets/team-member-sheet-DvmioeQL.js +0 -2
- package/dist/web/assets/use-open-team-member-CopUuDHG.js +0 -1
- package/dist/web/assets/vendor-markdown-CKTlmXbz.js +0 -291
- package/dist/web/assets/video-preview-C2_fdunE.js +0 -1
- package/dist/web/assets/x-Dzu9ecti.js +0 -1
- /package/dist/web/assets/{abap-Cjj2olMr.js → abap-C5DFF0s3.js} +0 -0
- /package/dist/web/assets/{actionscript-3-DCkW7kWC.js → actionscript-3-B0qyRNbc.js} +0 -0
- /package/dist/web/assets/{ada-AEWvZXuX.js → ada-bAJzplug.js} +0 -0
- /package/dist/web/assets/{andromeeda-D7cSpME6.js → andromeeda-DUCpISy6.js} +0 -0
- /package/dist/web/assets/{apache-DjvX0RSe.js → apache-BhAr84Z_.js} +0 -0
- /package/dist/web/assets/{apex-70xIZ0pH.js → apex-CRv8YnNq.js} +0 -0
- /package/dist/web/assets/{applescript-C3qKqxmA.js → applescript-CD-Cw8bt.js} +0 -0
- /package/dist/web/assets/{ara-iGt2zixk.js → ara-BUzeQYoG.js} +0 -0
- /package/dist/web/assets/{asciidoc-Bs2t-06W.js → asciidoc-CT2hTg-v.js} +0 -0
- /package/dist/web/assets/{asm-B-T0eqhE.js → asm-CAu35JeY.js} +0 -0
- /package/dist/web/assets/{aurora-x-D3h_9M3E.js → aurora-x-BeFJoIA1.js} +0 -0
- /package/dist/web/assets/{awk-CZwfwpPi.js → awk-Vau-DrGN.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DulkglEA.js → ayu-dark-CLe-OTsQ.js} +0 -0
- /package/dist/web/assets/{ayu-light-DyNQiRpn.js → ayu-light-CJbQfu4M.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-CcEt4xkI.js → ayu-mirage-GWaKwdrx.js} +0 -0
- /package/dist/web/assets/{ballerina-OhQPjyHS.js → ballerina-DWu0USI5.js} +0 -0
- /package/dist/web/assets/{bat-DqKcJTUn.js → bat-_07Huw7E.js} +0 -0
- /package/dist/web/assets/{beancount-BdXlMi8T.js → beancount-EcvogrcF.js} +0 -0
- /package/dist/web/assets/{berry-DvgY6BkY.js → berry-BBB87SY7.js} +0 -0
- /package/dist/web/assets/{bibtex-BSRJq0yM.js → bibtex-DbueS87A.js} +0 -0
- /package/dist/web/assets/{bicep-DpAdumKt.js → bicep-tcmXnUo4.js} +0 -0
- /package/dist/web/assets/{bird2-rlbtp8di.js → bird2-B2qwilIR.js} +0 -0
- /package/dist/web/assets/{c-AjOLBXIs.js → c-Z58oivSt.js} +0 -0
- /package/dist/web/assets/{c3-DqIXz4Ia.js → c3-U40cGT9p.js} +0 -0
- /package/dist/web/assets/{cadence-DAJtUpOE.js → cadence-BFcD-LyM.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DUPoeZfb.js → catppuccin-frappe-RkC9AX6J.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CK3U68Ql.js → catppuccin-latte-Bsi4l-nO.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-BzteLnIX.js → catppuccin-macchiato-B8JIdTW-.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-ozLd0aBR.js → catppuccin-mocha-oH8wwrDj.js} +0 -0
- /package/dist/web/assets/{clarity-6KqEoBfz.js → clarity-C3WNeRCR.js} +0 -0
- /package/dist/web/assets/{clojure-B_uBL5C7.js → clojure-CFoAz7u9.js} +0 -0
- /package/dist/web/assets/{cmake-B3nxBNmj.js → cmake-Bcsqp9yR.js} +0 -0
- /package/dist/web/assets/{codeowners-DkS-51at.js → codeowners-DC2Z7heP.js} +0 -0
- /package/dist/web/assets/{codeql-rpr1z_mR.js → codeql-BfG68Al5.js} +0 -0
- /package/dist/web/assets/{common-lisp-BfNbv_W6.js → common-lisp-Z5P-lKQe.js} +0 -0
- /package/dist/web/assets/{coq-EAW-1nw0.js → coq-BJslqrnv.js} +0 -0
- /package/dist/web/assets/{csharp-BE3Gn5bq.js → csharp-DhhZTOTs.js} +0 -0
- /package/dist/web/assets/{css-Cr27CfPY.js → css-DiFIpMfE.js} +0 -0
- /package/dist/web/assets/{csv-B5VtaPtH.js → csv-DViLbHxI.js} +0 -0
- /package/dist/web/assets/{cue-jm6lYNrx.js → cue-CoG3mg2I.js} +0 -0
- /package/dist/web/assets/{cypher-DXkvK-pn.js → cypher-jTxC8NAB.js} +0 -0
- /package/dist/web/assets/{d-CgG1_yzd.js → d-6hKIUAbU.js} +0 -0
- /package/dist/web/assets/{dark-plus-CALzcIVB.js → dark-plus-D_-np2Sr.js} +0 -0
- /package/dist/web/assets/{dart-BEVoLlWg.js → dart-CVpxLpVn.js} +0 -0
- /package/dist/web/assets/{dax-H0BW3Q_P.js → dax-BPqJ8qIY.js} +0 -0
- /package/dist/web/assets/{desktop-BtYq1l_0.js → desktop-u_y5_Hr5.js} +0 -0
- /package/dist/web/assets/{diff-CnBYdfTg.js → diff-Duj1XMrd.js} +0 -0
- /package/dist/web/assets/{dist-CJJKpgR7.js → dist-CEZ4plgH.js} +0 -0
- /package/dist/web/assets/{docker-COBitpld.js → docker-8UGSCc7N.js} +0 -0
- /package/dist/web/assets/{dotenv-CG0x1m7c.js → dotenv-DVnj8kyR.js} +0 -0
- /package/dist/web/assets/{dracula-CLCi47X-.js → dracula-D0cEmSg7.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BMv5cCAV.js → dracula-soft-DfGS5Yfr.js} +0 -0
- /package/dist/web/assets/{dream-maker-1Bd4OSww.js → dream-maker-qG8kMGTF.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-B-2zKLn-.js → emacs-lisp-iVqyMLyT.js} +0 -0
- /package/dist/web/assets/{everforest-dark-BGVynCbV.js → everforest-dark-Cruygd5U.js} +0 -0
- /package/dist/web/assets/{everforest-light--rn3R7Cm.js → everforest-light-BiJa8RHL.js} +0 -0
- /package/dist/web/assets/{fennel-SWg9Us_u.js → fennel-H6XSEIoz.js} +0 -0
- /package/dist/web/assets/{fish-B04oqgKk.js → fish-ho_7CxX6.js} +0 -0
- /package/dist/web/assets/{fluent-BefzAGMN.js → fluent-1l5ssJTF.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-alw7jMxb.js → fortran-free-form-Bo1x233A.js} +0 -0
- /package/dist/web/assets/{gdscript-BAyhvjCx.js → gdscript-DffFMp_k.js} +0 -0
- /package/dist/web/assets/{gdshader-Crl09X-B.js → gdshader-CvnpAiAF.js} +0 -0
- /package/dist/web/assets/{genie-BSIGRHu7.js → genie-2eOvAEzW.js} +0 -0
- /package/dist/web/assets/{gherkin-DJhSnSWH.js → gherkin-DL-5OUlV.js} +0 -0
- /package/dist/web/assets/{github-dark-r8C6qwug.js → github-dark-DrTZprgG.js} +0 -0
- /package/dist/web/assets/{github-dark-default-FVtXjcm8.js → github-dark-default-_mDH8IMP.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CzrUPgSb.js → github-dark-dimmed-C48hJuLn.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-1s9rF5EB.js → github-dark-high-contrast-BQ-zGGNe.js} +0 -0
- /package/dist/web/assets/{github-light-BvIWCkl7.js → github-light-BYc90Yx8.js} +0 -0
- /package/dist/web/assets/{github-light-default-CVdhzrxV.js → github-light-default-Daecj4J5.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-CBAf6hjF.js → github-light-high-contrast-DoafCbsU.js} +0 -0
- /package/dist/web/assets/{gleam-DvL1q7aa.js → gleam-hUWWjmnp.js} +0 -0
- /package/dist/web/assets/{gn-BG5NvwAf.js → gn-7PJ3R70o.js} +0 -0
- /package/dist/web/assets/{gnuplot-BARnM-zG.js → gnuplot-V-Pxck7b.js} +0 -0
- /package/dist/web/assets/{go-DXvjjq4q.js → go-ARF6DprD.js} +0 -0
- /package/dist/web/assets/{groovy-CRylSKhr.js → groovy-CKGBIPOz.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-BVPjnqm7.js → gruvbox-dark-hard-Cm9wqdI3.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-U_PDgq0a.js → gruvbox-dark-medium-Cmsd6dG8.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-CkzNtpbr.js → gruvbox-dark-soft-BBGlGGCz.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-DfMmR2G8.js → gruvbox-light-hard-BCR7KmqG.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-BGCak-dd.js → gruvbox-light-medium-CP32bga8.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DnpM3fkN.js → gruvbox-light-soft-qjxk8ps6.js} +0 -0
- /package/dist/web/assets/{haskell-DXgGIDD_.js → haskell-C5DzwGRa.js} +0 -0
- /package/dist/web/assets/{haxe-B7qlTpRz.js → haxe-C8f_jCVv.js} +0 -0
- /package/dist/web/assets/{hcl-ADWfVe_Q.js → hcl-DetJkf6E.js} +0 -0
- /package/dist/web/assets/{hjson-BaM9gLbq.js → hjson-DeZhwIdo.js} +0 -0
- /package/dist/web/assets/{hlsl-iqaRA4MG.js → hlsl-SMIDLRyX.js} +0 -0
- /package/dist/web/assets/{horizon-BPpTnhB9.js → horizon-BMAVDxMx.js} +0 -0
- /package/dist/web/assets/{horizon-bright-yBLg3QbE.js → horizon-bright-3BaV_VGD.js} +0 -0
- /package/dist/web/assets/{houston-eFOp0ySL.js → houston-BWBbEPvb.js} +0 -0
- /package/dist/web/assets/{hy-BALeMn3i.js → hy-DcsmE-Nm.js} +0 -0
- /package/dist/web/assets/{imba-CdGUm_GD.js → imba-CnkgnYMf.js} +0 -0
- /package/dist/web/assets/{ini-DG7imsqe.js → ini-BnvTdau7.js} +0 -0
- /package/dist/web/assets/{java-D5GTT474.js → java-BLFACXat.js} +0 -0
- /package/dist/web/assets/{javascript-V7WwJvRm.js → javascript-BKcFWENk.js} +0 -0
- /package/dist/web/assets/{json-B9FhwAgR.js → json-3AJtJADf.js} +0 -0
- /package/dist/web/assets/{json5--Abk7MUy.js → json5-DLyDVlwe.js} +0 -0
- /package/dist/web/assets/{jsonc-C8jb-Z6x.js → jsonc-B5DgF1jW.js} +0 -0
- /package/dist/web/assets/{jsonl-CaJNxfkm.js → jsonl-CV_O71a2.js} +0 -0
- /package/dist/web/assets/{jsonnet-CPrKMSCU.js → jsonnet-DZfPLgYz.js} +0 -0
- /package/dist/web/assets/{jssm-DEu-9_uo.js → jssm-Cr1zYOsY.js} +0 -0
- /package/dist/web/assets/{jsx-MfAsCWDW.js → jsx-VN-JS2_q.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-DnbcAduF.js → kanagawa-dragon-D9CcT3xg.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-yxsY3ew8.js → kanagawa-lotus-DLXsB8fr.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-C2RyvZaM.js → kanagawa-wave-weDs3Wjz.js} +0 -0
- /package/dist/web/assets/{kdl-CvssmvcU.js → kdl-0XF56jnT.js} +0 -0
- /package/dist/web/assets/{kotlin-jufO49mU.js → kotlin-BPkH9vzk.js} +0 -0
- /package/dist/web/assets/{kusto-BAFnylR7.js → kusto-DMVpADb9.js} +0 -0
- /package/dist/web/assets/{laserwave-BgxFC4Ge.js → laserwave-Cl6T2cGP.js} +0 -0
- /package/dist/web/assets/{lean-Bgt24V1D.js → lean-BvwzQGl1.js} +0 -0
- /package/dist/web/assets/{less-Bk_UaqsM.js → less-DI_03xkC.js} +0 -0
- /package/dist/web/assets/{light-plus-nMxFfInN.js → light-plus-hIiIsQN_.js} +0 -0
- /package/dist/web/assets/{llvm-3_PASDOO.js → llvm-B5hjjSWc.js} +0 -0
- /package/dist/web/assets/{log-CKm0CxB1.js → log-D28raQiE.js} +0 -0
- /package/dist/web/assets/{logo-DFP1_5U6.js → logo-6Cdd4ASb.js} +0 -0
- /package/dist/web/assets/{luau-BcZnoyh3.js → luau-CK9nKpOX.js} +0 -0
- /package/dist/web/assets/{make-CpNSwgwT.js → make-CVnxKCB6.js} +0 -0
- /package/dist/web/assets/{markdown-DhU3Djpv.js → markdown-B9mMQzD5.js} +0 -0
- /package/dist/web/assets/{material-theme-DD5L3OqF.js → material-theme-BZSs2o0j.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-CG2_tD3R.js → material-theme-darker-BeAQBzQ_.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-CzNkUc9Y.js → material-theme-lighter-BAe0IXcs.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-BtLiAaF_.js → material-theme-ocean-CiPIf0Np.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-Brvjz035.js → material-theme-palenight-DJBb4_XN.js} +0 -0
- /package/dist/web/assets/{matlab-CCY0KtMD.js → matlab-CY0dbp_s.js} +0 -0
- /package/dist/web/assets/{mdx-CH6YY0aS.js → mdx-DU9zbF7h.js} +0 -0
- /package/dist/web/assets/{mermaid-Bqh3gFI0.js → mermaid-UmmjGwdh.js} +0 -0
- /package/dist/web/assets/{min-dark-BUwfTzI6.js → min-dark-C59GkzOm.js} +0 -0
- /package/dist/web/assets/{min-light-oLanXCOk.js → min-light-B9QglrdQ.js} +0 -0
- /package/dist/web/assets/{mipsasm-CTh3hPWK.js → mipsasm-Dc6Jsr_D.js} +0 -0
- /package/dist/web/assets/{mojo-DXdszZKa.js → mojo-D5ij-nGW.js} +0 -0
- /package/dist/web/assets/{monokai-TNE_tRxc.js → monokai-QAtE_yCP.js} +0 -0
- /package/dist/web/assets/{moonbit-jrk-Gx1X.js → moonbit-BMtIdiTL.js} +0 -0
- /package/dist/web/assets/{move-DItn61bH.js → move-BB_jMHYZ.js} +0 -0
- /package/dist/web/assets/{narrat-3kB4dkh0.js → narrat-BvvoKGVn.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CSKtwETr.js → nextflow-groovy-CqxtAsnF.js} +0 -0
- /package/dist/web/assets/{night-owl-Dm91nSIq.js → night-owl-CLoE9kjt.js} +0 -0
- /package/dist/web/assets/{night-owl-light-riwrcPRg.js → night-owl-light-BwIFT5Xd.js} +0 -0
- /package/dist/web/assets/{nix-sLgl9zGF.js → nix-BKlnVT9r.js} +0 -0
- /package/dist/web/assets/{nord-TWkJPI6M.js → nord-B7R1KRAK.js} +0 -0
- /package/dist/web/assets/{nushell-CDZi4-Gf.js → nushell-CzUme-NJ.js} +0 -0
- /package/dist/web/assets/{objective-c-DW9PboNp.js → objective-c-D-UKKiOc.js} +0 -0
- /package/dist/web/assets/{objective-cpp-2XCCX6q1.js → objective-cpp-BucWn4Fx.js} +0 -0
- /package/dist/web/assets/{ocaml-CxPdnjrh.js → ocaml-EJCNXMGo.js} +0 -0
- /package/dist/web/assets/{odin-BJjlKpkM.js → odin-CEjnRU_3.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-K1KOmBZ7.js → one-dark-pro-Bv469NX5.js} +0 -0
- /package/dist/web/assets/{one-light-DQB-vUyZ.js → one-light-BlzO5RWI.js} +0 -0
- /package/dist/web/assets/{openscad-DCoLwn4j.js → openscad-Czl6alLw.js} +0 -0
- /package/dist/web/assets/{pascal-z2ktbbWp.js → pascal-BI9fLvG4.js} +0 -0
- /package/dist/web/assets/{pkl-Dyq4-v6z.js → pkl-ChQLsyjU.js} +0 -0
- /package/dist/web/assets/{plastic-C4Rq5PnD.js → plastic-DGL3mtdL.js} +0 -0
- /package/dist/web/assets/{plsql-BCIjO63y.js → plsql-BJDjqcWp.js} +0 -0
- /package/dist/web/assets/{po-_kXpd_0n.js → po-DK5O3OpF.js} +0 -0
- /package/dist/web/assets/{poimandres-C9XggjB3.js → poimandres-CnuqZT4T.js} +0 -0
- /package/dist/web/assets/{polar-DV_1L04z.js → polar-DCsUxSsQ.js} +0 -0
- /package/dist/web/assets/{postcss-BuqS91mQ.js → postcss-8UjdFmis.js} +0 -0
- /package/dist/web/assets/{powerquery-DK13B2Dc.js → powerquery-CAp0CGQj.js} +0 -0
- /package/dist/web/assets/{powershell-tkuhhtuP.js → powershell-CbBsKZJP.js} +0 -0
- /package/dist/web/assets/{prisma-WNXfBtpr.js → prisma-BnNpXCJN.js} +0 -0
- /package/dist/web/assets/{prolog-BzEdNKfO.js → prolog-CC-l9E8Y.js} +0 -0
- /package/dist/web/assets/{proto-B5meVsrA.js → proto-CV_1k2rL.js} +0 -0
- /package/dist/web/assets/{puppet-C7h_p4r2.js → puppet-2D8HU2bv.js} +0 -0
- /package/dist/web/assets/{purescript-qH1KxAs6.js → purescript-BEJAORgU.js} +0 -0
- /package/dist/web/assets/{python-DeTIYd5V.js → python-BBdbb7nn.js} +0 -0
- /package/dist/web/assets/{qmldir-4Rm5AvD3.js → qmldir-D5ZYI4Ie.js} +0 -0
- /package/dist/web/assets/{qss-YRbeNtr0.js → qss-BOJVGyXE.js} +0 -0
- /package/dist/web/assets/{r-DHwHK0Jh.js → r-B5dlEtkj.js} +0 -0
- /package/dist/web/assets/{racket-gKn4SFLQ.js → racket-BUAEygJ6.js} +0 -0
- /package/dist/web/assets/{raku-DWTmAj3A.js → raku-BG0Ss0ID.js} +0 -0
- /package/dist/web/assets/{red-BFuHRZFS.js → red-C5yyQbtG.js} +0 -0
- /package/dist/web/assets/{reg-DYoOF5tH.js → reg-DHoBWtLf.js} +0 -0
- /package/dist/web/assets/{regexp-6rXQHD2-.js → regexp-DDtJrlKv.js} +0 -0
- /package/dist/web/assets/{rel-Z_tTruCU.js → rel-CR6WJDRD.js} +0 -0
- /package/dist/web/assets/{riscv-DfTfKuvU.js → riscv-2V9tC-nx.js} +0 -0
- /package/dist/web/assets/{ron-CsUtW8gQ.js → ron-QLTmqGUw.js} +0 -0
- /package/dist/web/assets/{rose-pine-Dng_vCo6.js → rose-pine-be0__X3A.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-Ds88Nezf.js → rose-pine-dawn-n_WQJwsW.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-DWpzrqNz.js → rose-pine-moon-t2aLtzgB.js} +0 -0
- /package/dist/web/assets/{rosmsg-h2sO84oD.js → rosmsg-Ce1JD8vt.js} +0 -0
- /package/dist/web/assets/{rust-Dx86yfCi.js → rust-5fHH-ZoW.js} +0 -0
- /package/dist/web/assets/{sass-BwwVhdT9.js → sass-DPmGFiOJ.js} +0 -0
- /package/dist/web/assets/{scala-WJqNEN-w.js → scala-BRMVFbZF.js} +0 -0
- /package/dist/web/assets/{scheme-uwj-t1Rp.js → scheme-BMSJWevF.js} +0 -0
- /package/dist/web/assets/{sdbl-BYy5hgEz.js → sdbl-DTZ-pEHI.js} +0 -0
- /package/dist/web/assets/{shellscript-DWTW_w2K.js → shellscript-6-9yg5Cz.js} +0 -0
- /package/dist/web/assets/{slack-dark-BvhGq5vQ.js → slack-dark-DKJgiR7e.js} +0 -0
- /package/dist/web/assets/{slack-ochin-C9g0ZRFr.js → slack-ochin-WjehVgyK.js} +0 -0
- /package/dist/web/assets/{smalltalk-ZEiZ6i7U.js → smalltalk-BChf4SN1.js} +0 -0
- /package/dist/web/assets/{snazzy-light-29OA24q5.js → snazzy-light-C1WAwOx6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-Bnq9IOn_.js → solarized-dark-BxREZrGx.js} +0 -0
- /package/dist/web/assets/{solarized-light-CYdI7T5g.js → solarized-light-DG29B1pR.js} +0 -0
- /package/dist/web/assets/{solidity-DcuIDB-_.js → solidity-bDbtGwdR.js} +0 -0
- /package/dist/web/assets/{splunk-D5ONE91g.js → splunk-Ceqx5njI.js} +0 -0
- /package/dist/web/assets/{sql-DIwJJDU-.js → sql-OwMEKbz8.js} +0 -0
- /package/dist/web/assets/{ssh-config-D8717tLI.js → ssh-config-WAR3lu2k.js} +0 -0
- /package/dist/web/assets/{stylus-Cm1lO4Y6.js → stylus-Cjmoz3d8.js} +0 -0
- /package/dist/web/assets/{swift-X8oWpSeU.js → swift-S_ZSBG1q.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DF-t1UKY.js → synthwave-84-DiUeWMSh.js} +0 -0
- /package/dist/web/assets/{system-verilog-JYyHFpPf.js → system-verilog-RRjqPjUV.js} +0 -0
- /package/dist/web/assets/{systemd-Da-5Ma_c.js → systemd-BD3SkXoU.js} +0 -0
- /package/dist/web/assets/{talonscript-CXraVPho.js → talonscript-DWefISOm.js} +0 -0
- /package/dist/web/assets/{tasl-BCmWezH9.js → tasl-DCjSS-09.js} +0 -0
- /package/dist/web/assets/{tcl-BTufzfbf.js → tcl-DTYqT_66.js} +0 -0
- /package/dist/web/assets/{terraform-CtUA9c8W.js → terraform-CkbP5QnR.js} +0 -0
- /package/dist/web/assets/{tokyo-night-BCThYXmz.js → tokyo-night-qVSqO9xy.js} +0 -0
- /package/dist/web/assets/{toml-D5YlDr6Y.js → toml-D9F5Osqs.js} +0 -0
- /package/dist/web/assets/{tsv-CBB-P_Sl.js → tsv-C5tpHoxm.js} +0 -0
- /package/dist/web/assets/{tsx-D5B4juDp.js → tsx-CMWkGtGm.js} +0 -0
- /package/dist/web/assets/{turtle-D8PCeJ-v.js → turtle-Cn4z3xOi.js} +0 -0
- /package/dist/web/assets/{typescript-CMPnTawp.js → typescript-DSyAEWsn.js} +0 -0
- /package/dist/web/assets/{typespec-CHxldxIW.js → typespec-B93Qap7y.js} +0 -0
- /package/dist/web/assets/{typst-Q8CqX5OR.js → typst-DtlOmFlX.js} +0 -0
- /package/dist/web/assets/{v-Bf2hKAMu.js → v-Dfzf-szS.js} +0 -0
- /package/dist/web/assets/{vala-Cbete-UE.js → vala-CyiQZQ9f.js} +0 -0
- /package/dist/web/assets/{vb-CA-xWAcX.js → vb-C7DGSldd.js} +0 -0
- /package/dist/web/assets/{verilog-B3wb8CfT.js → verilog-kF-hClY2.js} +0 -0
- /package/dist/web/assets/{vesper-D6r3VJq_.js → vesper-BmKcADuz.js} +0 -0
- /package/dist/web/assets/{vhdl-C42S_797.js → vhdl-CDnb16KJ.js} +0 -0
- /package/dist/web/assets/{viml-BePrIAs8.js → viml-B8cs6Ady.js} +0 -0
- /package/dist/web/assets/{vitesse-black-DRqn61Ck.js → vitesse-black-BR6o9Gto.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-39_8UE-w.js → vitesse-dark-Dye14-KB.js} +0 -0
- /package/dist/web/assets/{vitesse-light-B1jwrxDm.js → vitesse-light-BLpswIxf.js} +0 -0
- /package/dist/web/assets/{vyper-tDJv3imF.js → vyper-DU51Z97W.js} +0 -0
- /package/dist/web/assets/{wasm-DR7eskZH.js → wasm-CVqlJAR2.js} +0 -0
- /package/dist/web/assets/{wenyan-CezVfqN3.js → wenyan-DhCa3hCn.js} +0 -0
- /package/dist/web/assets/{wgsl-CwOcIiIG.js → wgsl-Bj1Ec4vv.js} +0 -0
- /package/dist/web/assets/{wikitext-C5coomYc.js → wikitext-C90DHNa0.js} +0 -0
- /package/dist/web/assets/{wit-C9yyX6-U.js → wit-DUgbOD7Z.js} +0 -0
- /package/dist/web/assets/{wolfram-BWi0rMAa.js → wolfram-BgwUqD1I.js} +0 -0
- /package/dist/web/assets/{yaml-CGXhKUtT.js → yaml-DEQulj-8.js} +0 -0
- /package/dist/web/assets/{zenscript-Do7WLoJh.js → zenscript-C7Jp7TgT.js} +0 -0
- /package/dist/web/assets/{zig-Cr80Qrru.js → zig-9x4qXYLg.js} +0 -0
|
@@ -2,6 +2,8 @@ import { useState, useRef, useCallback, useEffect, memo, type KeyboardEvent, typ
|
|
|
2
2
|
import { ArrowUp, Square, Paperclip, Loader2, Mic, MicOff, Zap, ListOrdered, Clock, Bot, X } from "lucide-react";
|
|
3
3
|
import { useVoiceInput } from "@/hooks/use-voice-input";
|
|
4
4
|
import { api, projectUrl, getAuthToken } from "@/lib/api-client";
|
|
5
|
+
import { downscaleImage } from "@/lib/image-resize";
|
|
6
|
+
import { INLINE_IMAGE_LIMITS } from "@/lib/image-resize-limits";
|
|
5
7
|
import { randomId } from "@/lib/utils";
|
|
6
8
|
import { ownsGlobalShortcut } from "@/lib/owns-global-shortcut";
|
|
7
9
|
import { SEND_TO_CHAT_EVENT, SEND_TO_CHAT_ACK_EVENT, type SendToChatDetail } from "@/lib/send-to-chat";
|
|
@@ -17,6 +19,29 @@ import { fetchSlashItems, clearSlashItemsCache } from "@/lib/slash-items-cache";
|
|
|
17
19
|
import type { FileNode } from "../../../types/project";
|
|
18
20
|
import { useFileStore } from "@/stores/file-store";
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Base64 payload for an image file, or undefined when it cannot be read.
|
|
24
|
+
*
|
|
25
|
+
* Reads through FileReader rather than assembling the string by hand: a phone photo is
|
|
26
|
+
* millions of characters, and building it in chunks on the main thread freezes the composer
|
|
27
|
+
* for as long as it takes.
|
|
28
|
+
*/
|
|
29
|
+
async function readImageData(file: File): Promise<{ data: string; mediaType: string } | undefined> {
|
|
30
|
+
try {
|
|
31
|
+
const dataUrl = await new Promise<string>((resolve, reject) => {
|
|
32
|
+
const reader = new FileReader();
|
|
33
|
+
reader.onload = () => resolve(String(reader.result ?? ""));
|
|
34
|
+
reader.onerror = () => reject(reader.error);
|
|
35
|
+
reader.readAsDataURL(file);
|
|
36
|
+
});
|
|
37
|
+
const comma = dataUrl.indexOf(",");
|
|
38
|
+
const data = comma >= 0 ? dataUrl.slice(comma + 1) : "";
|
|
39
|
+
return data ? { data, mediaType: file.type || "image/png" } : undefined;
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
20
45
|
export interface ChatAttachment {
|
|
21
46
|
id: string;
|
|
22
47
|
name: string;
|
|
@@ -27,6 +52,16 @@ export interface ChatAttachment {
|
|
|
27
52
|
serverPath?: string;
|
|
28
53
|
/** Inline text content (e.g. terminal output) — no upload needed */
|
|
29
54
|
textContent?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Base64 payload for an image, sent as part of the message itself.
|
|
57
|
+
*
|
|
58
|
+
* Passing the path instead would make the model spend a whole extra round trip calling Read
|
|
59
|
+
* to fetch it, and every round trip re-sends the entire transcript. The uploaded copy is
|
|
60
|
+
* still kept, so removing the payload from a transcript later stays recoverable.
|
|
61
|
+
*/
|
|
62
|
+
imageData?: { data: string; mediaType: string };
|
|
63
|
+
/** Dimensions before and after downscaling, when one happened — surfaced in the chip. */
|
|
64
|
+
resized?: { from: { width: number; height: number }; to: { width: number; height: number } };
|
|
30
65
|
status: "uploading" | "ready" | "error";
|
|
31
66
|
}
|
|
32
67
|
|
|
@@ -436,6 +471,27 @@ export const MessageInput = memo(function MessageInput({
|
|
|
436
471
|
onExternalPathsConsumed?.();
|
|
437
472
|
}, [externalPaths]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
438
473
|
|
|
474
|
+
/** Delete an upload the user removed before sending. Best effort — nothing depends on it. */
|
|
475
|
+
const discardUpload = useCallback(
|
|
476
|
+
async (serverPath: string) => {
|
|
477
|
+
if (!projectName) return;
|
|
478
|
+
const filename = serverPath.split(/[\\/]/).pop();
|
|
479
|
+
if (!filename) return;
|
|
480
|
+
try {
|
|
481
|
+
const headers: HeadersInit = {};
|
|
482
|
+
const token = getAuthToken();
|
|
483
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
484
|
+
await fetch(`${projectUrl(projectName)}/chat/uploads/${encodeURIComponent(filename)}`, {
|
|
485
|
+
method: "DELETE",
|
|
486
|
+
headers,
|
|
487
|
+
});
|
|
488
|
+
} catch {
|
|
489
|
+
// An orphaned file is not worth surfacing to the user.
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
[projectName],
|
|
493
|
+
);
|
|
494
|
+
|
|
439
495
|
/** Upload a single file to the server, return server path */
|
|
440
496
|
const uploadFile = useCallback(
|
|
441
497
|
async (file: File): Promise<string | null> => {
|
|
@@ -466,32 +522,64 @@ export const MessageInput = memo(function MessageInput({
|
|
|
466
522
|
/** Process files — always uploads to server. Path resolution only happens via @ picker. */
|
|
467
523
|
const processFiles = useCallback(
|
|
468
524
|
async (files: File[]) => {
|
|
469
|
-
for (const
|
|
525
|
+
for (const original of files) {
|
|
470
526
|
const id = randomId();
|
|
471
|
-
const isImg = isImageFile(
|
|
472
|
-
const previewUrl = isImg ? URL.createObjectURL(file) : undefined;
|
|
527
|
+
const isImg = isImageFile(original);
|
|
473
528
|
|
|
474
529
|
const att: ChatAttachment = {
|
|
475
530
|
id,
|
|
476
|
-
name:
|
|
477
|
-
file,
|
|
531
|
+
name: original.name,
|
|
532
|
+
file: original,
|
|
478
533
|
isImage: isImg,
|
|
479
|
-
previewUrl,
|
|
534
|
+
previewUrl: isImg ? URL.createObjectURL(original) : undefined,
|
|
480
535
|
status: "uploading",
|
|
481
536
|
};
|
|
482
537
|
|
|
483
538
|
setAttachments((prev) => [...prev, att]);
|
|
484
539
|
|
|
485
|
-
//
|
|
486
|
-
|
|
540
|
+
// Shrink first, so the upload, the preview and the message all carry the same
|
|
541
|
+
// already-reduced image — an oversized original must not reach the transcript.
|
|
542
|
+
//
|
|
543
|
+
// Only a `scaled` or `inlineable` result is attached inline. An image that could not
|
|
544
|
+
// be measured or re-encoded travels by path alone: sending unverified dimensions
|
|
545
|
+
// inline risks a payload the API refuses, and that refusal outlives the turn, since
|
|
546
|
+
// the transcript replays it into every later one.
|
|
547
|
+
try {
|
|
548
|
+
const outcome = isImg ? await downscaleImage(original) : null;
|
|
549
|
+
const file = outcome?.file ?? original;
|
|
550
|
+
// Point the thumbnail at the reduced file and let the original go. It is the larger
|
|
551
|
+
// of the two and nothing needs it once the resize is done, but it stayed alive until
|
|
552
|
+
// the message was sent because the preview still referenced it.
|
|
553
|
+
const previewUrl = isImg && file !== original ? URL.createObjectURL(file) : undefined;
|
|
554
|
+
const serverPath = await uploadFile(file);
|
|
555
|
+
const inlineable = outcome ? outcome.kind !== "asis" : false;
|
|
556
|
+
const imageData = inlineable ? await readImageData(file) : undefined;
|
|
557
|
+
const withinPayloadCap =
|
|
558
|
+
!!imageData && imageData.data.length <= INLINE_IMAGE_LIMITS.maxBase64PerImage;
|
|
559
|
+
|
|
560
|
+
if (previewUrl && att.previewUrl) URL.revokeObjectURL(att.previewUrl);
|
|
487
561
|
setAttachments((prev) =>
|
|
488
562
|
prev.map((a) =>
|
|
489
563
|
a.id === id
|
|
490
|
-
? {
|
|
564
|
+
? {
|
|
565
|
+
...a,
|
|
566
|
+
file,
|
|
567
|
+
...(previewUrl && { previewUrl }),
|
|
568
|
+
serverPath: serverPath ?? undefined,
|
|
569
|
+
imageData: withinPayloadCap ? imageData : undefined,
|
|
570
|
+
resized: outcome?.to ? { from: outcome.from!, to: outcome.to } : undefined,
|
|
571
|
+
// An image can still be sent inline when the upload failed; anything
|
|
572
|
+
// else has nothing left to send without its path.
|
|
573
|
+
status: serverPath || withinPayloadCap ? "ready" : "error",
|
|
574
|
+
}
|
|
491
575
|
: a,
|
|
492
576
|
),
|
|
493
577
|
);
|
|
494
|
-
}
|
|
578
|
+
} catch {
|
|
579
|
+
// Nothing here may leave an attachment stuck on "uploading" — both the send button
|
|
580
|
+
// and the auto-send effect wait on that state, so a swallowed throw hangs the composer.
|
|
581
|
+
setAttachments((prev) => prev.map((a) => (a.id === id ? { ...a, status: "error" } : a)));
|
|
582
|
+
}
|
|
495
583
|
}
|
|
496
584
|
(mobileTextareaRef.current ?? textareaRef.current)?.focus();
|
|
497
585
|
},
|
|
@@ -502,9 +590,12 @@ export const MessageInput = memo(function MessageInput({
|
|
|
502
590
|
setAttachments((prev) => {
|
|
503
591
|
const att = prev.find((a) => a.id === id);
|
|
504
592
|
if (att?.previewUrl) URL.revokeObjectURL(att.previewUrl);
|
|
593
|
+
// Uploads are never swept, because chat history points at them indefinitely, so one
|
|
594
|
+
// abandoned before it was ever sent would sit there with nothing referencing it.
|
|
595
|
+
if (att?.serverPath) void discardUpload(att.serverPath);
|
|
505
596
|
return prev.filter((a) => a.id !== id);
|
|
506
597
|
});
|
|
507
|
-
}, []);
|
|
598
|
+
}, [discardUpload]);
|
|
508
599
|
|
|
509
600
|
/** Execute the actual send (called directly or after uploads complete) */
|
|
510
601
|
const executeSend = useCallback(() => {
|
|
@@ -147,7 +147,8 @@ function TranscriptImages({ sessionId, projectName }: { sessionId: string; proje
|
|
|
147
147
|
{audit.oversizedAttachments} of those {audit.oversizedAttachments === 1 ? "is an image you" : "are images you"}
|
|
148
148
|
{" "}attached yourself, so the buttons below leave {audit.oversizedAttachments === 1 ? "it" : "them"} in place.
|
|
149
149
|
While {audit.oversizedAttachments === 1 ? "it stays" : "they stay"}, every turn in this session keeps failing.
|
|
150
|
-
|
|
150
|
+
Removing {audit.oversizedAttachments === 1 ? "it" : "them"} cannot be undone: an attachment PPM sent keeps
|
|
151
|
+
a copy on disk with its path still in the message, but one from another client does not.
|
|
151
152
|
</span>
|
|
152
153
|
</div>
|
|
153
154
|
<Button
|
|
@@ -82,24 +82,46 @@ export function parseIdeOpenedFile(text: string): { idePath: string | null; clea
|
|
|
82
82
|
return { idePath, cleanText };
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Parse user message content, extracting attached file paths and the actual text.
|
|
87
|
+
*
|
|
88
|
+
* Two markers mean the same thing to the reader and different things to the model. An image
|
|
89
|
+
* sent inline says so — the payload is already in the message, and a bare path would invite
|
|
90
|
+
* the model to spend a round trip opening what it was handed. Either way the path is what the
|
|
91
|
+
* chat draws a thumbnail from, so both forms have to be recognised here.
|
|
92
|
+
*/
|
|
86
93
|
export function parseUserAttachments(content: string): { files: string[]; text: string } {
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
// Trailing newlines are optional — an attachment-only message has the marker trimmed to the
|
|
95
|
+
// end of the string (extractTerminalBlocks trims), so the separator newlines may be absent.
|
|
96
|
+
const files: string[] = [];
|
|
97
|
+
let rest = content;
|
|
98
|
+
for (;;) {
|
|
99
|
+
const inline = rest.match(/^\[Attached image \(contents included in this message\): (.+?)\]\n*/);
|
|
100
|
+
if (inline) {
|
|
101
|
+
files.push(inline[1]!);
|
|
102
|
+
rest = rest.slice(inline[0].length);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (files.length > 0) {
|
|
108
|
+
// A message may pair inline images with plain file attachments.
|
|
109
|
+
const tail = parseUserAttachments(rest);
|
|
110
|
+
return { files: [...files, ...tail.files], text: tail.text };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const singleMatch = rest.match(/^\[Attached file: (.+?)\]\n*/);
|
|
92
114
|
if (singleMatch) {
|
|
93
|
-
return { files: [singleMatch[1]!], text:
|
|
115
|
+
return { files: [singleMatch[1]!], text: rest.slice(singleMatch[0].length) };
|
|
94
116
|
}
|
|
95
117
|
|
|
96
|
-
const multiMatch =
|
|
118
|
+
const multiMatch = rest.match(/^\[Attached files:\n([\s\S]+?)\]\n*/);
|
|
97
119
|
if (multiMatch) {
|
|
98
|
-
const
|
|
99
|
-
return { files, text:
|
|
120
|
+
const list = multiMatch[1]!.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
121
|
+
return { files: list, text: rest.slice(multiMatch[0].length) };
|
|
100
122
|
}
|
|
101
123
|
|
|
102
|
-
return { files: [], text:
|
|
124
|
+
return { files: [], text: rest };
|
|
103
125
|
}
|
|
104
126
|
|
|
105
127
|
/** Extract leading terminal code fences from message text */
|
|
@@ -7,6 +7,7 @@ import { useConnections } from "./use-connections";
|
|
|
7
7
|
import { SqlQueryEditor } from "./sql-query-editor";
|
|
8
8
|
import { ExportButton } from "./export-button";
|
|
9
9
|
import { GlideDataGrid } from "./glide-data-grid";
|
|
10
|
+
import { extractQueryTable } from "./extract-query-table";
|
|
10
11
|
import type { SchemaInfo } from "./sql-completion-provider";
|
|
11
12
|
|
|
12
13
|
/** Parse WHERE "col" ILIKE '%val%' clauses from SQL */
|
|
@@ -140,6 +141,8 @@ export function DatabaseViewer({ metadata, tabId }: Props) {
|
|
|
140
141
|
|
|
141
142
|
// Track whether user ran a custom query (show results instead of table grid)
|
|
142
143
|
const [showingQueryResult, setShowingQueryResult] = useState(!!initialSql);
|
|
144
|
+
// SQL behind the currently displayed query result — tells us where to write edits back to
|
|
145
|
+
const [executedSql, setExecutedSql] = useState<string | null>(initialSql ?? null);
|
|
143
146
|
const handleExecuteQuery = useCallback((sql: string) => {
|
|
144
147
|
const trimmed = sql.trim();
|
|
145
148
|
// Check if query is a simple SELECT on the current table — stay in table grid mode
|
|
@@ -154,6 +157,7 @@ export function DatabaseViewer({ metadata, tabId }: Props) {
|
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
setShowingQueryResult(true);
|
|
160
|
+
setExecutedSql(trimmed);
|
|
157
161
|
db.executeQuery(sql);
|
|
158
162
|
}, [db.executeQuery, db.queryAsTable, db.selectedTable]);
|
|
159
163
|
|
|
@@ -171,6 +175,18 @@ export function DatabaseViewer({ metadata, tabId }: Props) {
|
|
|
171
175
|
db.setPage(p);
|
|
172
176
|
}, [db.setPage]);
|
|
173
177
|
|
|
178
|
+
// Ad-hoc query results are editable only when the result maps to one table and
|
|
179
|
+
// the connection allows writes — otherwise the grid stays read-only so edits
|
|
180
|
+
// can't pile up with nowhere to save them to.
|
|
181
|
+
const queryEditTable = useMemo(
|
|
182
|
+
() => (conn && !conn.readonly && executedSql ? extractQueryTable(executedSql, db.selectedSchema) : null),
|
|
183
|
+
[conn, executedSql, db.selectedSchema],
|
|
184
|
+
);
|
|
185
|
+
const handleQueryCellUpdate = useCallback((pkCol: string, pkVal: unknown, col: string, val: unknown) => {
|
|
186
|
+
if (!queryEditTable) return;
|
|
187
|
+
db.updateCellIn(queryEditTable.table, queryEditTable.schema, pkCol, pkVal, col, val);
|
|
188
|
+
}, [queryEditTable, db.updateCellIn]);
|
|
189
|
+
|
|
174
190
|
const qr = db.queryResult;
|
|
175
191
|
const showQueryResults = showingQueryResult && !!(qr || db.queryError);
|
|
176
192
|
const showTableGrid = db.selectedTable && !showQueryResults;
|
|
@@ -246,7 +262,8 @@ export function DatabaseViewer({ metadata, tabId }: Props) {
|
|
|
246
262
|
)}
|
|
247
263
|
|
|
248
264
|
{showQueryResults && (
|
|
249
|
-
<QueryResultPanel result={qr} error={db.queryError} loading={db.queryLoading} schema={db.schema}
|
|
265
|
+
<QueryResultPanel result={qr} error={db.queryError} loading={db.queryLoading} schema={db.schema}
|
|
266
|
+
connectionName={connectionName} onCellUpdate={queryEditTable ? handleQueryCellUpdate : undefined} />
|
|
250
267
|
)}
|
|
251
268
|
|
|
252
269
|
{showInitialLoading && (
|
|
@@ -262,27 +279,32 @@ export function DatabaseViewer({ metadata, tabId }: Props) {
|
|
|
262
279
|
|
|
263
280
|
const NOOP = () => {};
|
|
264
281
|
|
|
265
|
-
/**
|
|
266
|
-
|
|
282
|
+
/** Result panel for ad-hoc query results — uses DataGrid for SELECT to get checkboxes + export.
|
|
283
|
+
* Editable only when the caller supplies onCellUpdate (single-table SELECT on a writable connection). */
|
|
284
|
+
function QueryResultPanel({ result, error, loading, schema, connectionName, onCellUpdate }: {
|
|
267
285
|
result: { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: "select" | "modify"; executionTimeMs?: number } | null;
|
|
268
286
|
error: string | null;
|
|
269
287
|
loading?: boolean;
|
|
270
288
|
schema?: DbColumnInfo[];
|
|
271
289
|
connectionName?: string;
|
|
290
|
+
onCellUpdate?: (pkCol: string, pkVal: unknown, col: string, val: unknown) => void;
|
|
272
291
|
}) {
|
|
273
|
-
// Build a
|
|
292
|
+
// Build a DataGrid-compatible tableData from query result
|
|
274
293
|
const queryTableData = useMemo(() => (
|
|
275
294
|
result?.changeType === "select" && result.rows.length > 0
|
|
276
295
|
? { columns: result.columns, rows: result.rows, total: result.rows.length, limit: result.rows.length }
|
|
277
296
|
: null
|
|
278
297
|
), [result]);
|
|
279
298
|
|
|
280
|
-
// Use schema
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
299
|
+
// Use the sidebar table's schema only when it describes every result column —
|
|
300
|
+
// it belongs to the selected table, which may not be what the query returned.
|
|
301
|
+
// The grid drops columns missing from its schema, so a mismatch hides data.
|
|
302
|
+
const querySchema = useMemo(() => {
|
|
303
|
+
const cols = result?.columns ?? [];
|
|
304
|
+
const names = new Set((schema ?? []).map((c) => c.name));
|
|
305
|
+
if (schema?.length && cols.every((c) => names.has(c))) return schema;
|
|
306
|
+
return cols.map((c) => ({ name: c, type: "text", nullable: true, pk: false, defaultValue: null }));
|
|
307
|
+
}, [schema, result?.columns]);
|
|
286
308
|
|
|
287
309
|
return (
|
|
288
310
|
<div className="flex flex-col h-full overflow-hidden text-xs">
|
|
@@ -301,7 +323,8 @@ function QueryResultPanel({ result, error, loading, schema, connectionName }: {
|
|
|
301
323
|
columns={queryTableData.columns} rows={queryTableData.rows}
|
|
302
324
|
total={queryTableData.total} limit={queryTableData.limit}
|
|
303
325
|
schema={querySchema} loading={!!loading}
|
|
304
|
-
page={1} onPageChange={NOOP}
|
|
326
|
+
page={1} onPageChange={NOOP}
|
|
327
|
+
onCellUpdate={onCellUpdate ?? NOOP} readOnly={!onCellUpdate}
|
|
305
328
|
orderBy={null} orderDir="ASC" onToggleSort={NOOP}
|
|
306
329
|
connectionName={connectionName}
|
|
307
330
|
/>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single table an ad-hoc SELECT reads from, or null when its rows can't be
|
|
3
|
+
* mapped back to one table (JOIN, set operation, subquery, aggregate).
|
|
4
|
+
*
|
|
5
|
+
* Editing a query result writes an UPDATE against this table, so anything
|
|
6
|
+
* ambiguous must return null — the grid then stays read-only instead of
|
|
7
|
+
* collecting edits that have nowhere to go.
|
|
8
|
+
*/
|
|
9
|
+
export function extractQueryTable(sql: string, defaultSchema: string): { table: string; schema: string } | null {
|
|
10
|
+
const stripped = sql
|
|
11
|
+
.replace(/--[^\r\n]*/g, " ")
|
|
12
|
+
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
|
13
|
+
.trim()
|
|
14
|
+
.replace(/;\s*$/, "");
|
|
15
|
+
if (!/^select\s/i.test(stripped)) return null;
|
|
16
|
+
if (/\b(join|union|intersect|except|group\s+by|distinct)\b/i.test(stripped)) return null;
|
|
17
|
+
// More than one FROM means a subquery or derived table is involved.
|
|
18
|
+
if ((stripped.match(/\bfrom\b/gi) ?? []).length !== 1) return null;
|
|
19
|
+
const m = /\bfrom\s+(?:"([^"]+)"|(\w+))\s*(?:\.\s*(?:"([^"]+)"|(\w+)))?/i.exec(stripped);
|
|
20
|
+
if (!m) return null;
|
|
21
|
+
const first = m[1] ?? m[2]!;
|
|
22
|
+
const second = m[3] ?? m[4];
|
|
23
|
+
return second ? { schema: first, table: second } : { table: first, schema: defaultSchema };
|
|
24
|
+
}
|
|
@@ -16,6 +16,9 @@ import { GlideSaveBar } from "./glide-save-bar";
|
|
|
16
16
|
import { GlideGridPagination } from "./glide-grid-pagination";
|
|
17
17
|
import { GlideDataPreviewPanel } from "./glide-data-preview-panel";
|
|
18
18
|
|
|
19
|
+
/** Swallows edits (paste, context-menu actions) while the grid is read-only. */
|
|
20
|
+
const NOOP_EDIT = () => {};
|
|
21
|
+
|
|
19
22
|
const HEADER_ICONS: Record<string, (p: { fgColor: string }) => string> = {
|
|
20
23
|
sortAsc: (p) => `<svg viewBox="0 0 16 16" fill="${p.fgColor}"><path d="M8 4l4 6H4z"/></svg>`,
|
|
21
24
|
sortDesc: (p) => `<svg viewBox="0 0 16 16" fill="${p.fgColor}"><path d="M8 12l4-6H4z"/></svg>`,
|
|
@@ -31,7 +34,7 @@ export function GlideDataGrid(props: GlideGridProps) {
|
|
|
31
34
|
columns: rawColumnNames, rows, total, limit, schema, loading,
|
|
32
35
|
page, onPageChange, onCellUpdate, onRowDelete, onBulkDelete, onInsertRow,
|
|
33
36
|
orderBy, orderDir, onToggleSort, onClearSort, columnFilters = {}, onColumnFilter,
|
|
34
|
-
connectionId, selectedTable, selectedSchema, connectionName,
|
|
37
|
+
connectionId, selectedTable, selectedSchema, connectionName, readOnly,
|
|
35
38
|
} = props;
|
|
36
39
|
|
|
37
40
|
const theme = useGlideTheme();
|
|
@@ -78,14 +81,18 @@ export function GlideDataGrid(props: GlideGridProps) {
|
|
|
78
81
|
}, [pinnedCount, pkCol, searchTerm, displayRows, pinnedPks]);
|
|
79
82
|
|
|
80
83
|
// Hooks
|
|
84
|
+
// Without a primary key there is no way to address a row in an UPDATE, so
|
|
85
|
+
// edits could never be saved — don't let the user make them in the first place.
|
|
86
|
+
const cellsReadOnly = !!readOnly || !pkCol;
|
|
87
|
+
|
|
81
88
|
const { pendingRef, addEdit, commitAll, discardAll, hasPending, pendingCount, committedRef } = useGlidePendingEdits(pkCol, onCellUpdate, onInsertRow);
|
|
82
89
|
const { columns, freezeColumns, columnOrder } = useGlideColumns(schema, columnNames, pinnedCols, colWidths, displayRows, orderBy, orderDir);
|
|
83
|
-
const { getCellContent, onCellEdited } = useGlideCellContent(displayRows, columnOrder, schema, pkCol, addEdit, pendingRef);
|
|
90
|
+
const { getCellContent, onCellEdited } = useGlideCellContent(displayRows, columnOrder, schema, pkCol, addEdit, pendingRef, cellsReadOnly);
|
|
84
91
|
const { gridSelection, onGridSelectionChange, selectedRowIndices, clearSelection } = useGlideSelection();
|
|
85
92
|
const {
|
|
86
93
|
previewData, closePreview, openRowPreview, openCellPreview, openPreviewInTab,
|
|
87
94
|
handlePaste, getContextFk, isCellViewable, openFkTable,
|
|
88
|
-
} = useGlideGridActions({ displayRows, columnOrder, schema, pkCol, connectionId, connectionName, selectedTable, selectedSchema, addEdit, gridSelection, containerRef });
|
|
95
|
+
} = useGlideGridActions({ displayRows, columnOrder, schema, pkCol, connectionId, connectionName, selectedTable, selectedSchema, addEdit: cellsReadOnly ? NOOP_EDIT : addEdit, gridSelection, containerRef });
|
|
89
96
|
|
|
90
97
|
useEffect(() => {
|
|
91
98
|
if (committedRef.current) { committedRef.current = false; discardAll(); setInsertedRows([]); }
|
|
@@ -166,7 +173,7 @@ export function GlideDataGrid(props: GlideGridProps) {
|
|
|
166
173
|
<div className="flex-1 min-h-0">
|
|
167
174
|
<DataEditor ref={gridRef} columns={columns} rows={displayRows.length}
|
|
168
175
|
getCellContent={getCellContent} getCellsForSelection={true}
|
|
169
|
-
onCellEdited={onCellEdited} onPaste={handlePaste}
|
|
176
|
+
onCellEdited={onCellEdited} onPaste={cellsReadOnly ? false : handlePaste}
|
|
170
177
|
theme={theme} freezeColumns={freezeColumns} freezeTrailingRows={frozenTrailingRows}
|
|
171
178
|
rowMarkers={pkCol ? "checkbox-visible" : "number"}
|
|
172
179
|
gridSelection={gridSelection} onGridSelectionChange={onGridSelectionChange}
|
|
@@ -29,6 +29,10 @@ export interface GlideGridProps {
|
|
|
29
29
|
onPageChange: (page: number) => void;
|
|
30
30
|
/** Cell edit: (pkColumn, pkValue, editedColumn, newValue) */
|
|
31
31
|
onCellUpdate: (pkCol: string, pkVal: unknown, col: string, val: unknown) => void;
|
|
32
|
+
/** Block all cell editing — cells stay viewable/copyable but cannot be changed.
|
|
33
|
+
* Set when there is nowhere to write the edit back to (ad-hoc query results
|
|
34
|
+
* whose source table can't be determined, readonly connections). */
|
|
35
|
+
readOnly?: boolean;
|
|
32
36
|
onRowDelete?: (pkCol: string, pkVal: unknown) => void;
|
|
33
37
|
onBulkDelete?: (pkCol: string, pkValues: unknown[]) => void;
|
|
34
38
|
onInsertRow?: (values: Record<string, unknown>) => Promise<void>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export interface SqlStatement {
|
|
2
|
+
/** Statement text from its first token through its terminating semicolon. */
|
|
3
|
+
sql: string;
|
|
4
|
+
/** 1-based line of the statement's first token (blank/comment lines skipped). */
|
|
5
|
+
startLine: number;
|
|
6
|
+
/** 1-based line the statement ends on. */
|
|
7
|
+
endLine: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const IDENT_CHAR = /[A-Za-z0-9_]/;
|
|
11
|
+
const DOLLAR_TAG = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Split SQL into statements on top-level semicolons.
|
|
15
|
+
*
|
|
16
|
+
* A semicolon only terminates a statement when it is not inside a string, a
|
|
17
|
+
* quoted identifier, a comment, or a dollar-quoted block — so a trailing
|
|
18
|
+
* `-- comment` after the semicolon, or a `;` inside a literal, no longer glues
|
|
19
|
+
* two statements together.
|
|
20
|
+
*/
|
|
21
|
+
export function splitSqlStatements(text: string): SqlStatement[] {
|
|
22
|
+
const statements: SqlStatement[] = [];
|
|
23
|
+
let line = 1;
|
|
24
|
+
let firstTokenIdx = -1;
|
|
25
|
+
let firstTokenLine = 1;
|
|
26
|
+
let i = 0;
|
|
27
|
+
|
|
28
|
+
const flush = (endIdx: number, endLine: number) => {
|
|
29
|
+
if (firstTokenIdx === -1) return;
|
|
30
|
+
const sql = text.slice(firstTokenIdx, endIdx).trim();
|
|
31
|
+
if (sql) statements.push({ sql, startLine: firstTokenLine, endLine });
|
|
32
|
+
firstTokenIdx = -1;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
while (i < text.length) {
|
|
36
|
+
const ch = text[i]!;
|
|
37
|
+
|
|
38
|
+
if (ch === "\n") { line++; i++; continue; }
|
|
39
|
+
if (ch === " " || ch === "\t" || ch === "\r") { i++; continue; }
|
|
40
|
+
|
|
41
|
+
// Line comment — runs to end of line, never part of a statement's start
|
|
42
|
+
if (ch === "-" && text[i + 1] === "-") {
|
|
43
|
+
while (i < text.length && text[i] !== "\n") i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Block comment — Postgres allows these to nest
|
|
48
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
49
|
+
let depth = 1;
|
|
50
|
+
i += 2;
|
|
51
|
+
while (i < text.length && depth > 0) {
|
|
52
|
+
if (text[i] === "/" && text[i + 1] === "*") { depth++; i += 2; continue; }
|
|
53
|
+
if (text[i] === "*" && text[i + 1] === "/") { depth--; i += 2; continue; }
|
|
54
|
+
if (text[i] === "\n") line++;
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (firstTokenIdx === -1) { firstTokenIdx = i; firstTokenLine = line; }
|
|
61
|
+
|
|
62
|
+
if (ch === ";") { flush(i + 1, line); i++; continue; }
|
|
63
|
+
|
|
64
|
+
// String literal or quoted identifier
|
|
65
|
+
if (ch === "'" || ch === '"') {
|
|
66
|
+
// E'...' honours backslash escapes; plain strings only double the quote
|
|
67
|
+
const prev = text[i - 1];
|
|
68
|
+
const prev2 = text[i - 2];
|
|
69
|
+
const escapes = ch === "'" && (prev === "e" || prev === "E") && !(prev2 !== undefined && IDENT_CHAR.test(prev2));
|
|
70
|
+
i++;
|
|
71
|
+
while (i < text.length) {
|
|
72
|
+
const c = text[i]!;
|
|
73
|
+
if (c === "\n") { line++; i++; continue; }
|
|
74
|
+
if (escapes && c === "\\") { i += 2; continue; }
|
|
75
|
+
if (c === ch) {
|
|
76
|
+
if (text[i + 1] === ch) { i += 2; continue; } // doubled quote = literal
|
|
77
|
+
i++;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Dollar-quoted block ($$ … $$ / $tag$ … $tag$) — bodies hold their own semicolons
|
|
86
|
+
if (ch === "$") {
|
|
87
|
+
const m = DOLLAR_TAG.exec(text.slice(i));
|
|
88
|
+
if (m) {
|
|
89
|
+
const tag = m[0];
|
|
90
|
+
const close = text.indexOf(tag, i + tag.length);
|
|
91
|
+
const stop = close === -1 ? text.length : close + tag.length;
|
|
92
|
+
for (let k = i; k < stop; k++) if (text[k] === "\n") line++;
|
|
93
|
+
i = stop;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
i++;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
flush(text.length, line);
|
|
102
|
+
return statements;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The statement the cursor sits in. Falls back to the next statement below the
|
|
107
|
+
* cursor (cursor parked on a blank line between statements), then to the last.
|
|
108
|
+
*/
|
|
109
|
+
export function getStatementAtCursor(text: string, cursorLine: number): string {
|
|
110
|
+
const statements = splitSqlStatements(text);
|
|
111
|
+
if (statements.length === 0) return "";
|
|
112
|
+
const containing = statements.find((s) => s.startLine <= cursorLine && cursorLine <= s.endLine);
|
|
113
|
+
if (containing) return containing.sql;
|
|
114
|
+
const following = statements.find((s) => s.startLine > cursorLine);
|
|
115
|
+
return (following ?? statements[statements.length - 1]!).sql;
|
|
116
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type * as MonacoType from "monaco-editor";
|
|
2
|
+
import { getStatementAtCursor } from "./split-sql-statements";
|
|
2
3
|
|
|
3
4
|
export interface SchemaInfo {
|
|
4
5
|
tables: { name: string; schema: string }[];
|
|
@@ -22,32 +23,6 @@ export const AGGREGATE_FNS = ["COUNT", "SUM", "AVG", "MIN", "MAX"];
|
|
|
22
23
|
export const OPERATORS = ["=", "!=", "<>", ">", "<", ">=", "<=", "LIKE", "ILIKE", "IN", "NOT IN", "BETWEEN", "IS NULL", "IS NOT NULL"];
|
|
23
24
|
export const SORT_DIRS = ["ASC", "DESC"];
|
|
24
25
|
|
|
25
|
-
/** Find the SQL statement surrounding the cursor line (split by ;) */
|
|
26
|
-
export function getStatementAtCursor(text: string, cursorLine: number): string {
|
|
27
|
-
const lines = text.split("\n");
|
|
28
|
-
let stmtStart = 0;
|
|
29
|
-
for (let i = 0; i < lines.length; i++) {
|
|
30
|
-
const trimmed = lines[i]!.trim();
|
|
31
|
-
if (i < cursorLine - 1 && trimmed.endsWith(";")) {
|
|
32
|
-
stmtStart = i + 1;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
let stmtEnd = lines.length - 1;
|
|
36
|
-
for (let i = cursorLine - 1; i < lines.length; i++) {
|
|
37
|
-
const trimmed = lines[i]!.trim();
|
|
38
|
-
if (trimmed.endsWith(";")) {
|
|
39
|
-
stmtEnd = i;
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
while (stmtStart <= stmtEnd) {
|
|
44
|
-
const t = lines[stmtStart]!.trim();
|
|
45
|
-
if (t && !t.startsWith("--")) break;
|
|
46
|
-
stmtStart++;
|
|
47
|
-
}
|
|
48
|
-
return lines.slice(stmtStart, stmtEnd + 1).join("\n").trim();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
26
|
/** Client-side column cache to avoid redundant fetches */
|
|
52
27
|
const columnCache = new Map<string, { name: string; type: string }[]>();
|
|
53
28
|
|
|
@@ -2,7 +2,8 @@ import { useState, useCallback, useRef, useEffect } from "react";
|
|
|
2
2
|
import Editor, { type OnMount } from "@monaco-editor/react";
|
|
3
3
|
import type * as MonacoType from "monaco-editor";
|
|
4
4
|
import { useMonacoTheme } from "@/lib/use-monaco-theme";
|
|
5
|
-
import { createSqlCompletionProvider, clearCompletionCache,
|
|
5
|
+
import { createSqlCompletionProvider, clearCompletionCache, type SchemaInfo } from "./sql-completion-provider";
|
|
6
|
+
import { getStatementAtCursor } from "./split-sql-statements";
|
|
6
7
|
|
|
7
8
|
interface SqlQueryEditorProps {
|
|
8
9
|
onExecute: (sql: string) => void;
|
|
@@ -232,12 +232,27 @@ export function useDatabase(connectionId: number) {
|
|
|
232
232
|
fetchTableData();
|
|
233
233
|
}, [executeQuery, queryAsTable, fetchTableData]);
|
|
234
234
|
|
|
235
|
+
/** Update a cell in an explicitly named table, then reload the current view.
|
|
236
|
+
* Used by ad-hoc query results, where the target table comes from the SQL
|
|
237
|
+
* rather than the sidebar selection. */
|
|
238
|
+
const updateCellIn = useCallback(async (
|
|
239
|
+
table: string, tableSchema: string,
|
|
240
|
+
pkColumn: string, pkValue: unknown, column: string, value: unknown,
|
|
241
|
+
) => {
|
|
242
|
+
try {
|
|
243
|
+
await api.put(`${base}/cell`, { table, schema: tableSchema, pkColumn, pkValue, column, value });
|
|
244
|
+
reload();
|
|
245
|
+
} catch (e) {
|
|
246
|
+
failMutation("Update cell", e);
|
|
247
|
+
}
|
|
248
|
+
}, [base, reload, failMutation]);
|
|
249
|
+
|
|
235
250
|
return {
|
|
236
251
|
selectedTable, selectedSchema, selectTable, tableData, schema,
|
|
237
252
|
loading, error, page, setPage: changePage,
|
|
238
253
|
orderBy, orderDir, toggleSort, clearSort,
|
|
239
254
|
queryResult, queryError, queryLoading, executeQuery,
|
|
240
|
-
updateCell, deleteRow, bulkDelete, insertRow,
|
|
255
|
+
updateCell, updateCellIn, deleteRow, bulkDelete, insertRow,
|
|
241
256
|
refreshData: fetchTableData, queryAsTable, reload,
|
|
242
257
|
};
|
|
243
258
|
}
|