@hienlh/ppm 0.9.65 → 0.9.67

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.
Files changed (173) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/web/assets/{_basePickBy-3Xe18azI.js → _basePickBy-5PGDJbfF.js} +1 -1
  3. package/dist/web/assets/{_baseUniq-Yy35llnn.js → _baseUniq-BT4Ow4Kk.js} +1 -1
  4. package/dist/web/assets/{api-settings-CgBII8jW.js → api-settings-Bn-bIxD1.js} +1 -1
  5. package/dist/web/assets/{arc-B9n1Gvb5.js → arc-BAOivWpI.js} +1 -1
  6. package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
  7. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DqAZP_F6.js → architectureDiagram-2XIMDMQ5-Z-4eN4za.js} +1 -1
  8. package/dist/web/assets/{blockDiagram-WCTKOSBZ-h3cDF2vI.js → blockDiagram-WCTKOSBZ-BCLqzhuZ.js} +1 -1
  9. package/dist/web/assets/{c4Diagram-IC4MRINW--pF1r5lr.js → c4Diagram-IC4MRINW-0Vp0Jeas.js} +1 -1
  10. package/dist/web/assets/channel-By7bn0Yq.js +1 -0
  11. package/dist/web/assets/chat-tab-onkz52iv.js +10 -0
  12. package/dist/web/assets/{chunk-4BX2VUAB-C3aZvW7B.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
  13. package/dist/web/assets/{chunk-55IACEB6-D5cABeB9.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
  14. package/dist/web/assets/{chunk-7E7YKBS2-CkFGv6Zs.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
  15. package/dist/web/assets/{chunk-7R4GIKGN-Dvbyu4Zw.js → chunk-7R4GIKGN-Dv-4cAYn.js} +2 -2
  16. package/dist/web/assets/{chunk-C72U2L5F-CtqKiH4q.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
  17. package/dist/web/assets/{chunk-EGIJ26TM-Cpr87sBR.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
  18. package/dist/web/assets/{chunk-FMBD7UC4-D23YVTOU.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
  19. package/dist/web/assets/{chunk-GEFDOKGD-tDjHsAUs.js → chunk-GEFDOKGD-D-pKjlVd.js} +1 -1
  20. package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +2 -0
  21. package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +1 -0
  22. package/dist/web/assets/{chunk-JSJVCQXG-BBmymCjA.js → chunk-JSJVCQXG-99JzIdPr.js} +1 -1
  23. package/dist/web/assets/{chunk-KX2RTZJC-DP36BDiU.js → chunk-KX2RTZJC-CRq1OBZv.js} +1 -1
  24. package/dist/web/assets/{chunk-KYZI473N-Djw13C-3.js → chunk-KYZI473N-Bb0MCaIO.js} +1 -1
  25. package/dist/web/assets/{chunk-L3YUKLVL-HG_eMj_C.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
  26. package/dist/web/assets/{chunk-MX3YWQON-C2UEioMs.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
  27. package/dist/web/assets/{chunk-NQ4KR5QH-DXUTQ-BL.js → chunk-NQ4KR5QH-z_blpjxi.js} +1 -1
  28. package/dist/web/assets/{chunk-O4XLMI2P-BsUWb9d0.js → chunk-O4XLMI2P-nDhi_cVu.js} +1 -1
  29. package/dist/web/assets/{chunk-OZEHJAEY-rG0P22U9.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
  30. package/dist/web/assets/{chunk-PQ6SQG4A-DX0xW7kO.js → chunk-PQ6SQG4A-TF58UVMU.js} +1 -1
  31. package/dist/web/assets/{chunk-PU5JKC2W-C7Gry6md.js → chunk-PU5JKC2W-ek7k4QVB.js} +1 -1
  32. package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
  33. package/dist/web/assets/{chunk-R5LLSJPH-CMY0PkRK.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
  34. package/dist/web/assets/{chunk-WL4C6EOR-CXuQvlyu.js → chunk-WL4C6EOR-ByUrSRin.js} +1 -1
  35. package/dist/web/assets/{chunk-XIRO2GV7-DRJEb7Zb.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
  36. package/dist/web/assets/{chunk-XPW4576I-BPEX8KhL.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
  37. package/dist/web/assets/{chunk-XZSTWKYB-Cb0iqycX.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
  38. package/dist/web/assets/{chunk-YBOYWFTD-av5aeHLq.js → chunk-YBOYWFTD-rQG3QH5s.js} +1 -1
  39. package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +1 -0
  40. package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +1 -0
  41. package/dist/web/assets/clone-LRxlvnMj.js +1 -0
  42. package/dist/web/assets/code-editor-BixOXePn.js +8 -0
  43. package/dist/web/assets/{cose-bilkent-S5V4N54A-qudEiMCT.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
  44. package/dist/web/assets/csv-parser-CNNw2RVA.js +6 -0
  45. package/dist/web/assets/{csv-preview-sx6DC51G.js → csv-preview-D2pJJj3K.js} +3 -8
  46. package/dist/web/assets/{dagre-BFcnKyBF.js → dagre-DHq9bhnd.js} +1 -1
  47. package/dist/web/assets/{dagre-KLK3FWXG-C3O-MTLf.js → dagre-KLK3FWXG-BdJr7Byp.js} +1 -1
  48. package/dist/web/assets/database-viewer-DfLe8ewt.js +2 -0
  49. package/dist/web/assets/{diagram-E7M64L7V-DxPjK7_c.js → diagram-E7M64L7V-_db4pBVA.js} +1 -1
  50. package/dist/web/assets/{diagram-IFDJBPK2-sqTog_XV.js → diagram-IFDJBPK2-xKoeuiJx.js} +1 -1
  51. package/dist/web/assets/{diagram-P4PSJMXO-hzmp0GHK.js → diagram-P4PSJMXO-C8tjJsev.js} +1 -1
  52. package/dist/web/assets/diff-viewer-eFO08m_L.js +4 -0
  53. package/dist/web/assets/dist-DIV6WgAG.js +41 -0
  54. package/dist/web/assets/{erDiagram-INFDFZHY-DLeYhAAT.js → erDiagram-INFDFZHY-BSh2z9Df.js} +1 -1
  55. package/dist/web/assets/{extension-webview-2XjXXtyy.js → extension-webview-4CL9kCKR.js} +1 -1
  56. package/dist/web/assets/{flowDiagram-PKNHOUZH-CRxlE9Sr.js → flowDiagram-PKNHOUZH-oYaovqyp.js} +1 -1
  57. package/dist/web/assets/{ganttDiagram-A5KZAMGK-BdjmoMLS.js → ganttDiagram-A5KZAMGK-DmL26q2P.js} +1 -1
  58. package/dist/web/assets/git-graph-BqeE_o17.js +1 -0
  59. package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
  60. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js → gitGraphDiagram-K3NZZRJ6-CMoukSrY.js} +1 -1
  61. package/dist/web/assets/{graphlib-Duh_bWLa.js → graphlib-BcsNnGcW.js} +1 -1
  62. package/dist/web/assets/index-BWLy2h18.css +2 -0
  63. package/dist/web/assets/index-DwrCg0TN.js +30 -0
  64. package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
  65. package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +2 -0
  66. package/dist/web/assets/{isEmpty-B9L-Ge-H.js → isEmpty-bnrF3Qbc.js} +1 -1
  67. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js → ishikawaDiagram-PHBUUO56-D05_LyL7.js} +1 -1
  68. package/dist/web/assets/{journeyDiagram-4ABVD52K-CgDI-UG4.js → journeyDiagram-4ABVD52K-B_L20qMe.js} +1 -1
  69. package/dist/web/assets/{kanban-definition-K7BYSVSG-h4g10UHL.js → kanban-definition-K7BYSVSG-CZ535BbZ.js} +1 -1
  70. package/dist/web/assets/keybindings-store-BNBONtSd.js +1 -0
  71. package/dist/web/assets/{line-B75-Rx70.js → line-CVvo3dRu.js} +1 -1
  72. package/dist/web/assets/{linear-Bcjv9FQt.js → linear-DP4mkX3m.js} +1 -1
  73. package/dist/web/assets/{markdown-renderer-Bb7OSpxF.js → markdown-renderer-BUqab2os.js} +5 -5
  74. package/dist/web/assets/{mermaid-parser.core-8u2leTXI.js → mermaid-parser.core-C7UwoIh6.js} +2 -2
  75. package/dist/web/assets/{mindmap-definition-YRQLILUH-BaOBwb-W.js → mindmap-definition-YRQLILUH-x0MTutJp.js} +1 -1
  76. package/dist/web/assets/{ordinal-LFEjVtwQ.js → ordinal-_K3x1fkz.js} +1 -1
  77. package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
  78. package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
  79. package/dist/web/assets/{pieDiagram-SKSYHLDU-At5Kz0KK.js → pieDiagram-SKSYHLDU-C1Gjrtzy.js} +1 -1
  80. package/dist/web/assets/{port-forwarding-tab-bD8MKumH.js → port-forwarding-tab-CfO-UJ84.js} +1 -1
  81. package/dist/web/assets/postgres-viewer-BVJZ44eU.js +13 -0
  82. package/dist/web/assets/{quadrantDiagram-337W2JSQ-CdjGIDfw.js → quadrantDiagram-337W2JSQ-C8bzJCjQ.js} +1 -1
  83. package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
  84. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-B9F_Cx_p.js → requirementDiagram-Z7DCOOCP-pQyah6WB.js} +1 -1
  85. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-RolPi8bU.js → sankeyDiagram-WA2Y5GQK-T6RgG-N8.js} +1 -1
  86. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-DM-tMAhx.js → sequenceDiagram-2WXFIKYE-BQDJ4CVs.js} +1 -1
  87. package/dist/web/assets/settings-tab-C6hdJujW.js +1 -0
  88. package/dist/web/assets/sql-completion-provider-DM9Qov6L.js +1 -0
  89. package/dist/web/assets/sql-query-editor-OhZa4Z9F.js +3 -0
  90. package/dist/web/assets/sqlite-viewer-C8p1_jz4.js +1 -0
  91. package/dist/web/assets/{stateDiagram-RAJIS63D-C4EMl6jf.js → stateDiagram-RAJIS63D-66vhiIuk.js} +1 -1
  92. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +1 -0
  93. package/dist/web/assets/{terminal-tab-Cq6vQ9W9.js → terminal-tab-CaO0WnIo.js} +2 -2
  94. package/dist/web/assets/text-wrap-BWNOVswA.js +1 -0
  95. package/dist/web/assets/{timeline-definition-YZTLITO2-A4PN_Efm.js → timeline-definition-YZTLITO2-DwZqB3nn.js} +1 -1
  96. package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
  97. package/dist/web/assets/use-monaco-theme-U9ZhfvHB.js +11 -0
  98. package/dist/web/assets/{vennDiagram-LZ73GAT5-ywK7LMaH.js → vennDiagram-LZ73GAT5-s9Z71fz-.js} +1 -1
  99. package/dist/web/assets/x-D2_KzIET.js +1 -0
  100. package/dist/web/assets/{xychartDiagram-JWTSCODW-DylHYNtJ.js → xychartDiagram-JWTSCODW-DRa_TH4B.js} +1 -1
  101. package/dist/web/index.html +9 -8
  102. package/dist/web/sw.js +1 -1
  103. package/package.json +1 -1
  104. package/src/cli/commands/bot-cmd.ts +4 -0
  105. package/src/cli/commands/db-cmd.ts +4 -3
  106. package/src/server/routes/database.ts +126 -9
  107. package/src/services/cloud.service.ts +2 -1
  108. package/src/services/database/sqlite-adapter.ts +1 -0
  109. package/src/services/db.service.ts +42 -3
  110. package/src/services/postgres.service.ts +37 -6
  111. package/src/services/sqlite.service.ts +18 -4
  112. package/src/services/table-cache.service.ts +2 -3
  113. package/src/types/database.ts +2 -0
  114. package/src/web/components/database/connection-form-dialog.tsx +17 -8
  115. package/src/web/components/database/connection-list.tsx +191 -139
  116. package/src/web/components/database/data-grid.tsx +634 -0
  117. package/src/web/components/database/database-sidebar.tsx +4 -1
  118. package/src/web/components/database/database-viewer.tsx +204 -225
  119. package/src/web/components/database/export-button.tsx +100 -0
  120. package/src/web/components/database/sql-completion-provider.ts +301 -0
  121. package/src/web/components/database/sql-query-editor.tsx +123 -0
  122. package/src/web/components/database/use-connections.ts +21 -1
  123. package/src/web/components/database/use-database.ts +59 -7
  124. package/src/web/components/editor/code-editor.tsx +224 -16
  125. package/src/web/components/sqlite/sqlite-query-editor.tsx +3 -90
  126. package/src/web/components/sqlite/sqlite-viewer.tsx +0 -2
  127. package/src/web/components/sqlite/use-sqlite.ts +1 -1
  128. package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +0 -1
  129. package/dist/web/assets/channel-C2fMafck.js +0 -1
  130. package/dist/web/assets/chat-tab-CfdMDCBK.js +0 -10
  131. package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +0 -2
  132. package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +0 -1
  133. package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +0 -1
  134. package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +0 -1
  135. package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +0 -1
  136. package/dist/web/assets/clone-B2hUek6n.js +0 -1
  137. package/dist/web/assets/code-editor-DhCfJIpG.js +0 -2
  138. package/dist/web/assets/database-viewer-DLkAUBpm.js +0 -1
  139. package/dist/web/assets/diff-viewer-DWVWsekJ.js +0 -4
  140. package/dist/web/assets/dist-C40JmyoH.js +0 -13
  141. package/dist/web/assets/dist-DRTW9IWi.js +0 -41
  142. package/dist/web/assets/git-graph-Ds5bs1cM.js +0 -1
  143. package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +0 -1
  144. package/dist/web/assets/index-8b0LM6IC.js +0 -30
  145. package/dist/web/assets/index-BnMECpW3.css +0 -2
  146. package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +0 -1
  147. package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +0 -2
  148. package/dist/web/assets/keybindings-store-C06Z0Zhk.js +0 -1
  149. package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +0 -1
  150. package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +0 -1
  151. package/dist/web/assets/postgres-viewer-C3OND65T.js +0 -1
  152. package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +0 -1
  153. package/dist/web/assets/settings-tab-cuMIkUNV.js +0 -1
  154. package/dist/web/assets/sqlite-viewer-CpjnwDtk.js +0 -1
  155. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +0 -1
  156. package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +0 -1
  157. package/dist/web/assets/use-monaco-theme-BH9sQ-Yu.js +0 -11
  158. /package/dist/web/assets/{api-client-BKIT_Qeg.js → api-client-BfBM3I7n.js} +0 -0
  159. /package/dist/web/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  160. /package/dist/web/assets/{chevron-right-5HgK6l7K.js → chevron-right-4zq1jPv6.js} +0 -0
  161. /package/dist/web/assets/{columns-2-cEVJHYd7.js → columns-2-BoZAN-iw.js} +0 -0
  162. /package/dist/web/assets/{cytoscape.esm-CWPXKqbJ.js → cytoscape.esm-BW-DbntU.js} +0 -0
  163. /package/dist/web/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  164. /package/dist/web/assets/{dist-Cep75xXf.js → dist-CSJdAyA9.js} +0 -0
  165. /package/dist/web/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  166. /package/dist/web/assets/{isArrayLikeObject-CGBoxvCD.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
  167. /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
  168. /package/dist/web/assets/{lib-mag4ySk-.js → lib-DurwGtQO.js} +0 -0
  169. /package/dist/web/assets/{math-y9zN1W-N.js → math-069Z4SuC.js} +0 -0
  170. /package/dist/web/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  171. /package/dist/web/assets/{rough.esm-nHaDi0Kw.js → rough.esm-JX0wREDd.js} +0 -0
  172. /package/dist/web/assets/{src-Dw4QhedI.js → src-BqX54PbV.js} +0 -0
  173. /package/dist/web/assets/{utils-DMiycH3O.js → utils-BNytJOb1.js} +0 -0
@@ -1,4 +1,4 @@
1
- import { useEffect, useState, useCallback, useRef } from "react";
1
+ import { useEffect, useState, useCallback, useRef, useMemo } from "react";
2
2
  import Editor, { type OnMount } from "@monaco-editor/react";
3
3
  import type * as MonacoType from "monaco-editor";
4
4
  import { MarkdownRenderer } from "@/components/shared/markdown-renderer";
@@ -7,10 +7,12 @@ import { useTabStore } from "@/stores/tab-store";
7
7
  import { useSettingsStore } from "@/stores/settings-store";
8
8
  import { basename } from "@/lib/utils";
9
9
  import { useMonacoTheme } from "@/lib/use-monaco-theme";
10
- import { Loader2, FileWarning, ExternalLink } from "lucide-react";
10
+ import { Loader2, FileWarning, ExternalLink, Play, Database } from "lucide-react";
11
11
  import { EditorBreadcrumb } from "./editor-breadcrumb";
12
12
  import { EditorToolbar } from "./editor-toolbar";
13
13
  import { lazy, Suspense } from "react";
14
+ import { createSqlCompletionProvider, clearCompletionCache, type SchemaInfo } from "../database/sql-completion-provider";
15
+ import { useConnections, type Connection } from "../database/use-connections";
14
16
 
15
17
  const CsvPreview = lazy(() => import("./csv-preview").then((m) => ({ default: m.CsvPreview })));
16
18
 
@@ -33,6 +35,7 @@ function getMonacoLanguage(filename: string): string {
33
35
  json: "json", md: "markdown", mdx: "markdown",
34
36
  yaml: "yaml", yml: "yaml",
35
37
  sh: "shell", bash: "shell",
38
+ sql: "sql",
36
39
  };
37
40
  return map[ext] ?? "plaintext";
38
41
  }
@@ -45,7 +48,10 @@ interface CodeEditorProps {
45
48
  export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
46
49
  const filePath = metadata?.filePath as string | undefined;
47
50
  const projectName = metadata?.projectName as string | undefined;
48
- const [content, setContent] = useState<string | null>(null);
51
+ // Inline content mode: read-only Monaco with pre-loaded content (e.g. cell viewer)
52
+ const inlineContent = metadata?.inlineContent as string | undefined;
53
+ const inlineLanguage = metadata?.inlineLanguage as string | undefined;
54
+ const [content, setContent] = useState<string | null>(inlineContent ?? null);
49
55
  const [encoding, setEncoding] = useState<string>("utf-8");
50
56
  const [loading, setLoading] = useState(true);
51
57
  const [error, setError] = useState<string | null>(null);
@@ -64,9 +70,85 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
64
70
  const isSqlite = SQLITE_EXTS.has(ext);
65
71
  const isMarkdown = ext === "md" || ext === "mdx";
66
72
  const isCsv = ext === "csv";
73
+ const isSql = ext === "sql";
67
74
  const [mdMode, setMdMode] = useState<"edit" | "preview">("preview");
68
75
  const [csvMode, setCsvMode] = useState<"table" | "raw">("table");
69
76
 
77
+ // SQL file: connection picker + autocomplete + run in DB viewer
78
+ const { connections, cachedTables, refreshTables } = useConnections();
79
+ const [sqlConnId, setSqlConnId] = useState<number | null>(() => {
80
+ if (!isSql || !filePath) return null;
81
+ const stored = localStorage.getItem(`ppm:sql-conn:${filePath}`);
82
+ return stored ? Number(stored) : null;
83
+ });
84
+ const monacoInstanceRef = useRef<typeof MonacoType | null>(null);
85
+ const completionDisposable = useRef<MonacoType.IDisposable | null>(null);
86
+
87
+ const selectedSqlConn = useMemo(() => connections.find((c) => c.id === sqlConnId) ?? null, [connections, sqlConnId]);
88
+
89
+ // Persist selected connection per file
90
+ const handleSqlConnChange = useCallback((connId: number) => {
91
+ setSqlConnId(connId);
92
+ if (filePath) localStorage.setItem(`ppm:sql-conn:${filePath}`, String(connId));
93
+ // Refresh tables for autocomplete
94
+ refreshTables(connId).catch(() => {});
95
+ }, [filePath, refreshTables]);
96
+
97
+ // Build SchemaInfo for .sql file autocomplete
98
+ const sqlSchemaInfo = useMemo<SchemaInfo | undefined>(() => {
99
+ if (!isSql || !sqlConnId) return undefined;
100
+ const tables = (cachedTables.get(sqlConnId) ?? []).map((t) => ({ name: t.tableName, schema: t.schemaName }));
101
+ if (tables.length === 0) return undefined;
102
+ return {
103
+ tables,
104
+ getColumns: async (table: string, schema?: string) => {
105
+ return api.get<{ name: string; type: string }[]>(
106
+ `/api/db/connections/${sqlConnId}/schema?table=${encodeURIComponent(table)}${schema ? `&schema=${encodeURIComponent(schema)}` : ""}`,
107
+ );
108
+ },
109
+ };
110
+ }, [isSql, sqlConnId, cachedTables]);
111
+
112
+ // Register/dispose completion provider when connection changes
113
+ useEffect(() => {
114
+ if (!monacoInstanceRef.current || !sqlSchemaInfo) return;
115
+ completionDisposable.current?.dispose();
116
+ clearCompletionCache();
117
+ completionDisposable.current = monacoInstanceRef.current.languages.registerCompletionItemProvider(
118
+ "sql",
119
+ createSqlCompletionProvider(monacoInstanceRef.current, sqlSchemaInfo),
120
+ );
121
+ return () => { completionDisposable.current?.dispose(); };
122
+ }, [sqlSchemaInfo]);
123
+
124
+ // Run in DB Viewer
125
+ const openTab = useTabStore((s) => s.openTab);
126
+ const runSqlInViewer = useCallback((sqlText: string) => {
127
+ if (!selectedSqlConn) return;
128
+ openTab({
129
+ type: "database",
130
+ title: `${selectedSqlConn.name} · Query`,
131
+ projectId: null,
132
+ closable: true,
133
+ metadata: { connectionId: selectedSqlConn.id, connectionName: selectedSqlConn.name, dbType: selectedSqlConn.type, initialSql: sqlText },
134
+ });
135
+ }, [selectedSqlConn, openTab]);
136
+
137
+ const handleRunInDbViewer = useCallback(() => {
138
+ if (!editorRef.current || !selectedSqlConn) return;
139
+ const editor = editorRef.current;
140
+ const selection = editor.getSelection();
141
+ const sqlText = selection && !selection.isEmpty()
142
+ ? editor.getModel()?.getValueInRange(selection) ?? editor.getValue()
143
+ : editor.getValue();
144
+ runSqlInViewer(sqlText);
145
+ }, [selectedSqlConn, runSqlInViewer]);
146
+
147
+ // CodeLens: inline Run buttons between SQL statements
148
+ const codeLensDisposable = useRef<MonacoType.IDisposable[]>([]);
149
+ const runSqlRef = useRef(runSqlInViewer);
150
+ runSqlRef.current = runSqlInViewer;
151
+
70
152
  // Redirect .db files to sqlite viewer by changing tab type
71
153
  useEffect(() => {
72
154
  if (isSqlite && tabId) updateTab(tabId, { type: "sqlite" });
@@ -77,6 +159,7 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
77
159
 
78
160
  // Load file content
79
161
  useEffect(() => {
162
+ if (inlineContent != null) { setLoading(false); return; }
80
163
  if (!filePath) return;
81
164
  if (!isExternalFile && !projectName) return;
82
165
  if (isImage || isPdf) { setLoading(false); return; }
@@ -141,33 +224,84 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
141
224
  const lineNumber = metadata?.lineNumber as number | undefined;
142
225
  const handleEditorMount: OnMount = useCallback((editor, monaco) => {
143
226
  editorRef.current = editor;
227
+ monacoInstanceRef.current = monaco;
144
228
  if (lineNumber && lineNumber > 0) {
145
- // Defer until content is rendered
146
229
  setTimeout(() => {
147
230
  editor.revealLineInCenter(lineNumber);
148
231
  editor.setPosition({ lineNumber, column: 1 });
149
232
  editor.focus();
150
233
  }, 100);
151
234
  }
152
- // Alt+Z → toggle word wrap
153
235
  editor.addCommand(
154
236
  monaco.KeyMod.Alt | monaco.KeyCode.KeyZ,
155
237
  () => useSettingsStore.getState().toggleWordWrap(),
156
238
  );
157
- // Disable all diagnostics — PPM is a lightweight editor, not a full IDE
158
239
  monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
159
- noSemanticValidation: true,
160
- noSyntaxValidation: true,
161
- noSuggestionDiagnostics: true,
240
+ noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,
162
241
  });
163
242
  monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
164
- noSemanticValidation: true,
165
- noSyntaxValidation: true,
166
- noSuggestionDiagnostics: true,
243
+ noSemanticValidation: true, noSyntaxValidation: true, noSuggestionDiagnostics: true,
167
244
  });
168
- }, []);
245
+ // Register SQL completion if schema available
246
+ if (sqlSchemaInfo) {
247
+ completionDisposable.current?.dispose();
248
+ completionDisposable.current = monaco.languages.registerCompletionItemProvider(
249
+ "sql", createSqlCompletionProvider(monaco, sqlSchemaInfo),
250
+ );
251
+ }
252
+
253
+ // Register CodeLens for inline Run buttons on .sql files
254
+ if (isSql) {
255
+ codeLensDisposable.current.forEach((d) => d.dispose());
256
+ codeLensDisposable.current = [];
257
+
258
+ const cmdId = editor.addCommand(0, (_accessor: unknown, sql: string) => {
259
+ if (sql) runSqlRef.current(sql);
260
+ });
261
+
262
+ if (cmdId) {
263
+ const provider = monaco.languages.registerCodeLensProvider("sql", {
264
+ provideCodeLenses: (model: MonacoType.editor.ITextModel) => {
265
+ const lenses: MonacoType.languages.CodeLens[] = [];
266
+ const lines = model.getValue().split("\n");
267
+ let stmtStartLine = -1;
268
+ let stmtLines: string[] = [];
269
+
270
+ const addLens = (line: number, stmt: string) => {
271
+ const trimmed = stmt.trim();
272
+ if (!trimmed || trimmed.startsWith("--")) return;
273
+ lenses.push({
274
+ range: { startLineNumber: line, startColumn: 1, endLineNumber: line, endColumn: 1 },
275
+ command: { id: cmdId, title: "\u25B7 Run", arguments: [trimmed] },
276
+ });
277
+ };
278
+
279
+ for (let i = 0; i < lines.length; i++) {
280
+ const trimmed = lines[i]!.trim();
281
+ if (stmtStartLine === -1) {
282
+ if (!trimmed || trimmed.startsWith("--")) continue;
283
+ stmtStartLine = i + 1;
284
+ stmtLines = [];
285
+ }
286
+ stmtLines.push(lines[i]!);
287
+ if (trimmed.endsWith(";")) {
288
+ addLens(stmtStartLine, stmtLines.join("\n"));
289
+ stmtStartLine = -1;
290
+ stmtLines = [];
291
+ }
292
+ }
293
+ if (stmtStartLine > 0 && stmtLines.join("").trim()) {
294
+ addLens(stmtStartLine, stmtLines.join("\n"));
295
+ }
296
+ return { lenses, dispose: () => {} };
297
+ },
298
+ });
299
+ codeLensDisposable.current.push(provider);
300
+ }
301
+ }
302
+ }, [sqlSchemaInfo]); // eslint-disable-line react-hooks/exhaustive-deps
169
303
 
170
- if (!filePath || (!isExternalFile && !projectName)) {
304
+ if (!inlineContent && (!filePath || (!isExternalFile && !projectName))) {
171
305
  return (
172
306
  <div className="flex items-center justify-center h-full text-text-secondary text-sm">
173
307
  No file selected.
@@ -203,8 +337,72 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
203
337
  );
204
338
  }
205
339
 
340
+ /** SQL connection picker bar (shared between breadcrumb and standalone) */
341
+ const sqlPickerBar = isSql ? (
342
+ <div className="shrink-0 flex items-center gap-1 px-2 border-l border-border">
343
+ <Database className="size-3 text-muted-foreground" />
344
+ <select
345
+ value={sqlConnId ?? ""}
346
+ onChange={(e) => { const v = Number(e.target.value); if (v) handleSqlConnChange(v); }}
347
+ className="h-5 text-[10px] bg-transparent border border-border rounded px-1 text-foreground outline-none max-w-[140px]"
348
+ title="Select connection for autocomplete"
349
+ >
350
+ <option value="">Connection…</option>
351
+ {connections.map((c) => <option key={c.id} value={c.id}>{c.name}</option>)}
352
+ </select>
353
+ <button
354
+ type="button"
355
+ onClick={handleRunInDbViewer}
356
+ disabled={!selectedSqlConn}
357
+ className="p-0.5 rounded text-muted-foreground hover:text-primary disabled:opacity-30 transition-colors"
358
+ title="Run all in DB Viewer"
359
+ >
360
+ <Play className="size-3.5" />
361
+ </button>
362
+ </div>
363
+ ) : null;
364
+
365
+ // Beautify for inline content
366
+ const canBeautifyInline = inlineContent != null && (inlineLanguage === "json" || inlineLanguage === "xml");
367
+ const [isBeautified, setIsBeautified] = useState(false);
368
+ const handleBeautifyInline = useCallback(() => {
369
+ if (!inlineContent) return;
370
+ if (isBeautified) {
371
+ setContent(inlineContent);
372
+ setIsBeautified(false);
373
+ } else {
374
+ const trimmed = inlineContent.trimStart();
375
+ if (inlineLanguage === "json") {
376
+ try { setContent(JSON.stringify(JSON.parse(trimmed), null, 2)); setIsBeautified(true); } catch { /* not valid */ }
377
+ } else if (inlineLanguage === "xml") {
378
+ let indent = 0;
379
+ const formatted = trimmed.replace(/(>)(<)(\/*)/g, "$1\n$2$3")
380
+ .split("\n")
381
+ .map((line) => {
382
+ const l = line.trim();
383
+ if (l.startsWith("</")) indent = Math.max(0, indent - 1);
384
+ const padded = " ".repeat(indent) + l;
385
+ if (l.startsWith("<") && !l.startsWith("</") && !l.endsWith("/>") && !l.includes("</")) indent++;
386
+ return padded;
387
+ })
388
+ .join("\n");
389
+ setContent(formatted);
390
+ setIsBeautified(true);
391
+ }
392
+ }
393
+ }, [inlineContent, inlineLanguage, isBeautified]);
394
+
206
395
  return (
207
396
  <div className="flex flex-col h-full w-full overflow-hidden">
397
+ {/* Inline content toolbar (cell viewer mode) */}
398
+ {inlineContent != null && canBeautifyInline && (
399
+ <div className="flex items-center h-7 border-b border-border bg-background shrink-0 px-2 gap-2">
400
+ <button type="button" onClick={handleBeautifyInline}
401
+ className="text-[10px] px-2 py-0.5 rounded border border-border hover:bg-muted transition-colors text-foreground">
402
+ {isBeautified ? "Raw" : "Beautify"}
403
+ </button>
404
+ </div>
405
+ )}
208
406
  {/* Breadcrumb + Toolbar bar — desktop only */}
209
407
  {filePath && projectName && tabId && (
210
408
  <div className="hidden md:flex items-center h-7 border-b border-border bg-background shrink-0">
@@ -214,6 +412,7 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
214
412
  tabId={tabId}
215
413
  className="flex items-center flex-1 min-w-0 overflow-x-auto scrollbar-none px-2 gap-0.5"
216
414
  />
415
+ {sqlPickerBar}
217
416
  <EditorToolbar
218
417
  ext={ext}
219
418
  mdMode={mdMode}
@@ -229,6 +428,14 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
229
428
  </div>
230
429
  )}
231
430
 
431
+ {/* Standalone SQL toolbar for external files (no breadcrumb available) */}
432
+ {isSql && (!projectName || !tabId) && (
433
+ <div className="hidden md:flex items-center h-7 border-b border-border bg-background shrink-0 px-2">
434
+ <span className="text-xs text-muted-foreground truncate flex-1">{filePath ? basename(filePath) : "SQL"}</span>
435
+ {sqlPickerBar}
436
+ </div>
437
+ )}
438
+
232
439
  {/* Content area */}
233
440
  {isCsv && csvMode === "table" ? (
234
441
  <Suspense fallback={<div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>}>
@@ -240,9 +447,9 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
240
447
  <div className="flex-1 overflow-hidden">
241
448
  <Editor
242
449
  height="100%"
243
- language={getMonacoLanguage(filePath)}
450
+ language={inlineLanguage ?? getMonacoLanguage(filePath ?? "")}
244
451
  value={content ?? ""}
245
- onChange={handleChange}
452
+ onChange={inlineContent != null ? undefined : handleChange}
246
453
  onMount={handleEditorMount}
247
454
  theme={monacoTheme}
248
455
  options={{
@@ -255,6 +462,7 @@ export function CodeEditor({ metadata, tabId }: CodeEditorProps) {
255
462
  lineNumbers: "on",
256
463
  folding: true,
257
464
  bracketPairColorization: { enabled: true },
465
+ readOnly: inlineContent != null,
258
466
  }}
259
467
  loading={<Loader2 className="size-5 animate-spin text-text-subtle" />}
260
468
  />
@@ -1,97 +1,10 @@
1
- import { useState, useCallback } from "react";
2
- import CodeMirror from "@uiw/react-codemirror";
3
- import { sql, SQLite } from "@codemirror/lang-sql";
4
- import { Play, Loader2 } from "lucide-react";
5
- import type { QueryResult } from "./use-sqlite";
1
+ import { SqlQueryEditor } from "../database/sql-query-editor";
6
2
 
7
3
  interface Props {
8
4
  onExecute: (sql: string) => void;
9
- result: QueryResult | null;
10
- error: string | null;
11
5
  loading: boolean;
12
6
  }
13
7
 
14
- export function SqliteQueryEditor({ onExecute, result, error, loading }: Props) {
15
- const [query, setQuery] = useState("SELECT * FROM ");
16
-
17
- const handleExecute = useCallback(() => {
18
- const trimmed = query.trim();
19
- if (!trimmed) return;
20
- onExecute(trimmed);
21
- }, [query, onExecute]);
22
-
23
- const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
24
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
25
- e.preventDefault();
26
- handleExecute();
27
- }
28
- }, [handleExecute]);
29
-
30
- return (
31
- <div className="flex flex-col h-full overflow-hidden">
32
- {/* Editor area */}
33
- <div className="flex items-start gap-1 border-b border-border bg-background" onKeyDown={handleKeyDown}>
34
- <div className="flex-1 max-h-[120px] overflow-auto">
35
- <CodeMirror
36
- value={query}
37
- onChange={setQuery}
38
- extensions={[sql({ dialect: SQLite })]}
39
- basicSetup={{ lineNumbers: false, foldGutter: false, highlightActiveLine: false }}
40
- className="text-xs [&_.cm-editor]:!outline-none [&_.cm-scroller]:!overflow-auto"
41
- />
42
- </div>
43
- <button
44
- type="button"
45
- onClick={handleExecute}
46
- disabled={loading}
47
- className="shrink-0 m-1 p-1.5 rounded bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors"
48
- title="Execute (Cmd+Enter)"
49
- >
50
- {loading ? <Loader2 className="size-3.5 animate-spin" /> : <Play className="size-3.5" />}
51
- </button>
52
- </div>
53
-
54
- {/* Results area */}
55
- <div className="flex-1 overflow-auto text-xs">
56
- {error && (
57
- <div className="px-3 py-2 text-destructive bg-destructive/5">{error}</div>
58
- )}
59
-
60
- {result && result.changeType === "modify" && (
61
- <div className="px-3 py-2 text-green-500">
62
- Query executed. {result.rowsAffected} row(s) affected.
63
- </div>
64
- )}
65
-
66
- {result && result.changeType === "select" && result.rows.length > 0 && (
67
- <table className="w-full border-collapse">
68
- <thead className="sticky top-0 bg-muted">
69
- <tr>
70
- {result.columns.map((col) => (
71
- <th key={col} className="px-2 py-1 text-left font-medium text-muted-foreground border-b border-border whitespace-nowrap">
72
- {col}
73
- </th>
74
- ))}
75
- </tr>
76
- </thead>
77
- <tbody>
78
- {result.rows.map((row, i) => (
79
- <tr key={i} className="hover:bg-muted/30 border-b border-border/50">
80
- {result.columns.map((col) => (
81
- <td key={col} className="px-2 py-1 max-w-[300px] truncate" title={row[col] == null ? "NULL" : String(row[col])}>
82
- {row[col] == null ? <span className="text-muted-foreground/40 italic">NULL</span> : String(row[col])}
83
- </td>
84
- ))}
85
- </tr>
86
- ))}
87
- </tbody>
88
- </table>
89
- )}
90
-
91
- {result && result.changeType === "select" && result.rows.length === 0 && (
92
- <div className="px-3 py-2 text-muted-foreground">No results</div>
93
- )}
94
- </div>
95
- </div>
96
- );
8
+ export function SqliteQueryEditor({ onExecute, loading }: Props) {
9
+ return <SqlQueryEditor onExecute={onExecute} loading={loading} />;
97
10
  }
@@ -136,8 +136,6 @@ function SqliteViewerInner({
136
136
  <div className="border-t border-border h-[40%] shrink-0">
137
137
  <SqliteQueryEditor
138
138
  onExecute={sqlite.executeQuery}
139
- result={sqlite.queryResult}
140
- error={sqlite.queryError}
141
139
  loading={sqlite.queryLoading}
142
140
  />
143
141
  </div>
@@ -3,7 +3,7 @@ import { api, projectUrl } from "@/lib/api-client";
3
3
 
4
4
  export interface TableInfo { name: string; rowCount: number }
5
5
  export interface ColumnInfo { cid: number; name: string; type: string; notnull: boolean; pk: boolean; dflt_value: string | null }
6
- export interface QueryResult { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: "select" | "modify" }
6
+ export interface QueryResult { columns: string[]; rows: Record<string, unknown>[]; rowsAffected: number; changeType: "select" | "modify"; executionTimeMs?: number }
7
7
  interface TableData { columns: string[]; rows: Record<string, unknown>[]; total: number; page: number; limit: number }
8
8
 
9
9
  export function useSqlite(projectName: string, dbPath: string, connectionId?: number) {
@@ -1 +0,0 @@
1
- import"./chunk-XZSTWKYB-Cb0iqycX.js";import{n as e}from"./chunk-R5LLSJPH-CMY0PkRK.js";export{e as createArchitectureServices};
@@ -1 +0,0 @@
1
- import{it as e,rt as t}from"./chunk-7R4GIKGN-Dvbyu4Zw.js";var n=(n,r)=>e.lang.round(t.parse(n)[r]);export{n as t};