@hienlh/ppm 0.11.5 → 0.11.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/CHANGELOG.md +15 -0
- package/dist/web/assets/{ai-settings-section-D2vqiydT.js → ai-settings-section-C6FDY8qE.js} +1 -1
- package/dist/web/assets/{api-settings-2eTz4SgY.js → api-settings-C__hxGX2.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-XX6_EZsC.js +1 -0
- package/dist/web/assets/audio-preview--VaB403K.js +1 -0
- package/dist/web/assets/{chat-tab-CWc9ZYVY.js → chat-tab-DS455Mwt.js} +4 -4
- package/dist/web/assets/code-editor-BsUsDjJz.js +8 -0
- package/dist/web/assets/{conflict-editor-BmuhLalI.js → conflict-editor-DTMT0ZxI.js} +1 -1
- package/dist/web/assets/{csv-preview-D37K2LRd.js → csv-preview-BEBJD4a_.js} +1 -1
- package/dist/web/assets/{database-viewer-CqW0fH7Q.js → database-viewer-B7aIs1XX.js} +1 -1
- package/dist/web/assets/{diff-viewer-ium8zJhL.js → diff-viewer-C9unqCxZ.js} +1 -1
- package/dist/web/assets/{esm-B99v94EE.js → esm-K1XIK4vc.js} +1 -1
- package/dist/web/assets/{extension-store-CkyOvGbF.js → extension-store-3yZYn07W.js} +1 -1
- package/dist/web/assets/{extension-webview-AxcQX8ML.js → extension-webview-DUs8mqO_.js} +1 -1
- package/dist/web/assets/file-exclamation-point-BwzaQ50n.js +1 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-BhjTKsbg.js +1 -0
- package/dist/web/assets/image-preview-CoiyMXuX.js +1 -0
- package/dist/web/assets/{index-CyciQ-Gz.js → index-B1GZswlR.js} +5 -5
- package/dist/web/assets/index-C3ZstZ9f.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-CzgVqYTx.js +1 -0
- package/dist/web/assets/{input-CHRMley8.js → input-ClhO__YM.js} +1 -1
- package/dist/web/assets/keybindings-store-BkZjvU9J.js +1 -0
- package/dist/web/assets/{keybindings-store-CpP5_miA.js → keybindings-store-C9KsBH7z.js} +1 -1
- package/dist/web/assets/{markdown-renderer-Ckjp96ej.js → markdown-renderer-4LYdtPZU.js} +3 -3
- package/dist/web/assets/packet-RMMSAZCW-C7agXrtd.js +1 -0
- package/dist/web/assets/pdf-preview-CFO6CjHG.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-BRZ7alnf.js +1 -0
- package/dist/web/assets/{port-forwarding-tab-DR5Bngg2.js → port-forwarding-tab-DpoVwtlp.js} +1 -1
- package/dist/web/assets/{postgres-viewer-hy2EksiB.js → postgres-viewer-Ku3X9rJQ.js} +3 -3
- package/dist/web/assets/{project-store-CczGNZyf.js → project-store-BYmQ0fDC.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DSn_ekR5.js +1 -0
- package/dist/web/assets/{scroll-area-DwWF9FpN.js → scroll-area-DW7L4Gnc.js} +1 -1
- package/dist/web/assets/{settings-store-CuYjM0FF.js → settings-store-B9axDbuA.js} +2 -2
- package/dist/web/assets/settings-tab-l-SopAs4.js +1 -0
- package/dist/web/assets/{sql-query-editor-CVEi0jLM.js → sql-query-editor-BnpKNGG0.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DDPZjt-X.js → sqlite-viewer-DzS8-nbv.js} +1 -1
- package/dist/web/assets/{tab-store-Jvy1eZGM.js → tab-store-B3M9hjho.js} +1 -1
- package/dist/web/assets/{terminal-tab-DSWdc4AO.js → terminal-tab-ERPwhU5O.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-C8puYVyN.js +1 -0
- package/dist/web/assets/use-blob-url-BK7zshV7.js +1 -0
- package/dist/web/assets/{use-monaco-theme-kjiAwvOp.js → use-monaco-theme-BERjR8IA.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-CylkVm4U.js → vendor-mermaid-BlWh9BJO.js} +2 -2
- package/dist/web/assets/video-preview-BZenXc_U.js +1 -0
- package/dist/web/index.html +17 -17
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/server/ws/chat.ts +10 -0
- package/src/services/file-watcher.service.ts +72 -0
- package/src/services/supervisor.ts +29 -9
- package/src/web/components/editor/audio-preview.tsx +26 -0
- package/src/web/components/editor/code-editor.tsx +44 -81
- package/src/web/components/editor/image-preview.tsx +23 -0
- package/src/web/components/editor/pdf-preview.tsx +32 -0
- package/src/web/components/editor/use-blob-url.ts +35 -0
- package/src/web/components/editor/video-preview.tsx +23 -0
- package/src/web/components/explorer/file-tree.tsx +16 -5
- package/src/web/hooks/use-chat.ts +6 -0
- package/dist/web/assets/architecture-PBZL5I3N-BRW4VwMk.js +0 -1
- package/dist/web/assets/code-editor-CSej2S2J.js +0 -8
- package/dist/web/assets/gitGraph-HDMCJU4V-Bt68dqWT.js +0 -1
- package/dist/web/assets/index-iZHWllzQ.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-ySD5z855.js +0 -1
- package/dist/web/assets/keybindings-store-qfYScgY0.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-CLxaXgIf.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-C9wPZfkn.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-DxEpzVN_.js +0 -1
- package/dist/web/assets/settings-tab-D5SXI74b.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-yelcZZqO.js +0 -1
- /package/dist/web/assets/{api-client-C3tXCh0r.js → api-client-Bn-Pi9k5.js} +0 -0
- /package/dist/web/assets/{csv-parser-BAa56Nnn.js → csv-parser--2WJNgS7.js} +0 -0
- /package/dist/web/assets/{dist-On3hz9_g.js → dist-im4ynINo.js} +0 -0
- /package/dist/web/assets/{katex-Bbu770d9.js → katex-CKoArbIw.js} +0 -0
- /package/dist/web/assets/{lib-BqkcKGFq.js → lib-DQHnkzGy.js} +0 -0
- /package/dist/web/assets/{react-BkWDCPD7.js → react-GqWghJ-L.js} +0 -0
- /package/dist/web/assets/{refresh-cw-CSFrDtiu.js → refresh-cw-LlbZDJpO.js} +0 -0
- /package/dist/web/assets/{sql-completion-provider-D3acAhav.js → sql-completion-provider-C3cq9j99.js} +0 -0
- /package/dist/web/assets/{table-DbSviOmw.js → table-Dq575bPF.js} +0 -0
- /package/dist/web/assets/{text-wrap-DzvCTq_i.js → text-wrap-Cn6BNQfq.js} +0 -0
- /package/dist/web/assets/{trash-2-BgDIBl6f.js → trash-2-CJYoLw7Q.js} +0 -0
- /package/dist/web/assets/{utils-ChWX7pZv.js → utils-CTg5uAYR.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-B9BUAFKA.js → vendor-xterm-CU2c3f0A.js} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as e}from"./vendor-markdown-0Mxgxy0L.js";import{t}from"./file-exclamation-point-BwzaQ50n.js";import"./api-client-Bn-Pi9k5.js";import{M as n}from"./index-B1GZswlR.js";import{t as r}from"./use-blob-url-BK7zshV7.js";var i=e();function a({filePath:e,projectName:a}){let{blobUrl:o,error:s}=r(e,a);return s?(0,i.jsxs)(`div`,{className:`flex flex-col items-center justify-center h-full gap-3 text-text-secondary`,children:[(0,i.jsx)(t,{className:`size-10 text-text-subtle`}),(0,i.jsx)(`p`,{className:`text-sm`,children:`Failed to load video.`})]}):o?(0,i.jsx)(`div`,{className:`flex items-center justify-center h-full p-4 bg-surface overflow-auto`,children:(0,i.jsx)(`video`,{src:o,controls:!0,className:`max-w-full max-h-full`})}):(0,i.jsx)(`div`,{className:`flex items-center justify-center h-full`,children:(0,i.jsx)(n,{className:`size-5 animate-spin text-text-subtle`})})}export{a as VideoPreview};
|
package/dist/web/index.html
CHANGED
|
@@ -39,32 +39,32 @@
|
|
|
39
39
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
40
40
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
41
41
|
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600;700&family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
42
|
-
<script type="module" crossorigin src="/assets/index-
|
|
42
|
+
<script type="module" crossorigin src="/assets/index-B1GZswlR.js"></script>
|
|
43
43
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-FhOqtrmT.js">
|
|
44
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-
|
|
44
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-mermaid-BlWh9BJO.js">
|
|
45
45
|
<link rel="modulepreload" crossorigin href="/assets/vendor-markdown-0Mxgxy0L.js">
|
|
46
46
|
<link rel="modulepreload" crossorigin href="/assets/vendor-ui-B-T_damt.js">
|
|
47
|
-
<link rel="modulepreload" crossorigin href="/assets/utils-
|
|
47
|
+
<link rel="modulepreload" crossorigin href="/assets/utils-CTg5uAYR.js">
|
|
48
48
|
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-BjHrJDVb.js">
|
|
49
49
|
<link rel="modulepreload" crossorigin href="/assets/x-DlFGzN8d.js">
|
|
50
|
-
<link rel="modulepreload" crossorigin href="/assets/input-
|
|
51
|
-
<link rel="modulepreload" crossorigin href="/assets/scroll-area-
|
|
50
|
+
<link rel="modulepreload" crossorigin href="/assets/input-ClhO__YM.js">
|
|
51
|
+
<link rel="modulepreload" crossorigin href="/assets/scroll-area-DW7L4Gnc.js">
|
|
52
52
|
<link rel="modulepreload" crossorigin href="/assets/dist-C5IgeqrV.js">
|
|
53
53
|
<link rel="modulepreload" crossorigin href="/assets/plus-51UQ45rf.js">
|
|
54
|
-
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-
|
|
55
|
-
<link rel="modulepreload" crossorigin href="/assets/trash-2-
|
|
56
|
-
<link rel="modulepreload" crossorigin href="/assets/api-client-
|
|
57
|
-
<link rel="modulepreload" crossorigin href="/assets/api-settings-
|
|
58
|
-
<link rel="modulepreload" crossorigin href="/assets/ai-settings-section-
|
|
54
|
+
<link rel="modulepreload" crossorigin href="/assets/refresh-cw-LlbZDJpO.js">
|
|
55
|
+
<link rel="modulepreload" crossorigin href="/assets/trash-2-CJYoLw7Q.js">
|
|
56
|
+
<link rel="modulepreload" crossorigin href="/assets/api-client-Bn-Pi9k5.js">
|
|
57
|
+
<link rel="modulepreload" crossorigin href="/assets/api-settings-C__hxGX2.js">
|
|
58
|
+
<link rel="modulepreload" crossorigin href="/assets/ai-settings-section-C6FDY8qE.js">
|
|
59
59
|
<link rel="modulepreload" crossorigin href="/assets/chevron-right-BzAdxJRG.js">
|
|
60
60
|
<link rel="modulepreload" crossorigin href="/assets/database-D4DIhgi-.js">
|
|
61
|
-
<link rel="modulepreload" crossorigin href="/assets/react-
|
|
62
|
-
<link rel="modulepreload" crossorigin href="/assets/extension-store-
|
|
63
|
-
<link rel="modulepreload" crossorigin href="/assets/keybindings-store-
|
|
64
|
-
<link rel="modulepreload" crossorigin href="/assets/tab-store-
|
|
65
|
-
<link rel="modulepreload" crossorigin href="/assets/project-store-
|
|
66
|
-
<link rel="modulepreload" crossorigin href="/assets/settings-store-
|
|
67
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
61
|
+
<link rel="modulepreload" crossorigin href="/assets/react-GqWghJ-L.js">
|
|
62
|
+
<link rel="modulepreload" crossorigin href="/assets/extension-store-3yZYn07W.js">
|
|
63
|
+
<link rel="modulepreload" crossorigin href="/assets/keybindings-store-C9KsBH7z.js">
|
|
64
|
+
<link rel="modulepreload" crossorigin href="/assets/tab-store-B3M9hjho.js">
|
|
65
|
+
<link rel="modulepreload" crossorigin href="/assets/project-store-BYmQ0fDC.js">
|
|
66
|
+
<link rel="modulepreload" crossorigin href="/assets/settings-store-B9axDbuA.js">
|
|
67
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C3ZstZ9f.css">
|
|
68
68
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
69
69
|
<body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
|
|
70
70
|
<div id="root"></div>
|
package/dist/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"3e55d147f4b877c27fb3df0baed9051d","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/lib-BqkcKGFq.js"},{"revision":null,"url":"assets/input-CHRMley8.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/terminal-tab-DSWdc4AO.js"},{"revision":null,"url":"assets/tab-store-Jvy1eZGM.js"},{"revision":null,"url":"assets/database-D4DIhgi-.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-Bt68dqWT.js"},{"revision":null,"url":"assets/postgres-viewer-hy2EksiB.js"},{"revision":null,"url":"assets/markdown-renderer-Ckjp96ej.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/index-CyciQ-Gz.js"},{"revision":null,"url":"assets/settings-tab-D5SXI74b.js"},{"revision":null,"url":"assets/ai-settings-section-D2vqiydT.js"},{"revision":null,"url":"assets/vendor-ui-B-T_damt.js"},{"revision":null,"url":"assets/database-viewer-CqW0fH7Q.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/settings-store-CuYjM0FF.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-C9wPZfkn.js"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/sql-query-editor-CVEi0jLM.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-CLxaXgIf.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/trash-2-BgDIBl6f.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/dist-C5IgeqrV.js"},{"revision":null,"url":"assets/extension-webview-AxcQX8ML.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/chat-tab-CWc9ZYVY.js"},{"revision":null,"url":"assets/esm-B99v94EE.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/api-settings-2eTz4SgY.js"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DxEpzVN_.js"},{"revision":null,"url":"assets/refresh-cw-CSFrDtiu.js"},{"revision":null,"url":"assets/vendor-xterm-B9BUAFKA.js"},{"revision":null,"url":"assets/sqlite-viewer-DDPZjt-X.js"},{"revision":null,"url":"assets/conflict-editor-BmuhLalI.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/api-client-C3tXCh0r.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/use-monaco-theme-kjiAwvOp.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/extension-store-CkyOvGbF.js"},{"revision":null,"url":"assets/info-3K5VOQVL-ySD5z855.js"},{"revision":null,"url":"assets/columns-2-4fQcE4PF.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/sql-completion-provider-D3acAhav.js"},{"revision":null,"url":"assets/react-BkWDCPD7.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/code-editor-CSej2S2J.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/architecture-PBZL5I3N-BRW4VwMk.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-yelcZZqO.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/port-forwarding-tab-DR5Bngg2.js"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/csv-parser-BAa56Nnn.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/project-store-CczGNZyf.js"},{"revision":null,"url":"assets/katex-Bbu770d9.js"},{"revision":null,"url":"assets/keybindings-store-qfYScgY0.js"},{"revision":null,"url":"assets/utils-ChWX7pZv.js"},{"revision":null,"url":"assets/text-wrap-DzvCTq_i.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/scroll-area-DwWF9FpN.js"},{"revision":null,"url":"assets/index-iZHWllzQ.css"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/keybindings-store-CpP5_miA.js"},{"revision":null,"url":"assets/csv-preview-D37K2LRd.js"},{"revision":null,"url":"assets/vendor-mermaid-CylkVm4U.js"},{"revision":null,"url":"assets/diff-viewer-ium8zJhL.js"},{"revision":null,"url":"assets/table-DbSviOmw.js"},{"revision":null,"url":"assets/dist-On3hz9_g.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
|
1
|
+
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||=(self.addEventListener(`install`,this.install),self.addEventListener(`activate`,this.activate),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o==`boolean`)&&(i=void 0),{route:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"ff071aa161c8548ee3f427470d37187e","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/input-ClhO__YM.js"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/diff-viewer-C9unqCxZ.js"},{"revision":null,"url":"assets/database-D4DIhgi-.js"},{"revision":null,"url":"assets/csv-parser--2WJNgS7.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/video-preview-BZenXc_U.js"},{"revision":null,"url":"assets/conflict-editor-DTMT0ZxI.js"},{"revision":null,"url":"assets/csv-preview-BEBJD4a_.js"},{"revision":null,"url":"assets/vendor-xterm-CU2c3f0A.js"},{"revision":null,"url":"assets/keybindings-store-C9KsBH7z.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-BhjTKsbg.js"},{"revision":null,"url":"assets/refresh-cw-LlbZDJpO.js"},{"revision":null,"url":"assets/sql-query-editor-BnpKNGG0.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-XX6_EZsC.js"},{"revision":null,"url":"assets/react-GqWghJ-L.js"},{"revision":null,"url":"assets/pdf-preview-CFO6CjHG.js"},{"revision":null,"url":"assets/vendor-ui-B-T_damt.js"},{"revision":null,"url":"assets/markdown-renderer-4LYdtPZU.js"},{"revision":null,"url":"assets/katex-CKoArbIw.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/chat-tab-DS455Mwt.js"},{"revision":null,"url":"assets/index-B1GZswlR.js"},{"revision":null,"url":"assets/extension-store-3yZYn07W.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/project-store-BYmQ0fDC.js"},{"revision":null,"url":"assets/scroll-area-DW7L4Gnc.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/postgres-viewer-Ku3X9rJQ.js"},{"revision":null,"url":"assets/vendor-mermaid-BlWh9BJO.js"},{"revision":null,"url":"assets/dist-im4ynINo.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/table-Dq575bPF.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-C7agXrtd.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/dist-C5IgeqrV.js"},{"revision":null,"url":"assets/keybindings-store-BkZjvU9J.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2"},{"revision":null,"url":"assets/use-monaco-theme-BERjR8IA.js"},{"revision":null,"url":"assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2"},{"revision":null,"url":"assets/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DSn_ekR5.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/ai-settings-section-C6FDY8qE.js"},{"revision":null,"url":"assets/info-3K5VOQVL-CzgVqYTx.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/text-wrap-Cn6BNQfq.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/trash-2-CJYoLw7Q.js"},{"revision":null,"url":"assets/code-editor-BsUsDjJz.js"},{"revision":null,"url":"assets/extension-webview-DUs8mqO_.js"},{"revision":null,"url":"assets/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/terminal-tab-ERPwhU5O.js"},{"revision":null,"url":"assets/api-settings-C__hxGX2.js"},{"revision":null,"url":"assets/index-C3ZstZ9f.css"},{"revision":null,"url":"assets/utils-CTg5uAYR.js"},{"revision":null,"url":"assets/columns-2-4fQcE4PF.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/sqlite-viewer-DzS8-nbv.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/audio-preview--VaB403K.js"},{"revision":null,"url":"assets/image-preview-CoiyMXuX.js"},{"revision":null,"url":"assets/tab-store-B3M9hjho.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-C8puYVyN.js"},{"revision":null,"url":"assets/api-client-Bn-Pi9k5.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/sql-completion-provider-C3cq9j99.js"},{"revision":null,"url":"assets/settings-store-B9axDbuA.js"},{"revision":null,"url":"assets/use-blob-url-BK7zshV7.js"},{"revision":null,"url":"assets/esm-K1XIK4vc.js"},{"revision":null,"url":"assets/settings-tab-l-SopAs4.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/port-forwarding-tab-DpoVwtlp.js"},{"revision":null,"url":"assets/lib-DQHnkzGy.js"},{"revision":null,"url":"assets/database-viewer-B7aIs1XX.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-BRZ7alnf.js"},{"revision":"d0f94ce046cf8cf09605ee7664dac557","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"a424156a79b9c1b907db93aa3180585a","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"b3a7f967560c9816492a1567b3f7f0dc","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":"a5d8a1acfc29c2a4c882a54ffc93def3","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"948e060affb598c339be40d69e1f6f9c","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
package/package.json
CHANGED
package/src/server/ws/chat.ts
CHANGED
|
@@ -5,6 +5,12 @@ import { logSessionEvent } from "../../services/session-log.service.ts";
|
|
|
5
5
|
import { listSessions as sdkListSessions } from "@anthropic-ai/claude-agent-sdk";
|
|
6
6
|
import { getSessionTitle } from "../../services/db.service.ts";
|
|
7
7
|
import type { ChatWsClientMessage, SessionPhase } from "../../types/api.ts";
|
|
8
|
+
import { startWatching, stopWatching, onFileChange } from "../../services/file-watcher.service.ts";
|
|
9
|
+
|
|
10
|
+
// Broadcast file changes to all WS clients for real-time editor reload
|
|
11
|
+
onFileChange((projectName, path) => {
|
|
12
|
+
broadcastGlobalEvent({ type: "file:changed", projectName, path });
|
|
13
|
+
});
|
|
8
14
|
|
|
9
15
|
const PING_INTERVAL_MS = 15_000; // 15s keepalive
|
|
10
16
|
const CLEANUP_TIMEOUT_MS = 5 * 60_000; // 5min after Claude done + no FE
|
|
@@ -184,6 +190,7 @@ function startCleanupTimer(sessionId: string): void {
|
|
|
184
190
|
entry.pingIntervals.clear();
|
|
185
191
|
for (const w of entry.teamWatchers.values()) w.cleanup();
|
|
186
192
|
entry.teamWatchers.clear();
|
|
193
|
+
if (entry.projectName) stopWatching(entry.projectName);
|
|
187
194
|
activeSessions.delete(sessionId);
|
|
188
195
|
}, CLEANUP_TIMEOUT_MS);
|
|
189
196
|
}
|
|
@@ -481,6 +488,7 @@ export const chatWebSocket = {
|
|
|
481
488
|
}
|
|
482
489
|
}).catch(() => {});
|
|
483
490
|
}
|
|
491
|
+
if (projectName && projectPath) startWatching(projectName, projectPath);
|
|
484
492
|
console.log(`[chat] session=${sessionId} FE reconnected (phase=${existing.phase}, clients=${existing.clients.size})`);
|
|
485
493
|
return;
|
|
486
494
|
}
|
|
@@ -500,6 +508,7 @@ export const chatWebSocket = {
|
|
|
500
508
|
};
|
|
501
509
|
activeSessions.set(sessionId, newEntry);
|
|
502
510
|
setupClientPing(newEntry, ws);
|
|
511
|
+
if (projectName && projectPath) startWatching(projectName, projectPath);
|
|
503
512
|
|
|
504
513
|
ws.send(JSON.stringify({
|
|
505
514
|
type: "session_state",
|
|
@@ -707,6 +716,7 @@ export const chatWebSocket = {
|
|
|
707
716
|
|
|
708
717
|
// Remove from clients Set + clear per-client ping
|
|
709
718
|
evictClient(entry, ws);
|
|
719
|
+
if (entry.projectName) stopWatching(entry.projectName);
|
|
710
720
|
console.log(`[chat] session=${sessionId} FE disconnected (phase=${entry.phase}, clients=${entry.clients.size})`);
|
|
711
721
|
|
|
712
722
|
if (entry.clients.size === 0) {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { watch, type FSWatcher } from "node:fs";
|
|
2
|
+
|
|
3
|
+
const IGNORE = new Set([
|
|
4
|
+
".git", "node_modules", "dist", "build", ".next",
|
|
5
|
+
".turbo", "coverage", "__pycache__", "bun.lock",
|
|
6
|
+
]);
|
|
7
|
+
const DEBOUNCE_MS = 500;
|
|
8
|
+
|
|
9
|
+
type ChangeCallback = (projectName: string, path: string) => void;
|
|
10
|
+
|
|
11
|
+
interface WatchEntry {
|
|
12
|
+
watcher: FSWatcher;
|
|
13
|
+
refCount: number;
|
|
14
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
15
|
+
pending: Set<string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const watchers = new Map<string, WatchEntry>();
|
|
19
|
+
let changeCallback: ChangeCallback | null = null;
|
|
20
|
+
|
|
21
|
+
/** Register callback for file change events */
|
|
22
|
+
export function onFileChange(cb: ChangeCallback): void {
|
|
23
|
+
changeCallback = cb;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function shouldIgnore(filePath: string): boolean {
|
|
27
|
+
const parts = filePath.split(/[/\\]/);
|
|
28
|
+
return parts.some((p) => IGNORE.has(p));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Start watching a project directory (ref-counted — safe to call multiple times) */
|
|
32
|
+
export function startWatching(projectName: string, projectPath: string): void {
|
|
33
|
+
const existing = watchers.get(projectName);
|
|
34
|
+
if (existing) {
|
|
35
|
+
existing.refCount++;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const watcher = watch(projectPath, { recursive: true }, (_event, filename) => {
|
|
41
|
+
if (!filename || shouldIgnore(filename)) return;
|
|
42
|
+
const entry = watchers.get(projectName);
|
|
43
|
+
if (!entry) return;
|
|
44
|
+
|
|
45
|
+
entry.pending.add(filename);
|
|
46
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
47
|
+
entry.timer = setTimeout(() => {
|
|
48
|
+
const paths = [...entry.pending];
|
|
49
|
+
entry.pending.clear();
|
|
50
|
+
for (const p of paths) changeCallback?.(projectName, p.replaceAll("\\", "/"));
|
|
51
|
+
}, DEBOUNCE_MS);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
watchers.set(projectName, { watcher, refCount: 1, pending: new Set() });
|
|
55
|
+
console.log(`[file-watcher] Started watching: ${projectName}`);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.warn(`[file-watcher] Failed to watch ${projectPath}: ${(e as Error).message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Decrement ref count — stops watcher when no clients remain */
|
|
62
|
+
export function stopWatching(projectName: string): void {
|
|
63
|
+
const entry = watchers.get(projectName);
|
|
64
|
+
if (!entry) return;
|
|
65
|
+
entry.refCount--;
|
|
66
|
+
if (entry.refCount <= 0) {
|
|
67
|
+
if (entry.timer) clearTimeout(entry.timer);
|
|
68
|
+
entry.watcher.close();
|
|
69
|
+
watchers.delete(projectName);
|
|
70
|
+
console.log(`[file-watcher] Stopped watching: ${projectName}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -75,9 +75,8 @@ function log(level: string, msg: string) {
|
|
|
75
75
|
const ts = new Date().toISOString();
|
|
76
76
|
const line = `[${ts}] [${level}] [supervisor] ${msg}\n`;
|
|
77
77
|
try { appendFileSync(logFile(), line); } catch {}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
78
|
+
// Always write supervisor logs to stderr so journalctl captures them
|
|
79
|
+
try { process.stderr.write(line); } catch {}
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
// ─── Backoff calc ──────────────────────────────────────────────────────
|
|
@@ -734,9 +733,20 @@ export function shutdown() {
|
|
|
734
733
|
if (upgradeDelayTimer) clearTimeout(upgradeDelayTimer);
|
|
735
734
|
if (cloudMonitorTimer) clearInterval(cloudMonitorTimer);
|
|
736
735
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
if (
|
|
736
|
+
// Use SIGKILL for children — SIGTERM leaves grandchildren (Claude SDK, etc.)
|
|
737
|
+
// alive, causing systemd to wait 90s then SIGKILL the entire cgroup
|
|
738
|
+
if (serverChild) {
|
|
739
|
+
log("INFO", `Killing server child (PID: ${serverChild.pid})`);
|
|
740
|
+
try { serverChild.kill("SIGKILL"); } catch {}
|
|
741
|
+
}
|
|
742
|
+
if (tunnelChild) {
|
|
743
|
+
log("INFO", `Killing tunnel child (PID: ${tunnelChild.pid})`);
|
|
744
|
+
try { tunnelChild.kill("SIGKILL"); } catch {}
|
|
745
|
+
}
|
|
746
|
+
if (adoptedTunnelPid) {
|
|
747
|
+
log("INFO", `Killing adopted tunnel (PID: ${adoptedTunnelPid})`);
|
|
748
|
+
try { process.kill(adoptedTunnelPid, "SIGKILL"); } catch {}
|
|
749
|
+
}
|
|
740
750
|
}
|
|
741
751
|
|
|
742
752
|
// ─── Main entry ────────────────────────────────────────────────────────
|
|
@@ -788,9 +798,19 @@ export async function runSupervisor(opts: {
|
|
|
788
798
|
_logFd = logFd;
|
|
789
799
|
_opts = { port: opts.port, host: opts.host, share: opts.share };
|
|
790
800
|
|
|
791
|
-
// Signal handlers
|
|
792
|
-
|
|
793
|
-
|
|
801
|
+
// Signal handlers — force exit after 5s if process.exit doesn't work
|
|
802
|
+
const forceShutdown = (signal: string) => {
|
|
803
|
+
log("INFO", `${signal} received`);
|
|
804
|
+
shutdown();
|
|
805
|
+
// Safety net: force kill self if process.exit(0) doesn't terminate
|
|
806
|
+
setTimeout(() => {
|
|
807
|
+
log("WARN", `Force exit after ${signal} — process.exit(0) did not terminate`);
|
|
808
|
+
try { process.kill(process.pid, "SIGKILL"); } catch {}
|
|
809
|
+
}, 5000).unref();
|
|
810
|
+
process.exit(0);
|
|
811
|
+
};
|
|
812
|
+
process.on("SIGTERM", () => forceShutdown("SIGTERM"));
|
|
813
|
+
process.on("SIGINT", () => forceShutdown("SIGINT"));
|
|
794
814
|
|
|
795
815
|
// SIGUSR2 = command file dispatch OR graceful server restart
|
|
796
816
|
process.on("SIGUSR2", () => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Loader2, FileWarning, Music } from "lucide-react";
|
|
2
|
+
import { useBlobUrl } from "./use-blob-url";
|
|
3
|
+
import { basename } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export function AudioPreview({ filePath, projectName }: { filePath: string; projectName: string }) {
|
|
6
|
+
const { blobUrl, error } = useBlobUrl(filePath, projectName);
|
|
7
|
+
|
|
8
|
+
if (error) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
|
|
11
|
+
<FileWarning className="size-10 text-text-subtle" />
|
|
12
|
+
<p className="text-sm">Failed to load audio.</p>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
if (!blobUrl) {
|
|
17
|
+
return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
|
|
18
|
+
}
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex flex-col items-center justify-center h-full gap-4 p-4 bg-surface">
|
|
21
|
+
<Music className="size-16 text-text-subtle" />
|
|
22
|
+
<p className="text-sm text-text-secondary truncate max-w-xs">{basename(filePath)}</p>
|
|
23
|
+
<audio src={blobUrl} controls className="w-full max-w-md" />
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { useEffect, useState, useCallback, useRef, useMemo, memo, lazy, Suspense } from "react";
|
|
2
2
|
import Editor, { type OnMount } from "@monaco-editor/react";
|
|
3
3
|
import type * as MonacoType from "monaco-editor";
|
|
4
|
-
import { api, projectUrl
|
|
4
|
+
import { api, projectUrl } from "@/lib/api-client";
|
|
5
5
|
import { useShallow } from "zustand/react/shallow";
|
|
6
6
|
import { useTabStore } from "@/stores/tab-store";
|
|
7
7
|
import { usePanelStore } from "@/stores/panel-store";
|
|
8
8
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
9
9
|
import { basename } from "@/lib/utils";
|
|
10
10
|
import { useMonacoTheme } from "@/lib/use-monaco-theme";
|
|
11
|
-
import { Loader2, FileWarning,
|
|
11
|
+
import { Loader2, FileWarning, Play, Database } from "lucide-react";
|
|
12
12
|
import { EditorBreadcrumb } from "./editor-breadcrumb";
|
|
13
13
|
import { EditorToolbar } from "./editor-toolbar";
|
|
14
14
|
import { SaveAsDialog } from "./save-as-dialog";
|
|
@@ -19,9 +19,17 @@ const MarkdownRenderer = lazy(() =>
|
|
|
19
19
|
import("@/components/shared/markdown-renderer").then((m) => ({ default: m.MarkdownRenderer }))
|
|
20
20
|
);
|
|
21
21
|
const CsvPreview = lazy(() => import("./csv-preview").then((m) => ({ default: m.CsvPreview })));
|
|
22
|
+
const ImagePreview = lazy(() => import("./image-preview").then((m) => ({ default: m.ImagePreview })));
|
|
23
|
+
const PdfPreview = lazy(() => import("./pdf-preview").then((m) => ({ default: m.PdfPreview })));
|
|
24
|
+
const VideoPreview = lazy(() => import("./video-preview").then((m) => ({ default: m.VideoPreview })));
|
|
25
|
+
const AudioPreview = lazy(() => import("./audio-preview").then((m) => ({ default: m.AudioPreview })));
|
|
22
26
|
|
|
23
27
|
/** Image extensions renderable inline */
|
|
24
28
|
const IMAGE_EXTS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "ico"]);
|
|
29
|
+
/** Video extensions playable inline */
|
|
30
|
+
const VIDEO_EXTS = new Set(["mp4", "webm", "mov", "ogg", "avi", "mkv"]);
|
|
31
|
+
/** Audio extensions playable inline */
|
|
32
|
+
const AUDIO_EXTS = new Set(["mp3", "wav", "flac", "aac", "m4a", "wma"]);
|
|
25
33
|
/** SQLite extensions — redirect to sqlite viewer */
|
|
26
34
|
const SQLITE_EXTS = new Set(["db", "sqlite", "sqlite3"]);
|
|
27
35
|
|
|
@@ -75,6 +83,8 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
|
|
|
75
83
|
const ext = filePath ? getFileExt(filePath) : "";
|
|
76
84
|
const isImage = IMAGE_EXTS.has(ext);
|
|
77
85
|
const isPdf = ext === "pdf";
|
|
86
|
+
const isVideo = VIDEO_EXTS.has(ext);
|
|
87
|
+
const isAudio = AUDIO_EXTS.has(ext);
|
|
78
88
|
const isSqlite = SQLITE_EXTS.has(ext);
|
|
79
89
|
const isMarkdown = ext === "md" || ext === "mdx";
|
|
80
90
|
const isCsv = ext === "csv";
|
|
@@ -215,7 +225,7 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
|
|
|
215
225
|
}
|
|
216
226
|
if (!filePath) return;
|
|
217
227
|
if (!isExternalFile && !projectName) return;
|
|
218
|
-
if (isImage || isPdf) { setLoading(false); return; }
|
|
228
|
+
if (isImage || isPdf || isVideo || isAudio) { setLoading(false); return; }
|
|
219
229
|
|
|
220
230
|
setLoading(true);
|
|
221
231
|
setError(null);
|
|
@@ -240,6 +250,29 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
|
|
|
240
250
|
return () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current); };
|
|
241
251
|
}, [filePath, projectName, isImage, isPdf, isExternalFile, isUntitled]);
|
|
242
252
|
|
|
253
|
+
// Real-time reload: listen for file:changed WS events, re-fetch if editor is clean
|
|
254
|
+
const unsavedRef = useRef(unsaved);
|
|
255
|
+
unsavedRef.current = unsaved;
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (!filePath || !projectName || inlineContent != null || isUntitled) return;
|
|
258
|
+
const handler = (e: Event) => {
|
|
259
|
+
const detail = (e as CustomEvent).detail;
|
|
260
|
+
if (detail.projectName !== projectName || detail.path !== filePath) return;
|
|
261
|
+
if (unsavedRef.current) return; // don't overwrite unsaved changes
|
|
262
|
+
const readUrl = isExternalFile
|
|
263
|
+
? `/api/fs/read?path=${encodeURIComponent(filePath)}`
|
|
264
|
+
: `${projectUrl(projectName)}/files/read?path=${encodeURIComponent(filePath)}`;
|
|
265
|
+
api.get<{ content: string; encoding?: string }>(readUrl).then((data) => {
|
|
266
|
+
if (data.content === latestContentRef.current) return; // skip if unchanged (e.g. self-save)
|
|
267
|
+
setContent(data.content);
|
|
268
|
+
latestContentRef.current = data.content;
|
|
269
|
+
if (data.encoding) setEncoding(data.encoding);
|
|
270
|
+
}).catch(() => {});
|
|
271
|
+
};
|
|
272
|
+
window.addEventListener("file:changed", handler);
|
|
273
|
+
return () => window.removeEventListener("file:changed", handler);
|
|
274
|
+
}, [filePath, projectName, isExternalFile, inlineContent, isUntitled]);
|
|
275
|
+
|
|
243
276
|
// Update tab title unsaved indicator
|
|
244
277
|
useEffect(() => {
|
|
245
278
|
if (!ownTab) return;
|
|
@@ -428,8 +461,10 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
|
|
|
428
461
|
);
|
|
429
462
|
}
|
|
430
463
|
|
|
431
|
-
if (isImage) return <ImagePreview filePath={filePath!} projectName={projectName!}
|
|
432
|
-
if (isPdf) return <PdfPreview filePath={filePath!} projectName={projectName!}
|
|
464
|
+
if (isImage) return <Suspense fallback={<LoadingSpinner />}><ImagePreview filePath={filePath!} projectName={projectName!} /></Suspense>;
|
|
465
|
+
if (isPdf) return <Suspense fallback={<LoadingSpinner />}><PdfPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
|
|
466
|
+
if (isVideo) return <Suspense fallback={<LoadingSpinner />}><VideoPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
|
|
467
|
+
if (isAudio) return <Suspense fallback={<LoadingSpinner />}><AudioPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
|
|
433
468
|
|
|
434
469
|
if (encoding === "base64") {
|
|
435
470
|
return (
|
|
@@ -557,6 +592,10 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
|
|
|
557
592
|
);
|
|
558
593
|
});
|
|
559
594
|
|
|
595
|
+
function LoadingSpinner() {
|
|
596
|
+
return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
|
|
597
|
+
}
|
|
598
|
+
|
|
560
599
|
function MarkdownPreview({ content }: { content: string }) {
|
|
561
600
|
return (
|
|
562
601
|
<Suspense fallback={<div className="animate-pulse h-4 bg-muted rounded m-4" />}>
|
|
@@ -565,79 +604,3 @@ function MarkdownPreview({ content }: { content: string }) {
|
|
|
565
604
|
);
|
|
566
605
|
}
|
|
567
606
|
|
|
568
|
-
function ImagePreview({ filePath, projectName }: { filePath: string; projectName: string }) {
|
|
569
|
-
const [blobUrl, setBlobUrl] = useState<string | null>(null);
|
|
570
|
-
const [error, setError] = useState(false);
|
|
571
|
-
|
|
572
|
-
useEffect(() => {
|
|
573
|
-
let revoke: string | undefined;
|
|
574
|
-
const url = `${projectUrl(projectName)}/files/raw?path=${encodeURIComponent(filePath)}`;
|
|
575
|
-
const token = getAuthToken();
|
|
576
|
-
fetch(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} })
|
|
577
|
-
.then((r) => { if (!r.ok) throw new Error("Failed"); return r.blob(); })
|
|
578
|
-
.then((blob) => { const u = URL.createObjectURL(blob); revoke = u; setBlobUrl(u); })
|
|
579
|
-
.catch(() => setError(true));
|
|
580
|
-
return () => { if (revoke) URL.revokeObjectURL(revoke); };
|
|
581
|
-
}, [filePath, projectName]);
|
|
582
|
-
|
|
583
|
-
if (error) {
|
|
584
|
-
return (
|
|
585
|
-
<div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
|
|
586
|
-
<FileWarning className="size-10 text-text-subtle" />
|
|
587
|
-
<p className="text-sm">Failed to load image.</p>
|
|
588
|
-
</div>
|
|
589
|
-
);
|
|
590
|
-
}
|
|
591
|
-
if (!blobUrl) {
|
|
592
|
-
return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
|
|
593
|
-
}
|
|
594
|
-
return (
|
|
595
|
-
<div className="flex items-center justify-center h-full p-4 bg-surface overflow-auto">
|
|
596
|
-
<img src={blobUrl} alt={filePath} className="max-w-full max-h-full object-contain" />
|
|
597
|
-
</div>
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function PdfPreview({ filePath, projectName }: { filePath: string; projectName: string }) {
|
|
602
|
-
const [blobUrl, setBlobUrl] = useState<string | null>(null);
|
|
603
|
-
const [error, setError] = useState(false);
|
|
604
|
-
|
|
605
|
-
useEffect(() => {
|
|
606
|
-
let revoke: string | undefined;
|
|
607
|
-
const url = `${projectUrl(projectName)}/files/raw?path=${encodeURIComponent(filePath)}`;
|
|
608
|
-
const token = getAuthToken();
|
|
609
|
-
fetch(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} })
|
|
610
|
-
.then((r) => { if (!r.ok) throw new Error("Failed"); return r.blob(); })
|
|
611
|
-
.then((blob) => {
|
|
612
|
-
const u = URL.createObjectURL(new Blob([blob], { type: "application/pdf" }));
|
|
613
|
-
revoke = u; setBlobUrl(u);
|
|
614
|
-
})
|
|
615
|
-
.catch(() => setError(true));
|
|
616
|
-
return () => { if (revoke) URL.revokeObjectURL(revoke); };
|
|
617
|
-
}, [filePath, projectName]);
|
|
618
|
-
|
|
619
|
-
const openInNewTab = useCallback(() => { if (blobUrl) window.open(blobUrl, "_blank"); }, [blobUrl]);
|
|
620
|
-
|
|
621
|
-
if (error) {
|
|
622
|
-
return (
|
|
623
|
-
<div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
|
|
624
|
-
<FileWarning className="size-10 text-text-subtle" />
|
|
625
|
-
<p className="text-sm">Failed to load PDF.</p>
|
|
626
|
-
</div>
|
|
627
|
-
);
|
|
628
|
-
}
|
|
629
|
-
if (!blobUrl) {
|
|
630
|
-
return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
|
|
631
|
-
}
|
|
632
|
-
return (
|
|
633
|
-
<div className="flex flex-col h-full">
|
|
634
|
-
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border bg-background shrink-0">
|
|
635
|
-
<span className="text-xs text-text-secondary truncate">{filePath}</span>
|
|
636
|
-
<button onClick={openInNewTab} className="flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary transition-colors">
|
|
637
|
-
<ExternalLink className="size-3" /> Open in new tab
|
|
638
|
-
</button>
|
|
639
|
-
</div>
|
|
640
|
-
<iframe src={blobUrl} title={filePath} className="flex-1 w-full border-none" />
|
|
641
|
-
</div>
|
|
642
|
-
);
|
|
643
|
-
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Loader2, FileWarning } from "lucide-react";
|
|
2
|
+
import { useBlobUrl } from "./use-blob-url";
|
|
3
|
+
|
|
4
|
+
export function ImagePreview({ filePath, projectName }: { filePath: string; projectName: string }) {
|
|
5
|
+
const { blobUrl, error } = useBlobUrl(filePath, projectName);
|
|
6
|
+
|
|
7
|
+
if (error) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
|
|
10
|
+
<FileWarning className="size-10 text-text-subtle" />
|
|
11
|
+
<p className="text-sm">Failed to load image.</p>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
if (!blobUrl) {
|
|
16
|
+
return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
|
|
17
|
+
}
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex items-center justify-center h-full p-4 bg-surface overflow-auto">
|
|
20
|
+
<img src={blobUrl} alt={filePath} className="max-w-full max-h-full object-contain" />
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { Loader2, FileWarning, ExternalLink } from "lucide-react";
|
|
3
|
+
import { useBlobUrl } from "./use-blob-url";
|
|
4
|
+
|
|
5
|
+
export function PdfPreview({ filePath, projectName }: { filePath: string; projectName: string }) {
|
|
6
|
+
const { blobUrl, error } = useBlobUrl(filePath, projectName, "application/pdf");
|
|
7
|
+
|
|
8
|
+
const openInNewTab = useCallback(() => { if (blobUrl) window.open(blobUrl, "_blank"); }, [blobUrl]);
|
|
9
|
+
|
|
10
|
+
if (error) {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
|
|
13
|
+
<FileWarning className="size-10 text-text-subtle" />
|
|
14
|
+
<p className="text-sm">Failed to load PDF.</p>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (!blobUrl) {
|
|
19
|
+
return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
<div className="flex flex-col h-full">
|
|
23
|
+
<div className="flex items-center justify-between px-3 py-1.5 border-b border-border bg-background shrink-0">
|
|
24
|
+
<span className="text-xs text-text-secondary truncate">{filePath}</span>
|
|
25
|
+
<button onClick={openInNewTab} className="flex items-center gap-1 text-xs text-text-secondary hover:text-text-primary transition-colors">
|
|
26
|
+
<ExternalLink className="size-3" /> Open in new tab
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
<iframe src={blobUrl} title={filePath} className="flex-1 w-full border-none" />
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|