@hienlh/ppm 0.13.62 → 0.13.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/http-api.md +2 -1
  4. package/bun.lock +2170 -0
  5. package/bunfig.toml +2 -0
  6. package/dist/web/assets/audio-preview-Bog1sIoF.js +1 -0
  7. package/dist/web/assets/{chat-tab-nNh5rLB2.js → chat-tab-B-uVAh4d.js} +7 -7
  8. package/dist/web/assets/code-editor-cDv3opsJ.js +8 -0
  9. package/dist/web/assets/{conflict-editor-B4f2ilts.js → conflict-editor-D5sEfbcX.js} +3 -3
  10. package/dist/web/assets/database-viewer-BGBVsG5J.js +1 -0
  11. package/dist/web/assets/diff-viewer-B-O1mvHO.js +4 -0
  12. package/dist/web/assets/docx-preview-ByzSlSgn.js +12 -0
  13. package/dist/web/assets/{extension-webview-CUPyfczi.js → extension-webview-0qfU1r7z.js} +1 -1
  14. package/dist/web/assets/git-log-panel-C1T8bav0.js +1 -0
  15. package/dist/web/assets/{glide-data-grid-1rBqhi2J.js → glide-data-grid-DV8ht1BP.js} +3 -3
  16. package/dist/web/assets/image-preview-Dbo7SAVb.js +1 -0
  17. package/dist/web/assets/{index-DwvSM9vu.css → index-BuXdQZjD.css} +1 -1
  18. package/dist/web/assets/index-DU_JZ5MY.js +27 -0
  19. package/dist/web/assets/keybindings-store-0FUOwc9I.js +1 -0
  20. package/dist/web/assets/{markdown-renderer-BS-EgLZm.js → markdown-renderer-D-QbsfIC.js} +1 -1
  21. package/dist/web/assets/notification-store-bwd1UKbs.js +1 -0
  22. package/dist/web/assets/pdf-preview-DV96VPTb.js +1 -0
  23. package/dist/web/assets/port-forwarding-tab-C4OYC71C.js +1 -0
  24. package/dist/web/assets/{postgres-viewer-BZ7RHn6E.js → postgres-viewer-hb-_twEU.js} +2 -2
  25. package/dist/web/assets/{settings-tab-B3U6o2Cv.js → settings-tab-BUCIqVAl.js} +1 -1
  26. package/dist/web/assets/sql-query-editor-C7YgtDR3.js +3 -0
  27. package/dist/web/assets/sqlite-viewer-z3pGFSje.js +1 -0
  28. package/dist/web/assets/system-monitor-tab-Bj6pcRmV.js +1 -0
  29. package/dist/web/assets/{terminal-tab-BEGaXjCj.js → terminal-tab-DbxLHofN.js} +2 -2
  30. package/dist/web/assets/video-preview-DylSBAzo.js +1 -0
  31. package/dist/web/index.html +2 -2
  32. package/dist/web/sw.js +1 -1
  33. package/docs/codebase-summary.md +2 -1
  34. package/docs/project-changelog.md +10 -1
  35. package/package.json +2 -1
  36. package/src/index.ts +0 -0
  37. package/src/server/routes/files.ts +20 -0
  38. package/src/server/routes/fs-browse.ts +16 -0
  39. package/src/web/components/database/database-viewer.tsx +13 -2
  40. package/src/web/components/database/sql-query-editor.tsx +8 -10
  41. package/src/web/components/editor/code-editor.tsx +5 -2
  42. package/src/web/components/editor/docx-preview.tsx +92 -0
  43. package/src/web/components/layout/command-palette-filter-chips.tsx +61 -0
  44. package/src/web/components/layout/command-palette.tsx +54 -7
  45. package/dist/web/assets/audio-preview-B3rIhhbi.js +0 -1
  46. package/dist/web/assets/code-editor-CMSwWKuR.js +0 -8
  47. package/dist/web/assets/database-viewer-D-28-BdV.js +0 -1
  48. package/dist/web/assets/diff-viewer-Bv8DUUIR.js +0 -4
  49. package/dist/web/assets/git-log-panel-CFLQD23g.js +0 -1
  50. package/dist/web/assets/image-preview-DHrZPetH.js +0 -1
  51. package/dist/web/assets/index-CkQX29w3.js +0 -27
  52. package/dist/web/assets/keybindings-store-Bigfs0Ss.js +0 -1
  53. package/dist/web/assets/notification-store-NXr7Mag_.js +0 -1
  54. package/dist/web/assets/pdf-preview-CeN9W7kd.js +0 -1
  55. package/dist/web/assets/port-forwarding-tab-CYUcx2_j.js +0 -1
  56. package/dist/web/assets/sql-query-editor-CfDG3duv.js +0 -3
  57. package/dist/web/assets/sqlite-viewer-ofYBAsHX.js +0 -1
  58. package/dist/web/assets/system-monitor-tab-By-j_TO1.js +0 -1
  59. package/dist/web/assets/video-preview-CHOiQ3I9.js +0 -1
  60. /package/dist/web/assets/{vendor-xterm-t3d5xZdz.js → vendor-xterm-msgiskDb.js} +0 -0
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":"307b67ad2eab6a465427cd64440bbe8a","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/react-CfveccaI.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/api-settings-uQKmeGkl.js"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/project-store-BnvrVKBw.js"},{"revision":null,"url":"assets/table-2wDtM4_B.js"},{"revision":null,"url":"assets/sql-query-editor-CfDG3duv.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-Ar00Wbhd.js"},{"revision":null,"url":"assets/arrow-down-D825m4vm.js"},{"revision":null,"url":"assets/use-blob-url-DCUIEzjB.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/settings-tab-B3U6o2Cv.js"},{"revision":null,"url":"assets/notification-store-NXr7Mag_.js"},{"revision":null,"url":"assets/chevron-down-BMo4cBth.js"},{"revision":null,"url":"assets/dist-DeY41KFi.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-Q4ssDdib.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/radar-KQ55EAFF-kq5v4OKX.js"},{"revision":null,"url":"assets/system-monitor-tab-By-j_TO1.js"},{"revision":null,"url":"assets/settings-store-CSDOihqv.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/info-3K5VOQVL-CWKw4e0V.js"},{"revision":null,"url":"assets/glide-data-grid-nthEL3fk.css"},{"revision":null,"url":"assets/esm-JPvheKDJ.js"},{"revision":null,"url":"assets/lib-DrypSCq8.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/panel-store-B1pOXkyS.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/wifi-LJEyIdXf.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/database-viewer-D-28-BdV.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/pdf-preview-CeN9W7kd.js"},{"revision":null,"url":"assets/chevron-right-CD8e6Aj4.js"},{"revision":null,"url":"assets/vendor-mermaid-DCie7hiR.js"},{"revision":null,"url":"assets/video-preview-CHOiQ3I9.js"},{"revision":null,"url":"assets/port-forwarding-tab-CYUcx2_j.js"},{"revision":null,"url":"assets/trash-2-DkIfBY8d.js"},{"revision":null,"url":"assets/text-wrap-AZErifCu.js"},{"revision":null,"url":"assets/conflict-editor-B4f2ilts.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/globe-CQ8NAYvi.js"},{"revision":null,"url":"assets/utils-E0yyGxXt.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/use-monaco-theme-qx6SfVRk.js"},{"revision":null,"url":"assets/input-B78ol0hV.js"},{"revision":null,"url":"assets/code-DiNmA3eR.js"},{"revision":null,"url":"assets/refresh-cw-CRD2qr4U.js"},{"revision":null,"url":"assets/postgres-viewer-BZ7RHn6E.js"},{"revision":null,"url":"assets/search-D90WJ5fo.js"},{"revision":null,"url":"assets/vendor-ui-UXCWAcmi.js"},{"revision":null,"url":"assets/terminal-tab-BEGaXjCj.js"},{"revision":null,"url":"assets/file-exclamation-point-B__2Hrd6.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/data-grid-types-C29KDkZJ.js"},{"revision":null,"url":"assets/katex-DUj5OG1J.js"},{"revision":null,"url":"assets/index-DwvSM9vu.css"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/number-overlay-editor-DS-qf63L.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/arrow-up-Rcw6_KKu.js"},{"revision":null,"url":"assets/ai-settings-section-BH2UOQH-.js"},{"revision":null,"url":"assets/database-Dc8mr-dP.js"},{"revision":null,"url":"assets/eye-off-BacF7RVS.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/index-CkQX29w3.js"},{"revision":null,"url":"assets/keybindings-store-Bigfs0Ss.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/vendor-xterm-t3d5xZdz.js"},{"revision":null,"url":"assets/image-preview-DHrZPetH.js"},{"revision":null,"url":"assets/api-client-DiZgVOok.js"},{"revision":null,"url":"assets/chat-tab-nNh5rLB2.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/x-DfF6D5Js.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-DChODgHt.js"},{"revision":null,"url":"assets/diff-viewer-Bv8DUUIR.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/data-grid-overlay-editor-CmduzuPM.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-2a0r4GHr.js"},{"revision":null,"url":"assets/markdown-renderer-BS-EgLZm.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/code-editor-CMSwWKuR.js"},{"revision":null,"url":"assets/audio-preview-B3rIhhbi.js"},{"revision":null,"url":"assets/sparkles-KCOEy7QI.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DLKD1Xjj.js"},{"revision":null,"url":"assets/csv-parser-D8VHWVA6.js"},{"revision":null,"url":"assets/csv-preview-DgArUJhd.js"},{"revision":null,"url":"assets/tab-store-DzftzxTL.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/git-log-panel-CFLQD23g.js"},{"revision":null,"url":"assets/extension-webview-CUPyfczi.js"},{"revision":null,"url":"assets/glide-data-grid-1rBqhi2J.js"},{"revision":null,"url":"assets/dist-PPUhQONj.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/sqlite-viewer-ofYBAsHX.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":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"26dccd02a2ef7522892015154f5e3680","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.svg`,badge:`/icon-192.svg`,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":"df3ac56b54813e3e0cd57f64f3c6ce7f","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/vendor-xterm-msgiskDb.js"},{"revision":null,"url":"assets/react-CfveccaI.js"},{"revision":null,"url":"assets/extension-webview-0qfU1r7z.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/api-settings-uQKmeGkl.js"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/project-store-BnvrVKBw.js"},{"revision":null,"url":"assets/sqlite-viewer-z3pGFSje.js"},{"revision":null,"url":"assets/keybindings-store-0FUOwc9I.js"},{"revision":null,"url":"assets/table-2wDtM4_B.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-Ar00Wbhd.js"},{"revision":null,"url":"assets/arrow-down-D825m4vm.js"},{"revision":null,"url":"assets/notification-store-bwd1UKbs.js"},{"revision":null,"url":"assets/markdown-renderer-D-QbsfIC.js"},{"revision":null,"url":"assets/use-blob-url-DCUIEzjB.js"},{"revision":null,"url":"assets/settings-tab-BUCIqVAl.js"},{"revision":null,"url":"assets/KaTeX_Main-Regular-B22Nviop.woff2"},{"revision":null,"url":"assets/conflict-editor-D5sEfbcX.js"},{"revision":null,"url":"assets/chevron-down-BMo4cBth.js"},{"revision":null,"url":"assets/dist-DeY41KFi.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-Q4ssDdib.js"},{"revision":null,"url":"assets/github.min-D2BCvnWf.css"},{"revision":null,"url":"assets/radar-KQ55EAFF-kq5v4OKX.js"},{"revision":null,"url":"assets/settings-store-CSDOihqv.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/info-3K5VOQVL-CWKw4e0V.js"},{"revision":null,"url":"assets/glide-data-grid-nthEL3fk.css"},{"revision":null,"url":"assets/esm-JPvheKDJ.js"},{"revision":null,"url":"assets/index-BuXdQZjD.css"},{"revision":null,"url":"assets/lib-DrypSCq8.js"},{"revision":null,"url":"assets/database-viewer-BGBVsG5J.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/panel-store-B1pOXkyS.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.js"},{"revision":null,"url":"assets/wifi-LJEyIdXf.js"},{"revision":null,"url":"assets/sql-query-editor-C7YgtDR3.js"},{"revision":null,"url":"assets/KaTeX_Main-Italic-NWA7e6Wa.woff2"},{"revision":null,"url":"assets/port-forwarding-tab-C4OYC71C.js"},{"revision":null,"url":"assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2"},{"revision":null,"url":"assets/chevron-right-CD8e6Aj4.js"},{"revision":null,"url":"assets/vendor-mermaid-DCie7hiR.js"},{"revision":null,"url":"assets/trash-2-DkIfBY8d.js"},{"revision":null,"url":"assets/text-wrap-AZErifCu.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/system-monitor-tab-Bj6pcRmV.js"},{"revision":null,"url":"assets/globe-CQ8NAYvi.js"},{"revision":null,"url":"assets/glide-data-grid-DV8ht1BP.js"},{"revision":null,"url":"assets/utils-E0yyGxXt.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/use-monaco-theme-qx6SfVRk.js"},{"revision":null,"url":"assets/input-B78ol0hV.js"},{"revision":null,"url":"assets/code-DiNmA3eR.js"},{"revision":null,"url":"assets/refresh-cw-CRD2qr4U.js"},{"revision":null,"url":"assets/search-D90WJ5fo.js"},{"revision":null,"url":"assets/vendor-ui-UXCWAcmi.js"},{"revision":null,"url":"assets/file-exclamation-point-B__2Hrd6.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/data-grid-types-C29KDkZJ.js"},{"revision":null,"url":"assets/katex-DUj5OG1J.js"},{"revision":null,"url":"assets/diff-viewer-B-O1mvHO.js"},{"revision":null,"url":"assets/github-dark-dimmed.min-BrpRStFV.css"},{"revision":null,"url":"assets/image-preview-Dbo7SAVb.js"},{"revision":null,"url":"assets/number-overlay-editor-DS-qf63L.js"},{"revision":null,"url":"assets/video-preview-DylSBAzo.js"},{"revision":null,"url":"assets/audio-preview-Bog1sIoF.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2"},{"revision":null,"url":"assets/KaTeX_Script-Regular-D3wIWfF6.woff2"},{"revision":null,"url":"assets/arrow-up-Rcw6_KKu.js"},{"revision":null,"url":"assets/ai-settings-section-BH2UOQH-.js"},{"revision":null,"url":"assets/database-Dc8mr-dP.js"},{"revision":null,"url":"assets/eye-off-BacF7RVS.js"},{"revision":null,"url":"assets/git-log-panel-C1T8bav0.js"},{"revision":null,"url":"assets/KaTeX_Main-Bold-Cx986IdX.woff2"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/api-client-DiZgVOok.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/x-DfF6D5Js.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-DChODgHt.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/data-grid-overlay-editor-CmduzuPM.js"},{"revision":null,"url":"assets/index-DU_JZ5MY.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/docx-preview-ByzSlSgn.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-2a0r4GHr.js"},{"revision":null,"url":"assets/pdf-preview-DV96VPTb.js"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/terminal-tab-DbxLHofN.js"},{"revision":null,"url":"assets/sparkles-KCOEy7QI.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-DLKD1Xjj.js"},{"revision":null,"url":"assets/code-editor-cDv3opsJ.js"},{"revision":null,"url":"assets/postgres-viewer-hb-_twEU.js"},{"revision":null,"url":"assets/csv-parser-D8VHWVA6.js"},{"revision":null,"url":"assets/csv-preview-DgArUJhd.js"},{"revision":null,"url":"assets/tab-store-DzftzxTL.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/chat-tab-B-uVAh4d.js"},{"revision":null,"url":"assets/dist-PPUhQONj.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"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":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"26dccd02a2ef7522892015154f5e3680","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.svg`,badge:`/icon-192.svg`,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||`/`)}))});
@@ -210,7 +210,8 @@ src/
210
210
  │ │ ├── draggable-tab.tsx # Draggable tab with context menu, rename, connection color
