@hienlh/ppm 0.9.5 → 0.9.7
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/.claude.bak/agent-memory/tester/MEMORY.md +3 -0
- package/.claude.bak/agent-memory/tester/project-ppm-test-conventions.md +32 -0
- package/CHANGELOG.md +17 -0
- package/bun.lock +129 -7
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-Z-4eN4za.js → architectureDiagram-2XIMDMQ5-DWBCPMLF.js} +1 -1
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-BCLqzhuZ.js → blockDiagram-WCTKOSBZ-TEF8Ally.js} +1 -1
- package/dist/web/assets/browser-tab-ZjPyWngv.js +1 -0
- package/dist/web/assets/{c4Diagram-IC4MRINW-0Vp0Jeas.js → c4Diagram-IC4MRINW-dV22iAsY.js} +1 -1
- package/dist/web/assets/channel-wrd-NHWf.js +1 -0
- package/dist/web/assets/chat-tab-DPFg4Mec.js +8 -0
- package/dist/web/assets/{chunk-7R4GIKGN-Dv-4cAYn.js → chunk-7R4GIKGN-BbIFzsIv.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-D-pKjlVd.js → chunk-GEFDOKGD-BbQkJu8C.js} +1 -1
- package/dist/web/assets/{chunk-GLR3WWYH-DKikpoJM.js → chunk-GLR3WWYH-CzYx4w-r.js} +2 -2
- package/dist/web/assets/{chunk-HHEYEP7N-C7vxA5i9.js → chunk-HHEYEP7N-HRhYy3kG.js} +1 -1
- package/dist/web/assets/{chunk-JSJVCQXG-99JzIdPr.js → chunk-JSJVCQXG-23tyvw8k.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-CRq1OBZv.js → chunk-KX2RTZJC-sQ0o-39C.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-Bb0MCaIO.js → chunk-KYZI473N-BcUZNnwd.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-z_blpjxi.js → chunk-NQ4KR5QH-wMgTlP7f.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-nDhi_cVu.js → chunk-O4XLMI2P-JC6EGoUz.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-TF58UVMU.js → chunk-PQ6SQG4A-D6BTbCQw.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-ek7k4QVB.js → chunk-PU5JKC2W-Dw8ClWch.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-ByUrSRin.js → chunk-WL4C6EOR-DfofndiH.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-rQG3QH5s.js → chunk-YBOYWFTD-CeU4Q-xC.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-lse8oZoJ.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-CxkwuInd.js +1 -0
- package/dist/web/assets/code-editor-B1T1uAc_.js +2 -0
- package/dist/web/assets/{csv-preview-ncSOnJSC.js → csv-preview-Doo6XsK0.js} +1 -1
- package/dist/web/assets/{dagre-DHq9bhnd.js → dagre-Dbb5k38K.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-BdJr7Byp.js → dagre-KLK3FWXG-BH7aWGRP.js} +1 -1
- package/dist/web/assets/database-viewer-ChNK7Hv_.js +1 -0
- package/dist/web/assets/{diagram-E7M64L7V-_db4pBVA.js → diagram-E7M64L7V-B1Qz70Do.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-xKoeuiJx.js → diagram-IFDJBPK2-k55eVqVU.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO-C8tjJsev.js → diagram-P4PSJMXO-BkfNRc9U.js} +1 -1
- package/dist/web/assets/{diff-viewer-CsPH6s_E.js → diff-viewer-B1YHbfur.js} +1 -1
- package/dist/web/assets/dist-C4urk5JP.js +41 -0
- package/dist/web/assets/{dist-ovWkrgO-.js → dist-wVfsYfHS.js} +1 -1
- package/dist/web/assets/{erDiagram-INFDFZHY-BSh2z9Df.js → erDiagram-INFDFZHY-CKzVujYI.js} +1 -1
- package/dist/web/assets/{extension-webview-DYuYgKhD.js → extension-webview-7d3Q0aIg.js} +1 -1
- package/dist/web/assets/{flowDiagram-PKNHOUZH-oYaovqyp.js → flowDiagram-PKNHOUZH-DIqcTrDV.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-DmL26q2P.js → ganttDiagram-A5KZAMGK-D4v7ZbVE.js} +1 -1
- package/dist/web/assets/git-graph-NaHViGO2.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-CMoukSrY.js → gitGraphDiagram-K3NZZRJ6-BTXo57mF.js} +1 -1
- package/dist/web/assets/{index-Dx21KBME.css → index-CjwJM7yL.css} +1 -1
- package/dist/web/assets/index-WGGO0qq9.js +37 -0
- package/dist/web/assets/{infoDiagram-LFFYTUFH-DWwumDkq.js → infoDiagram-LFFYTUFH-B1CX0pbC.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-D05_LyL7.js → ishikawaDiagram-PHBUUO56-BOyvKMmB.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-B_L20qMe.js → journeyDiagram-4ABVD52K-ufoasAy6.js} +1 -1
- package/dist/web/assets/{kanban-definition-K7BYSVSG-CZ535BbZ.js → kanban-definition-K7BYSVSG-Bi0UTUeN.js} +1 -1
- package/dist/web/assets/keybindings-store-Bus-WOTq.js +1 -0
- package/dist/web/assets/{line-CVvo3dRu.js → line-B78g-52T.js} +1 -1
- package/dist/web/assets/{markdown-renderer-C1hJEMtm.js → markdown-renderer-CWeCayPE.js} +5 -5
- package/dist/web/assets/{mermaid-parser.core-C7UwoIh6.js → mermaid-parser.core-DMIWdgEW.js} +1 -1
- package/dist/web/assets/{mindmap-definition-YRQLILUH-x0MTutJp.js → mindmap-definition-YRQLILUH-BsfWvIoO.js} +1 -1
- package/dist/web/assets/{pieDiagram-SKSYHLDU-C1Gjrtzy.js → pieDiagram-SKSYHLDU-WP0XXw51.js} +1 -1
- package/dist/web/assets/postgres-viewer-O_X63FPB.js +1 -0
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-C8bzJCjQ.js → quadrantDiagram-337W2JSQ-FHMogtsh.js} +1 -1
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-pQyah6WB.js → requirementDiagram-Z7DCOOCP-BatTxyWb.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-T6RgG-N8.js → sankeyDiagram-WA2Y5GQK-ClJuW3Hv.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-BQDJ4CVs.js → sequenceDiagram-2WXFIKYE-ByxQqGgs.js} +1 -1
- package/dist/web/assets/{settings-tab-C_jaua57.js → settings-tab-CQ5fOx4Z.js} +1 -1
- package/dist/web/assets/sqlite-viewer-BDw7xrqT.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-66vhiIuk.js → stateDiagram-RAJIS63D-f8opcZNY.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-DrxVDY9q.js +1 -0
- package/dist/web/assets/{tab-store-BOgTrqRr.js → tab-store-CeOacjuH.js} +1 -1
- package/dist/web/assets/{terminal-tab-COW3438-.js → terminal-tab-DHqCtUKE.js} +1 -1
- package/dist/web/assets/{timeline-definition-YZTLITO2-DwZqB3nn.js → timeline-definition-YZTLITO2-58BlOSf9.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DqA6WXo2.js → use-monaco-theme-C52E96dj.js} +1 -1
- package/dist/web/assets/{vennDiagram-LZ73GAT5-s9Z71fz-.js → vennDiagram-LZ73GAT5-BOSy9ma9.js} +1 -1
- package/dist/web/assets/{xychartDiagram-JWTSCODW-DRa_TH4B.js → xychartDiagram-JWTSCODW-z5MVJauZ.js} +1 -1
- package/dist/web/index.html +8 -8
- package/dist/web/sw.js +1 -1
- package/docs/design-guidelines.md +5 -2
- package/docs/project-changelog.md +31 -1
- package/docs/project-roadmap.md +4 -1
- package/package.json +3 -1
- package/src/server/helpers/error-status.ts +10 -0
- package/src/server/middleware/auth.ts +17 -6
- package/src/server/routes/file-download.ts +63 -0
- package/src/server/routes/files.ts +8 -17
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/services/download-token.service.ts +37 -0
- package/src/web/components/chat/chat-history-bar.tsx +29 -39
- package/src/web/components/chat/chat-tab.tsx +0 -1
- package/src/web/components/chat/chat-welcome.tsx +1 -1
- package/src/web/components/chat/message-list.tsx +1 -1
- package/src/web/components/chat/session-picker.tsx +2 -2
- package/src/web/components/database/connection-list.tsx +1 -1
- package/src/web/components/editor/code-editor.tsx +2 -0
- package/src/web/components/editor/editor-toolbar.tsx +14 -1
- package/src/web/components/explorer/file-tree.tsx +15 -0
- package/src/web/components/extensions/extension-tree-view.tsx +1 -1
- package/src/web/components/git/git-status-panel.tsx +1 -1
- package/src/web/components/layout/draggable-tab.tsx +1 -1
- package/src/web/components/layout/editor-panel.tsx +1 -1
- package/src/web/components/layout/panel-layout.tsx +1 -1
- package/src/web/components/projects/project-list.tsx +1 -1
- package/src/web/components/shared/markdown-renderer.tsx +1 -1
- package/src/web/lib/file-download.ts +27 -0
- package/src/web/styles/globals.css +3 -0
- package/dist/web/assets/browser-tab-D2Z_380K.js +0 -1
- package/dist/web/assets/channel-By7bn0Yq.js +0 -1
- package/dist/web/assets/chat-tab-DyCHqHNy.js +0 -8
- package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +0 -1
- package/dist/web/assets/code-editor-CLBY3U7l.js +0 -2
- package/dist/web/assets/database-viewer-BWGt4Ufn.js +0 -1
- package/dist/web/assets/dist-DIV6WgAG.js +0 -41
- package/dist/web/assets/git-graph-XDyQYPZk.js +0 -1
- package/dist/web/assets/index-BffsIdqe.js +0 -37
- package/dist/web/assets/keybindings-store-B7gFbUFf.js +0 -1
- package/dist/web/assets/postgres-viewer-BXJLyZp4.js +0 -1
- package/dist/web/assets/sqlite-viewer-BFdZBZgC.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +0 -1
- package/docs/streaming-input-guide.md +0 -267
- package/snapshot-state.md +0 -1526
- package/test-session-ops.mjs +0 -444
- package/test-tokens.mjs +0 -212
- /package/dist/web/assets/{jsx-runtime-kMwlnEGE.js → jsx-runtime-O3jQaKZd.js} +0 -0
- /package/dist/web/assets/{preload-helper-Bf_JiD2A.js → preload-helper-uTix4PVD.js} +0 -0
- /package/dist/web/assets/{react-SKk5z-bm.js → react-ER-4DN55.js} +0 -0
- /package/dist/web/assets/{table-DFevCOMd.js → table-CWv1Oy39.js} +0 -0
- /package/dist/web/assets/{tag-CXMT0QB6.js → tag-CFrhsWuM.js} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./react-nm2Ru1Pt.js";import"./api-client-BfBM3I7n.js";import{U as e}from"./index-BffsIdqe.js";export{e as useKeybindingsStore};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o as e}from"./chunk-CFjPhJqf.js";import{t}from"./react-nm2Ru1Pt.js";import{t as n}from"./chevron-right-5HgK6l7K.js";import{a as r,i,r as a,t as o}from"./dist-ovWkrgO-.js";import{t as s}from"./table-DFevCOMd.js";import{t as c}from"./jsx-runtime-kMwlnEGE.js";import{t as l}from"./api-client-BfBM3I7n.js";import{$ as u,Et as d,ct as f,wt as p,yt as m}from"./index-BffsIdqe.js";import{n as h,r as g,t as _}from"./lib-BQ34Db2e.js";var v=e(t(),1),y=`/api/postgres`;function b(e){let[t,n]=(0,v.useState)(``),[r,i]=(0,v.useState)(!1),a=e?`/api/db/connections/${e}`:null,[o,s]=(0,v.useState)([]),[c,u]=(0,v.useState)(null),[d,f]=(0,v.useState)(`public`),[p,m]=(0,v.useState)(null),[h,g]=(0,v.useState)([]),[_,b]=(0,v.useState)(!1),[x,S]=(0,v.useState)(null),[C,w]=(0,v.useState)(1),[T,E]=(0,v.useState)(null),[D,O]=(0,v.useState)(null),[k,A]=(0,v.useState)(!1),j=(0,v.useCallback)(async e=>{b(!0),S(null);try{let t=await l.post(`${y}/test`,{connectionString:e});if(!t.ok){S(t.error??`Connection failed`);return}n(e),i(!0);let r=await l.post(`${y}/tables`,{connectionString:e});s(r),r.length>0&&(u(r[0].name),f(r[0].schema))}catch(e){S(e.message)}finally{b(!1)}},[]),M=(0,v.useCallback)(async()=>{if(a){b(!0);try{s(await l.get(`${a}/tables?cached=1`))}catch(e){S(e.message)}finally{b(!1)}return}if(t){b(!0);try{s(await l.post(`${y}/tables`,{connectionString:t}))}catch(e){S(e.message)}finally{b(!1)}}},[a,t,c]);(0,v.useEffect)(()=>{a&&(i(!0),M())},[a]);let N=(0,v.useCallback)(async(e,n,r)=>{let i=e??c,o=n??d;if(i){b(!0);try{if(a){let[e,t]=await Promise.all([l.get(`${a}/data?table=${encodeURIComponent(i)}&schema=${o}&page=${r??C}&limit=100`),l.get(`${a}/schema?table=${encodeURIComponent(i)}&schema=${o}`)]);m(e),g(t)}else{if(!t)return;let[e,n]=await Promise.all([l.post(`${y}/data`,{connectionString:t,table:i,schema:o,page:r??C,limit:100}),l.post(`${y}/schema`,{connectionString:t,table:i,schema:o})]);m(e),g(n)}}catch(e){S(e.message)}finally{b(!1)}}},[a,t,c,d,C]);return{connectionString:t,connected:r,connect:j,tables:o,selectedTable:c,selectTable:(0,v.useCallback)((e,t=`public`)=>{u(e),f(t),w(1),E(null),N(e,t,1)},[N]),tableData:p,schema:h,loading:_,error:x,page:C,setPage:(0,v.useCallback)(e=>{w(e),N(void 0,void 0,e)},[N]),queryResult:T,queryError:D,queryLoading:k,executeQuery:(0,v.useCallback)(async e=>{if(!(!a&&!t)){A(!0),O(null);try{let n=a?await l.post(`${a}/query`,{sql:e}):await l.post(`${y}/query`,{connectionString:t,sql:e});E(n),n.changeType===`modify`&&N()}catch(e){O(e.message)}finally{A(!1)}}},[a,t,N]),updateCell:(0,v.useCallback)(async(e,n,r,i)=>{if(c)try{if(a)await l.put(`${a}/cell`,{table:c,schema:d,pkColumn:e,pkValue:n,column:r,value:i});else{if(!t)return;await l.post(`${y}/cell`,{connectionString:t,table:c,schema:d,pkColumn:e,pkValue:n,column:r,value:i})}N()}catch(e){S(e.message)}},[a,t,c,d,N]),refreshTables:M,refreshData:N}}var x=c();function S({metadata:e}){let t=e?.connectionString??``,n=e?.connectionId,r=b(n);return r.connected?(0,x.jsx)(w,{pg:r,initialTable:e?.tableName,hideTableList:!!n,connectionName:e?.connectionName}):(0,x.jsx)(C,{initialValue:t,onConnect:r.connect,loading:r.loading,error:r.error})}function C({initialValue:e,onConnect:t,loading:n,error:r}){let[i,a]=(0,v.useState)(e);return(0,x.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,x.jsxs)(`div`,{className:`flex flex-col gap-3 w-full max-w-lg px-4`,children:[(0,x.jsxs)(`div`,{className:`flex items-center gap-2 text-sm font-medium`,children:[(0,x.jsx)(m,{className:`size-4`}),` Connect to PostgreSQL`]}),(0,x.jsx)(`input`,{className:`w-full px-3 py-2 rounded border border-border bg-background text-sm font-mono outline-none focus:border-primary`,placeholder:`postgresql://user:pass@host:5432/db`,value:i,onChange:e=>a(e.target.value),onKeyDown:e=>{e.key===`Enter`&&i.trim()&&t(i.trim())}}),r&&(0,x.jsxs)(`p`,{className:`text-xs text-destructive flex items-center gap-1`,children:[(0,x.jsx)(p,{className:`size-3`}),r]}),(0,x.jsx)(`button`,{type:`button`,disabled:n||!i.trim(),onClick:()=>t(i.trim()),className:`px-4 py-2 rounded bg-primary text-primary-foreground text-sm hover:bg-primary/90 disabled:opacity-50 transition-colors`,children:n?(0,x.jsx)(f,{className:`size-4 animate-spin mx-auto`}):`Connect`})]})})}function w({pg:e,initialTable:t,hideTableList:n,connectionName:r}){let[i,a]=(0,v.useState)(!1),o=(0,v.useRef)(!1);return(0,v.useEffect)(()=>{if(!(!t||o.current)){if(n&&e.connected)o.current=!0,e.selectTable(t);else if(e.tables.length>0){let n=e.tables.find(e=>e.name===t);n&&(o.current=!0,e.selectTable(n.name,n.schema))}}},[t,e.connected,e.tables]),(0,x.jsxs)(`div`,{className:`flex h-full w-full overflow-hidden`,children:[!n&&(0,x.jsxs)(`div`,{className:`w-48 shrink-0 flex flex-col bg-background overflow-hidden`,children:[(0,x.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-2 border-b border-border`,children:[(0,x.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground uppercase tracking-wider`,children:`Tables`}),(0,x.jsx)(`button`,{type:`button`,onClick:e.refreshTables,className:`text-muted-foreground hover:text-foreground transition-colors`,title:`Refresh`,children:(0,x.jsx)(u,{className:`size-3`})})]}),(0,x.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[e.tables.map(t=>(0,x.jsxs)(`button`,{type:`button`,onClick:()=>e.selectTable(t.name,t.schema),className:`w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors ${e.selectedTable===t.name?`bg-muted text-foreground`:`text-muted-foreground hover:bg-muted/50 hover:text-foreground`}`,children:[(0,x.jsx)(s,{className:`size-3 shrink-0`}),(0,x.jsxs)(`span`,{className:`truncate flex-1`,children:[t.schema===`public`?``:`${t.schema}.`,t.name]}),(0,x.jsx)(`span`,{className:`text-[10px] opacity-60`,children:t.rowCount})]},`${t.schema}.${t.name}`)),e.tables.length===0&&(0,x.jsx)(`p`,{className:`px-3 py-4 text-xs text-muted-foreground text-center`,children:`No tables found`})]})]}),(0,x.jsxs)(`div`,{className:`flex-1 flex flex-col overflow-hidden ${n?``:`border-l border-border`}`,children:[(0,x.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,x.jsx)(m,{className:`size-3.5 text-muted-foreground`}),(0,x.jsx)(`span`,{className:`text-xs text-muted-foreground truncate`,children:r??`PostgreSQL`}),e.selectedTable&&(0,x.jsxs)(`span`,{className:`text-xs text-muted-foreground`,children:[`/ `,e.selectedTable]}),(0,x.jsx)(`div`,{className:`ml-auto`,children:(0,x.jsx)(`button`,{type:`button`,onClick:()=>a(e=>!e),className:`px-2 py-1 rounded text-xs transition-colors ${i?`bg-muted text-foreground`:`text-muted-foreground hover:text-foreground`}`,children:`SQL`})})]}),(0,x.jsx)(`div`,{className:`flex-1 overflow-hidden ${i?`max-h-[60%]`:``}`,children:(0,x.jsx)(T,{tableData:e.tableData,schema:e.schema,loading:e.loading,page:e.page,onPageChange:e.setPage,onCellUpdate:e.updateCell})}),i&&(0,x.jsx)(`div`,{className:`border-t border-border h-[40%] shrink-0`,children:(0,x.jsx)(E,{onExecute:e.executeQuery,result:e.queryResult,error:e.queryError,loading:e.queryLoading})})]})]})}function T({tableData:e,schema:t,loading:r,page:i,onPageChange:a,onCellUpdate:o}){let[s,c]=(0,v.useState)(null),[l,u]=(0,v.useState)(``),p=(0,v.useMemo)(()=>t.find(e=>e.pk)?.name??null,[t]),m=(0,v.useCallback)((e,t,n)=>{c({rowIdx:e,col:t}),u(n==null?``:String(n))},[]),y=(0,v.useCallback)(()=>{if(!s||!e||!p)return;let t=e.rows[s.rowIdx];if(!t)return;let n=t[s.col];String(n??``)!==l&&o(p,t[p],s.col,l===``?null:l),c(null)},[s,l,e,p,o]),b=(0,v.useCallback)(()=>c(null),[]),S=(0,v.useMemo)(()=>(e?.columns??[]).map(e=>({id:e,accessorFn:t=>t[e],header:()=>(0,x.jsx)(`span`,{className:t.find(t=>t.name===e)?.pk?`font-bold`:``,children:e}),cell:({row:t,getValue:n})=>{let r=s?.rowIdx===t.index&&s?.col===e,i=n();return r?(0,x.jsx)(`input`,{autoFocus:!0,className:`w-full bg-transparent border border-primary/50 rounded px-1 py-0 text-xs outline-none`,value:l,onChange:e=>u(e.target.value),onBlur:y,onKeyDown:e=>{e.key===`Enter`&&y(),e.key===`Escape`&&b()}}):(0,x.jsx)(`span`,{className:`cursor-pointer truncate block ${i==null?`text-muted-foreground/40 italic`:``}`,onDoubleClick:()=>p&&m(t.index,e,i),title:i==null?`NULL`:String(i),children:i==null?`NULL`:String(i)})}})),[e?.columns,t,s,l,y,b,m,p]),C=h({data:e?.rows??[],columns:S,getCoreRowModel:g()});if(!e)return(0,x.jsx)(`div`,{className:`flex items-center justify-center h-full text-xs text-muted-foreground`,children:r?(0,x.jsx)(f,{className:`size-4 animate-spin`}):`Select a table`});let w=Math.ceil(e.total/e.limit)||1;return(0,x.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,x.jsx)(`div`,{className:`flex-1 overflow-auto`,children:(0,x.jsxs)(`table`,{className:`w-full text-xs border-collapse`,children:[(0,x.jsx)(`thead`,{className:`sticky top-0 z-10 bg-muted`,children:C.getHeaderGroups().map(e=>(0,x.jsx)(`tr`,{children:e.headers.map(e=>(0,x.jsx)(`th`,{className:`px-2 py-1.5 text-left font-medium text-muted-foreground border-b border-border whitespace-nowrap`,children:_(e.column.columnDef.header,e.getContext())},e.id))},e.id))}),(0,x.jsxs)(`tbody`,{children:[C.getRowModel().rows.map(e=>(0,x.jsx)(`tr`,{className:`hover:bg-muted/30 border-b border-border/50`,children:e.getVisibleCells().map(e=>(0,x.jsx)(`td`,{className:`px-2 py-1 max-w-[300px]`,children:_(e.column.columnDef.cell,e.getContext())},e.id))},e.id)),e.rows.length===0&&(0,x.jsx)(`tr`,{children:(0,x.jsx)(`td`,{colSpan:e.columns.length,className:`px-2 py-8 text-center text-muted-foreground`,children:`No data`})})]})]})}),(0,x.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-1.5 border-t border-border bg-background shrink-0 text-xs text-muted-foreground`,children:[(0,x.jsxs)(`span`,{children:[e.total.toLocaleString(),` rows`]}),(0,x.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,x.jsx)(`button`,{type:`button`,disabled:i<=1,onClick:()=>a(i-1),className:`p-0.5 rounded hover:bg-muted disabled:opacity-30`,children:(0,x.jsx)(d,{className:`size-3.5`})}),(0,x.jsxs)(`span`,{children:[i,` / `,w]}),(0,x.jsx)(`button`,{type:`button`,disabled:i>=w,onClick:()=>a(i+1),className:`p-0.5 rounded hover:bg-muted disabled:opacity-30`,children:(0,x.jsx)(n,{className:`size-3.5`})})]})]})]})}function E({onExecute:e,result:t,error:n,loading:s}){let[c,l]=(0,v.useState)(`SELECT * FROM `),u=(0,v.useCallback)(()=>{let t=c.trim();t&&e(t)},[c,e]);return(0,x.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,x.jsxs)(`div`,{className:`flex items-start gap-1 border-b border-border bg-background`,onKeyDown:(0,v.useCallback)(e=>{(e.metaKey||e.ctrlKey)&&e.key===`Enter`&&(e.preventDefault(),u())},[u]),children:[(0,x.jsx)(`div`,{className:`flex-1 max-h-[120px] overflow-auto`,children:(0,x.jsx)(i,{value:c,onChange:l,extensions:[a({dialect:o})],basicSetup:{lineNumbers:!1,foldGutter:!1,highlightActiveLine:!1},className:`text-xs [&_.cm-editor]:!outline-none [&_.cm-scroller]:!overflow-auto`})}),(0,x.jsx)(`button`,{type:`button`,onClick:u,disabled:s,title:`Execute (Cmd+Enter)`,className:`shrink-0 m-1 p-1.5 rounded bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors`,children:s?(0,x.jsx)(f,{className:`size-3.5 animate-spin`}):(0,x.jsx)(r,{className:`size-3.5`})})]}),(0,x.jsxs)(`div`,{className:`flex-1 overflow-auto text-xs`,children:[n&&(0,x.jsx)(`div`,{className:`px-3 py-2 text-destructive bg-destructive/5`,children:n}),t?.changeType===`modify`&&(0,x.jsxs)(`div`,{className:`px-3 py-2 text-green-500`,children:[`Query executed. `,t.rowsAffected,` row(s) affected.`]}),t?.changeType===`select`&&t.rows.length>0&&(0,x.jsxs)(`table`,{className:`w-full border-collapse`,children:[(0,x.jsx)(`thead`,{className:`sticky top-0 bg-muted`,children:(0,x.jsx)(`tr`,{children:t.columns.map(e=>(0,x.jsx)(`th`,{className:`px-2 py-1 text-left font-medium text-muted-foreground border-b border-border whitespace-nowrap`,children:e},e))})}),(0,x.jsx)(`tbody`,{children:t.rows.map((e,n)=>(0,x.jsx)(`tr`,{className:`hover:bg-muted/30 border-b border-border/50`,children:t.columns.map(t=>(0,x.jsx)(`td`,{className:`px-2 py-1 max-w-[300px] truncate`,title:e[t]==null?`NULL`:String(e[t]),children:e[t]==null?(0,x.jsx)(`span`,{className:`text-muted-foreground/40 italic`,children:`NULL`}):String(e[t])},t))},n))})]}),t?.changeType===`select`&&t.rows.length===0&&(0,x.jsx)(`div`,{className:`px-3 py-2 text-muted-foreground`,children:`No results`})]})]})}export{S as PostgresViewer};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o as e}from"./chunk-CFjPhJqf.js";import{t}from"./react-nm2Ru1Pt.js";import{t as n}from"./chevron-right-5HgK6l7K.js";import{a as r,i,n as a,r as o}from"./dist-ovWkrgO-.js";import{t as s}from"./table-DFevCOMd.js";import{t as c}from"./jsx-runtime-kMwlnEGE.js";import{i as l,t as u}from"./api-client-BfBM3I7n.js";import{$ as d,Et as f,ct as p,wt as m,yt as h}from"./index-BffsIdqe.js";import{n as g,r as _,t as v}from"./lib-BQ34Db2e.js";var y=e(t(),1);function b(e,t,n){let[r,i]=(0,y.useState)([]),[a,o]=(0,y.useState)(null),[s,c]=(0,y.useState)(null),[d,f]=(0,y.useState)([]),[p,m]=(0,y.useState)(!1),[h,g]=(0,y.useState)(null),[_,v]=(0,y.useState)(1),[b,x]=(0,y.useState)(null),[S,C]=(0,y.useState)(null),[w,T]=(0,y.useState)(!1),E=n?`/api/db/connections/${n}`:null,D=E??`${l(e)}/sqlite`,O=E?``:`path=${encodeURIComponent(t)}`,k=(0,y.useCallback)(async()=>{m(!0),g(null);try{let e=E?`?cached=1`:O?`?${O}`:``,t=await u.get(`${D}/tables${e}`);i(t),!E&&t.length>0&&!a&&o(t[0].name)}catch(e){g(e.message)}finally{m(!1)}},[D,O,E]);(0,y.useEffect)(()=>{k()},[k]);let A=(0,y.useCallback)(async()=>{if(a){m(!0);try{let e=O?`${O}&`:``,[t,n]=await Promise.all([u.get(`${D}/data?${e}table=${encodeURIComponent(a)}&page=${_}&limit=100`),u.get(`${D}/schema?${e}table=${encodeURIComponent(a)}`)]);c(t),f(n)}catch(e){g(e.message)}finally{m(!1)}}},[D,O,a,_]);return(0,y.useEffect)(()=>{A()},[A]),{tables:r,selectedTable:a,selectTable:(0,y.useCallback)(e=>{o(e),v(1),x(null)},[]),tableData:s,schema:d,loading:p,error:h,page:_,setPage:v,queryResult:b,queryError:S,queryLoading:w,executeQuery:(0,y.useCallback)(async e=>{T(!0),C(null);try{let n=E?{sql:e}:{path:t,sql:e},r=await u.post(`${D}/query`,n);x(r),r.changeType===`modify`&&A()}catch(e){C(e.message)}finally{T(!1)}},[D,E,t,A]),updateCell:(0,y.useCallback)(async(e,n,r)=>{if(a)try{E?await u.put(`${D}/cell`,{table:a,pkColumn:`rowid`,pkValue:e,column:n,value:r}):await u.put(`${D}/cell`,{path:t,table:a,rowid:e,column:n,value:r}),A()}catch(e){g(e.message)}},[D,E,t,a,A]),refreshTables:k,refreshData:A}}var x=c();function S({tables:e,selectedTable:t,onSelect:n,onRefresh:r}){return(0,x.jsxs)(`div`,{className:`w-48 shrink-0 flex flex-col bg-background overflow-hidden`,children:[(0,x.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-2 border-b border-border`,children:[(0,x.jsx)(`span`,{className:`text-xs font-medium text-muted-foreground uppercase tracking-wider`,children:`Tables`}),(0,x.jsx)(`button`,{type:`button`,onClick:r,className:`text-muted-foreground hover:text-foreground transition-colors`,title:`Refresh tables`,children:(0,x.jsx)(d,{className:`size-3`})})]}),(0,x.jsxs)(`div`,{className:`flex-1 overflow-y-auto`,children:[e.map(e=>(0,x.jsxs)(`button`,{type:`button`,onClick:()=>n(e.name),className:`w-full flex items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors ${t===e.name?`bg-muted text-foreground`:`text-muted-foreground hover:bg-muted/50 hover:text-foreground`}`,children:[(0,x.jsx)(s,{className:`size-3 shrink-0`}),(0,x.jsx)(`span`,{className:`truncate flex-1`,children:e.name}),(0,x.jsx)(`span`,{className:`text-[10px] opacity-60`,children:e.rowCount})]},e.name)),e.length===0&&(0,x.jsx)(`p`,{className:`px-3 py-4 text-xs text-muted-foreground text-center`,children:`No tables found`})]})]})}function C({tableData:e,schema:t,loading:r,page:i,onPageChange:a,onCellUpdate:o}){if(!e)return(0,x.jsx)(`div`,{className:`flex items-center justify-center h-full text-xs text-muted-foreground`,children:r?(0,x.jsx)(p,{className:`size-4 animate-spin`}):`Select a table`});let s=Math.ceil(e.total/e.limit)||1;return(0,x.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,x.jsx)(`div`,{className:`flex-1 overflow-auto`,children:(0,x.jsx)(w,{columns:e.columns,rows:e.rows,schema:t,onCellUpdate:o})}),(0,x.jsxs)(`div`,{className:`flex items-center justify-between px-3 py-1.5 border-t border-border bg-background shrink-0 text-xs text-muted-foreground`,children:[(0,x.jsxs)(`span`,{children:[e.total.toLocaleString(),` rows`]}),(0,x.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,x.jsx)(`button`,{type:`button`,disabled:i<=1,onClick:()=>a(i-1),className:`p-0.5 rounded hover:bg-muted disabled:opacity-30`,children:(0,x.jsx)(f,{className:`size-3.5`})}),(0,x.jsxs)(`span`,{children:[i,` / `,s]}),(0,x.jsx)(`button`,{type:`button`,disabled:i>=s,onClick:()=>a(i+1),className:`p-0.5 rounded hover:bg-muted disabled:opacity-30`,children:(0,x.jsx)(n,{className:`size-3.5`})})]})]})]})}function w({columns:e,rows:t,schema:n,onCellUpdate:r}){let[i,a]=(0,y.useState)(null),[o,s]=(0,y.useState)(``),c=(0,y.useMemo)(()=>new Set(n.filter(e=>e.pk).map(e=>e.name)),[n]),l=(0,y.useCallback)((e,t,n)=>{t!==`rowid`&&(a({rowIdx:e,col:t}),s(n==null?``:String(n)))},[]),u=(0,y.useCallback)(()=>{if(!i)return;let e=t[i.rowIdx];if(!e)return;let n=e.rowid,s=e[i.col];String(s??``)!==o&&r(n,i.col,o===``?null:o),a(null)},[i,o,t,r]),d=(0,y.useCallback)(()=>a(null),[]),f=g({data:t,columns:(0,y.useMemo)(()=>e.map(e=>({id:e,accessorFn:t=>t[e],header:()=>(0,x.jsx)(`span`,{className:`${c.has(e)?`font-bold`:``} ${e===`rowid`?`text-muted-foreground/50`:``}`,children:e}),cell:({row:t,getValue:n})=>{let r=t.index,a=i?.rowIdx===r&&i?.col===e,c=n();return a?(0,x.jsx)(`input`,{autoFocus:!0,className:`w-full bg-transparent border border-primary/50 rounded px-1 py-0 text-xs outline-none`,value:o,onChange:e=>s(e.target.value),onBlur:u,onKeyDown:e=>{e.key===`Enter`&&u(),e.key===`Escape`&&d()}}):(0,x.jsx)(`span`,{className:`cursor-pointer truncate block ${c==null?`text-muted-foreground/40 italic`:``} ${e===`rowid`?`text-muted-foreground/50`:``}`,onDoubleClick:()=>l(r,e,c),title:c==null?`NULL`:String(c),children:c==null?`NULL`:String(c)})}})),[e,c,i,o,u,d,l]),getCoreRowModel:_()});return(0,x.jsxs)(`table`,{className:`w-full text-xs border-collapse`,children:[(0,x.jsx)(`thead`,{className:`sticky top-0 z-10 bg-muted`,children:f.getHeaderGroups().map(e=>(0,x.jsx)(`tr`,{children:e.headers.map(e=>(0,x.jsx)(`th`,{className:`px-2 py-1.5 text-left font-medium text-muted-foreground border-b border-border whitespace-nowrap`,children:v(e.column.columnDef.header,e.getContext())},e.id))},e.id))}),(0,x.jsxs)(`tbody`,{children:[f.getRowModel().rows.map(e=>(0,x.jsx)(`tr`,{className:`hover:bg-muted/30 border-b border-border/50`,children:e.getVisibleCells().map(e=>(0,x.jsx)(`td`,{className:`px-2 py-1 max-w-[300px]`,children:v(e.column.columnDef.cell,e.getContext())},e.id))},e.id)),t.length===0&&(0,x.jsx)(`tr`,{children:(0,x.jsx)(`td`,{colSpan:e.length,className:`px-2 py-8 text-center text-muted-foreground`,children:`No data`})})]})]})}function T({onExecute:e,result:t,error:n,loading:s}){let[c,l]=(0,y.useState)(`SELECT * FROM `),u=(0,y.useCallback)(()=>{let t=c.trim();t&&e(t)},[c,e]);return(0,x.jsxs)(`div`,{className:`flex flex-col h-full overflow-hidden`,children:[(0,x.jsxs)(`div`,{className:`flex items-start gap-1 border-b border-border bg-background`,onKeyDown:(0,y.useCallback)(e=>{(e.metaKey||e.ctrlKey)&&e.key===`Enter`&&(e.preventDefault(),u())},[u]),children:[(0,x.jsx)(`div`,{className:`flex-1 max-h-[120px] overflow-auto`,children:(0,x.jsx)(i,{value:c,onChange:l,extensions:[o({dialect:a})],basicSetup:{lineNumbers:!1,foldGutter:!1,highlightActiveLine:!1},className:`text-xs [&_.cm-editor]:!outline-none [&_.cm-scroller]:!overflow-auto`})}),(0,x.jsx)(`button`,{type:`button`,onClick:u,disabled:s,className:`shrink-0 m-1 p-1.5 rounded bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors`,title:`Execute (Cmd+Enter)`,children:s?(0,x.jsx)(p,{className:`size-3.5 animate-spin`}):(0,x.jsx)(r,{className:`size-3.5`})})]}),(0,x.jsxs)(`div`,{className:`flex-1 overflow-auto text-xs`,children:[n&&(0,x.jsx)(`div`,{className:`px-3 py-2 text-destructive bg-destructive/5`,children:n}),t&&t.changeType===`modify`&&(0,x.jsxs)(`div`,{className:`px-3 py-2 text-green-500`,children:[`Query executed. `,t.rowsAffected,` row(s) affected.`]}),t&&t.changeType===`select`&&t.rows.length>0&&(0,x.jsxs)(`table`,{className:`w-full border-collapse`,children:[(0,x.jsx)(`thead`,{className:`sticky top-0 bg-muted`,children:(0,x.jsx)(`tr`,{children:t.columns.map(e=>(0,x.jsx)(`th`,{className:`px-2 py-1 text-left font-medium text-muted-foreground border-b border-border whitespace-nowrap`,children:e},e))})}),(0,x.jsx)(`tbody`,{children:t.rows.map((e,n)=>(0,x.jsx)(`tr`,{className:`hover:bg-muted/30 border-b border-border/50`,children:t.columns.map(t=>(0,x.jsx)(`td`,{className:`px-2 py-1 max-w-[300px] truncate`,title:e[t]==null?`NULL`:String(e[t]),children:e[t]==null?(0,x.jsx)(`span`,{className:`text-muted-foreground/40 italic`,children:`NULL`}):String(e[t])},t))},n))})]}),t&&t.changeType===`select`&&t.rows.length===0&&(0,x.jsx)(`div`,{className:`px-3 py-2 text-muted-foreground`,children:`No results`})]})]})}function E({metadata:e}){let t=e?.filePath,n=e?.projectName,r=e?.connectionId,i=e?.tableName,[a,o]=(0,y.useState)(!1);return r?(0,x.jsx)(D,{projectName:``,dbPath:``,connectionId:r,connectionName:e?.connectionName,initialTable:i,queryPanelOpen:a,onToggleQueryPanel:()=>o(e=>!e),hideTableList:!0}):!t||!n?(0,x.jsxs)(`div`,{className:`flex items-center justify-center h-full text-text-secondary text-sm`,children:[(0,x.jsx)(h,{className:`size-5 mr-2`}),` No database file selected.`]}):(0,x.jsx)(D,{projectName:n,dbPath:t,queryPanelOpen:a,onToggleQueryPanel:()=>o(e=>!e)})}function D({projectName:e,dbPath:t,connectionId:n,connectionName:r,initialTable:i,queryPanelOpen:a,onToggleQueryPanel:o,hideTableList:s}){let c=b(e,t,n),l=(0,y.useRef)(!1);return(0,y.useEffect)(()=>{i&&!l.current&&c.tables.length>0&&(l.current=!0,c.selectTable(i))},[i,c.tables]),c.error&&c.tables.length===0?(0,x.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,x.jsx)(m,{className:`size-10 text-destructive`}),(0,x.jsx)(`p`,{className:`text-sm`,children:c.error})]}):c.loading&&c.tables.length===0?(0,x.jsxs)(`div`,{className:`flex items-center justify-center h-full gap-2 text-text-secondary`,children:[(0,x.jsx)(p,{className:`size-5 animate-spin`}),(0,x.jsx)(`span`,{className:`text-sm`,children:`Loading database...`})]}):(0,x.jsxs)(`div`,{className:`flex h-full w-full overflow-hidden`,children:[!s&&(0,x.jsx)(S,{tables:c.tables,selectedTable:c.selectedTable,onSelect:c.selectTable,onRefresh:c.refreshTables}),(0,x.jsxs)(`div`,{className:`flex-1 flex flex-col overflow-hidden ${s?``:`border-l border-border`}`,children:[(0,x.jsxs)(`div`,{className:`flex items-center gap-2 px-3 py-1.5 border-b border-border bg-background shrink-0`,children:[(0,x.jsx)(h,{className:`size-3.5 text-muted-foreground`}),(0,x.jsx)(`span`,{className:`text-xs text-muted-foreground truncate`,children:r??t}),(0,x.jsx)(`span`,{className:`text-xs text-muted-foreground`,children:c.selectedTable&&`/ ${c.selectedTable}`}),(0,x.jsx)(`div`,{className:`ml-auto`,children:(0,x.jsx)(`button`,{type:`button`,onClick:o,className:`px-2 py-1 rounded text-xs transition-colors ${a?`bg-muted text-foreground`:`text-muted-foreground hover:text-foreground`}`,children:`SQL`})})]}),(0,x.jsx)(`div`,{className:`flex-1 overflow-hidden ${a?`max-h-[60%]`:``}`,children:(0,x.jsx)(C,{tableData:c.tableData,schema:c.schema,loading:c.loading,page:c.page,onPageChange:c.setPage,onCellUpdate:c.updateCell})}),a&&(0,x.jsx)(`div`,{className:`border-t border-border h-[40%] shrink-0`,children:(0,x.jsx)(T,{onExecute:c.executeQuery,result:c.queryResult,error:c.queryError,loading:c.queryLoading})})]})]})}export{E as SqliteViewer};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./chunk-GEFDOKGD-D-pKjlVd.js";import{h as e}from"./src-BqX54PbV.js";import"./chunk-7R4GIKGN-Dv-4cAYn.js";import"./dist-CSJdAyA9.js";import"./chunk-PU5JKC2W-ek7k4QVB.js";import"./chunk-MX3YWQON-BpS_PtKp.js";import"./chunk-YBOYWFTD-rQG3QH5s.js";import"./chunk-55IACEB6-DJ6BynZ4.js";import"./chunk-KX2RTZJC-CRq1OBZv.js";import"./chunk-PQ6SQG4A-TF58UVMU.js";import"./chunk-KYZI473N-Bb0MCaIO.js";import"./chunk-O4XLMI2P-nDhi_cVu.js";import"./chunk-GLR3WWYH-DKikpoJM.js";import{i as t,n,r,t as i}from"./chunk-NQ4KR5QH-z_blpjxi.js";var a={parser:n,get db(){return new i(2)},renderer:r,styles:t,init:e(e=>{e.state||={},e.state.arrowMarkerAbsolute=e.arrowMarkerAbsolute},`init`)};export{a as diagram};
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
# Streaming Input Migration Quick Reference (v0.8.55+)
|
|
2
|
-
|
|
3
|
-
## What Changed?
|
|
4
|
-
|
|
5
|
-
**Before (v0.8.54):** Each message triggered a new SDK query
|
|
6
|
-
```
|
|
7
|
-
Message 1 → SDK subprocess spawn → generate response → close
|
|
8
|
-
Message 2 → SDK subprocess spawn → generate response → close
|
|
9
|
-
(Slow, context resets between messages)
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
**After (v0.8.55):** Single persistent streaming session
|
|
13
|
-
```
|
|
14
|
-
Session created → AsyncGenerator streaming input opened
|
|
15
|
-
Message 1 → Push into generator → process events
|
|
16
|
-
Message 2 → Push into same generator → continue streaming
|
|
17
|
-
(Fast, continuous context, no SDK restarts)
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Key Concepts
|
|
21
|
-
|
|
22
|
-
### Session State (BE-Owned)
|
|
23
|
-
The backend maintains a `SessionEntry` per chat session:
|
|
24
|
-
- Tracks connected clients (can be zero if FE disconnected)
|
|
25
|
-
- Maintains streaming phase (idle, connecting, thinking, streaming)
|
|
26
|
-
- Buffers events for reconnection sync
|
|
27
|
-
- Auto-cleans after 5 minutes of FE inactivity
|
|
28
|
-
|
|
29
|
-
### Message Priority (v0.8.55+)
|
|
30
|
-
```typescript
|
|
31
|
-
// Send message with priority
|
|
32
|
-
ws.send({
|
|
33
|
-
type: "message",
|
|
34
|
-
content: "Debug this code",
|
|
35
|
-
priority: "now" // "now" | "next" | "later"
|
|
36
|
-
})
|
|
37
|
-
```
|
|
38
|
-
- **"now"** — Abort current query, restart with this message
|
|
39
|
-
- **"next"** — Queue after current, run next
|
|
40
|
-
- **"later"** — Append to queue, run last
|
|
41
|
-
|
|
42
|
-
### Event Buffering on Reconnect
|
|
43
|
-
When FE WS reconnects after disconnect:
|
|
44
|
-
1. BE sends `session_state` with current phase + pending approval
|
|
45
|
-
2. BE sends `turn_events` with all buffered events since last connection
|
|
46
|
-
3. FE rebuilds chat UI state from buffered events
|
|
47
|
-
4. No message loss (unless session cleaned up after 5min)
|
|
48
|
-
|
|
49
|
-
## Common Patterns
|
|
50
|
-
|
|
51
|
-
### Frontend: Send Message
|
|
52
|
-
```typescript
|
|
53
|
-
// In useChat hook or message input handler
|
|
54
|
-
ws.send(JSON.stringify({
|
|
55
|
-
type: "message",
|
|
56
|
-
content: userInput,
|
|
57
|
-
priority: "now", // Optional
|
|
58
|
-
images: [{ id: "img1", data: "base64..." }] // Optional
|
|
59
|
-
}));
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Frontend: Handle Reconnection
|
|
63
|
-
```typescript
|
|
64
|
-
function handleReconnect() {
|
|
65
|
-
// 1. WS open fires
|
|
66
|
-
// 2. Server sends session_state
|
|
67
|
-
const sessionState = JSON.parse(msg);
|
|
68
|
-
// 3. Server sends turn_events
|
|
69
|
-
const turnEvents = JSON.parse(msg);
|
|
70
|
-
|
|
71
|
-
// 4. FE rebuilds state from buffered events
|
|
72
|
-
turnEvents.events.forEach(event => {
|
|
73
|
-
chatStore.addEvent(event);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// 5. FE is now synced with BE
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Backend: Session Lifecycle
|
|
81
|
-
```typescript
|
|
82
|
-
// 1. FE connects
|
|
83
|
-
open(ws) {
|
|
84
|
-
const entry = activeSessions.get(sessionId);
|
|
85
|
-
if (!entry) {
|
|
86
|
-
// Create new session entry
|
|
87
|
-
activeSessions.set(sessionId, {
|
|
88
|
-
phase: "idle",
|
|
89
|
-
clients: new Set([ws]),
|
|
90
|
-
turnEvents: []
|
|
91
|
-
});
|
|
92
|
-
} else {
|
|
93
|
-
// Reconnect: clear cleanup timer, add client
|
|
94
|
-
entry.clients.add(ws);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 2. FE sends message
|
|
99
|
-
message(ws, data) {
|
|
100
|
-
const parsed = JSON.parse(data);
|
|
101
|
-
if (parsed.type === "message") {
|
|
102
|
-
// Abort current if streaming, wait for cleanup
|
|
103
|
-
if (entry.phase !== "idle") {
|
|
104
|
-
entry.abort.abort();
|
|
105
|
-
await entry.streamPromise;
|
|
106
|
-
}
|
|
107
|
-
// Start new streaming loop (detached)
|
|
108
|
-
entry.streamPromise = runStreamLoop(...);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 3. Streaming loop runs independently
|
|
113
|
-
async function runStreamLoop() {
|
|
114
|
-
for await (const event of chatService.sendMessage(...)) {
|
|
115
|
-
bufferAndBroadcast(sessionId, event); // To all connected clients
|
|
116
|
-
}
|
|
117
|
-
setPhase(sessionId, "idle"); // Back to idle when done
|
|
118
|
-
if (entry.clients.size === 0) {
|
|
119
|
-
startCleanupTimer(sessionId); // 5-min cleanup
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 4. FE disconnects
|
|
124
|
-
close(ws) {
|
|
125
|
-
entry.clients.delete(ws);
|
|
126
|
-
// Stream continues! (BE owns the connection)
|
|
127
|
-
// Timer started if no more clients
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## Phase State Machine
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
┌─ initializing (setup, session resume)
|
|
135
|
-
↓
|
|
136
|
-
idle ←→ connecting (waiting for first SDK event, heartbeat)
|
|
137
|
-
↑ ↓
|
|
138
|
-
│ ┌──→ thinking (extended thinking)
|
|
139
|
-
│ ↓ ↓
|
|
140
|
-
└─── streaming (text/tool_use content)
|
|
141
|
-
↑ ↓
|
|
142
|
-
└─────┘ (dynamic switch)
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
**Transitions:**
|
|
146
|
-
- Heartbeat: `connecting` → (5s elapsed updates) → `thinking` (when content arrives)
|
|
147
|
-
- Content: `thinking` → `streaming` (first text event)
|
|
148
|
-
- Dynamic: `streaming` ↔ `thinking` (based on event types)
|
|
149
|
-
- Done: Any → `idle` (stream complete, ready for next message)
|
|
150
|
-
|
|
151
|
-
## WebSocket Messages (v0.8.55+)
|
|
152
|
-
|
|
153
|
-
### Client → Server
|
|
154
|
-
```typescript
|
|
155
|
-
// Send message
|
|
156
|
-
{ type: "message"; content: string; priority?: string; images?: {...}[] }
|
|
157
|
-
|
|
158
|
-
// Approve tool
|
|
159
|
-
{ type: "approval_response"; requestId: string; approved: boolean }
|
|
160
|
-
|
|
161
|
-
// Cancel current
|
|
162
|
-
{ type: "cancel" }
|
|
163
|
-
|
|
164
|
-
// Handshake after open
|
|
165
|
-
{ type: "ready" }
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Server → Client
|
|
169
|
-
```typescript
|
|
170
|
-
// Content
|
|
171
|
-
{ type: "text"; content: string }
|
|
172
|
-
{ type: "thinking"; content: string }
|
|
173
|
-
|
|
174
|
-
// Tool execution
|
|
175
|
-
{ type: "tool_use"; tool: string; input: unknown }
|
|
176
|
-
{ type: "tool_result"; output: string; isError?: boolean }
|
|
177
|
-
|
|
178
|
-
// User approval request
|
|
179
|
-
{ type: "approval_request"; requestId: string; tool: string; input: unknown }
|
|
180
|
-
|
|
181
|
-
// Session state (sent on open/ready)
|
|
182
|
-
{ type: "session_state"; sessionId: string; phase: SessionPhase; pendingApproval: {...} | null }
|
|
183
|
-
|
|
184
|
-
// Buffered events (on reconnect)
|
|
185
|
-
{ type: "turn_events"; events: unknown[] }
|
|
186
|
-
|
|
187
|
-
// Metadata
|
|
188
|
-
{ type: "account_info"; accountId: string; accountLabel: string }
|
|
189
|
-
{ type: "phase_changed"; phase: SessionPhase; elapsed?: number }
|
|
190
|
-
{ type: "title_updated"; title: string }
|
|
191
|
-
|
|
192
|
-
// Completion
|
|
193
|
-
{ type: "done"; sessionId: string; contextWindowPct?: number }
|
|
194
|
-
|
|
195
|
-
// Error
|
|
196
|
-
{ type: "error"; message: string }
|
|
197
|
-
|
|
198
|
-
// Keepalive
|
|
199
|
-
{ type: "ping" }
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## Benefits
|
|
203
|
-
|
|
204
|
-
| Aspect | Before (v0.8.54) | After (v0.8.55) |
|
|
205
|
-
|--------|------------------|-----------------|
|
|
206
|
-
| **SDK Restarts** | Per message | Once per session |
|
|
207
|
-
| **Context** | Resets between messages | Persistent |
|
|
208
|
-
| **Startup Time** | 2-5s per message | Instant follow-ups |
|
|
209
|
-
| **Reconnection** | Message loss | Event buffering ensures sync |
|
|
210
|
-
| **Concurrency** | N/A | Multiple clients per session |
|
|
211
|
-
| **Tool Approvals** | Restarts query | Integrated in stream |
|
|
212
|
-
|
|
213
|
-
## Troubleshooting
|
|
214
|
-
|
|
215
|
-
### Session Cleaned Up (No Longer Exists)
|
|
216
|
-
**Cause:** FE disconnected for >5 minutes
|
|
217
|
-
**Solution:** Create new session, FE reconnects with new sessionId
|
|
218
|
-
|
|
219
|
-
### Events Missing After Reconnect
|
|
220
|
-
**Cause:** Server-side event buffer (10k event limit) overflowed
|
|
221
|
-
**Solution:** Flush buffer periodically or increase limit if needed
|
|
222
|
-
|
|
223
|
-
### Phase Stuck in "Connecting"
|
|
224
|
-
**Cause:** SDK subprocess not responding (120s timeout)
|
|
225
|
-
**Solution:** Check environment (ANTHROPIC_API_KEY, network), see error message for hints
|
|
226
|
-
|
|
227
|
-
### Multiple Clients Out of Sync
|
|
228
|
-
**Cause:** Broadcast failed for one client, others ahead
|
|
229
|
-
**Solution:** Evicted client will reconnect and re-sync from buffered events
|
|
230
|
-
|
|
231
|
-
## Debugging
|
|
232
|
-
|
|
233
|
-
### Enable Logging
|
|
234
|
-
```bash
|
|
235
|
-
# Check server logs for session lifecycle
|
|
236
|
-
[chat] session=abc123 phase → connecting
|
|
237
|
-
[chat] session=abc123 first SDK event after 1250ms: type=text
|
|
238
|
-
[chat] session=abc123 stream completed (45 events)
|
|
239
|
-
[chat] session=abc123 phase → idle
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
### Check Session State
|
|
243
|
-
```typescript
|
|
244
|
-
// On WS message handler
|
|
245
|
-
console.log(`Session entry:`, activeSessions.get(sessionId));
|
|
246
|
-
// Outputs: { phase, clients.size, pendingApprovalEvent, turnEvents.length }
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### Monitor Reconnections
|
|
250
|
-
```typescript
|
|
251
|
-
// In WS open handler
|
|
252
|
-
console.log(`FE reconnected (phase=${existing.phase}, clients=${existing.clients.size})`);
|
|
253
|
-
// Tells you: active streaming, how many clients connected
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## Performance Notes
|
|
257
|
-
|
|
258
|
-
- **No SDK overhead:** Persistent streaming eliminates subprocess spawn overhead
|
|
259
|
-
- **Event buffering:** Clients see all events after reconnect (max 10k events per turn)
|
|
260
|
-
- **Memory:** Session entries cleaned after 5min (bounded memory usage)
|
|
261
|
-
- **Latency:** Follow-up messages start immediately (no SDK init)
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
**For detailed architecture:** See `docs/system-architecture.md` → "Chat Streaming Flow" section
|
|
266
|
-
**For API types:** See `src/types/api.ts` and `src/types/chat.ts`
|
|
267
|
-
**For implementation:** See `src/server/ws/chat.ts` and `src/providers/claude-agent-sdk.ts`
|