@hienlh/ppm 0.8.86 → 0.8.88
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 +216 -4
- package/bun.lock +5 -0
- package/dist/web/assets/{_basePickBy-5eBmZ_lt.js → _basePickBy-3Xe18azI.js} +1 -1
- package/dist/web/assets/{_baseUniq-DimLlN0y.js → _baseUniq-Yy35llnn.js} +1 -1
- package/dist/web/assets/api-settings-Dh4oFOpX.js +1 -0
- package/dist/web/assets/{arc-D4SasZrA.js → arc-B9n1Gvb5.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-nv0WbM7d.js → architectureDiagram-2XIMDMQ5-DqAZP_F6.js} +1 -1
- package/dist/web/assets/arrow-up--LjUXLEt.js +1 -0
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-C1XvYrb8.js → blockDiagram-WCTKOSBZ-h3cDF2vI.js} +1 -1
- package/dist/web/assets/browser-tab-DJLH0eDY.js +1 -0
- package/dist/web/assets/{c4Diagram-IC4MRINW-CygDrbWJ.js → c4Diagram-IC4MRINW--pF1r5lr.js} +1 -1
- package/dist/web/assets/channel-C2fMafck.js +1 -0
- package/dist/web/assets/chat-tab-C8HFXqGS.js +8 -0
- package/dist/web/assets/chevron-right-CHnjJt4E.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-C2FDgsgT.js → chunk-4BX2VUAB-C3aZvW7B.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-jF4w6cat.js → chunk-55IACEB6-D5cABeB9.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-BVCECZFi.js → chunk-7E7YKBS2-CkFGv6Zs.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-DXTbeu5d.js → chunk-7R4GIKGN-Dvbyu4Zw.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-BaZqOsTs.js → chunk-C72U2L5F-CtqKiH4q.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-Bky2tcH7.js → chunk-EGIJ26TM-Cpr87sBR.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-Cp4BK9A8.js → chunk-FMBD7UC4-D23YVTOU.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-BosFEH7G.js → chunk-GEFDOKGD-tDjHsAUs.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-H5Gbjsbr.js → chunk-JSJVCQXG-BBmymCjA.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-CWerSUwS.js → chunk-KX2RTZJC-DP36BDiU.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-FvwP7jUy.js → chunk-KYZI473N-Djw13C-3.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL-D1PI_ORP.js → chunk-L3YUKLVL-HG_eMj_C.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-C7Vzk_AI.js → chunk-MX3YWQON-C2UEioMs.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-BceYBGYX.js → chunk-NQ4KR5QH-DXUTQ-BL.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-WPtzgxql.js → chunk-O4XLMI2P-BsUWb9d0.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-DlHXDeLY.js → chunk-OZEHJAEY-rG0P22U9.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-Ci_Prygb.js → chunk-PQ6SQG4A-DX0xW7kO.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-CO0zMN-z.js → chunk-PU5JKC2W-C7Gry6md.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-IAEEzfpM.js → chunk-R5LLSJPH-CMY0PkRK.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-BLXalOgc.js → chunk-WL4C6EOR-CXuQvlyu.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-Dx1Ri_p2.js → chunk-XIRO2GV7-DRJEb7Zb.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-m9pPGKn7.js → chunk-XPW4576I-BPEX8KhL.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-B_08ExbI.js → chunk-XZSTWKYB-Cb0iqycX.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-DqSOVcYe.js → chunk-YBOYWFTD-av5aeHLq.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +1 -0
- package/dist/web/assets/clone-B2hUek6n.js +1 -0
- package/dist/web/assets/code-editor-CaGdx-lS.js +2 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-DlL82QHu.js → cose-bilkent-S5V4N54A-qudEiMCT.js} +1 -1
- package/dist/web/assets/csv-preview-DUbHtTAS.js +10 -0
- package/dist/web/assets/{dagre-BmVoh2At.js → dagre-BFcnKyBF.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-sDrRW9MQ.js → dagre-KLK3FWXG-C3O-MTLf.js} +1 -1
- package/dist/web/assets/database-viewer-i4Ddk6mO.js +1 -0
- package/dist/web/assets/{diagram-E7M64L7V-ChnAhgni.js → diagram-E7M64L7V-DxPjK7_c.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-DW1J1uJd.js → diagram-IFDJBPK2-sqTog_XV.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-CQ32hyG_.js → diagram-P4PSJMXO-hzmp0GHK.js} +1 -1
- package/dist/web/assets/diff-viewer-DQDS7yjv.js +4 -0
- package/dist/web/assets/dist-CALwEtco.js +41 -0
- package/dist/web/assets/dist-DGDPTxs1.js +13 -0
- package/dist/web/assets/{erDiagram-INFDFZHY-6CHo6nOw.js → erDiagram-INFDFZHY-DLeYhAAT.js} +1 -1
- package/dist/web/assets/{flowDiagram-PKNHOUZH-DroDiNT0.js → flowDiagram-PKNHOUZH-CRxlE9Sr.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-DP0QBh8w.js → ganttDiagram-A5KZAMGK-BdjmoMLS.js} +1 -1
- package/dist/web/assets/git-graph-DUs-TN1u.js +1 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-DvU3JGZn.js → gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js} +1 -1
- package/dist/web/assets/{graphlib-CQBb2thr.js → graphlib-Duh_bWLa.js} +1 -1
- package/dist/web/assets/index-DhtLEnPD.css +2 -0
- package/dist/web/assets/index-Dm6RN1A1.js +37 -0
- package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +2 -0
- package/dist/web/assets/{isEmpty-B4kqZBtn.js → isEmpty-B9L-Ge-H.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-46yibrV5.js → ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-BcmRwjK-.js → journeyDiagram-4ABVD52K-CgDI-UG4.js} +1 -1
- package/dist/web/assets/{kanban-definition-K7BYSVSG-B619K53y.js → kanban-definition-K7BYSVSG-h4g10UHL.js} +1 -1
- package/dist/web/assets/keybindings-store-qVLDZz97.js +1 -0
- package/dist/web/assets/lib-BeaDXEkP.js +4 -0
- package/dist/web/assets/{line-1gcO63_w.js → line-B75-Rx70.js} +1 -1
- package/dist/web/assets/{linear-DfRqDoVd.js → linear-Bcjv9FQt.js} +1 -1
- package/dist/web/assets/markdown-renderer-L1NgC2Rw.js +69 -0
- package/dist/web/assets/{mermaid-parser.core-XtjZQOeM.js → mermaid-parser.core-8u2leTXI.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-CifOFo_q.js → mindmap-definition-YRQLILUH-BaOBwb-W.js} +1 -1
- package/dist/web/assets/{ordinal-BJYw-iDX.js → ordinal-LFEjVtwQ.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-BuHUh_fO.js → pieDiagram-SKSYHLDU-At5Kz0KK.js} +1 -1
- package/dist/web/assets/postgres-viewer-_uDispGW.js +1 -0
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-Bau_hj6Z.js → quadrantDiagram-337W2JSQ-CdjGIDfw.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +1 -0
- package/dist/web/assets/react-dom-Bpkvzu3U.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-Cq2b-uwp.js → requirementDiagram-Z7DCOOCP-B9F_Cx_p.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DrdGQxWQ.js → sankeyDiagram-WA2Y5GQK-RolPi8bU.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-qPxiTUcS.js → sequenceDiagram-2WXFIKYE-DM-tMAhx.js} +1 -1
- package/dist/web/assets/settings-tab-Bp4041i6.js +1 -0
- package/dist/web/assets/sqlite-viewer-GW-QCjHn.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-Dulj2oa8.js → stateDiagram-RAJIS63D-C4EMl6jf.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +1 -0
- package/dist/web/assets/tab-store--SlERlDs.js +1 -0
- package/dist/web/assets/{terminal-tab-wKgpSPAT.js → terminal-tab-E4cWujj4.js} +2 -2
- package/dist/web/assets/{timeline-definition-YZTLITO2-BWyDnCYq.js → timeline-definition-YZTLITO2-A4PN_Efm.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +1 -0
- package/dist/web/assets/use-monaco-theme-zABXAAla.js +11 -0
- package/dist/web/assets/{vennDiagram-LZ73GAT5-B9Iv2bNV.js → vennDiagram-LZ73GAT5-ywK7LMaH.js} +1 -1
- package/dist/web/assets/{xychartDiagram-JWTSCODW-ChXcMzBQ.js → xychartDiagram-JWTSCODW-DylHYNtJ.js} +1 -1
- package/dist/web/index.html +11 -11
- package/dist/web/sw.js +1 -1
- package/docs/code-standards.md +386 -6
- package/docs/codebase-summary.md +270 -98
- package/docs/design-guidelines.md +21 -0
- package/docs/project-changelog.md +150 -1
- package/docs/project-roadmap.md +41 -19
- package/docs/system-architecture.md +363 -15
- package/package.json +3 -2
- package/src/cli/commands/autostart.ts +1 -1
- package/src/cli/commands/restart.ts +9 -1
- package/src/cli/commands/status.ts +19 -0
- package/src/index.ts +2 -3
- package/src/providers/claude-agent-sdk.ts +316 -107
- package/src/providers/cli-provider-base.ts +238 -0
- package/src/providers/cursor-cli/cursor-event-mapper.ts +85 -0
- package/src/providers/cursor-cli/cursor-history.ts +207 -0
- package/src/providers/cursor-cli/cursor-provider.ts +146 -0
- package/src/providers/mock-provider.ts +7 -2
- package/src/providers/provider.interface.ts +1 -0
- package/src/providers/registry.ts +43 -4
- package/src/server/index.ts +44 -166
- package/src/server/routes/browser-preview.ts +159 -0
- package/src/server/routes/chat.ts +66 -6
- package/src/server/routes/mcp.ts +84 -0
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/proxy.ts +46 -53
- package/src/server/routes/settings.ts +14 -0
- package/src/server/routes/tunnel.ts +0 -32
- package/src/server/routes/workspace.ts +35 -0
- package/src/server/ws/chat.ts +302 -195
- package/src/services/account-selector.service.ts +16 -8
- package/src/services/account.service.ts +19 -13
- package/src/services/chat.service.ts +10 -15
- package/src/services/claude-usage.service.ts +48 -11
- package/src/services/cloud-ws.service.ts +227 -0
- package/src/services/cloud.service.ts +10 -6
- package/src/services/db.service.ts +119 -6
- package/src/services/mcp-config.service.ts +102 -0
- package/src/services/proxy.service.ts +4 -19
- package/src/services/supervisor.ts +285 -25
- package/src/types/api.ts +10 -2
- package/src/types/chat.ts +25 -2
- package/src/types/config.ts +33 -11
- package/src/types/mcp.ts +47 -0
- package/src/utils/ndjson-line-parser.ts +36 -0
- package/src/web/app.tsx +41 -35
- package/src/web/components/browser/browser-tab.tsx +106 -97
- package/src/web/components/chat/account-rotation-settings.tsx +163 -0
- package/src/web/components/chat/chat-history-bar.tsx +116 -31
- package/src/web/components/chat/chat-tab.tsx +31 -10
- package/src/web/components/chat/chat-welcome.tsx +148 -0
- package/src/web/components/chat/message-input.tsx +169 -16
- package/src/web/components/chat/message-list.tsx +27 -15
- package/src/web/components/chat/provider-selector.tsx +150 -0
- package/src/web/components/chat/session-picker.tsx +80 -31
- package/src/web/components/chat/usage-badge.tsx +11 -1
- package/src/web/components/editor/code-editor.tsx +36 -26
- package/src/web/components/editor/csv-preview.tsx +228 -0
- package/src/web/components/editor/editor-breadcrumb.tsx +216 -0
- package/src/web/components/editor/editor-toolbar.tsx +74 -0
- package/src/web/components/layout/command-palette.tsx +3 -1
- package/src/web/components/layout/editor-panel.tsx +162 -18
- package/src/web/components/layout/panel-layout.tsx +17 -1
- package/src/web/components/settings/ai-settings-section.tsx +196 -137
- package/src/web/components/settings/mcp-server-dialog.tsx +208 -0
- package/src/web/components/settings/mcp-settings-section.tsx +143 -0
- package/src/web/components/settings/proxy-settings-section.tsx +40 -42
- package/src/web/components/settings/settings-tab.tsx +5 -2
- package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
- package/src/web/hooks/use-chat.ts +234 -207
- package/src/web/hooks/use-global-keybindings.ts +25 -2
- package/src/web/hooks/use-server-reload.ts +9 -0
- package/src/web/hooks/use-url-sync.ts +173 -21
- package/src/web/hooks/use-voice-input.ts +111 -0
- package/src/web/lib/api-mcp.ts +38 -0
- package/src/web/lib/csv-parser.ts +134 -0
- package/src/web/stores/connection-store.ts +39 -0
- package/src/web/stores/keybindings-store.ts +1 -0
- package/src/web/stores/panel-store.ts +73 -19
- package/src/web/stores/panel-utils.ts +145 -3
- package/dist/web/assets/api-settings-CFw-lh5k.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-CJupe6q_.js +0 -1
- package/dist/web/assets/browser-tab-CmsL5eny.js +0 -1
- package/dist/web/assets/channel-DmKoFTd_.js +0 -1
- package/dist/web/assets/chat-tab-CFWsf13Z.js +0 -7
- package/dist/web/assets/chunk-GLR3WWYH-BnP-hOp6.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-DKDPTPEZ.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-C_wpI9wz.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-B1T5uY-F.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-xs5vI3xC.js +0 -1
- package/dist/web/assets/clone-CijCFRT5.js +0 -1
- package/dist/web/assets/code-editor-H_dAh_fJ.js +0 -1
- package/dist/web/assets/database-viewer-DBzsgEJ8.js +0 -1
- package/dist/web/assets/diff-viewer-DzS-OnAR.js +0 -4
- package/dist/web/assets/dist-0Va_2L7G.js +0 -16
- package/dist/web/assets/dist-D9irYETY.js +0 -41
- package/dist/web/assets/git-graph-D3C7F8o3.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-B0KvGQG8.js +0 -1
- package/dist/web/assets/index-CIkjfera.js +0 -31
- package/dist/web/assets/index-WKLuYsBY.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-1uJ6_hCm.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-DLA5Q-3y.js +0 -2
- package/dist/web/assets/input-CGp1nFIg.js +0 -1
- package/dist/web/assets/keybindings-store-BdaoLwSo.js +0 -1
- package/dist/web/assets/markdown-renderer-DH49Zag7.js +0 -69
- package/dist/web/assets/packet-RMMSAZCW-34C4o9yj.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-D9ekKlh9.js +0 -1
- package/dist/web/assets/postgres-viewer-B9FYk8sD.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-DEuXOXSD.js +0 -1
- package/dist/web/assets/settings-store-DWXGVHsE.js +0 -2
- package/dist/web/assets/settings-tab-D-q8pd-5.js +0 -1
- package/dist/web/assets/sqlite-viewer-CDqcTePw.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-CAkzLlhk.js +0 -1
- package/dist/web/assets/tab-store-BPeiymiH.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-nc7a1Ia1.js +0 -1
- package/dist/web/assets/use-monaco-theme-CCBTQ0S3.js +0 -11
- package/src/services/port-tunnel.service.ts +0 -97
- /package/dist/web/assets/{api-client-DOElml5u.js → api-client-BKIT_Qeg.js} +0 -0
- /package/dist/web/assets/{array-CYkMkqnU.js → array-DqLCdDFv.js} +0 -0
- /package/dist/web/assets/{columns-2-ChOTgl3e.js → columns-2-DbesTfa7.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-HeHO0VhB.js → cytoscape.esm-CWPXKqbJ.js} +0 -0
- /package/dist/web/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-CrJzLgRD.js} +0 -0
- /package/dist/web/assets/{dist-BUYzeuKe.js → dist-Cep75xXf.js} +0 -0
- /package/dist/web/assets/{init-Rr1s_RiX.js → init-C0r9Gk5G.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-BB-mzMLb.js → isArrayLikeObject-CGBoxvCD.js} +0 -0
- /package/dist/web/assets/{katex-CKoArbIw.js → katex-DzXRfQ_m.js} +0 -0
- /package/dist/web/assets/{math-B7b0HgJF.js → math-y9zN1W-N.js} +0 -0
- /package/dist/web/assets/{path-BAQ3hXlG.js → path-DIKpVbHL.js} +0 -0
- /package/dist/web/assets/{preload-helper-DeiOTZKJ.js → preload-helper-Bf_JiD2A.js} +0 -0
- /package/dist/web/assets/{react-Dev-wu-s.js → react-SKk5z-bm.js} +0 -0
- /package/dist/web/assets/{rough.esm-Dwml_la6.js → rough.esm-nHaDi0Kw.js} +0 -0
- /package/dist/web/assets/{src-B_cC68fH.js → src-Dw4QhedI.js} +0 -0
- /package/dist/web/assets/{table-COiJDPRA.js → table-CQVQM2SB.js} +0 -0
- /package/dist/web/assets/{tag-LMq02LfE.js → tag-Q2dZiSPX.js} +0 -0
- /package/dist/web/assets/{utils-btZ8C8-R.js → utils-DMiycH3O.js} +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { Plus, Download, Trash2, Pencil, Server } from "lucide-react";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import {
|
|
5
|
+
getMcpServers, deleteMcpServer, importMcpServers,
|
|
6
|
+
type McpServerEntry,
|
|
7
|
+
} from "@/lib/api-mcp";
|
|
8
|
+
import { McpServerDialog } from "./mcp-server-dialog";
|
|
9
|
+
|
|
10
|
+
export function McpSettingsSection() {
|
|
11
|
+
const [servers, setServers] = useState<McpServerEntry[]>([]);
|
|
12
|
+
const [loading, setLoading] = useState(true);
|
|
13
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
14
|
+
const [editingServer, setEditingServer] = useState<McpServerEntry | null>(null);
|
|
15
|
+
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
|
|
16
|
+
const [importing, setImporting] = useState(false);
|
|
17
|
+
const [importMsg, setImportMsg] = useState<string | null>(null);
|
|
18
|
+
|
|
19
|
+
const fetchServers = useCallback(async () => {
|
|
20
|
+
try {
|
|
21
|
+
const data = await getMcpServers();
|
|
22
|
+
setServers(data);
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error("Failed to load MCP servers:", e);
|
|
25
|
+
} finally {
|
|
26
|
+
setLoading(false);
|
|
27
|
+
}
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
useEffect(() => { fetchServers(); }, [fetchServers]);
|
|
31
|
+
|
|
32
|
+
const handleDelete = async (name: string) => {
|
|
33
|
+
try {
|
|
34
|
+
await deleteMcpServer(name);
|
|
35
|
+
setDeleteConfirm(null);
|
|
36
|
+
fetchServers();
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error("Failed to delete:", e);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleImport = async () => {
|
|
43
|
+
setImporting(true);
|
|
44
|
+
setImportMsg(null);
|
|
45
|
+
try {
|
|
46
|
+
const result = await importMcpServers();
|
|
47
|
+
setImportMsg(`Imported ${result.imported}, skipped ${result.skipped}`);
|
|
48
|
+
fetchServers();
|
|
49
|
+
} catch (e: any) {
|
|
50
|
+
setImportMsg(e.message || "Import failed");
|
|
51
|
+
} finally {
|
|
52
|
+
setImporting(false);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleDialogClose = (saved?: boolean) => {
|
|
57
|
+
setDialogOpen(false);
|
|
58
|
+
setEditingServer(null);
|
|
59
|
+
if (saved) fetchServers();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const openAdd = () => { setEditingServer(null); setDialogOpen(true); };
|
|
63
|
+
const openEdit = (s: McpServerEntry) => { setEditingServer(s); setDialogOpen(true); };
|
|
64
|
+
|
|
65
|
+
if (loading) {
|
|
66
|
+
return (
|
|
67
|
+
<div className="space-y-3">
|
|
68
|
+
{[1, 2, 3].map((i) => (
|
|
69
|
+
<div key={i} className="h-16 rounded-lg bg-muted animate-pulse" />
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="space-y-3">
|
|
77
|
+
{servers.length === 0 ? (
|
|
78
|
+
<div className="text-center py-8 space-y-2">
|
|
79
|
+
<Server className="size-8 mx-auto text-muted-foreground" />
|
|
80
|
+
<p className="text-xs text-muted-foreground">
|
|
81
|
+
No MCP servers configured. Add one or import from Claude Code.
|
|
82
|
+
</p>
|
|
83
|
+
</div>
|
|
84
|
+
) : (
|
|
85
|
+
<div className="space-y-2">
|
|
86
|
+
{servers.map((s) => (
|
|
87
|
+
<div key={s.name} className="rounded-lg border bg-card p-3 space-y-1.5">
|
|
88
|
+
<div className="flex items-center gap-2">
|
|
89
|
+
<span className="text-xs font-medium truncate flex-1">{s.name}</span>
|
|
90
|
+
<span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground shrink-0">
|
|
91
|
+
{s.transport}
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
<p className="text-[11px] text-muted-foreground truncate">
|
|
95
|
+
{serverPreview(s)}
|
|
96
|
+
</p>
|
|
97
|
+
<div className="flex justify-end gap-1.5">
|
|
98
|
+
{deleteConfirm === s.name ? (
|
|
99
|
+
<>
|
|
100
|
+
<span className="text-[11px] text-destructive self-center mr-1">Delete?</span>
|
|
101
|
+
<Button variant="destructive" size="sm" className="h-8 min-w-[44px] text-xs cursor-pointer" onClick={() => handleDelete(s.name)}>Yes</Button>
|
|
102
|
+
<Button variant="outline" size="sm" className="h-8 min-w-[44px] text-xs cursor-pointer" onClick={() => setDeleteConfirm(null)}>No</Button>
|
|
103
|
+
</>
|
|
104
|
+
) : (
|
|
105
|
+
<>
|
|
106
|
+
<Button variant="ghost" size="sm" className="h-7 text-xs gap-1 cursor-pointer" onClick={() => openEdit(s)}>
|
|
107
|
+
<Pencil className="size-3" /> Edit
|
|
108
|
+
</Button>
|
|
109
|
+
<Button variant="ghost" size="sm" className="h-7 text-xs gap-1 text-destructive hover:text-destructive cursor-pointer" onClick={() => setDeleteConfirm(s.name)}>
|
|
110
|
+
<Trash2 className="size-3" /> Delete
|
|
111
|
+
</Button>
|
|
112
|
+
</>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
{/* Actions — thumb zone */}
|
|
121
|
+
<div className="space-y-2 pt-1">
|
|
122
|
+
<Button className="w-full h-10 text-xs gap-1.5 cursor-pointer" onClick={openAdd}>
|
|
123
|
+
<Plus className="size-3.5" /> Add MCP Server
|
|
124
|
+
</Button>
|
|
125
|
+
<Button variant="outline" className="w-full h-10 text-xs gap-1.5 cursor-pointer" onClick={handleImport} disabled={importing}>
|
|
126
|
+
<Download className="size-3.5" /> {importing ? "Importing..." : "Import from Claude Code"}
|
|
127
|
+
</Button>
|
|
128
|
+
{importMsg && <p className="text-[11px] text-muted-foreground text-center">{importMsg}</p>}
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<McpServerDialog open={dialogOpen} onClose={handleDialogClose} editServer={editingServer} />
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function serverPreview(s: McpServerEntry): string {
|
|
137
|
+
const c = s.config;
|
|
138
|
+
if (s.transport === "stdio" || !("url" in c)) {
|
|
139
|
+
const stdio = c as { command?: string; args?: string[] };
|
|
140
|
+
return [stdio.command, ...(stdio.args ?? [])].join(" ");
|
|
141
|
+
}
|
|
142
|
+
return (c as { url?: string }).url ?? "";
|
|
143
|
+
}
|
|
@@ -127,78 +127,76 @@ export function ProxySettingsSection() {
|
|
|
127
127
|
<div className="space-y-2 rounded-md border p-3 bg-muted/30">
|
|
128
128
|
<h4 className="text-[11px] font-medium">Connection Info</h4>
|
|
129
129
|
|
|
130
|
-
{/*
|
|
130
|
+
{/* Local endpoint */}
|
|
131
131
|
<div className="space-y-1">
|
|
132
|
-
<Label className="text-[10px] text-muted-foreground">
|
|
132
|
+
<Label className="text-[10px] text-muted-foreground">Local Endpoint</Label>
|
|
133
133
|
<div className="flex gap-1.5 items-center">
|
|
134
134
|
<code className="text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate">
|
|
135
|
-
{
|
|
136
|
-
? `${settings.tunnelUrl}/proxy`
|
|
137
|
-
: `${window.location.origin}/proxy`}
|
|
135
|
+
{`${window.location.origin}/proxy/v1/messages`}
|
|
138
136
|
</code>
|
|
139
137
|
<Button
|
|
140
138
|
variant="ghost"
|
|
141
139
|
size="sm"
|
|
142
140
|
className="h-6 px-1.5 cursor-pointer shrink-0"
|
|
143
|
-
onClick={() => copyToClipboard(
|
|
144
|
-
hasTunnel && settings.tunnelUrl
|
|
145
|
-
? `${settings.tunnelUrl}/proxy`
|
|
146
|
-
: `${window.location.origin}/proxy`,
|
|
147
|
-
"baseurl",
|
|
148
|
-
)}
|
|
141
|
+
onClick={() => copyToClipboard(`${window.location.origin}/proxy/v1/messages`, "local")}
|
|
149
142
|
>
|
|
150
|
-
{copied === "
|
|
143
|
+
{copied === "local" ? "Copied!" : <Copy className="size-3" />}
|
|
151
144
|
</Button>
|
|
152
145
|
</div>
|
|
153
146
|
</div>
|
|
154
147
|
|
|
148
|
+
{/* Tunnel endpoint */}
|
|
149
|
+
{hasTunnel && settings.proxyEndpoint && (
|
|
150
|
+
<div className="space-y-1">
|
|
151
|
+
<Label className="text-[10px] text-muted-foreground">Public Endpoint (Tunnel)</Label>
|
|
152
|
+
<div className="flex gap-1.5 items-center">
|
|
153
|
+
<code className="text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate">
|
|
154
|
+
{settings.proxyEndpoint}
|
|
155
|
+
</code>
|
|
156
|
+
<Button
|
|
157
|
+
variant="ghost"
|
|
158
|
+
size="sm"
|
|
159
|
+
className="h-6 px-1.5 cursor-pointer shrink-0"
|
|
160
|
+
onClick={() => copyToClipboard(settings.proxyEndpoint!, "tunnel")}
|
|
161
|
+
>
|
|
162
|
+
{copied === "tunnel" ? "Copied!" : <Copy className="size-3" />}
|
|
163
|
+
</Button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
|
|
155
168
|
{!hasTunnel && (
|
|
156
169
|
<p className="text-[10px] text-muted-foreground">
|
|
157
170
|
Start a Cloudflare tunnel (Share) to get a public URL.
|
|
158
171
|
</p>
|
|
159
172
|
)}
|
|
160
173
|
|
|
161
|
-
{/*
|
|
162
|
-
<div className="space-y-1 pt-1">
|
|
163
|
-
<Label className="text-[10px] text-muted-foreground">Claude Code CLI</Label>
|
|
164
|
-
<div className="relative">
|
|
165
|
-
<pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">
|
|
166
|
-
{`ANTHROPIC_BASE_URL=${hasTunnel && settings.tunnelUrl ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"} \\
|
|
167
|
-
ANTHROPIC_API_KEY=${settings.authKey} \\
|
|
168
|
-
claude`}
|
|
169
|
-
</pre>
|
|
170
|
-
<Button
|
|
171
|
-
variant="ghost"
|
|
172
|
-
size="sm"
|
|
173
|
-
className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
|
|
174
|
-
onClick={() => copyToClipboard(
|
|
175
|
-
`ANTHROPIC_BASE_URL=${hasTunnel && settings.tunnelUrl ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"} ANTHROPIC_API_KEY=${settings.authKey} claude`,
|
|
176
|
-
"claude-cli",
|
|
177
|
-
)}
|
|
178
|
-
>
|
|
179
|
-
{copied === "claude-cli" ? "Copied!" : <Copy className="size-2.5" />}
|
|
180
|
-
</Button>
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
|
|
184
|
-
{/* Generic env vars */}
|
|
174
|
+
{/* Usage example */}
|
|
185
175
|
<div className="space-y-1 pt-1">
|
|
186
|
-
<Label className="text-[10px] text-muted-foreground">
|
|
176
|
+
<Label className="text-[10px] text-muted-foreground">Usage Example</Label>
|
|
187
177
|
<div className="relative">
|
|
188
178
|
<pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">
|
|
189
|
-
{
|
|
190
|
-
|
|
179
|
+
{`# Set as base URL in your tool
|
|
180
|
+
ANTHROPIC_BASE_URL=${hasTunnel && settings.proxyEndpoint ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"}
|
|
181
|
+
ANTHROPIC_API_KEY=${settings.authKey}
|
|
182
|
+
|
|
183
|
+
# Or use curl
|
|
184
|
+
curl ${hasTunnel && settings.proxyEndpoint ? settings.proxyEndpoint : window.location.origin + "/proxy/v1/messages"} \\
|
|
185
|
+
-H "x-api-key: ${settings.authKey}" \\
|
|
186
|
+
-H "content-type: application/json" \\
|
|
187
|
+
-H "anthropic-version: 2023-06-01" \\
|
|
188
|
+
-d '{"model":"claude-sonnet-4-6","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'`}
|
|
191
189
|
</pre>
|
|
192
190
|
<Button
|
|
193
191
|
variant="ghost"
|
|
194
192
|
size="sm"
|
|
195
193
|
className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
|
|
196
194
|
onClick={() => copyToClipboard(
|
|
197
|
-
`
|
|
198
|
-
"
|
|
195
|
+
`ANTHROPIC_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"}\nANTHROPIC_API_KEY=${settings.authKey}`,
|
|
196
|
+
"example",
|
|
199
197
|
)}
|
|
200
198
|
>
|
|
201
|
-
{copied === "
|
|
199
|
+
{copied === "example" ? "Copied!" : <Copy className="size-2.5" />}
|
|
202
200
|
</Button>
|
|
203
201
|
</div>
|
|
204
202
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useCallback, useRef } from "react";
|
|
2
2
|
import {
|
|
3
3
|
Moon, Sun, Monitor, Bell, BellOff, Check, ChevronRight, ArrowLeft,
|
|
4
|
-
Bot, BellRing, Keyboard, Globe,
|
|
4
|
+
Bot, BellRing, Keyboard, Globe, Plug,
|
|
5
5
|
} from "lucide-react";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import { Input } from "@/components/ui/input";
|
|
@@ -13,6 +13,7 @@ import { AISettingsSection } from "./ai-settings-section";
|
|
|
13
13
|
import { KeyboardShortcutsSection } from "./keyboard-shortcuts-section";
|
|
14
14
|
import { TelegramSettingsSection } from "./telegram-settings-section";
|
|
15
15
|
import { ProxySettingsSection } from "./proxy-settings-section";
|
|
16
|
+
import { McpSettingsSection } from "./mcp-settings-section";
|
|
16
17
|
import { usePushNotification } from "@/hooks/use-push-notification";
|
|
17
18
|
|
|
18
19
|
const THEME_OPTIONS: { value: Theme; label: string; icon: React.ElementType }[] = [
|
|
@@ -25,13 +26,14 @@ const pushSupported = "PushManager" in window && "serviceWorker" in navigator;
|
|
|
25
26
|
const isIosNonPwa = /iPhone|iPad/.test(navigator.userAgent) &&
|
|
26
27
|
!window.matchMedia("(display-mode: standalone)").matches;
|
|
27
28
|
|
|
28
|
-
type SettingsCategory = "ai" | "notifications" | "proxy" | "shortcuts";
|
|
29
|
+
type SettingsCategory = "ai" | "notifications" | "proxy" | "shortcuts" | "mcp";
|
|
29
30
|
|
|
30
31
|
const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; icon: React.ElementType }[] = [
|
|
31
32
|
{ value: "ai", label: "AI Provider", subtitle: "Model, execution mode, limits", icon: Bot },
|
|
32
33
|
{ value: "notifications", label: "Notifications", subtitle: "Push & Telegram alerts", icon: BellRing },
|
|
33
34
|
{ value: "proxy", label: "API Proxy", subtitle: "Expose accounts as Anthropic API", icon: Globe },
|
|
34
35
|
{ value: "shortcuts", label: "Keyboard Shortcuts", subtitle: "Customize key bindings", icon: Keyboard },
|
|
36
|
+
{ value: "mcp", label: "MCP Servers", subtitle: "Model Context Protocol tools", icon: Plug },
|
|
35
37
|
];
|
|
36
38
|
|
|
37
39
|
export function SettingsTab() {
|
|
@@ -84,6 +86,7 @@ export function SettingsTab() {
|
|
|
84
86
|
{activeCategory === "notifications" && <NotificationsContent isSubscribed={isSubscribed} loading={loading} permission={permission} pushError={pushError} subscribe={subscribe} unsubscribe={unsubscribe} />}
|
|
85
87
|
{activeCategory === "proxy" && <ProxySettingsSection />}
|
|
86
88
|
{activeCategory === "shortcuts" && <KeyboardShortcutsSection />}
|
|
89
|
+
{activeCategory === "mcp" && <McpSettingsSection />}
|
|
87
90
|
</div>
|
|
88
91
|
</ScrollArea>
|
|
89
92
|
</div>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { WifiOff, ServerOff, RefreshCw } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { useConnectionStore } from "@/stores/connection-store";
|
|
4
|
+
|
|
5
|
+
const CLOUD_URL = "https://ppm.hienle.tech";
|
|
6
|
+
|
|
7
|
+
function isTunnelDomain(): boolean {
|
|
8
|
+
return window.location.hostname.endsWith(".trycloudflare.com");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ConnectionLostOverlay() {
|
|
12
|
+
const showOverlay = useConnectionStore((s) => s.showOverlay);
|
|
13
|
+
const [retrying, setRetrying] = useState(false);
|
|
14
|
+
|
|
15
|
+
if (!showOverlay) return null;
|
|
16
|
+
|
|
17
|
+
const isTunnel = isTunnelDomain();
|
|
18
|
+
|
|
19
|
+
async function handleRetry() {
|
|
20
|
+
setRetrying(true);
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch("/api/health", { cache: "no-store" });
|
|
23
|
+
if (res.ok) {
|
|
24
|
+
useConnectionStore.getState().markUp();
|
|
25
|
+
if ("caches" in window) {
|
|
26
|
+
const keys = await caches.keys();
|
|
27
|
+
await Promise.all(keys.map((k) => caches.delete(k)));
|
|
28
|
+
}
|
|
29
|
+
window.location.reload();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// still down
|
|
34
|
+
}
|
|
35
|
+
setRetrying(false);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const Icon = isTunnel ? WifiOff : ServerOff;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="fixed inset-0 z-[200] bg-background/95 backdrop-blur-sm flex items-center justify-center p-4">
|
|
42
|
+
<div className="max-w-sm w-full text-center space-y-6">
|
|
43
|
+
<div className="flex justify-center">
|
|
44
|
+
<div className="rounded-full bg-destructive/10 p-4">
|
|
45
|
+
<Icon className="h-10 w-10 text-destructive" />
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div className="space-y-2">
|
|
50
|
+
<h2 className="text-xl font-semibold text-foreground">
|
|
51
|
+
{isTunnel ? "Connection Lost" : "Server Unreachable"}
|
|
52
|
+
</h2>
|
|
53
|
+
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
54
|
+
{isTunnel
|
|
55
|
+
? "The tunnel appears to have closed. The server may have restarted with a new URL."
|
|
56
|
+
: "Cannot connect to the PPM server. It may have stopped or is restarting."}
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="flex flex-col gap-3">
|
|
61
|
+
{isTunnel && (
|
|
62
|
+
<a
|
|
63
|
+
href={CLOUD_URL}
|
|
64
|
+
target="_blank"
|
|
65
|
+
rel="noopener noreferrer"
|
|
66
|
+
className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
67
|
+
>
|
|
68
|
+
Open PPM Cloud
|
|
69
|
+
</a>
|
|
70
|
+
)}
|
|
71
|
+
<button
|
|
72
|
+
onClick={handleRetry}
|
|
73
|
+
disabled={retrying}
|
|
74
|
+
className="inline-flex items-center justify-center gap-2 rounded-md border border-border px-4 py-2.5 text-sm font-medium text-foreground hover:bg-accent transition-colors disabled:opacity-50"
|
|
75
|
+
>
|
|
76
|
+
<RefreshCw className={`h-4 w-4 ${retrying ? "animate-spin" : ""}`} />
|
|
77
|
+
{retrying ? "Retrying…" : "Retry Connection"}
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
{!isTunnel && (
|
|
82
|
+
<p className="text-xs text-muted-foreground">
|
|
83
|
+
If the server was stopped, run <code className="bg-muted px-1 py-0.5 rounded text-[11px]">ppm start</code> to restart it.
|
|
84
|
+
</p>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|