211
211
  │ │ ├── tab-content.tsx # Router for tab content (v0.9.85+: fallback guards)
212
212
  │ │ ├── split-drop-overlay.tsx # Drop zone for tab splitting
213
- │ │ ├── command-palette.tsx # Global command palette (Shift+Shift, DB table search)
213
+ │ │ ├── command-palette.tsx # Global command palette (Shift+Shift, DB table search, filter chips for Actions/Files/DB/Filesystem)
214
+ │ │ ├── command-palette-filter-chips.tsx # Presentational filter chip bar — group toggle buttons with count badges (hidden when ≤1 group)
214
215
  │ │ ├── add-project-form.tsx # Modal form to add projects
215
216
  │ │ ├── mobile-nav.tsx # Bottom navigation for mobile (v0.9.85+: fallback guards)
216
217
  │ │ └── mobile-drawer.tsx # Mobile overlay drawer
@@ -20,9 +20,18 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
20
20
 
21
21
  ---
22
22
 
23
- ## [Unreleased] — Resource Monitor, Lazy-Load File Tree + Palette Index, Session Tagging, File Compare, Draft Messages, Jira Debug Session Redesign, Frontend Memory Optimization, Git-Graph Enhancements
23
+ ## [Unreleased] — Resource Monitor, Lazy-Load File Tree + Palette Index, Session Tagging, File Compare, Draft Messages, Jira Debug Session Redesign, Frontend Memory Optimization, Git-Graph Enhancements, Command Palette Filter Chips
24
24
 
