@hienlh/ppm 0.9.0-beta.9 → 0.9.1
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 +233 -0
- package/bun.lock +17 -0
- package/dist/web/assets/{_basePickBy-3Xe18azI.js → _basePickBy-5PGDJbfF.js} +1 -1
- package/dist/web/assets/{_baseUniq-Yy35llnn.js → _baseUniq-BT4Ow4Kk.js} +1 -1
- package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
- package/dist/web/assets/{arc-B9n1Gvb5.js → arc-BAOivWpI.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DqAZP_F6.js → architectureDiagram-2XIMDMQ5-Z-4eN4za.js} +1 -1
- package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-h3cDF2vI.js → blockDiagram-WCTKOSBZ-BCLqzhuZ.js} +1 -1
- package/dist/web/assets/browser-tab-CrkhFCaw.js +1 -0
- package/dist/web/assets/{c4Diagram-IC4MRINW--pF1r5lr.js → c4Diagram-IC4MRINW-0Vp0Jeas.js} +1 -1
- package/dist/web/assets/channel-By7bn0Yq.js +1 -0
- package/dist/web/assets/chat-tab-C6jpiwh7.js +8 -0
- package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-C3aZvW7B.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-D5cABeB9.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-CkFGv6Zs.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-Dvbyu4Zw.js → chunk-7R4GIKGN-Dv-4cAYn.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-CtqKiH4q.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-Cpr87sBR.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-D23YVTOU.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-tDjHsAUs.js → chunk-GEFDOKGD-D-pKjlVd.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-BBmymCjA.js → chunk-JSJVCQXG-99JzIdPr.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-DP36BDiU.js → chunk-KX2RTZJC-CRq1OBZv.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-Djw13C-3.js → chunk-KYZI473N-Bb0MCaIO.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL-HG_eMj_C.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-C2UEioMs.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-DXUTQ-BL.js → chunk-NQ4KR5QH-z_blpjxi.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-BsUWb9d0.js → chunk-O4XLMI2P-nDhi_cVu.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-rG0P22U9.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-DX0xW7kO.js → chunk-PQ6SQG4A-TF58UVMU.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-C7Gry6md.js → chunk-PU5JKC2W-ek7k4QVB.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-CMY0PkRK.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-CXuQvlyu.js → chunk-WL4C6EOR-ByUrSRin.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-DRJEb7Zb.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-BPEX8KhL.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-Cb0iqycX.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-av5aeHLq.js → chunk-YBOYWFTD-rQG3QH5s.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +1 -0
- package/dist/web/assets/clone-LRxlvnMj.js +1 -0
- package/dist/web/assets/code-editor-CBIPzlP2.js +2 -0
- package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-qudEiMCT.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
- package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
- package/dist/web/assets/{csv-preview-DUbHtTAS.js → csv-preview-ncSOnJSC.js} +2 -2
- package/dist/web/assets/{dagre-BFcnKyBF.js → dagre-DHq9bhnd.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-C3O-MTLf.js → dagre-KLK3FWXG-BdJr7Byp.js} +1 -1
- package/dist/web/assets/database-viewer-BqOJR_zi.js +1 -0
- package/dist/web/assets/{diagram-E7M64L7V-DxPjK7_c.js → diagram-E7M64L7V-_db4pBVA.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-sqTog_XV.js → diagram-IFDJBPK2-xKoeuiJx.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-hzmp0GHK.js → diagram-P4PSJMXO-C8tjJsev.js} +1 -1
- package/dist/web/assets/diff-viewer-CcLyp4eY.js +4 -0
- package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
- package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
- package/dist/web/assets/{erDiagram-INFDFZHY-DLeYhAAT.js → erDiagram-INFDFZHY-BSh2z9Df.js} +1 -1
- package/dist/web/assets/extension-webview-NiZ7Ybvv.js +3 -0
- package/dist/web/assets/{flowDiagram-PKNHOUZH-CRxlE9Sr.js → flowDiagram-PKNHOUZH-oYaovqyp.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-BdjmoMLS.js → ganttDiagram-A5KZAMGK-DmL26q2P.js} +1 -1
- package/dist/web/assets/git-graph-CoTvMrIo.js +1 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js → gitGraphDiagram-K3NZZRJ6-CMoukSrY.js} +1 -1
- package/dist/web/assets/{graphlib-Duh_bWLa.js → graphlib-BcsNnGcW.js} +1 -1
- package/dist/web/assets/index-C8byznLO.js +37 -0
- package/dist/web/assets/index-KwC2YrG4.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +2 -0
- package/dist/web/assets/{isEmpty-B9L-Ge-H.js → isEmpty-bnrF3Qbc.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js → ishikawaDiagram-PHBUUO56-D05_LyL7.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-CgDI-UG4.js → journeyDiagram-4ABVD52K-B_L20qMe.js} +1 -1
- package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
- package/dist/web/assets/{kanban-definition-K7BYSVSG-h4g10UHL.js → kanban-definition-K7BYSVSG-CZ535BbZ.js} +1 -1
- package/dist/web/assets/keybindings-store-DPYzBe_M.js +1 -0
- package/dist/web/assets/{line-B75-Rx70.js → line-CVvo3dRu.js} +1 -1
- package/dist/web/assets/{linear-Bcjv9FQt.js → linear-DP4mkX3m.js} +1 -1
- package/dist/web/assets/{markdown-renderer-VIZB1GXE.js → markdown-renderer-DPLdR9xc.js} +5 -5
- package/dist/web/assets/{mermaid-parser.core-8u2leTXI.js → mermaid-parser.core-C7UwoIh6.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-BaOBwb-W.js → mindmap-definition-YRQLILUH-x0MTutJp.js} +1 -1
- package/dist/web/assets/{ordinal-LFEjVtwQ.js → ordinal-_K3x1fkz.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-At5Kz0KK.js → pieDiagram-SKSYHLDU-C1Gjrtzy.js} +1 -1
- package/dist/web/assets/postgres-viewer-BeiK4lCa.js +1 -0
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-CdjGIDfw.js → quadrantDiagram-337W2JSQ-C8bzJCjQ.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-B9F_Cx_p.js → requirementDiagram-Z7DCOOCP-pQyah6WB.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-RolPi8bU.js → sankeyDiagram-WA2Y5GQK-T6RgG-N8.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-DM-tMAhx.js → sequenceDiagram-2WXFIKYE-BQDJ4CVs.js} +1 -1
- package/dist/web/assets/settings-tab-D3AvU4lu.js +1 -0
- package/dist/web/assets/sqlite-viewer-nA2sD4Yv.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-C4EMl6jf.js → stateDiagram-RAJIS63D-66vhiIuk.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +1 -0
- package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
- package/dist/web/assets/table-DFevCOMd.js +1 -0
- package/dist/web/assets/tag-CXMT0QB6.js +1 -0
- package/dist/web/assets/{terminal-tab-XhKfb4ei.js → terminal-tab-BBi0pEji.js} +1 -1
- package/dist/web/assets/{timeline-definition-YZTLITO2-A4PN_Efm.js → timeline-definition-YZTLITO2-DwZqB3nn.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
- package/dist/web/assets/{use-monaco-theme-0p0-84jJ.js → use-monaco-theme-B5pG2d1w.js} +1 -1
- package/dist/web/assets/{vennDiagram-LZ73GAT5-ywK7LMaH.js → vennDiagram-LZ73GAT5-s9Z71fz-.js} +1 -1
- package/dist/web/assets/{xychartDiagram-JWTSCODW-DylHYNtJ.js → xychartDiagram-JWTSCODW-DRa_TH4B.js} +1 -1
- package/dist/web/index.html +10 -9
- 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/docs/code-standards.md +128 -1
- package/docs/codebase-summary.md +79 -12
- package/docs/extension-development-guide.md +532 -0
- package/docs/project-changelog.md +51 -1
- package/docs/project-roadmap.md +9 -3
- package/docs/streaming-input-guide.md +267 -0
- package/docs/system-architecture.md +432 -3
- package/package.json +6 -3
- package/packages/ext-database/package.json +41 -0
- package/packages/ext-database/src/connection-tree.ts +142 -0
- package/packages/ext-database/src/extension.ts +346 -0
- package/packages/ext-database/src/query-panel.ts +120 -0
- package/packages/ext-database/src/table-viewer-panel.ts +410 -0
- package/packages/ext-database/tsconfig.json +8 -0
- package/packages/vscode-compat/package.json +16 -0
- package/packages/vscode-compat/src/commands.ts +39 -0
- package/packages/vscode-compat/src/context.ts +65 -0
- package/packages/vscode-compat/src/disposable.ts +21 -0
- package/packages/vscode-compat/src/env.ts +20 -0
- package/packages/vscode-compat/src/event-emitter.ts +28 -0
- package/packages/vscode-compat/src/index.ts +93 -0
- package/packages/vscode-compat/src/not-supported.ts +15 -0
- package/packages/vscode-compat/src/types.ts +167 -0
- package/packages/vscode-compat/src/uri.ts +65 -0
- package/packages/vscode-compat/src/window.ts +229 -0
- package/packages/vscode-compat/src/workspace.ts +76 -0
- package/packages/vscode-compat/tsconfig.json +10 -0
- package/snapshot-state.md +1526 -0
- package/src/cli/commands/autostart.ts +1 -1
- package/src/cli/commands/ext-cmd.ts +121 -0
- package/src/cli/commands/restart.ts +9 -1
- package/src/cli/commands/status.ts +19 -0
- package/src/index.ts +5 -3
- package/src/providers/claude-agent-sdk.ts +221 -17
- package/src/providers/cli-provider-base.ts +6 -0
- package/src/server/index.ts +55 -155
- package/src/server/routes/chat.ts +81 -11
- package/src/server/routes/extensions.ts +81 -0
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/settings.ts +27 -0
- package/src/server/routes/workspace.ts +35 -0
- package/src/server/ws/chat.ts +9 -3
- package/src/server/ws/extensions.ts +175 -0
- package/src/services/account-selector.service.ts +14 -5
- package/src/services/account.service.ts +7 -7
- package/src/services/claude-usage.service.ts +11 -11
- package/src/services/cloud-ws.service.ts +228 -0
- package/src/services/cloud.service.ts +1 -0
- package/src/services/contribution-registry.ts +110 -0
- package/src/services/db.service.ts +181 -4
- package/src/services/extension-host-worker.ts +160 -0
- package/src/services/extension-installer.ts +112 -0
- package/src/services/extension-manifest.ts +65 -0
- package/src/services/extension-rpc-handlers.ts +235 -0
- package/src/services/extension-rpc.ts +105 -0
- package/src/services/extension.service.ts +228 -0
- package/src/services/mcp-config.service.ts +15 -6
- package/src/services/supervisor.ts +271 -25
- package/src/types/api.ts +1 -0
- package/src/types/chat.ts +4 -0
- package/src/types/extension-messages.ts +64 -0
- package/src/types/extension.ts +131 -0
- package/src/web/app.tsx +69 -48
- package/src/web/components/chat/account-rotation-settings.tsx +163 -0
- package/src/web/components/chat/chat-history-bar.tsx +106 -10
- package/src/web/components/chat/chat-tab.tsx +15 -10
- package/src/web/components/chat/chat-welcome.tsx +148 -0
- package/src/web/components/chat/message-list.tsx +19 -6
- package/src/web/components/chat/session-picker.tsx +80 -32
- package/src/web/components/chat/usage-badge.tsx +68 -8
- package/src/web/components/extensions/extension-inputbox.tsx +92 -0
- package/src/web/components/extensions/extension-quickpick.tsx +194 -0
- package/src/web/components/extensions/extension-tree-view.tsx +240 -0
- package/src/web/components/extensions/extension-webview.tsx +83 -0
- package/src/web/components/layout/command-palette.tsx +22 -2
- package/src/web/components/layout/editor-panel.tsx +163 -18
- package/src/web/components/layout/mobile-nav.tsx +2 -1
- package/src/web/components/layout/sidebar.tsx +21 -3
- package/src/web/components/layout/status-bar.tsx +64 -0
- package/src/web/components/layout/tab-bar.tsx +2 -0
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/upgrade-banner.tsx +15 -5
- package/src/web/components/settings/change-password-section.tsx +128 -0
- package/src/web/components/settings/extension-manager-section.tsx +214 -0
- package/src/web/components/settings/settings-tab.tsx +9 -2
- package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
- package/src/web/hooks/use-chat.ts +28 -0
- package/src/web/hooks/use-extension-ws.ts +181 -0
- package/src/web/hooks/use-global-keybindings.ts +18 -2
- package/src/web/hooks/use-server-reload.ts +9 -0
- package/src/web/hooks/use-url-sync.ts +173 -21
- package/src/web/stores/connection-store.ts +39 -0
- package/src/web/stores/extension-store.ts +204 -0
- package/src/web/stores/panel-store.ts +63 -9
- package/src/web/stores/panel-utils.ts +145 -3
- package/src/web/stores/settings-store.ts +7 -2
- package/src/web/stores/tab-store.ts +2 -1
- package/test-session-ops.mjs +444 -0
- package/test-tokens.mjs +212 -0
- package/tsconfig.json +3 -1
- package/dist/web/assets/api-settings-CEMxVMCV.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +0 -1
- package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
- package/dist/web/assets/browser-tab-D1Zua62g.js +0 -1
- package/dist/web/assets/channel-C2fMafck.js +0 -1
- package/dist/web/assets/chat-tab-BnD27Vp9.js +0 -7
- package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
- package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +0 -1
- package/dist/web/assets/clone-B2hUek6n.js +0 -1
- package/dist/web/assets/code-editor-DGRg8stf.js +0 -2
- package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
- package/dist/web/assets/database-viewer-DxCXZQcE.js +0 -1
- package/dist/web/assets/diff-viewer-C1sDJG35.js +0 -4
- package/dist/web/assets/git-graph-BDn-EiGE.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +0 -1
- package/dist/web/assets/index-Bun94AK3.js +0 -37
- package/dist/web/assets/index-Db8uky1a.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +0 -2
- package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
- package/dist/web/assets/keybindings-store-COmK4Dte.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +0 -1
- package/dist/web/assets/postgres-viewer-CvQZ8gkh.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +0 -1
- package/dist/web/assets/settings-tab-RCnvZ29H.js +0 -1
- package/dist/web/assets/sqlite-viewer-CEEm2W4C.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +0 -1
- package/dist/web/assets/tab-store-Bjh6bXFP.js +0 -1
- package/dist/web/assets/table-CQVQM2SB.js +0 -1
- package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +0 -1
- /package/dist/web/assets/{api-client-BKIT_Qeg.js → api-client-BfBM3I7n.js} +0 -0
- /package/dist/web/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-CWPXKqbJ.js → cytoscape.esm-BW-DbntU.js} +0 -0
- /package/dist/web/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
- /package/dist/web/assets/{dist-Cep75xXf.js → dist-CSJdAyA9.js} +0 -0
- /package/dist/web/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-CGBoxvCD.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
- /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
- /package/dist/web/assets/{lib-BeaDXEkP.js → lib-BQ34Db2e.js} +0 -0
- /package/dist/web/assets/{math-y9zN1W-N.js → math-069Z4SuC.js} +0 -0
- /package/dist/web/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
- /package/dist/web/assets/{rough.esm-nHaDi0Kw.js → rough.esm-JX0wREDd.js} +0 -0
- /package/dist/web/assets/{src-Dw4QhedI.js → src-BqX54PbV.js} +0 -0
- /package/dist/web/assets/{utils-DMiycH3O.js → utils-BNytJOb1.js} +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Suspense, lazy } from "react";
|
|
2
|
-
import { Loader2, Terminal, MessageSquare, GitBranch } from "lucide-react";
|
|
1
|
+
import { Suspense, lazy, useEffect, useState, useCallback } from "react";
|
|
2
|
+
import { ChevronDown, ChevronUp, Loader2, Terminal, MessageSquare, GitBranch, Pin, PinOff } from "lucide-react";
|
|
3
3
|
import { usePanelStore } from "@/stores/panel-store";
|
|
4
4
|
import { useProjectStore } from "@/stores/project-store";
|
|
5
5
|
import type { TabType } from "@/stores/tab-store";
|
|
6
|
+
import { api, projectUrl } from "@/lib/api-client";
|
|
7
|
+
import type { SessionInfo } from "../../../types/chat";
|
|
6
8
|
import { TabBar } from "./tab-bar";
|
|
7
9
|
import { SplitDropOverlay } from "./split-drop-overlay";
|
|
8
10
|
import { cn } from "@/lib/utils";
|
|
@@ -24,6 +26,7 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
|
|
|
24
26
|
"git-diff": lazy(() => import("@/components/editor/diff-viewer").then((m) => ({ default: m.DiffViewer }))),
|
|
25
27
|
settings: lazy(() => import("@/components/settings/settings-tab").then((m) => ({ default: m.SettingsTab }))),
|
|
26
28
|
browser: lazy(() => import("@/components/browser/browser-tab").then((m) => ({ default: m.BrowserTab }))),
|
|
29
|
+
"extension-webview": lazy(() => import("@/components/extensions/extension-webview").then((m) => ({ default: m.ExtensionWebview }))),
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
interface EditorPanelProps {
|
|
@@ -74,8 +77,70 @@ export function EditorPanel({ panelId, projectName }: EditorPanelProps) {
|
|
|
74
77
|
);
|
|
75
78
|
}
|
|
76
79
|
|
|
80
|
+
function formatRelativeDate(iso: string): string {
|
|
81
|
+
try {
|
|
82
|
+
const date = new Date(iso);
|
|
83
|
+
const now = new Date();
|
|
84
|
+
const diffMs = now.getTime() - date.getTime();
|
|
85
|
+
const diffMin = Math.floor(diffMs / 60_000);
|
|
86
|
+
if (diffMin < 1) return "Just now";
|
|
87
|
+
if (diffMin < 60) return `${diffMin}m ago`;
|
|
88
|
+
const diffHr = Math.floor(diffMin / 60);
|
|
89
|
+
if (diffHr < 24) return `${diffHr}h ago`;
|
|
90
|
+
const diffDay = Math.floor(diffHr / 24);
|
|
91
|
+
if (diffDay < 7) return `${diffDay}d ago`;
|
|
92
|
+
return date.toLocaleDateString(undefined, { month: "short", day: "numeric" });
|
|
93
|
+
} catch {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const MAX_RECENT_SESSIONS = 5;
|
|
99
|
+
const FETCH_SESSIONS_LIMIT = 20;
|
|
100
|
+
|
|
77
101
|
function EmptyPanel({ panelId }: { panelId: string }) {
|
|
78
102
|
const activeProject = useProjectStore((s) => s.activeProject);
|
|
103
|
+
const [sessions, setSessions] = useState<SessionInfo[]>([]);
|
|
104
|
+
const [loadingSessions, setLoadingSessions] = useState(false);
|
|
105
|
+
const [showAll, setShowAll] = useState(false);
|
|
106
|
+
|
|
107
|
+
const loadSessions = useCallback(async () => {
|
|
108
|
+
if (!activeProject?.name) return;
|
|
109
|
+
setLoadingSessions(true);
|
|
110
|
+
try {
|
|
111
|
+
const data = await api.get<SessionInfo[]>(`${projectUrl(activeProject.name)}/chat/sessions`);
|
|
112
|
+
setSessions(data.slice(0, FETCH_SESSIONS_LIMIT));
|
|
113
|
+
} catch {
|
|
114
|
+
// silently ignore — empty state still functional without sessions
|
|
115
|
+
} finally {
|
|
116
|
+
setLoadingSessions(false);
|
|
117
|
+
}
|
|
118
|
+
}, [activeProject?.name]);
|
|
119
|
+
|
|
120
|
+
useEffect(() => { loadSessions(); }, [loadSessions]);
|
|
121
|
+
|
|
122
|
+
const togglePin = useCallback(async (e: React.MouseEvent, session: SessionInfo) => {
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
if (!activeProject?.name) return;
|
|
125
|
+
const url = `${projectUrl(activeProject.name)}/chat/sessions/${session.id}/pin`;
|
|
126
|
+
try {
|
|
127
|
+
if (session.pinned) {
|
|
128
|
+
await api.del(url);
|
|
129
|
+
} else {
|
|
130
|
+
await api.put(url);
|
|
131
|
+
}
|
|
132
|
+
setSessions((prev) => {
|
|
133
|
+
const updated = prev.map((s) => s.id === session.id ? { ...s, pinned: !s.pinned } : s);
|
|
134
|
+
return updated.sort((a, b) => {
|
|
135
|
+
if (a.pinned && !b.pinned) return -1;
|
|
136
|
+
if (!a.pinned && b.pinned) return 1;
|
|
137
|
+
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
} catch {
|
|
141
|
+
// silently ignore
|
|
142
|
+
}
|
|
143
|
+
}, [activeProject?.name]);
|
|
79
144
|
|
|
80
145
|
function openTab(type: TabType) {
|
|
81
146
|
const needsProject = type !== "settings";
|
|
@@ -86,23 +151,103 @@ function EmptyPanel({ panelId }: { panelId: string }) {
|
|
|
86
151
|
);
|
|
87
152
|
}
|
|
88
153
|
|
|
154
|
+
function openSession(session: SessionInfo) {
|
|
155
|
+
usePanelStore.getState().openTab(
|
|
156
|
+
{
|
|
157
|
+
type: "chat",
|
|
158
|
+
title: session.title || "Chat",
|
|
159
|
+
projectId: activeProject?.name ?? null,
|
|
160
|
+
metadata: { projectName: activeProject?.name, sessionId: session.id, providerId: session.providerId },
|
|
161
|
+
closable: true,
|
|
162
|
+
},
|
|
163
|
+
panelId,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const pinnedSessions = sessions.filter((s) => s.pinned);
|
|
168
|
+
const allRecentSessions = sessions.filter((s) => !s.pinned);
|
|
169
|
+
const recentSessions = showAll ? allRecentSessions : allRecentSessions.slice(0, MAX_RECENT_SESSIONS);
|
|
170
|
+
const hasMore = allRecentSessions.length > MAX_RECENT_SESSIONS;
|
|
171
|
+
|
|
172
|
+
function renderSessionRow(session: SessionInfo) {
|
|
173
|
+
return (
|
|
174
|
+
<button
|
|
175
|
+
key={session.id}
|
|
176
|
+
onClick={() => openSession(session)}
|
|
177
|
+
className="group flex items-center gap-2.5 w-full px-3 py-2.5 text-left hover:bg-surface-elevated active:bg-surface-elevated transition-colors border-b border-border/50 last:border-0"
|
|
178
|
+
>
|
|
179
|
+
<MessageSquare className="size-3.5 shrink-0 text-text-subtle" />
|
|
180
|
+
<span className="flex-1 min-w-0 text-xs font-medium truncate text-text-primary">
|
|
181
|
+
{session.title || "Untitled"}
|
|
182
|
+
</span>
|
|
183
|
+
{session.updatedAt && (
|
|
184
|
+
<span className="text-[10px] text-text-subtle shrink-0">
|
|
185
|
+
{formatRelativeDate(session.updatedAt)}
|
|
186
|
+
</span>
|
|
187
|
+
)}
|
|
188
|
+
<span
|
|
189
|
+
role="button"
|
|
190
|
+
tabIndex={0}
|
|
191
|
+
onClick={(e) => togglePin(e, session)}
|
|
192
|
+
className={`p-1 rounded transition-colors shrink-0 ${
|
|
193
|
+
session.pinned
|
|
194
|
+
? "text-primary hover:text-primary/70"
|
|
195
|
+
: "text-text-subtle md:opacity-0 md:group-hover:opacity-100 hover:text-text-primary"
|
|
196
|
+
}`}
|
|
197
|
+
aria-label={session.pinned ? "Unpin session" : "Pin session"}
|
|
198
|
+
>
|
|
199
|
+
{session.pinned ? <PinOff className="size-3" /> : <Pin className="size-3" />}
|
|
200
|
+
</span>
|
|
201
|
+
</button>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
89
205
|
return (
|
|
90
|
-
<div className="flex flex-col
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
206
|
+
<div className="flex flex-col h-full overflow-y-auto text-text-secondary">
|
|
207
|
+
<div className="flex flex-col items-center justify-center gap-6 px-4 flex-1">
|
|
208
|
+
<p className="text-sm">Open a tab to get started</p>
|
|
209
|
+
<div className="grid grid-cols-3 gap-2 w-full max-w-sm">
|
|
210
|
+
{QUICK_OPEN_TABS.map((opt) => {
|
|
211
|
+
const Icon = opt.icon;
|
|
212
|
+
return (
|
|
213
|
+
<button
|
|
214
|
+
key={opt.type}
|
|
215
|
+
onClick={() => openTab(opt.type)}
|
|
216
|
+
className="flex flex-col items-center justify-center gap-1.5 px-2 py-3 rounded-md border border-border bg-surface hover:bg-surface-elevated active:bg-surface-elevated text-xs text-foreground transition-colors"
|
|
217
|
+
>
|
|
218
|
+
<Icon className="size-5" />
|
|
219
|
+
{opt.label}
|
|
220
|
+
</button>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{activeProject && !loadingSessions && pinnedSessions.length > 0 && (
|
|
226
|
+
<div className="flex flex-col gap-2 w-full max-w-sm">
|
|
227
|
+
<p className="text-xs text-text-subtle text-center">Pinned</p>
|
|
228
|
+
<div className="w-full rounded-md border border-border bg-surface overflow-hidden">
|
|
229
|
+
{pinnedSessions.map(renderSessionRow)}
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
|
|
234
|
+
{activeProject && !loadingSessions && recentSessions.length > 0 && (
|
|
235
|
+
<div className="flex flex-col gap-2 w-full max-w-sm">
|
|
236
|
+
<p className="text-xs text-text-subtle text-center">Recent chats</p>
|
|
237
|
+
<div className="w-full rounded-md border border-border bg-surface overflow-hidden">
|
|
238
|
+
{recentSessions.map(renderSessionRow)}
|
|
239
|
+
</div>
|
|
240
|
+
{hasMore && (
|
|
241
|
+
<button
|
|
242
|
+
onClick={() => setShowAll(!showAll)}
|
|
243
|
+
className="flex items-center justify-center gap-1 text-[11px] text-text-subtle hover:text-text-primary transition-colors py-1"
|
|
244
|
+
>
|
|
245
|
+
{showAll ? <ChevronUp className="size-3" /> : <ChevronDown className="size-3" />}
|
|
246
|
+
{showAll ? "Show less" : `Show more (${allRecentSessions.length - MAX_RECENT_SESSIONS})`}
|
|
247
|
+
</button>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
)}
|
|
106
251
|
</div>
|
|
107
252
|
</div>
|
|
108
253
|
);
|
|
@@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
Terminal, MessageSquare, GitBranch, Database,
|
|
4
4
|
FileDiff, FileCode, Settings, Menu, X, ArrowLeft, ArrowRight, SplitSquareVertical, MoveVertical, Layers, Plus,
|
|
5
|
-
ChevronLeft, ChevronRight, Globe,
|
|
5
|
+
ChevronLeft, ChevronRight, Globe, Puzzle,
|
|
6
6
|
} from "lucide-react";
|
|
7
7
|
import { usePanelStore } from "@/stores/panel-store";
|
|
8
8
|
import { useProjectStore, resolveOrder } from "@/stores/project-store";
|
|
@@ -26,6 +26,7 @@ const NEW_TAB_LABELS: Partial<Record<TabType, string>> = Object.fromEntries(NEW_
|
|
|
26
26
|
const TAB_ICONS: Record<TabType, React.ElementType> = {
|
|
27
27
|
terminal: Terminal, chat: MessageSquare, editor: FileCode, database: Database, sqlite: Database, postgres: Database,
|
|
28
28
|
"git-graph": GitBranch, "git-diff": FileDiff, settings: Settings, browser: Globe,
|
|
29
|
+
"extension-webview": Puzzle,
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
interface MobileNavProps { onMenuPress: () => void; onProjectsPress: () => void; }
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { useCallback, useRef } from "react";
|
|
2
|
-
import { PanelLeftClose, PanelLeftOpen, FolderOpen, GitBranch, Settings, Database, Search } from "lucide-react";
|
|
1
|
+
import { useCallback, useRef, useMemo } from "react";
|
|
2
|
+
import { PanelLeftClose, PanelLeftOpen, FolderOpen, GitBranch, Settings, Database, Search, Puzzle } from "lucide-react";
|
|
3
3
|
import { useProjectStore } from "@/stores/project-store";
|
|
4
4
|
import { useSettingsStore, type SidebarActiveTab } from "@/stores/settings-store";
|
|
5
|
+
import { useExtensionStore } from "@/stores/extension-store";
|
|
5
6
|
import { FileTree } from "@/components/explorer/file-tree";
|
|
6
7
|
import { GitStatusPanel } from "@/components/git/git-status-panel";
|
|
7
8
|
import { SettingsTab } from "@/components/settings/settings-tab";
|
|
8
9
|
import { DatabaseSidebar } from "@/components/database/database-sidebar";
|
|
9
10
|
import { SearchPanel } from "@/components/explorer/search-panel";
|
|
11
|
+
import { ExtensionTreeView } from "@/components/extensions/extension-tree-view";
|
|
10
12
|
import { cn } from "@/lib/utils";
|
|
11
13
|
|
|
12
|
-
const
|
|
14
|
+
const BUILTIN_TABS: { id: SidebarActiveTab; label: string; icon: React.ElementType }[] = [
|
|
13
15
|
{ id: "explorer", label: "Explorer", icon: FolderOpen },
|
|
14
16
|
{ id: "search", label: "Search", icon: Search },
|
|
15
17
|
{ id: "git", label: "Git", icon: GitBranch },
|
|
@@ -63,6 +65,19 @@ export function Sidebar() {
|
|
|
63
65
|
const setSidebarWidth = useSettingsStore((s) => s.setSidebarWidth);
|
|
64
66
|
const sidebarActiveTab = useSettingsStore((s) => s.sidebarActiveTab);
|
|
65
67
|
const setSidebarActiveTab = useSettingsStore((s) => s.setSidebarActiveTab);
|
|
68
|
+
const contributions = useExtensionStore((s) => s.contributions);
|
|
69
|
+
|
|
70
|
+
// Build tabs list: built-in + extension-contributed sidebar views
|
|
71
|
+
const TABS = useMemo(() => {
|
|
72
|
+
const tabs: { id: SidebarActiveTab; label: string; icon: React.ElementType }[] = [...BUILTIN_TABS];
|
|
73
|
+
if (contributions?.views) {
|
|
74
|
+
const sidebarViews = contributions.views["sidebar"] ?? contributions.views["explorer"] ?? [];
|
|
75
|
+
for (const view of sidebarViews) {
|
|
76
|
+
tabs.push({ id: `ext:${view.id}` as SidebarActiveTab, label: view.name, icon: Puzzle });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return tabs;
|
|
80
|
+
}, [contributions]);
|
|
66
81
|
|
|
67
82
|
if (sidebarCollapsed) {
|
|
68
83
|
return (
|
|
@@ -135,6 +150,9 @@ export function Sidebar() {
|
|
|
135
150
|
{sidebarActiveTab === "settings" && (
|
|
136
151
|
<SettingsTab />
|
|
137
152
|
)}
|
|
153
|
+
{typeof sidebarActiveTab === "string" && sidebarActiveTab.startsWith("ext:") && (
|
|
154
|
+
<ExtensionTreeView viewId={sidebarActiveTab.slice(4)} className="h-full" />
|
|
155
|
+
)}
|
|
138
156
|
</div>
|
|
139
157
|
|
|
140
158
|
{/* Resize handle */}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useExtensionStore, type StatusBarItemUI } from "@/stores/extension-store";
|
|
2
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
3
|
+
|
|
4
|
+
/** Fixed status bar at the bottom of the editor area (hidden on mobile) */
|
|
5
|
+
export function StatusBar() {
|
|
6
|
+
const items = useExtensionStore((s) => s.statusBarItems);
|
|
7
|
+
|
|
8
|
+
const left = items
|
|
9
|
+
.filter((i) => i.alignment === "left")
|
|
10
|
+
.sort((a, b) => b.priority - a.priority);
|
|
11
|
+
|
|
12
|
+
const right = items
|
|
13
|
+
.filter((i) => i.alignment === "right")
|
|
14
|
+
.sort((a, b) => b.priority - a.priority);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="hidden md:flex items-center justify-between h-[22px] px-2 bg-surface border-t border-border text-[11px] text-text-subtle select-none shrink-0">
|
|
18
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
19
|
+
{left.map((item) => (
|
|
20
|
+
<StatusBarEntry key={item.id} item={item} />
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
24
|
+
{right.map((item) => (
|
|
25
|
+
<StatusBarEntry key={item.id} item={item} />
|
|
26
|
+
))}
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function StatusBarEntry({ item }: { item: StatusBarItemUI }) {
|
|
33
|
+
const content = (
|
|
34
|
+
<button
|
|
35
|
+
className={`truncate px-1 rounded-sm transition-colors ${
|
|
36
|
+
item.command
|
|
37
|
+
? "hover:bg-accent/15 hover:text-text-primary cursor-pointer"
|
|
38
|
+
: "cursor-default"
|
|
39
|
+
}`}
|
|
40
|
+
onClick={() => {
|
|
41
|
+
if (item.command) {
|
|
42
|
+
window.dispatchEvent(new CustomEvent("ext:command:execute", {
|
|
43
|
+
detail: { command: item.command },
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
{item.text}
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (item.tooltip) {
|
|
53
|
+
return (
|
|
54
|
+
<Tooltip>
|
|
55
|
+
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
|
56
|
+
<TooltipContent side="top" className="text-xs">
|
|
57
|
+
{item.tooltip}
|
|
58
|
+
</TooltipContent>
|
|
59
|
+
</Tooltip>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return content;
|
|
64
|
+
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
ChevronLeft,
|
|
12
12
|
ChevronRight,
|
|
13
13
|
Globe,
|
|
14
|
+
Puzzle,
|
|
14
15
|
} from "lucide-react";
|
|
15
16
|
import { useTabStore, type TabType } from "@/stores/tab-store";
|
|
16
17
|
import { usePanelStore } from "@/stores/panel-store";
|
|
@@ -35,6 +36,7 @@ const TAB_ICONS: Record<TabType, React.ElementType> = {
|
|
|
35
36
|
"git-diff": FileDiff,
|
|
36
37
|
settings: Settings,
|
|
37
38
|
browser: Globe,
|
|
39
|
+
"extension-webview": Puzzle,
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
interface TabBarProps {
|
|
@@ -53,6 +53,11 @@ const TAB_COMPONENTS: Record<TabType, React.LazyExoticComponent<React.ComponentT
|
|
|
53
53
|
default: m.BrowserTab,
|
|
54
54
|
})),
|
|
55
55
|
),
|
|
56
|
+
"extension-webview": lazy(() =>
|
|
57
|
+
import("@/components/extensions/extension-webview").then((m) => ({
|
|
58
|
+
default: m.ExtensionWebview,
|
|
59
|
+
})),
|
|
60
|
+
),
|
|
56
61
|
};
|
|
57
62
|
|
|
58
63
|
function LoadingFallback() {
|
|
@@ -12,7 +12,11 @@ interface UpgradeStatus {
|
|
|
12
12
|
installMethod: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
interface UpgradeBannerProps {
|
|
16
|
+
onVisibilityChange?: (visible: boolean) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function UpgradeBanner({ onVisibilityChange }: UpgradeBannerProps) {
|
|
16
20
|
const [availableVersion, setAvailableVersion] = useState<string | null>(null);
|
|
17
21
|
const [upgrading, setUpgrading] = useState(false);
|
|
18
22
|
const [dismissed, setDismissed] = useState(false);
|
|
@@ -61,10 +65,16 @@ export function UpgradeBanner() {
|
|
|
61
65
|
setDismissed(true);
|
|
62
66
|
}, [availableVersion]);
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
const visible = !!availableVersion && !dismissed;
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
onVisibilityChange?.(visible);
|
|
72
|
+
}, [visible, onVisibilityChange]);
|
|
73
|
+
|
|
74
|
+
if (!visible) return null;
|
|
65
75
|
|
|
66
76
|
return (
|
|
67
|
-
<div className="w-full bg-blue-600 dark:bg-blue-700 text-white px-3 py-
|
|
77
|
+
<div className="w-full bg-blue-600 dark:bg-blue-700 text-white px-3 py-1 flex items-center justify-between gap-2 z-50 text-sm shrink-0">
|
|
68
78
|
{upgrading ? (
|
|
69
79
|
<div className="flex items-center gap-2 flex-1 min-w-0">
|
|
70
80
|
<Loader2 className="size-4 animate-spin shrink-0" />
|
|
@@ -83,13 +93,13 @@ export function UpgradeBanner() {
|
|
|
83
93
|
<div className="flex items-center gap-1 shrink-0">
|
|
84
94
|
<button
|
|
85
95
|
onClick={handleUpgrade}
|
|
86
|
-
className="bg-white text-blue-600 font-medium rounded-full px-3 min-h-[
|
|
96
|
+
className="bg-white text-blue-600 font-medium rounded-full px-3 py-0.5 text-xs min-h-[28px] min-w-[28px] flex items-center justify-center hover:bg-blue-50 active:bg-blue-100 transition-colors"
|
|
87
97
|
>
|
|
88
98
|
Upgrade
|
|
89
99
|
</button>
|
|
90
100
|
<button
|
|
91
101
|
onClick={handleDismiss}
|
|
92
|
-
className="min-h-[
|
|
102
|
+
className="min-h-[28px] min-w-[28px] flex items-center justify-center rounded-full hover:bg-blue-500 active:bg-blue-800 transition-colors"
|
|
93
103
|
aria-label="Dismiss upgrade notification"
|
|
94
104
|
>
|
|
95
105
|
<X className="size-4" />
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import { KeyRound, Check, Eye, EyeOff } from "lucide-react";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { Input } from "@/components/ui/input";
|
|
5
|
+
import { api } from "@/lib/api-client";
|
|
6
|
+
import { setAuthToken } from "@/lib/api-client";
|
|
7
|
+
|
|
8
|
+
export function ChangePasswordSection() {
|
|
9
|
+
const [open, setOpen] = useState(false);
|
|
10
|
+
const [password, setPassword] = useState("");
|
|
11
|
+
const [confirm, setConfirm] = useState("");
|
|
12
|
+
const [showPw, setShowPw] = useState(false);
|
|
13
|
+
const [saving, setSaving] = useState(false);
|
|
14
|
+
const [saved, setSaved] = useState(false);
|
|
15
|
+
const [error, setError] = useState<string | null>(null);
|
|
16
|
+
|
|
17
|
+
const mismatch = confirm.length > 0 && password !== confirm;
|
|
18
|
+
const canSubmit = password.trim().length >= 4 && password === confirm && !saving;
|
|
19
|
+
|
|
20
|
+
const handleSubmit = useCallback(async () => {
|
|
21
|
+
if (!canSubmit) return;
|
|
22
|
+
setSaving(true);
|
|
23
|
+
setError(null);
|
|
24
|
+
try {
|
|
25
|
+
const { token } = await api.put<{ token: string }>("/api/settings/auth/password", {
|
|
26
|
+
password: password.trim(),
|
|
27
|
+
confirm: confirm.trim(),
|
|
28
|
+
});
|
|
29
|
+
// Update localStorage so current session stays authenticated
|
|
30
|
+
setAuthToken(token);
|
|
31
|
+
setSaved(true);
|
|
32
|
+
setPassword("");
|
|
33
|
+
setConfirm("");
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
setSaved(false);
|
|
36
|
+
setOpen(false);
|
|
37
|
+
}, 1500);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
setError(e instanceof Error ? e.message : "Failed to change password");
|
|
40
|
+
} finally {
|
|
41
|
+
setSaving(false);
|
|
42
|
+
}
|
|
43
|
+
}, [canSubmit, password, confirm]);
|
|
44
|
+
|
|
45
|
+
if (!open) {
|
|
46
|
+
return (
|
|
47
|
+
<section className="space-y-2">
|
|
48
|
+
<h3 className="text-xs font-medium text-muted-foreground">Security</h3>
|
|
49
|
+
<Button
|
|
50
|
+
variant="outline"
|
|
51
|
+
size="sm"
|
|
52
|
+
className="h-8 text-xs gap-1.5 cursor-pointer"
|
|
53
|
+
onClick={() => setOpen(true)}
|
|
54
|
+
>
|
|
55
|
+
<KeyRound className="size-3.5" />
|
|
56
|
+
Change Password
|
|
57
|
+
</Button>
|
|
58
|
+
</section>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<section className="space-y-2">
|
|
64
|
+
<h3 className="text-xs font-medium text-muted-foreground">Change Password</h3>
|
|
65
|
+
<div className="space-y-2">
|
|
66
|
+
<div className="relative">
|
|
67
|
+
<Input
|
|
68
|
+
type={showPw ? "text" : "password"}
|
|
69
|
+
placeholder="New password"
|
|
70
|
+
value={password}
|
|
71
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
72
|
+
className="h-8 text-xs pr-8"
|
|
73
|
+
autoFocus
|
|
74
|
+
/>
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground cursor-pointer"
|
|
78
|
+
onClick={() => setShowPw(!showPw)}
|
|
79
|
+
tabIndex={-1}
|
|
80
|
+
>
|
|
81
|
+
{showPw ? <EyeOff className="size-3.5" /> : <Eye className="size-3.5" />}
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
<Input
|
|
85
|
+
type={showPw ? "text" : "password"}
|
|
86
|
+
placeholder="Confirm password"
|
|
87
|
+
value={confirm}
|
|
88
|
+
onChange={(e) => setConfirm(e.target.value)}
|
|
89
|
+
onKeyDown={(e) => { if (e.key === "Enter") handleSubmit(); }}
|
|
90
|
+
className="h-8 text-xs"
|
|
91
|
+
/>
|
|
92
|
+
{mismatch && (
|
|
93
|
+
<p className="text-[11px] text-destructive">Passwords do not match</p>
|
|
94
|
+
)}
|
|
95
|
+
{error && (
|
|
96
|
+
<p className="text-[11px] text-destructive">{error}</p>
|
|
97
|
+
)}
|
|
98
|
+
<div className="flex gap-1.5">
|
|
99
|
+
<Button
|
|
100
|
+
variant="outline"
|
|
101
|
+
size="sm"
|
|
102
|
+
className="h-8 text-xs flex-1 cursor-pointer"
|
|
103
|
+
onClick={() => {
|
|
104
|
+
setOpen(false);
|
|
105
|
+
setPassword("");
|
|
106
|
+
setConfirm("");
|
|
107
|
+
setError(null);
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
Cancel
|
|
111
|
+
</Button>
|
|
112
|
+
<Button
|
|
113
|
+
variant={saved ? "default" : "outline"}
|
|
114
|
+
size="sm"
|
|
115
|
+
className="h-8 text-xs flex-1 cursor-pointer"
|
|
116
|
+
disabled={!canSubmit}
|
|
117
|
+
onClick={handleSubmit}
|
|
118
|
+
>
|
|
119
|
+
{saving ? "..." : saved ? <Check className="size-3.5" /> : "Save"}
|
|
120
|
+
</Button>
|
|
121
|
+
</div>
|
|
122
|
+
<p className="text-[11px] text-muted-foreground">
|
|
123
|
+
Min 4 characters. You'll stay logged in on this device.
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
</section>
|
|
127
|
+
);
|
|
128
|
+
}
|