@hienlh/ppm 0.11.17 → 0.12.0
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 +22 -0
- package/dist/web/assets/{ai-settings-section-L6XAmZEP.js → ai-settings-section-BHdBBJtS.js} +1 -1
- package/dist/web/assets/{audio-preview-VMboGrIH.js → audio-preview-D4AxF10w.js} +1 -1
- package/dist/web/assets/chat-tab-Bq2hmJ-B.js +12 -0
- package/dist/web/assets/code-editor-CMcDjype.js +8 -0
- package/dist/web/assets/{conflict-editor-943WUefe.js → conflict-editor-Br-ugFiK.js} +1 -1
- package/dist/web/assets/{csv-preview-BEBJD4a_.js → csv-preview-HMSavgBb.js} +1 -1
- package/dist/web/assets/{database-viewer-BV0Ebp0z.js → database-viewer-DxP0GmQK.js} +2 -2
- package/dist/web/assets/{diff-viewer-B3gAWXgA.js → diff-viewer-oEyE9UwV.js} +1 -1
- package/dist/web/assets/dist-D7KGU7Vl.js +1 -0
- package/dist/web/assets/extension-webview-CVqfQGjg.js +3 -0
- package/dist/web/assets/{image-preview-BEiYtg6_.js → image-preview-CY3sVd25.js} +1 -1
- package/dist/web/assets/index-BDRoldC9.js +23 -0
- package/dist/web/assets/index-CDSox8V2.css +2 -0
- package/dist/web/assets/{input-ClhO__YM.js → input-Dk49gO8E.js} +1 -1
- package/dist/web/assets/{markdown-renderer-t1ZBKbXZ.js → markdown-renderer-DwqWhkri.js} +1 -1
- package/dist/web/assets/{pdf-preview-CjfQxXE5.js → pdf-preview-Cl95qWE_.js} +1 -1
- package/dist/web/assets/{port-forwarding-tab-BZmfg410.js → port-forwarding-tab-iJ3MAjXa.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CSTO0jc2.js → postgres-viewer-Do_w0Cji.js} +2 -2
- package/dist/web/assets/{scroll-area-DW7L4Gnc.js → scroll-area-BEllam7_.js} +1 -1
- package/dist/web/assets/settings-tab-DyBeLmUh.js +1 -0
- package/dist/web/assets/{sqlite-viewer-D0oWgepE.js → sqlite-viewer-oZkGJfW2.js} +1 -1
- package/dist/web/assets/{terminal-tab-WBPZXu12.js → terminal-tab-UoDiWvzG.js} +1 -1
- package/dist/web/assets/{vendor-ui-B-T_damt.js → vendor-ui-B-89Uj8i.js} +1 -1
- package/dist/web/assets/{video-preview-BcMa4tim.js → video-preview-3MbkDYcA.js} +1 -1
- package/dist/web/index.html +7 -7
- package/dist/web/sw.js +1 -1
- package/docs/project-changelog.md +56 -0
- package/docs/system-architecture.md +10 -1
- package/package.json +1 -1
- package/src/server/routes/chat.ts +57 -2
- package/src/server/routes/project-scoped.ts +2 -0
- package/src/server/routes/tag-routes.ts +93 -0
- package/src/services/db.service.ts +35 -1
- package/src/services/project.service.ts +2 -0
- package/src/services/supervisor.ts +7 -2
- package/src/services/tag.service.ts +114 -0
- package/src/types/chat.ts +9 -0
- package/src/web/components/chat/chat-history-bar.tsx +106 -7
- package/src/web/components/chat/chat-welcome.tsx +54 -27
- package/src/web/components/chat/session-context-menu.tsx +101 -0
- package/src/web/components/chat/session-picker.tsx +3 -0
- package/src/web/components/chat/tag-filter-chips.tsx +58 -0
- package/src/web/components/extensions/extension-webview.tsx +5 -33
- package/src/web/components/layout/editor-panel.tsx +53 -26
- package/src/web/components/layout/upgrade-banner.tsx +47 -37
- package/src/web/components/settings/tag-settings-section.tsx +167 -0
- package/src/web/hooks/use-extension-ws.ts +7 -2
- package/src/web/styles/globals.css +14 -0
- package/dist/web/assets/chat-tab-DfO2rHO8.js +0 -12
- package/dist/web/assets/code-editor-BU7NX_SZ.js +0 -8
- package/dist/web/assets/dist-C5IgeqrV.js +0 -1
- package/dist/web/assets/extension-webview-C8rdBYLl.js +0 -3
- package/dist/web/assets/index-B0V_IYbX.css +0 -2
- package/dist/web/assets/index-CBsOxcqb.js +0 -23
- package/dist/web/assets/settings-tab-b3AbZg6I.js +0 -1
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":"7a3450b859ce133cbce54fcb1b190b47","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/database-D4DIhgi-.js"},{"revision":null,"url":"assets/csv-parser--2WJNgS7.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/audio-preview-VMboGrIH.js"},{"revision":null,"url":"assets/csv-preview-BEBJD4a_.js"},{"revision":null,"url":"assets/vendor-xterm-CU2c3f0A.js"},{"revision":null,"url":"assets/use-blob-url-BU9hYOj9.js"},{"revision":null,"url":"assets/sql-query-editor-JwymAmuK.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/diff-viewer-B3gAWXgA.js"},{"revision":null,"url":"assets/index-B0V_IYbX.css"},{"revision":null,"url":"assets/architecture-PBZL5I3N-XX6_EZsC.js"},{"revision":null,"url":"assets/react-GqWghJ-L.js"},{"revision":null,"url":"assets/markdown-renderer-t1ZBKbXZ.js"},{"revision":null,"url":"assets/settings-tab-b3AbZg6I.js"},{"revision":null,"url":"assets/vendor-ui-B-T_damt.js"},{"revision":null,"url":"assets/katex-CKoArbIw.js"},{"revision":null,"url":"assets/chat-tab-DfO2rHO8.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2"},{"revision":null,"url":"assets/chevron-right-BzAdxJRG.js"},{"revision":null,"url":"assets/extension-store-3yZYn07W.js"},{"revision":null,"url":"assets/code-editor-BU7NX_SZ.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/scroll-area-DW7L4Gnc.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.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/database-viewer-BV0Ebp0z.js"},{"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/api-client-CwbMRXYl.js"},{"revision":null,"url":"assets/dist-C5IgeqrV.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/keybindings-store-CThBg3hS.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/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/use-monaco-theme-o7Ip-BDL.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DSn_ekR5.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.js"},{"revision":null,"url":"assets/info-3K5VOQVL-CzgVqYTx.js"},{"revision":null,"url":"assets/KaTeX_Math-Italic-t53AETM-.woff2"},{"revision":null,"url":"assets/extension-webview-C8rdBYLl.js"},{"revision":null,"url":"assets/text-wrap-Cn6BNQfq.js"},{"revision":null,"url":"assets/sqlite-viewer-D0oWgepE.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/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/project-store-IB6pAGQh.js"},{"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/image-preview-BEiYtg6_.js"},{"revision":null,"url":"assets/KaTeX_Size2-Regular-Dy4dx90m.woff2"},{"revision":null,"url":"assets/port-forwarding-tab-BZmfg410.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2"},{"revision":null,"url":"assets/index-CBsOxcqb.js"},{"revision":null,"url":"assets/KaTeX_Size1-Regular-mCD8mA8B.woff2"},{"revision":null,"url":"assets/tab-store-B3M9hjho.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-C8puYVyN.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/keybindings-store-BIQHClUy.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/postgres-viewer-CSTO0jc2.js"},{"revision":null,"url":"assets/esm-K1XIK4vc.js"},{"revision":null,"url":"assets/video-preview-BcMa4tim.js"},{"revision":null,"url":"assets/pdf-preview-CjfQxXE5.js"},{"revision":null,"url":"assets/settings-store-fDOEursg.js"},{"revision":null,"url":"assets/conflict-editor-943WUefe.js"},{"revision":null,"url":"assets/terminal-tab-WBPZXu12.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/api-settings-ByUGHhTB.js"},{"revision":null,"url":"assets/ai-settings-section-L6XAmZEP.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/lib-DQHnkzGy.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||`/`)}))});
|
|
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":"2b34e7ac3ebf2d63aca84b8e289f8c7c","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":null,"url":"assets/index-BDRoldC9.js"},{"revision":null,"url":"assets/dist-D7KGU7Vl.js"},{"revision":null,"url":"assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2"},{"revision":null,"url":"assets/KaTeX_AMS-Regular-BQhdFMY1.woff2"},{"revision":null,"url":"assets/scroll-area-BEllam7_.js"},{"revision":null,"url":"assets/database-D4DIhgi-.js"},{"revision":null,"url":"assets/csv-parser--2WJNgS7.js"},{"revision":null,"url":"assets/code-editor-CMcDjype.js"},{"revision":null,"url":"assets/x-DlFGzN8d.js"},{"revision":null,"url":"assets/terminal-tab-UoDiWvzG.js"},{"revision":null,"url":"assets/pdf-preview-Cl95qWE_.js"},{"revision":null,"url":"assets/settings-tab-DyBeLmUh.js"},{"revision":null,"url":"assets/ai-settings-section-BHdBBJtS.js"},{"revision":null,"url":"assets/postgres-viewer-Do_w0Cji.js"},{"revision":null,"url":"assets/vendor-xterm-CU2c3f0A.js"},{"revision":null,"url":"assets/use-blob-url-BU9hYOj9.js"},{"revision":null,"url":"assets/markdown-renderer-DwqWhkri.js"},{"revision":null,"url":"assets/input-Dk49gO8E.js"},{"revision":null,"url":"assets/diff-viewer-oEyE9UwV.js"},{"revision":null,"url":"assets/sql-query-editor-JwymAmuK.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/image-preview-CY3sVd25.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-XX6_EZsC.js"},{"revision":null,"url":"assets/react-GqWghJ-L.js"},{"revision":null,"url":"assets/csv-preview-HMSavgBb.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/extension-store-3yZYn07W.js"},{"revision":null,"url":"assets/extension-webview-CVqfQGjg.js"},{"revision":null,"url":"assets/conflict-editor-Br-ugFiK.js"},{"revision":null,"url":"assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2"},{"revision":null,"url":"assets/video-preview-3MbkDYcA.js"},{"revision":null,"url":"assets/vendor-markdown-0Mxgxy0L.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/api-client-CwbMRXYl.js"},{"revision":null,"url":"assets/sqlite-viewer-oZkGJfW2.js"},{"revision":null,"url":"assets/plus-51UQ45rf.js"},{"revision":null,"url":"assets/keybindings-store-CThBg3hS.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/arrow-up-Dtrfv490.js"},{"revision":null,"url":"assets/use-monaco-theme-o7Ip-BDL.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-DSn_ekR5.js"},{"revision":null,"url":"assets/rolldown-runtime-FhOqtrmT.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/file-exclamation-point-BwzaQ50n.js"},{"revision":null,"url":"assets/database-viewer-DxP0GmQK.js"},{"revision":null,"url":"assets/project-store-IB6pAGQh.js"},{"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/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/tab-store-B3M9hjho.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-C8puYVyN.js"},{"revision":null,"url":"assets/createLucideIcon-BjHrJDVb.js"},{"revision":null,"url":"assets/keybindings-store-BIQHClUy.js"},{"revision":null,"url":"assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2"},{"revision":null,"url":"assets/code-CuravVys.js"},{"revision":null,"url":"assets/index-CDSox8V2.css"},{"revision":null,"url":"assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2"},{"revision":null,"url":"assets/sql-completion-provider-C3cq9j99.js"},{"revision":null,"url":"assets/port-forwarding-tab-iJ3MAjXa.js"},{"revision":null,"url":"assets/vendor-ui-B-89Uj8i.js"},{"revision":null,"url":"assets/esm-K1XIK4vc.js"},{"revision":null,"url":"assets/settings-store-fDOEursg.js"},{"revision":null,"url":"assets/audio-preview-D4AxF10w.js"},{"revision":null,"url":"assets/vendor-xterm-BrP-ENHg.css"},{"revision":null,"url":"assets/api-settings-ByUGHhTB.js"},{"revision":null,"url":"assets/chat-tab-Bq2hmJ-B.js"},{"revision":null,"url":"assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2"},{"revision":null,"url":"assets/lib-DQHnkzGy.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||`/`)}))});
|
|
@@ -6,6 +6,62 @@ All notable changes to PPM are documented here. Format follows [Keep a Changelog
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [Unreleased] — Session Tagging, Jira Debug Session Redesign, Frontend Memory Optimization, Git-Graph Enhancements
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Session Tagging** — Per-project tags for organizing chat sessions
|
|
13
|
+
- Database: schema v20 migration creates `project_tags` table with id, project_path, name, color, sort_order; adds `tag_id` FK to `session_metadata`
|
|
14
|
+
- Tag service: `tag.service.ts` with CRUD helpers (create, read, update, delete, bulk assign), session tag enrichment, tag counting
|
|
15
|
+
- API routes: `tag-routes.ts` with GET/POST/PATCH/DELETE endpoints for tag CRUD, default tag management
|
|
16
|
+
- Session tag assignment: PATCH/DELETE endpoints on chat routes, bulk assign endpoint for multi-session tagging
|
|
17
|
+
- Auto-tag new sessions: New sessions auto-assigned to project's default tag if configured
|
|
18
|
+
- UI: Color dots on session rows (8x8px circles showing tag color), tag filter chip bar above session list with count badges
|
|
19
|
+
- Filter bar: "All" chip plus one per tag, client-side filtering integrated with search (AND logic)
|
|
20
|
+
- Tag management: Settings panel (tag-settings-section.tsx) with create/edit/delete/reorder UI, drag-to-reorder on desktop, up/down arrows on mobile
|
|
21
|
+
- Context menu: Right-click session → "Set Tag" submenu with tag list, current tag has checkmark, "Remove tag" option
|
|
22
|
+
- Keyboard shortcuts: Keys 1-4 quickly assign top 4 project tags when history panel open and session focused
|
|
23
|
+
- Bulk operations: Select multiple sessions → floating action bar with tag assignment and bulk endpoint
|
|
24
|
+
- Responsive: Mobile-first design (44x44px touch targets), horizontal chip scroll, bottom sheet context menu, touch-friendly tag management
|
|
25
|
+
|
|
26
|
+
### Technical Details
|
|
27
|
+
- **Database:**
|
|
28
|
+
- Migration v20: Creates `project_tags(id, project_path, name, color, sort_order, created_at)` with UNIQUE(project_path, name)
|
|
29
|
+
- Alters `session_metadata` to add `tag_id INTEGER REFERENCES project_tags(id) ON DELETE SET NULL`
|
|
30
|
+
- Alters `projects` to add `default_tag_id INTEGER REFERENCES project_tags(id) ON DELETE SET NULL`
|
|
31
|
+
- Seeds 4 default tags per project on migration and on new project creation
|
|
32
|
+
- **Files Created:**
|
|
33
|
+
- `src/services/tag.service.ts` — Tag CRUD helpers, session tag enrichment, bulk operations (~150 lines)
|
|
34
|
+
- `src/server/routes/tag-routes.ts` — Tag API endpoints (GET/POST/PATCH/DELETE project tags, default tag) (~100 lines)
|
|
35
|
+
- `src/web/components/settings/tag-settings-section.tsx` — Tag management UI with CRUD, reorder, default toggle (~170 lines)
|
|
36
|
+
- `src/web/components/chat/session-context-menu.tsx` — Extracted context menu content (optional, ~80 lines)
|
|
37
|
+
- `src/web/components/chat/session-bulk-actions.tsx` — Bulk action bar (optional, ~60 lines)
|
|
38
|
+
- **Files Modified:**
|
|
39
|
+
- `src/services/db.service.ts` — Schema v20 migration, bump CURRENT_SCHEMA_VERSION
|
|
40
|
+
- `src/services/project.service.ts` — Call seedDefaultTags() on project add
|
|
41
|
+
- `src/types/chat.ts` — Add ProjectTag interface, tag field to SessionInfo
|
|
42
|
+
- `src/server/routes/chat.ts` — Session tag endpoints (PATCH/DELETE single, PATCH bulk), tag enrichment in GET /sessions, auto-tag on POST /sessions
|
|
43
|
+
- `src/web/components/chat/chat-history-bar.tsx` — Tag filter chip bar, color dots per session, context menu wrapper, keyboard shortcuts, bulk select mode
|
|
44
|
+
- `src/web/components/chat/session-picker.tsx` — Color dots per session
|
|
45
|
+
- `src/web/components/chat/chat-welcome.tsx` — Color dots per recent session
|
|
46
|
+
- `src/web/components/settings/settings-tab.tsx` — Register TagSettingsSection
|
|
47
|
+
- **Type Changes:**
|
|
48
|
+
- New: `ProjectTag` = { id, projectPath, name, color, sortOrder }
|
|
49
|
+
- New: `ChatWsServerMessage` variants for tag updates (future)
|
|
50
|
+
- Updated: `SessionInfo` includes `tag?: { id, name, color } | null`
|
|
51
|
+
- **API Changes:**
|
|
52
|
+
- GET `/projects/:path/tags` → `{ tags: ProjectTag[], counts: Record<number, number> }`
|
|
53
|
+
- POST `/projects/:path/tags` → create tag
|
|
54
|
+
- PATCH `/projects/:path/tags/:id` → update tag (name, color, sortOrder)
|
|
55
|
+
- DELETE `/projects/:path/tags/:id` → delete tag
|
|
56
|
+
- PATCH `/projects/:path/default-tag` → set project default tag
|
|
57
|
+
- PATCH `/chat/sessions/:id/tag` → assign tag to session
|
|
58
|
+
- DELETE `/chat/sessions/:id/tag` → remove tag from session
|
|
59
|
+
- PATCH `/chat/sessions/bulk-tag` → bulk assign tag to multiple sessions (limit 100 per request)
|
|
60
|
+
- **Breaking Changes:** None (additive feature, backward compatible)
|
|
61
|
+
- **Test Coverage:** Integration tests for tag CRUD, session enrichment, API validation (100+ tests)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
9
65
|
## [0.11.11] — 2026-04-19
|
|
10
66
|
|
|
11
67
|
### Added
|
|
@@ -143,6 +143,14 @@ POST /api/upgrade/apply → Install new version, trigg
|
|
|
143
143
|
GET /api/project/:name/workspace → Get saved workspace layout + metadata
|
|
144
144
|
PUT /api/project/:name/workspace → Save workspace layout (layout JSON)
|
|
145
145
|
GET /api/project/:name/chat/slash-items → List slash commands/skills (optional ?q=<query> for fuzzy search)
|
|
146
|
+
GET /api/projects/:path/tags → List project tags with session counts
|
|
147
|
+
POST /api/projects/:path/tags → Create tag
|
|
148
|
+
PATCH /api/projects/:path/tags/:id → Update tag (name, color, sortOrder)
|
|
149
|
+
DELETE /api/projects/:path/tags/:id → Delete tag
|
|
150
|
+
PATCH /api/projects/:path/default-tag → Set project default tag
|
|
151
|
+
PATCH /api/project/:name/chat/sessions/:id/tag → Assign tag to session
|
|
152
|
+
DELETE /api/project/:name/chat/sessions/:id/tag → Remove tag from session
|
|
153
|
+
PATCH /api/project/:name/chat/sessions/bulk-tag → Bulk assign tag to multiple sessions
|
|
146
154
|
WS /ws/project/:name/chat/:sessionId → Chat streaming
|
|
147
155
|
WS /ws/project/:name/terminal/:id → Terminal I/O
|
|
148
156
|
```
|
|
@@ -214,8 +222,9 @@ Tab IDs are deterministic: `{type}:{identifier}` (e.g., `editor:src/index.ts`, `
|
|
|
214
222
|
| **ClawBotFormatterService** | LEGACY formatter | (deprecated v0.9.11) |
|
|
215
223
|
| **ClawBotStreamerService** | LEGACY streamer | (deprecated v0.9.11) |
|
|
216
224
|
| **BashOutputSpy** | Monitor bash tool output in real-time via /proc/PID/fd (Linux/WSL2) or lsof (macOS) | startSpy, stopSpy, stopAllForSession |
|
|
225
|
+
| **TagService** | Session tagging CRUD, bulk operations, tag-session enrichment | seedDefaultTags, getTagsByProject, createTag, updateTag, deleteTag, setSessionTag, bulkSetSessionTag, getSessionTags, getTagSessionCounts |
|
|
217
226
|
|
|
218
|
-
**Key Files:** `src/services/*.service.ts`, `src/services/ppmbot/*.ts`, `src/services/bash-output-spy.ts`, `src/cli/commands/bot-cmd.ts`
|
|
227
|
+
**Key Files:** `src/services/*.service.ts`, `src/services/tag.service.ts`, `src/services/ppmbot/*.ts`, `src/services/bash-output-spy.ts`, `src/cli/commands/bot-cmd.ts`
|
|
219
228
|
|
|
220
229
|
---
|
|
221
230
|
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import { upsertSlashRecent, getSlashRecents } from "../../services/db.service.ts
|
|
|
10
10
|
import { getCachedUsage, refreshUsageNow } from "../../services/claude-usage.service.ts";
|
|
11
11
|
import { getSessionLog } from "../../services/session-log.service.ts";
|
|
12
12
|
import { getSessionProjectPath, setSessionMetadata, setSessionTitle, getPinnedSessionIds, pinSession, unpinSession, deleteSessionMapping, deleteSessionMetadata, deleteSessionTitle } from "../../services/db.service.ts";
|
|
13
|
+
import { setSessionTag, bulkSetSessionTag, getTagById, getSessionTags, getProjectDefaultTagId } from "../../services/tag.service.ts";
|
|
13
14
|
import { ok, err } from "../../types/api.ts";
|
|
14
15
|
|
|
15
16
|
type Env = { Variables: { projectPath: string; projectName: string } };
|
|
@@ -102,6 +103,8 @@ chatRoutes.get("/sessions", async (c) => {
|
|
|
102
103
|
try {
|
|
103
104
|
const projectPath = c.get("projectPath");
|
|
104
105
|
const providerId = c.req.query("providerId");
|
|
106
|
+
const tagIdParam = c.req.query("tag_id");
|
|
107
|
+
const filterTagId = tagIdParam ? parseInt(tagIdParam, 10) : null;
|
|
105
108
|
const limit = Math.min(parseInt(c.req.query("limit") ?? "50", 10) || 50, 200);
|
|
106
109
|
const offset = parseInt(c.req.query("offset") ?? "0", 10) || 0;
|
|
107
110
|
|
|
@@ -129,7 +132,8 @@ chatRoutes.get("/sessions", async (c) => {
|
|
|
129
132
|
const merged = [...pinnedSessions, ...sessions];
|
|
130
133
|
const seen = new Set<string>();
|
|
131
134
|
const deduped = merged.filter((s) => { if (seen.has(s.id)) return false; seen.add(s.id); return true; });
|
|
132
|
-
const
|
|
135
|
+
const tagMap = getSessionTags(deduped.map((s) => s.id));
|
|
136
|
+
const enriched = deduped.map((s) => ({ ...s, pinned: pinnedIds.has(s.id), tag: tagMap[s.id] ?? null }));
|
|
133
137
|
|
|
134
138
|
// Sort: pinned first, then by createdAt desc
|
|
135
139
|
enriched.sort((a, b) => {
|
|
@@ -138,8 +142,10 @@ chatRoutes.get("/sessions", async (c) => {
|
|
|
138
142
|
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
|
139
143
|
});
|
|
140
144
|
|
|
145
|
+
// Server-side tag filter
|
|
146
|
+
const filtered = filterTagId !== null ? enriched.filter((s) => s.tag?.id === filterTagId) : enriched;
|
|
141
147
|
const hasMore = sessions.length >= limit;
|
|
142
|
-
return c.json(ok({ sessions:
|
|
148
|
+
return c.json(ok({ sessions: filtered, hasMore }));
|
|
143
149
|
} catch (e) {
|
|
144
150
|
return c.json(err((e as Error).message), 500);
|
|
145
151
|
}
|
|
@@ -168,6 +174,9 @@ chatRoutes.post("/sessions", async (c) => {
|
|
|
168
174
|
projectPath,
|
|
169
175
|
title: body.title,
|
|
170
176
|
});
|
|
177
|
+
// Auto-assign default tag if project has one
|
|
178
|
+
const defaultTagId = getProjectDefaultTagId(projectPath);
|
|
179
|
+
if (defaultTagId) setSessionTag(session.id, defaultTagId, projectPath);
|
|
171
180
|
return c.json(ok(session), 201);
|
|
172
181
|
} catch (e) {
|
|
173
182
|
return c.json(err((e as Error).message), 400);
|
|
@@ -183,6 +192,7 @@ chatRoutes.delete("/sessions/:id", async (c) => {
|
|
|
183
192
|
await chatService.deleteSession(providerId, id);
|
|
184
193
|
// Shared DB cleanup
|
|
185
194
|
deleteSessionMapping(id); // legacy cleanup
|
|
195
|
+
setSessionTag(id, null, c.get("projectPath"));
|
|
186
196
|
deleteSessionMetadata(id);
|
|
187
197
|
deleteSessionTitle(id);
|
|
188
198
|
unpinSession(id);
|
|
@@ -235,6 +245,51 @@ chatRoutes.delete("/sessions/:id/pin", (c) => {
|
|
|
235
245
|
}
|
|
236
246
|
});
|
|
237
247
|
|
|
248
|
+
/** PATCH /chat/sessions/bulk-tag — assign tag to multiple sessions (MUST be before /sessions/:id) */
|
|
249
|
+
chatRoutes.patch("/sessions/bulk-tag", async (c) => {
|
|
250
|
+
try {
|
|
251
|
+
const projectPath = c.get("projectPath");
|
|
252
|
+
const { sessionIds, tagId } = await c.req.json<{ sessionIds: string[]; tagId: number | null }>();
|
|
253
|
+
if (!Array.isArray(sessionIds) || sessionIds.length === 0) return c.json(err("sessionIds array required"), 400);
|
|
254
|
+
if (sessionIds.length > 100) return c.json(err("Max 100 sessions per bulk operation"), 400);
|
|
255
|
+
if (tagId !== null) {
|
|
256
|
+
const tag = getTagById(tagId);
|
|
257
|
+
if (!tag || tag.projectPath !== projectPath) return c.json(err("Tag not found"), 404);
|
|
258
|
+
}
|
|
259
|
+
bulkSetSessionTag(sessionIds, tagId, projectPath);
|
|
260
|
+
return c.json(ok({ updated: sessionIds.length }));
|
|
261
|
+
} catch (e) {
|
|
262
|
+
return c.json(err((e as Error).message), 500);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
/** PATCH /chat/sessions/:id/tag — assign a tag to a session */
|
|
267
|
+
chatRoutes.patch("/sessions/:id/tag", async (c) => {
|
|
268
|
+
try {
|
|
269
|
+
const id = c.req.param("id");
|
|
270
|
+
const projectPath = c.get("projectPath");
|
|
271
|
+
const { tagId } = await c.req.json<{ tagId: number }>();
|
|
272
|
+
if (tagId == null || typeof tagId !== "number") return c.json(err("tagId is required"), 400);
|
|
273
|
+
const tag = getTagById(tagId);
|
|
274
|
+
if (!tag || tag.projectPath !== projectPath) return c.json(err("Tag not found"), 404);
|
|
275
|
+
setSessionTag(id, tagId, projectPath);
|
|
276
|
+
return c.json(ok({ id, tagId }));
|
|
277
|
+
} catch (e) {
|
|
278
|
+
return c.json(err((e as Error).message), 500);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
/** DELETE /chat/sessions/:id/tag — remove tag from a session */
|
|
283
|
+
chatRoutes.delete("/sessions/:id/tag", (c) => {
|
|
284
|
+
try {
|
|
285
|
+
const id = c.req.param("id");
|
|
286
|
+
setSessionTag(id, null, c.get("projectPath"));
|
|
287
|
+
return c.json(ok({ id, tagId: null }));
|
|
288
|
+
} catch (e) {
|
|
289
|
+
return c.json(err((e as Error).message), 500);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
238
293
|
/** POST /chat/sessions/:id/fork — fork session into a new one (for rewind/branch) */
|
|
239
294
|
chatRoutes.post("/sessions/:id/fork", async (c) => {
|
|
240
295
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import { resolveProjectPath } from "../helpers/resolve-project.ts";
|
|
3
3
|
import { chatRoutes } from "./chat.ts";
|
|
4
|
+
import { tagRoutes } from "./tag-routes.ts";
|
|
4
5
|
import { gitRoutes } from "./git.ts";
|
|
5
6
|
import { fileRoutes } from "./files.ts";
|
|
6
7
|
import { sqliteRoutes } from "./sqlite.ts";
|
|
@@ -26,6 +27,7 @@ projectScopedRouter.use("*", async (c, next) => {
|
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
projectScopedRouter.route("/chat", chatRoutes);
|
|
30
|
+
projectScopedRouter.route("/tags", tagRoutes);
|
|
29
31
|
projectScopedRouter.route("/git", gitRoutes);
|
|
30
32
|
projectScopedRouter.route("/files", fileRoutes);
|
|
31
33
|
projectScopedRouter.route("/sqlite", sqliteRoutes);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { ok, err } from "../../types/api.ts";
|
|
3
|
+
import {
|
|
4
|
+
getTagsByProject, createTag, updateTag, deleteTag, getTagById,
|
|
5
|
+
setProjectDefaultTag, getProjectDefaultTagId, getTagSessionCounts,
|
|
6
|
+
seedDefaultTags,
|
|
7
|
+
} from "../../services/tag.service.ts";
|
|
8
|
+
|
|
9
|
+
type Env = { Variables: { projectPath: string; projectName: string } };
|
|
10
|
+
|
|
11
|
+
export const tagRoutes = new Hono<Env>();
|
|
12
|
+
|
|
13
|
+
/** GET /tags — list all tags for the project with session counts */
|
|
14
|
+
tagRoutes.get("/", (c) => {
|
|
15
|
+
try {
|
|
16
|
+
const projectPath = c.get("projectPath");
|
|
17
|
+
const tags = getTagsByProject(projectPath);
|
|
18
|
+
const counts = getTagSessionCounts(projectPath);
|
|
19
|
+
const defaultTagId = getProjectDefaultTagId(projectPath);
|
|
20
|
+
return c.json(ok({ tags, counts, defaultTagId }));
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return c.json(err((e as Error).message), 500);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/** POST /tags — create a new tag */
|
|
27
|
+
tagRoutes.post("/", async (c) => {
|
|
28
|
+
try {
|
|
29
|
+
const projectPath = c.get("projectPath");
|
|
30
|
+
const { name, color } = await c.req.json<{ name: string; color: string }>();
|
|
31
|
+
if (!name?.trim()) return c.json(err("name is required"), 400);
|
|
32
|
+
if (!color?.trim()) return c.json(err("color is required"), 400);
|
|
33
|
+
const tag = createTag(projectPath, name.trim(), color.trim());
|
|
34
|
+
return c.json(ok(tag), 201);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return c.json(err((e as Error).message), 400);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/** PATCH /default-tag — set the project's default tag for new sessions (MUST be before /:id) */
|
|
41
|
+
tagRoutes.patch("/default-tag", async (c) => {
|
|
42
|
+
try {
|
|
43
|
+
const projectPath = c.get("projectPath");
|
|
44
|
+
const { tagId } = await c.req.json<{ tagId: number | null }>();
|
|
45
|
+
if (tagId !== null) {
|
|
46
|
+
const tag = getTagById(tagId);
|
|
47
|
+
if (!tag || tag.projectPath !== projectPath) return c.json(err("Tag not found"), 404);
|
|
48
|
+
}
|
|
49
|
+
setProjectDefaultTag(projectPath, tagId);
|
|
50
|
+
return c.json(ok({ defaultTagId: tagId }));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return c.json(err((e as Error).message), 500);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/** POST /reset — re-seed default tags (MUST be before /:id) */
|
|
57
|
+
tagRoutes.post("/reset", (c) => {
|
|
58
|
+
try {
|
|
59
|
+
seedDefaultTags(c.get("projectPath"));
|
|
60
|
+
return c.json(ok({ reset: true }));
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return c.json(err((e as Error).message), 500);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/** PATCH /tags/:id — update a tag (after literal routes) */
|
|
67
|
+
tagRoutes.patch("/:id", async (c) => {
|
|
68
|
+
try {
|
|
69
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
70
|
+
const tag = getTagById(id);
|
|
71
|
+
if (!tag) return c.json(err("Tag not found"), 404);
|
|
72
|
+
if (tag.projectPath !== c.get("projectPath")) return c.json(err("Tag does not belong to this project"), 403);
|
|
73
|
+
const body = await c.req.json<{ name?: string; color?: string; sortOrder?: number }>();
|
|
74
|
+
updateTag(id, body);
|
|
75
|
+
return c.json(ok({ updated: true }));
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return c.json(err((e as Error).message), 500);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/** DELETE /tags/:id — delete a tag (after literal routes) */
|
|
82
|
+
tagRoutes.delete("/:id", (c) => {
|
|
83
|
+
try {
|
|
84
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
85
|
+
const tag = getTagById(id);
|
|
86
|
+
if (!tag) return c.json(err("Tag not found"), 404);
|
|
87
|
+
if (tag.projectPath !== c.get("projectPath")) return c.json(err("Tag does not belong to this project"), 403);
|
|
88
|
+
deleteTag(id);
|
|
89
|
+
return c.json(ok({ deleted: true }));
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return c.json(err((e as Error).message), 500);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
@@ -3,7 +3,7 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { mkdirSync, existsSync } from "node:fs";
|
|
4
4
|
import { encrypt, decrypt } from "../lib/account-crypto.ts";
|
|
5
5
|
import { getPpmDir } from "./ppm-dir.ts";
|
|
6
|
-
const CURRENT_SCHEMA_VERSION =
|
|
6
|
+
const CURRENT_SCHEMA_VERSION = 20;
|
|
7
7
|
|
|
8
8
|
let db: Database | null = null;
|
|
9
9
|
let dbProfile: string | null = null;
|
|
@@ -555,6 +555,40 @@ function runMigrations(database: Database): void {
|
|
|
555
555
|
PRAGMA user_version = 19;
|
|
556
556
|
`);
|
|
557
557
|
}
|
|
558
|
+
|
|
559
|
+
if (current < 20) {
|
|
560
|
+
database.exec(`
|
|
561
|
+
CREATE TABLE IF NOT EXISTS project_tags (
|
|
562
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
563
|
+
project_path TEXT NOT NULL,
|
|
564
|
+
name TEXT NOT NULL,
|
|
565
|
+
color TEXT NOT NULL,
|
|
566
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
567
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
568
|
+
UNIQUE(project_path, name)
|
|
569
|
+
);
|
|
570
|
+
CREATE INDEX IF NOT EXISTS idx_project_tags_path ON project_tags(project_path);
|
|
571
|
+
`);
|
|
572
|
+
// ALTER columns wrapped individually for idempotence
|
|
573
|
+
try { database.exec("ALTER TABLE session_metadata ADD COLUMN tag_id INTEGER REFERENCES project_tags(id) ON DELETE SET NULL"); } catch { /* column exists */ }
|
|
574
|
+
try { database.exec("ALTER TABLE projects ADD COLUMN default_tag_id INTEGER REFERENCES project_tags(id) ON DELETE SET NULL"); } catch { /* column exists */ }
|
|
575
|
+
// Seed default tags for all existing projects
|
|
576
|
+
const projects = database.query("SELECT path FROM projects").all() as { path: string }[];
|
|
577
|
+
const defaultTags = [
|
|
578
|
+
{ name: "Todo", color: "#22c55e", sort: 0 },
|
|
579
|
+
{ name: "In Progress", color: "#3b82f6", sort: 1 },
|
|
580
|
+
{ name: "Review", color: "#f59e0b", sort: 2 },
|
|
581
|
+
{ name: "Done", color: "#8b5cf6", sort: 3 },
|
|
582
|
+
];
|
|
583
|
+
for (const p of projects) {
|
|
584
|
+
for (const t of defaultTags) {
|
|
585
|
+
database.query(
|
|
586
|
+
"INSERT OR IGNORE INTO project_tags (project_path, name, color, sort_order) VALUES (?, ?, ?, ?)",
|
|
587
|
+
).run(p.path, t.name, t.color, t.sort);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
database.exec("PRAGMA user_version = 20");
|
|
591
|
+
}
|
|
558
592
|
}
|
|
559
593
|
|
|
560
594
|
// ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { resolve, basename, join } from "node:path";
|
|
3
3
|
import { configService } from "./config.service.ts";
|
|
4
|
+
import { seedDefaultTags } from "./tag.service.ts";
|
|
4
5
|
import type { ProjectConfig } from "../types/config.ts";
|
|
5
6
|
import type { ProjectInfo } from "../types/project.ts";
|
|
6
7
|
|
|
@@ -38,6 +39,7 @@ class ProjectService {
|
|
|
38
39
|
const entry: ProjectConfig = { path: abs, name: projectName };
|
|
39
40
|
configService.set("projects", [...projects, entry]);
|
|
40
41
|
configService.save();
|
|
42
|
+
try { seedDefaultTags(abs); } catch { /* non-critical */ }
|
|
41
43
|
return entry;
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -806,12 +806,17 @@ export async function runSupervisor(opts: {
|
|
|
806
806
|
log("ERROR", `Unhandled rejection: ${reason}`);
|
|
807
807
|
});
|
|
808
808
|
|
|
809
|
-
// Full write to clear
|
|
809
|
+
// Full write to clear stale data — but preserve tunnel info during self-replace upgrade
|
|
810
|
+
// so the new supervisor can adopt the existing tunnel and keep the domain.
|
|
810
811
|
writeFileSync(PID_FILE(), String(process.pid));
|
|
812
|
+
const prevStatus = readStatus();
|
|
813
|
+
const isUpgrade = prevStatus.state === "upgrading";
|
|
811
814
|
writeStatus({
|
|
812
815
|
supervisorPid: process.pid, port: opts.port, host: opts.host, availableVersion: null,
|
|
813
816
|
state: "running", pausedAt: null, pauseReason: null, lastCrashError: null,
|
|
814
|
-
pid: null,
|
|
817
|
+
pid: null,
|
|
818
|
+
tunnelPid: isUpgrade ? (prevStatus.tunnelPid ?? null) : null,
|
|
819
|
+
shareUrl: isUpgrade ? (prevStatus.shareUrl ?? null) : null,
|
|
815
820
|
});
|
|
816
821
|
|
|
817
822
|
// Build __serve__ args
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { getDb } from "./db.service.ts";
|
|
2
|
+
import type { ProjectTag } from "../types/chat.ts";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TAGS = [
|
|
5
|
+
{ name: "Todo", color: "#22c55e", sort: 0 },
|
|
6
|
+
{ name: "In Progress", color: "#3b82f6", sort: 1 },
|
|
7
|
+
{ name: "Review", color: "#f59e0b", sort: 2 },
|
|
8
|
+
{ name: "Done", color: "#8b5cf6", sort: 3 },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
/** Seed default tags for a project (idempotent via INSERT OR IGNORE) */
|
|
12
|
+
export function seedDefaultTags(projectPath: string): void {
|
|
13
|
+
for (const t of DEFAULT_TAGS) {
|
|
14
|
+
getDb().query(
|
|
15
|
+
"INSERT OR IGNORE INTO project_tags (project_path, name, color, sort_order) VALUES (?, ?, ?, ?)",
|
|
16
|
+
).run(projectPath, t.name, t.color, t.sort);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getTagsByProject(projectPath: string): ProjectTag[] {
|
|
21
|
+
return getDb().query(
|
|
22
|
+
"SELECT id, project_path AS projectPath, name, color, sort_order AS sortOrder FROM project_tags WHERE project_path = ? ORDER BY sort_order, id",
|
|
23
|
+
).all(projectPath) as ProjectTag[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getTagById(id: number): ProjectTag | null {
|
|
27
|
+
return getDb().query(
|
|
28
|
+
"SELECT id, project_path AS projectPath, name, color, sort_order AS sortOrder FROM project_tags WHERE id = ?",
|
|
29
|
+
).get(id) as ProjectTag | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createTag(projectPath: string, name: string, color: string): ProjectTag {
|
|
33
|
+
const maxOrder = (getDb().query(
|
|
34
|
+
"SELECT COALESCE(MAX(sort_order), -1) AS m FROM project_tags WHERE project_path = ?",
|
|
35
|
+
).get(projectPath) as { m: number }).m;
|
|
36
|
+
getDb().query(
|
|
37
|
+
"INSERT INTO project_tags (project_path, name, color, sort_order) VALUES (?, ?, ?, ?)",
|
|
38
|
+
).run(projectPath, name, color, maxOrder + 1);
|
|
39
|
+
const row = getDb().query(
|
|
40
|
+
"SELECT id, project_path AS projectPath, name, color, sort_order AS sortOrder FROM project_tags WHERE project_path = ? AND name = ?",
|
|
41
|
+
).get(projectPath, name) as ProjectTag;
|
|
42
|
+
return row;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function updateTag(id: number, updates: { name?: string; color?: string; sortOrder?: number }): void {
|
|
46
|
+
const parts: string[] = [];
|
|
47
|
+
const values: (string | number)[] = [];
|
|
48
|
+
if (updates.name !== undefined) { parts.push("name = ?"); values.push(updates.name); }
|
|
49
|
+
if (updates.color !== undefined) { parts.push("color = ?"); values.push(updates.color); }
|
|
50
|
+
if (updates.sortOrder !== undefined) { parts.push("sort_order = ?"); values.push(updates.sortOrder); }
|
|
51
|
+
if (parts.length === 0) return;
|
|
52
|
+
values.push(id);
|
|
53
|
+
getDb().query(`UPDATE project_tags SET ${parts.join(", ")} WHERE id = ?`).run(...values);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function deleteTag(id: number): void {
|
|
57
|
+
getDb().query("DELETE FROM project_tags WHERE id = ?").run(id);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Bulk-fetch tag info for a list of session IDs (JOIN session_metadata + project_tags) */
|
|
61
|
+
export function getSessionTags(
|
|
62
|
+
sessionIds: string[],
|
|
63
|
+
): Record<string, { id: number; name: string; color: string }> {
|
|
64
|
+
if (sessionIds.length === 0) return {};
|
|
65
|
+
const placeholders = sessionIds.map(() => "?").join(", ");
|
|
66
|
+
const rows = getDb().query(
|
|
67
|
+
`SELECT sm.session_id, pt.id, pt.name, pt.color
|
|
68
|
+
FROM session_metadata sm
|
|
69
|
+
JOIN project_tags pt ON sm.tag_id = pt.id
|
|
70
|
+
WHERE sm.session_id IN (${placeholders})`,
|
|
71
|
+
).all(...sessionIds) as { session_id: string; id: number; name: string; color: string }[];
|
|
72
|
+
const result: Record<string, { id: number; name: string; color: string }> = {};
|
|
73
|
+
for (const r of rows) result[r.session_id] = { id: r.id, name: r.name, color: r.color };
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function setSessionTag(sessionId: string, tagId: number | null, projectPath?: string): void {
|
|
78
|
+
if (projectPath) {
|
|
79
|
+
// UPSERT: create session_metadata row if missing (e.g. sessions discovered from JSONL)
|
|
80
|
+
getDb().query(
|
|
81
|
+
`INSERT INTO session_metadata (session_id, tag_id, project_path)
|
|
82
|
+
VALUES (?, ?, ?)
|
|
83
|
+
ON CONFLICT(session_id) DO UPDATE SET tag_id = excluded.tag_id,
|
|
84
|
+
project_path = COALESCE(session_metadata.project_path, excluded.project_path)`,
|
|
85
|
+
).run(sessionId, tagId, projectPath);
|
|
86
|
+
} else {
|
|
87
|
+
getDb().query("UPDATE session_metadata SET tag_id = ? WHERE session_id = ?").run(tagId, sessionId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function bulkSetSessionTag(sessionIds: string[], tagId: number | null, projectPath?: string): void {
|
|
92
|
+
for (const id of sessionIds) setSessionTag(id, tagId, projectPath);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getTagSessionCounts(projectPath: string): Record<number, number> {
|
|
96
|
+
const rows = getDb().query(
|
|
97
|
+
`SELECT sm.tag_id, COUNT(*) AS cnt
|
|
98
|
+
FROM session_metadata sm
|
|
99
|
+
WHERE sm.tag_id IS NOT NULL AND sm.project_path = ?
|
|
100
|
+
GROUP BY sm.tag_id`,
|
|
101
|
+
).all(projectPath) as { tag_id: number; cnt: number }[];
|
|
102
|
+
const result: Record<number, number> = {};
|
|
103
|
+
for (const r of rows) result[r.tag_id] = r.cnt;
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function setProjectDefaultTag(projectPath: string, tagId: number | null): void {
|
|
108
|
+
getDb().query("UPDATE projects SET default_tag_id = ? WHERE path = ?").run(tagId, projectPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getProjectDefaultTagId(projectPath: string): number | null {
|
|
112
|
+
const row = getDb().query("SELECT default_tag_id FROM projects WHERE path = ?").get(projectPath) as { default_tag_id: number | null } | null;
|
|
113
|
+
return row?.default_tag_id ?? null;
|
|
114
|
+
}
|