25
25
  ### Added
26
+ - **Command Palette Filter Chips** — Filter palette results by type with toggle chips
27
+ - Chips appear above results when 2+ groups are available (Actions, Files, Database, Filesystem)
28
+ - Each chip shows group label, icon, and live result count for the current query
29
+ - Toggling a chip includes/excludes that group; multiple chips can be active simultaneously
30
+ - Chips hidden when only one group has results (no filtering value)
31
+ - Mobile-friendly: 44px touch targets, horizontally scrollable chip row
32
+ - Files: `command-palette-filter-chips.tsx` (new presentational component), `command-palette.tsx` (filter state + `availableGroups` + `groupCounts` + `displayItems` logic)
33
+
34
+
26
35
  - **System Resource Monitor** — Real-time process monitoring with SSE streaming, sidebar status bar, and dedicated System Monitor tab
27
36
  - Backend: `ResourceMonitorService` polls `ps` command every 3s, builds process tree from PPM root PID, categorizes processes (server/terminal/ai-tool/build/unknown), maintains 30-min ring buffer (600 snapshots)
28
37
  - Backend: SSE streaming route at `GET /api/system/resources/stream` with client count cap (max 5), manual reconnect with exponential backoff
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.13.62",
3
+ "version": "0.13.64",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -67,6 +67,7 @@
67
67
  "js-yaml": "^4.1.1",
68
68
  "katex": "^0.16.45",
69
69
  "lucide-react": "^0.577.0",
70
+ "mammoth": "^1.12.0",
70
71
  "mermaid": "^11.13.0",
71
72
  "monaco-editor": "0.55.1",
72
73
  "next-themes": "^0.4.6",
package/src/index.ts CHANGED
File without changes
@@ -5,6 +5,7 @@ import { fileService, SecurityError, NotFoundError, ValidationError } from "../.
5
5
  import { readSystemFile } from "../../services/fs-browse.service.ts";
6
6
  import { ok, err } from "../../types/api.ts";
7
7
  import { errorStatus } from "../helpers/error-status.ts";
8
+ import mammoth from "mammoth";
8
9
 
9
10
  type Env = { Variables: { projectPath: string; projectName: string } };
10
11
 
@@ -93,6 +94,25 @@ fileRoutes.get("/raw", (c) => {
93
94
  }
94
95
  });
95
96
 
97
+ /** GET /files/docx-html?path=... — convert project .docx to HTML via mammoth */
98
+ fileRoutes.get("/docx-html", async (c) => {
99
+ try {
100
+ const projectPath = c.get("projectPath");
101
+ const filePath = c.req.query("path");
102
+ if (!filePath) return c.json(err("Missing query parameter: path"), 400);
103
+
104
+ const absPath = resolve(projectPath, filePath);
105
+ if (!absPath.startsWith(projectPath)) return c.json(err("Access denied"), 403);
106
+ if (!existsSync(absPath)) return c.json(err("File not found"), 404);
107
+
108
+ const buffer = await Bun.file(absPath).arrayBuffer();
109
+ const result = await mammoth.convertToHtml({ arrayBuffer: buffer });
110
+ return c.json(ok({ html: result.value, warnings: result.messages }));
111
+ } catch (e) {
112
+ return c.json(err((e as Error).message), errorStatus(e));
113
+ }
114
+ });
115
+
96
116
  /** GET /files/read?path=... */
97
117
  fileRoutes.get("/read", (c) => {
98
118
  try {
@@ -2,6 +2,7 @@ import { Hono } from "hono";
2
2
  import { existsSync, mkdirSync, rmSync, statSync } from "fs";
3
3
  import { resolve } from "path";
4
4
  import { $ } from "bun";
5
+ import mammoth from "mammoth";
5
6
  import {
6
7
  browse,
7
8
  list,
@@ -75,6 +76,21 @@ fsBrowseRoutes.get("/raw", (c) => {
75
76
  }
76
77
  });
77
78
 
79
+ /** GET /api/fs/docx-html?path=/some/file.docx — convert .docx to HTML via mammoth */
80
+ fsBrowseRoutes.get("/docx-html", async (c) => {
81
+ try {
82
+ const filePath = c.req.query("path");
83
+ if (!filePath) return c.json(err("path is required"), 400);
84
+ if (!existsSync(filePath)) return c.json(err("File not found"), 404);
85
+
86
+ const buffer = await Bun.file(filePath).arrayBuffer();
87
+ const result = await mammoth.convertToHtml({ arrayBuffer: buffer });
88
+ return c.json(ok({ html: result.value, warnings: result.messages }));
89
+ } catch (e) {
90
+ return c.json(err((e as Error).message), errorStatus(e));
91
+ }
92
+ });
93
+
78
94
  /** DELETE /api/fs/rmdir — delete a directory { path } */
79
95
  fsBrowseRoutes.delete("/rmdir", async (c) => {
80
96
  try {
@@ -1,6 +1,7 @@
1
1
  import { useState, useMemo, useEffect, useRef, useCallback } from "react";
2
2
  import { Database, RefreshCw, GripHorizontal, Loader2 } from "lucide-react";
3
3
  import { api } from "@/lib/api-client";
4
+ import { useTabStore } from "@/stores/tab-store";
4
5
  import { useDatabase, type DbColumnInfo } from "./use-database";
5
6
  import { SqlQueryEditor } from "./sql-query-editor";
6
7
  import { ExportButton } from "./export-button";
@@ -21,12 +22,22 @@ function parseSqlFilters(sql: string): Record<string, string> {
21
22
  interface Props { metadata?: Record<string, unknown>; tabId?: string }
22
23
 
23
24
  /** Generic database viewer — works for any DB type via unified API */
24
- export function DatabaseViewer({ metadata }: Props) {
25
+ export function DatabaseViewer({ metadata, tabId }: Props) {
25
26
  const connectionId = metadata?.connectionId as number;
26
27
  const connectionName = metadata?.connectionName as string | undefined;
27
28
  const initialTable = metadata?.tableName as string | undefined;
28
29
  const initialSchema = (metadata?.schemaName as string) ?? "public";
29
30
  const initialSql = metadata?.initialSql as string | undefined;
31
+ const persistedSql = metadata?.currentSql as string | undefined;
32
+
33
+ // Persist SQL text to tab metadata (debounced via updateTab's built-in persist)
34
+ const updateTab = useTabStore((s) => s.updateTab);
35
+ const metadataRef = useRef(metadata);
36
+ metadataRef.current = metadata;
37
+ const handleSqlChange = useCallback((sql: string) => {
38
+ if (!tabId) return;
39
+ updateTab(tabId, { metadata: { ...metadataRef.current, currentSql: sql } });
40
+ }, [tabId, updateTab]);
30
41
 
31
42
  const db = useDatabase(connectionId);
32
43
  const [cachedTableNames, setCachedTableNames] = useState<{ name: string; schema: string }[]>([]);
@@ -183,7 +194,7 @@ export function DatabaseViewer({ metadata }: Props) {
183
194
  <SqlQueryEditor
184
195
  onExecute={handleExecuteQuery} loading={db.queryLoading}
185
196
  defaultValue={defaultQuery} schemaInfo={schemaInfo}
186
- cacheKey={connectionId ? String(connectionId) : undefined} />
197
+ onSqlChange={handleSqlChange} persistedSql={persistedSql} />
187
198
  </div>
188
199
 
189
200
  {/* Resize handle */}
@@ -9,8 +9,10 @@ interface SqlQueryEditorProps {
9
9
  loading: boolean;
10
10
  defaultValue?: string;
11
11
  schemaInfo?: SchemaInfo;
12
- /** Unique key for caching query text in sessionStorage (e.g. connectionId) */
13
- cacheKey?: string;
12
+ /** Called when the user edits the SQL text (for external persistence) */
13
+ onSqlChange?: (sql: string) => void;
14
+ /** Persisted SQL to restore on mount (takes priority over defaultValue if user hasn't edited) */
15
+ persistedSql?: string;
14
16
  }
15
17
 
16
18
  /** Find the SQL statement surrounding the cursor line (split by ;) */
@@ -43,13 +45,9 @@ export function getStatementAtCursor(text: string, cursorLine: number): string {
43
45
  }
44
46
 
45
47
  /** Shared Monaco-based SQL query editor (editor only, no results) */
46
- export function SqlQueryEditor({ onExecute, loading, defaultValue = "SELECT * FROM ", schemaInfo, cacheKey }: SqlQueryEditorProps) {
47
- const storageKey = cacheKey ? `ppm:sql-query:${cacheKey}` : null;
48
- const [query, setQuery] = useState(() => {
49
- if (storageKey) { try { return sessionStorage.getItem(storageKey) ?? defaultValue; } catch { /* */ } }
50
- return defaultValue;
51
- });
52
- const userEditedRef = useRef(false);
48
+ export function SqlQueryEditor({ onExecute, loading, defaultValue = "SELECT * FROM ", schemaInfo, onSqlChange, persistedSql }: SqlQueryEditorProps) {
49
+ const [query, setQuery] = useState(() => persistedSql ?? defaultValue);
50
+ const userEditedRef = useRef(!!persistedSql);
53
51
  const editorRef = useRef<MonacoType.editor.IStandaloneCodeEditor | null>(null);
54
52
  const monacoRef = useRef<typeof MonacoType | null>(null);
55
53
  const disposableRef = useRef<MonacoType.IDisposable | null>(null);
@@ -107,7 +105,7 @@ export function SqlQueryEditor({ onExecute, loading, defaultValue = "SELECT * FR
107
105
  language="sql"
108
106
  theme={monacoTheme}
109
107
  value={query}
110
- onChange={(v) => { const val = v ?? ""; setQuery(val); userEditedRef.current = true; if (storageKey) try { sessionStorage.setItem(storageKey, val); } catch {} }}
108
+ onChange={(v) => { const val = v ?? ""; setQuery(val); userEditedRef.current = true; onSqlChange?.(val); }}
111
109
  onMount={handleMount}
112
110
  options={{
113
111
  minimap: { enabled: false },
@@ -27,6 +27,7 @@ const ImagePreview = lazy(() => import("./image-preview").then((m) => ({ default
27
27
  const PdfPreview = lazy(() => import("./pdf-preview").then((m) => ({ default: m.PdfPreview })));
28
28
  const VideoPreview = lazy(() => import("./video-preview").then((m) => ({ default: m.VideoPreview })));
29
29
  const AudioPreview = lazy(() => import("./audio-preview").then((m) => ({ default: m.AudioPreview })));
30
+ const DocxPreview = lazy(() => import("./docx-preview").then((m) => ({ default: m.DocxPreview })));
30
31
 
31
32
  /** Image extensions renderable inline */
32
33
  const IMAGE_EXTS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "ico"]);
@@ -87,6 +88,7 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
87
88
  const ext = filePath ? getFileExt(filePath) : "";
88
89
  const isImage = IMAGE_EXTS.has(ext);
89
90
  const isPdf = ext === "pdf";
91
+ const isDocx = ext === "docx";
90
92
  const isVideo = VIDEO_EXTS.has(ext);
91
93
  const isAudio = AUDIO_EXTS.has(ext);
92
94
  const isSqlite = SQLITE_EXTS.has(ext);
@@ -273,7 +275,7 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
273
275
  }
274
276
  if (!filePath) return;
275
277
  if (!isExternalFile && !projectName) return;
276
- if (isImage || isPdf || isVideo || isAudio) { setLoading(false); return; }
278
+ if (isImage || isPdf || isDocx || isVideo || isAudio) { setLoading(false); return; }
277
279
 
278
280
  setLoading(true);
279
281
  setError(null);
@@ -296,7 +298,7 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
296
298
  });
297
299
 
298
300
  return () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current); };
299
- }, [filePath, projectName, isImage, isPdf, isExternalFile, isUntitled]);
301
+ }, [filePath, projectName, isImage, isPdf, isDocx, isExternalFile, isUntitled]);
300
302
 
301
303
  // Real-time reload: listen for file:changed WS events, re-fetch if editor is clean
302
304
  const unsavedRef = useRef(unsaved);
@@ -511,6 +513,7 @@ export const CodeEditor = memo(function CodeEditor({ metadata, tabId }: CodeEdit
511
513
 
512
514
  if (isImage) return <Suspense fallback={<LoadingSpinner />}><ImagePreview filePath={filePath!} projectName={projectName!} /></Suspense>;
513
515
  if (isPdf) return <Suspense fallback={<LoadingSpinner />}><PdfPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
516
+ if (isDocx) return <Suspense fallback={<LoadingSpinner />}><DocxPreview filePath={filePath!} projectName={projectName} /></Suspense>;
514
517
  if (isVideo) return <Suspense fallback={<LoadingSpinner />}><VideoPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
515
518
  if (isAudio) return <Suspense fallback={<LoadingSpinner />}><AudioPreview filePath={filePath!} projectName={projectName!} /></Suspense>;
516
519
 
@@ -0,0 +1,92 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Loader2, FileWarning } from "lucide-react";
3
+ import { api, projectUrl } from "@/lib/api-client";
4
+
5
+ interface DocxPreviewProps {
6
+ filePath: string;
7
+ projectName?: string;
8
+ }
9
+
10
+ /** Preview .docx files by converting to HTML via mammoth on the backend */
11
+ export function DocxPreview({ filePath, projectName }: DocxPreviewProps) {
12
+ const [html, setHtml] = useState<string | null>(null);
13
+ const [loading, setLoading] = useState(true);
14
+ const [error, setError] = useState<string | null>(null);
15
+
16
+ useEffect(() => {
17
+ setLoading(true);
18
+ setError(null);
19
+
20
+ const isExternal = /^(\/|[A-Za-z]:[/\\])/.test(filePath);
21
+ const url = isExternal
22
+ ? `/api/fs/docx-html?path=${encodeURIComponent(filePath)}`
23
+ : `${projectUrl(projectName!)}/files/docx-html?path=${encodeURIComponent(filePath)}`;
24
+
25
+ api
26
+ .get<{ html: string }>(url)
27
+ .then((data) => {
28
+ setHtml(data.html);
29
+ setLoading(false);
30
+ })
31
+ .catch((err) => {
32
+ setError(err instanceof Error ? err.message : "Failed to convert docx");
33
+ setLoading(false);
34
+ });
35
+ }, [filePath, projectName]);
36
+
37
+ // Re-fetch on file change events
38
+ useEffect(() => {
39
+ const handler = (e: Event) => {
40
+ const detail = (e as CustomEvent).detail;
41
+ if (detail.projectName !== projectName || detail.path !== filePath) return;
42
+
43
+ const isExternal = /^(\/|[A-Za-z]:[/\\])/.test(filePath);
44
+ const url = isExternal
45
+ ? `/api/fs/docx-html?path=${encodeURIComponent(filePath)}`
46
+ : `${projectUrl(projectName!)}/files/docx-html?path=${encodeURIComponent(filePath)}`;
47
+
48
+ api.get<{ html: string }>(url).then((data) => setHtml(data.html)).catch(() => {});
49
+ };
50
+ window.addEventListener("file:changed", handler);
51
+ return () => window.removeEventListener("file:changed", handler);
52
+ }, [filePath, projectName]);
53
+
54
+ if (loading) {
55
+ return (
56
+ <div className="flex items-center justify-center h-full gap-2 text-text-secondary">
57
+ <Loader2 className="size-5 animate-spin" />
58
+ <span className="text-sm">Converting document...</span>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ if (error) {
64
+ return (
65
+ <div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary">
66
+ <FileWarning className="size-10 text-text-subtle" />
67
+ <p className="text-sm">Failed to load document.</p>
68
+ <p className="text-xs text-text-subtle">{error}</p>
69
+ </div>
70
+ );
71
+ }
72
+
73
+ return (
74
+ <div className="h-full overflow-auto bg-white dark:bg-zinc-900">
75
+ <div
76
+ className="docx-preview max-w-3xl mx-auto px-6 py-8 text-sm text-foreground leading-relaxed
77
+ [&_table]:border-collapse [&_table]:w-full [&_table]:my-3
78
+ [&_td]:border [&_td]:border-border [&_td]:px-2 [&_td]:py-1
79
+ [&_th]:border [&_th]:border-border [&_th]:px-2 [&_th]:py-1 [&_th]:font-semibold [&_th]:bg-muted/50
80
+ [&_img]:max-w-full [&_img]:h-auto [&_img]:rounded
81
+ [&_h1]:text-2xl [&_h1]:font-bold [&_h1]:mt-6 [&_h1]:mb-3
82
+ [&_h2]:text-xl [&_h2]:font-semibold [&_h2]:mt-5 [&_h2]:mb-2
83
+ [&_h3]:text-lg [&_h3]:font-medium [&_h3]:mt-4 [&_h3]:mb-2
84
+ [&_p]:my-2
85
+ [&_ul]:list-disc [&_ul]:pl-6 [&_ul]:my-2
86
+ [&_ol]:list-decimal [&_ol]:pl-6 [&_ol]:my-2
87
+ [&_li]:my-0.5"
88
+ dangerouslySetInnerHTML={{ __html: html ?? "" }}
89
+ />
90
+ </div>
91
+ );
92
+ }
@@ -0,0 +1,61 @@
1
+ import { FileCode, Database, FolderOpen, Zap } from "lucide-react";
2
+
3
+ /** Metadata for each command group — label and icon for the filter chip */
4
+ const GROUP_META: Record<string, { label: string; icon: React.ElementType }> = {
5
+ action: { label: "Actions", icon: Zap },
6
+ file: { label: "Files", icon: FileCode },
7
+ db: { label: "Database", icon: Database },
8
+ fs: { label: "Filesystem", icon: FolderOpen },
9
+ };
10
+
11
+ interface CommandPaletteFilterChipsProps {
12
+ /** Groups that have data (stable, pre-query) */
13
+ availableGroups: string[];
14
+ /** Count of filtered results per group (updates with query) */
15
+ groupCounts: Record<string, number>;
16
+ /** Currently active filter groups */
17
+ activeFilters: Set<string>;
18
+ /** Toggle a group filter on/off */
19
+ onToggle: (group: string) => void;
20
+ }
21
+
22
+ export function CommandPaletteFilterChips({
23
+ availableGroups,
24
+ groupCounts,
25
+ activeFilters,
26
+ onToggle,
27
+ }: CommandPaletteFilterChipsProps) {
28
+ if (availableGroups.length <= 1) return null;
29
+
30
+ return (
31
+ <div className="flex items-center gap-1.5 border-b border-border/50 px-3 py-1.5 overflow-x-auto">
32
+ {availableGroups.map((group) => {
33
+ const meta = GROUP_META[group];
34
+ if (!meta) return null;
35
+ const count = groupCounts[group] ?? 0;
36
+ const isActive = activeFilters.has(group);
37
+ const Icon = meta.icon;
38
+
39
+ return (
40
+ <button
41
+ key={group}
42
+ type="button"
43
+ role="switch"
44
+ aria-checked={isActive}
45
+ aria-label={`Filter by ${meta.label}`}
46
+ onClick={() => onToggle(group)}
47
+ className={`inline-flex items-center gap-1 shrink-0 rounded-full border px-2.5 py-1 text-xs font-medium transition-colors ${
48
+ isActive
49
+ ? "bg-accent/15 border-accent text-accent"
50
+ : "bg-surface border-border text-text-subtle hover:bg-surface-elevated"
51
+ } ${count === 0 ? "opacity-50" : ""}`}
52
+ >
53
+ <Icon className="size-3" />
54
+ <span>{meta.label}</span>
55
+ <span className="text-[10px] opacity-70">({count})</span>
56
+ </button>
57
+ );
58
+ })}
59
+ </div>
60
+ );
61
+ }
@@ -28,6 +28,7 @@ import { useCompareStore } from "@/stores/compare-store";
28
28
  import { api } from "@/lib/api-client";
29
29
  import { basename } from "@/lib/utils";
30
30
  import { scoreFileSearchFast, compareScores, getFilename, type FileSearchScore } from "@/lib/score-file-search";
31
+ import { CommandPaletteFilterChips } from "@/components/layout/command-palette-filter-chips";
31
32
 
32
33
  /** Max results to display — prevents rendering thousands of matches */
33
34
  const MAX_RESULTS = 100;
@@ -120,6 +121,7 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
120
121
  const [fsFiles, setFsFiles] = useState<string[]>([]);
121
122
  const [fsLoading, setFsLoading] = useState(false);
122
123
  const [dbResults, setDbResults] = useState<DbSearchResult[]>([]);
124
+ const [activeFilters, setActiveFilters] = useState<Set<string>>(new Set());
123
125
  const inputRef = useRef<HTMLInputElement>(null);
124
126
  const listRef = useRef<HTMLDivElement>(null);
125
127
 
@@ -384,6 +386,38 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
384
386
  return deferredQuery.trim().length >= 2 ? [...dbCommands, ...matched] : matched;
385
387
  }, [searchIndex, actionCommands, fsCommands, dbCommands, deferredQuery]);
386
388
 
389
+ // Stable set of groups that have data (pre-query) — prevents chip flashing
390
+ const availableGroups = useMemo(() => {
391
+ const groups = new Set<string>();
392
+ for (const cmd of allCommands) groups.add(cmd.group);
393
+ if (dbResults.length > 0) groups.add("db");
394
+ if (fsFiles.length > 0) groups.add("fs");
395
+ return Array.from(groups);
396
+ }, [allCommands, dbResults.length, fsFiles.length]);
397
+
398
+ // Per-group counts from search-filtered results (updates with query)
399
+ const groupCounts = useMemo(() => {
400
+ const counts: Record<string, number> = {};
401
+ for (const cmd of filtered) counts[cmd.group] = (counts[cmd.group] ?? 0) + 1;
402
+ return counts;
403
+ }, [filtered]);
404
+
405
+ // Final display list — apply group filters as post-process
406
+ const displayItems = useMemo(() => {
407
+ if (activeFilters.size === 0) return filtered;
408
+ return filtered.filter((cmd) => activeFilters.has(cmd.group));
409
+ }, [filtered, activeFilters]);
410
+
411
+ const toggleFilter = useCallback((group: string) => {
412
+ setActiveFilters((prev) => {
413
+ const next = new Set(prev);
414
+ if (next.has(group)) next.delete(group);
415
+ else next.add(group);
416
+ return next;
417
+ });
418
+ setSelectedIdx(0);
419
+ }, []);
420
+
387
421
  // Auto-load file index when palette opens and index isn't ready
388
422
  useEffect(() => {
389
423
  if (open && indexStatus === "idle" && activeProject) {
@@ -398,14 +432,15 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
398
432
  setSelectedIdx(0);
399
433
  setFsFiles([]);
400
434
  setDbResults([]);
435
+ setActiveFilters(new Set());
401
436
  requestAnimationFrame(() => inputRef.current?.focus());
402
437
  }
403
438
  }, [open]);
404
439
 
405
- // Clamp selected index when filter changes
440
+ // Clamp selected index when display list changes
406
441
  useEffect(() => {
407
- setSelectedIdx((prev) => Math.min(prev, Math.max(filtered.length - 1, 0)));
408
- }, [filtered.length]);
442
+ setSelectedIdx((prev) => Math.min(prev, Math.max(displayItems.length - 1, 0)));
443
+ }, [displayItems.length]);
409
444
 
410
445
  // Scroll selected item into view
411
446
  useEffect(() => {
@@ -430,7 +465,7 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
430
465
  }, [query, activeProject, openTab, onClose]);
431
466
 
432
467
  function handleKeyDown(e: React.KeyboardEvent) {
433
- const len = filtered.length;
468
+ const len = displayItems.length;
434
469
  switch (e.key) {
435
470
  case "ArrowDown":
436
471
  e.preventDefault();
@@ -443,7 +478,7 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
443
478
  case "Enter":
444
479
  e.preventDefault();
445
480
  if (len > 0) {
446
- filtered[selectedIdx]?.action();
481
+ displayItems[selectedIdx]?.action();
447
482
  } else if (query.trim()) {
448
483
  askAi();
449
484
  }
@@ -510,11 +545,23 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
510
545
  </div>
511
546
  )}
512
547
 
548
+ {/* Filter chips — hidden in path mode */}
549
+ {!pathMode && (
550
+ <CommandPaletteFilterChips
551
+ availableGroups={availableGroups}
552
+ groupCounts={groupCounts}
553
+ activeFilters={activeFilters}
554
+ onToggle={toggleFilter}
555
+ />
556
+ )}
557
+
513
558
  {/* Results */}
514
559
  <div ref={listRef} className="max-h-72 overflow-y-auto py-1">
515
- {filtered.length === 0 ? (
560
+ {displayItems.length === 0 ? (
516
561
  fsLoading ? (
517
562
  <p className="px-3 py-4 text-sm text-text-subtle text-center">Searching...</p>
563
+ ) : activeFilters.size > 0 && filtered.length > 0 ? (
564
+ <p className="px-3 py-4 text-sm text-text-subtle text-center">No results in selected filters</p>
518
565
  ) : query.trim() ? (
519
566
  <button
520
567
  onClick={askAi}
@@ -527,7 +574,7 @@ export function CommandPalette({ open, onClose, initialQuery = "" }: { open: boo
527
574
  <p className="px-3 py-4 text-sm text-text-subtle text-center">No results</p>
528
575
  )
529
576
  ) : (
530
- filtered.map((cmd, i) => {
577
+ displayItems.map((cmd, i) => {
531
578
  const Icon = cmd.icon;
532
579
  return (
533
580
  <button