@hienlh/ppm 0.8.50 → 0.8.52
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 +18 -0
- package/dist/web/assets/api-settings-D4bgXrLU.js +1 -0
- package/dist/web/assets/chat-tab-wunayDmr.js +7 -0
- package/dist/web/assets/{code-editor-DB-y8tPy.js → code-editor-Fw_VrmHT.js} +1 -1
- package/dist/web/assets/{database-viewer-D4JQEtMD.js → database-viewer-CZjxdELm.js} +1 -1
- package/dist/web/assets/{diff-viewer-ToD0FLsL.js → diff-viewer-B51YfMeK.js} +1 -1
- package/dist/web/assets/{git-graph-Dg7SVF-R.js → git-graph-fCVmtbaj.js} +1 -1
- package/dist/web/assets/index-CoyMn-Mj.css +2 -0
- package/dist/web/assets/index-DMlEKjZt.js +37 -0
- package/dist/web/assets/keybindings-store-BzXZa5uC.js +1 -0
- package/dist/web/assets/{markdown-renderer-DMHeWMgi.js → markdown-renderer-D_OeJdOH.js} +1 -1
- package/dist/web/assets/{postgres-viewer-bUYwSwrp.js → postgres-viewer-BlEIES7N.js} +1 -1
- package/dist/web/assets/{settings-store-ChwdK0tt.js → settings-store-DL2KEbtc.js} +2 -2
- package/dist/web/assets/settings-tab-DnU5t6Fy.js +1 -0
- package/dist/web/assets/{sqlite-viewer-BLUoWIZ5.js → sqlite-viewer-BJ2s8Dng.js} +1 -1
- package/dist/web/assets/{terminal-tab-B5sI9TDZ.js → terminal-tab-DAFbT7Sv.js} +2 -2
- package/dist/web/assets/{use-monaco-theme-0hXmt0_2.js → use-monaco-theme-DwP4EHdO.js} +1 -1
- package/dist/web/index.html +4 -4
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/providers/claude-agent-sdk.ts +21 -8
- package/src/server/index.ts +4 -0
- package/src/server/routes/proxy.ts +79 -0
- package/src/server/routes/settings.ts +53 -1
- package/src/services/proxy.service.ts +139 -0
- package/src/types/config.ts +1 -0
- package/src/web/components/chat/message-list.tsx +2 -125
- package/src/web/components/settings/ai-settings-section.tsx +21 -0
- package/src/web/components/settings/proxy-settings-section.tsx +217 -0
- package/src/web/components/settings/settings-tab.tsx +5 -2
- package/src/web/hooks/use-global-keybindings.ts +13 -1
- package/src/web/lib/api-settings.ts +19 -0
- package/dist/web/assets/api-settings-DfTIjsPW.js +0 -1
- package/dist/web/assets/chat-tab-BZSpI1_2.js +0 -7
- package/dist/web/assets/index-DdLIa98_.js +0 -28
- package/dist/web/assets/index-XRJa3Ncz.css +0 -2
- package/dist/web/assets/keybindings-store-BofWHLIC.js +0 -1
- package/dist/web/assets/settings-tab-D2bgiL7t.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":"49865f1dc6c4195305930e2e5f60893d","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/utils-DC-bdPS3.js"},{"revision":null,"url":"assets/use-monaco-theme-0hXmt0_2.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/terminal-tab-B5sI9TDZ.js"},{"revision":null,"url":"assets/tag-DJUYe5BQ.js"},{"revision":null,"url":"assets/table-B6neW6Hr.js"},{"revision":null,"url":"assets/tab-store-NOBndc0_.js"},{"revision":null,"url":"assets/sqlite-viewer-BLUoWIZ5.js"},{"revision":null,"url":"assets/settings-tab-D2bgiL7t.js"},{"revision":null,"url":"assets/settings-store-ChwdK0tt.js"},{"revision":null,"url":"assets/react-rgzL83kk.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-bUYwSwrp.js"},{"revision":null,"url":"assets/markdown-renderer-DMHeWMgi.js"},{"revision":null,"url":"assets/keybindings-store-BofWHLIC.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-CE3bFwLk.js"},{"revision":null,"url":"assets/index-XRJa3Ncz.css"},{"revision":null,"url":"assets/index-DdLIa98_.js"},{"revision":null,"url":"assets/git-graph-Dg7SVF-R.js"},{"revision":null,"url":"assets/dist-QgqOdSYG.js"},{"revision":null,"url":"assets/diff-viewer-ToD0FLsL.js"},{"revision":null,"url":"assets/database-viewer-D4JQEtMD.js"},{"revision":null,"url":"assets/columns-2-BZ5wv2wA.js"},{"revision":null,"url":"assets/code-editor-DB-y8tPy.js"},{"revision":null,"url":"assets/chat-tab-BZSpI1_2.js"},{"revision":null,"url":"assets/api-settings-DfTIjsPW.js"},{"revision":null,"url":"assets/api-client-TUmacMRS.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":"9396025b863a420d5e04a9412c23581c","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/utils-DC-bdPS3.js"},{"revision":null,"url":"assets/use-monaco-theme-DwP4EHdO.js"},{"revision":null,"url":"assets/terminal-tab-DAFbT7Sv.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/tag-DJUYe5BQ.js"},{"revision":null,"url":"assets/table-B6neW6Hr.js"},{"revision":null,"url":"assets/tab-store-NOBndc0_.js"},{"revision":null,"url":"assets/sqlite-viewer-BJ2s8Dng.js"},{"revision":null,"url":"assets/settings-tab-DnU5t6Fy.js"},{"revision":null,"url":"assets/settings-store-DL2KEbtc.js"},{"revision":null,"url":"assets/react-rgzL83kk.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-BlEIES7N.js"},{"revision":null,"url":"assets/markdown-renderer-D_OeJdOH.js"},{"revision":null,"url":"assets/keybindings-store-BzXZa5uC.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-CE3bFwLk.js"},{"revision":null,"url":"assets/index-DMlEKjZt.js"},{"revision":null,"url":"assets/index-CoyMn-Mj.css"},{"revision":null,"url":"assets/git-graph-fCVmtbaj.js"},{"revision":null,"url":"assets/dist-QgqOdSYG.js"},{"revision":null,"url":"assets/diff-viewer-B51YfMeK.js"},{"revision":null,"url":"assets/database-viewer-CZjxdELm.js"},{"revision":null,"url":"assets/columns-2-BZ5wv2wA.js"},{"revision":null,"url":"assets/code-editor-Fw_VrmHT.js"},{"revision":null,"url":"assets/chat-tab-wunayDmr.js"},{"revision":null,"url":"assets/api-settings-D4bgXrLU.js"},{"revision":null,"url":"assets/api-client-TUmacMRS.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
package/package.json
CHANGED
|
@@ -68,20 +68,33 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
68
68
|
// Settings base_url has highest priority
|
|
69
69
|
const providerConfig = this.getProviderConfig();
|
|
70
70
|
|
|
71
|
-
//
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
// Priority: settings api_key > account token > shell env > "" (blocks project .env)
|
|
72
|
+
const settingsApiKey = providerConfig.api_key?.trim() || "";
|
|
73
|
+
|
|
74
|
+
let resolvedApiKey: string;
|
|
75
|
+
let resolvedOAuth: string;
|
|
76
|
+
|
|
77
|
+
if (settingsApiKey) {
|
|
78
|
+
// Settings api_key overrides everything — treat as direct API key
|
|
79
|
+
resolvedApiKey = settingsApiKey;
|
|
80
|
+
resolvedOAuth = "";
|
|
81
|
+
} else if (account) {
|
|
82
|
+
resolvedApiKey = account.accessToken.startsWith("sk-ant-oat") ? "" : account.accessToken;
|
|
83
|
+
resolvedOAuth = account.accessToken.startsWith("sk-ant-oat") ? account.accessToken : "";
|
|
84
|
+
} else {
|
|
85
|
+
resolvedApiKey = process.env.ANTHROPIC_API_KEY ?? "";
|
|
86
|
+
resolvedOAuth = process.env.CLAUDE_CODE_OAUTH_TOKEN ?? "";
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
const resolvedBaseUrl = providerConfig.base_url
|
|
79
90
|
|| process.env.ANTHROPIC_BASE_URL
|
|
80
91
|
|| "";
|
|
81
92
|
const resolvedAuthToken = process.env.ANTHROPIC_AUTH_TOKEN ?? "";
|
|
82
93
|
|
|
83
94
|
// Log resolved sources
|
|
84
|
-
if (
|
|
95
|
+
if (settingsApiKey) {
|
|
96
|
+
console.log(`[sdk] Auth from settings api_key (length=${settingsApiKey.length})`);
|
|
97
|
+
} else if (account) {
|
|
85
98
|
console.log(`[sdk] Auth from PPM account (${account.accessToken.startsWith("sk-ant-oat") ? "OAuth" : "API key"})`);
|
|
86
99
|
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
87
100
|
console.log(`[sdk] ANTHROPIC_API_KEY from shell env (length=${process.env.ANTHROPIC_API_KEY.length})`);
|
package/src/server/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { postgresRoutes } from "./routes/postgres.ts";
|
|
|
13
13
|
import { databaseRoutes } from "./routes/database.ts";
|
|
14
14
|
import { fsBrowseRoutes } from "./routes/fs-browse.ts";
|
|
15
15
|
import { accountsRoutes } from "./routes/accounts.ts";
|
|
16
|
+
import { proxyRoutes } from "./routes/proxy.ts";
|
|
16
17
|
import { initAdapters } from "../services/database/init-adapters.ts";
|
|
17
18
|
import { terminalWebSocket } from "./ws/terminal.ts";
|
|
18
19
|
import { chatWebSocket } from "./ws/chat.ts";
|
|
@@ -101,6 +102,9 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
101
102
|
app.get("/api/debug/crash", () => { process.exit(1); });
|
|
102
103
|
}
|
|
103
104
|
|
|
105
|
+
// Proxy routes — before auth middleware (uses own auth key)
|
|
106
|
+
app.route("/proxy", proxyRoutes);
|
|
107
|
+
|
|
104
108
|
// Auth check endpoint (behind auth middleware)
|
|
105
109
|
app.use("/api/*", authMiddleware);
|
|
106
110
|
app.get("/api/auth/check", (c) => c.json(ok(true)));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { proxyService } from "../../services/proxy.service.ts";
|
|
3
|
+
import { ok, err } from "../../types/api.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Proxy routes — Anthropic-compatible API proxy.
|
|
7
|
+
* External tools (opencode, cursor, etc.) send requests here
|
|
8
|
+
* and PPM forwards them to Anthropic using account rotation.
|
|
9
|
+
*
|
|
10
|
+
* Mounted at /proxy — so /proxy/v1/messages maps to Anthropic's POST /v1/messages.
|
|
11
|
+
* Uses its own auth (proxy auth key), NOT PPM's auth middleware.
|
|
12
|
+
*/
|
|
13
|
+
export const proxyRoutes = new Hono();
|
|
14
|
+
|
|
15
|
+
/** Validate proxy auth key from Authorization header */
|
|
16
|
+
function validateProxyAuth(authHeader: string | undefined): boolean {
|
|
17
|
+
if (!authHeader) return false;
|
|
18
|
+
const key = proxyService.getAuthKey();
|
|
19
|
+
if (!key) return false;
|
|
20
|
+
// Accept both "Bearer <key>" and raw "<key>" (x-api-key style)
|
|
21
|
+
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
|
|
22
|
+
return token === key;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** CORS preflight for external tools */
|
|
26
|
+
proxyRoutes.options("/*", (c) => {
|
|
27
|
+
return new Response(null, {
|
|
28
|
+
status: 204,
|
|
29
|
+
headers: {
|
|
30
|
+
"Access-Control-Allow-Origin": "*",
|
|
31
|
+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
|
|
32
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization, x-api-key, anthropic-version, anthropic-beta",
|
|
33
|
+
"Access-Control-Max-Age": "86400",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/** POST /proxy/v1/messages — Anthropic Messages API proxy */
|
|
39
|
+
proxyRoutes.post("/v1/messages", async (c) => {
|
|
40
|
+
if (!proxyService.isEnabled()) {
|
|
41
|
+
return c.json({ type: "error", error: { type: "api_error", message: "Proxy is disabled" } }, 503);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Auth check — accept both Authorization and x-api-key headers
|
|
45
|
+
const authHeader = c.req.header("authorization") || c.req.header("x-api-key");
|
|
46
|
+
if (!validateProxyAuth(authHeader)) {
|
|
47
|
+
return c.json({ type: "error", error: { type: "authentication_error", message: "Invalid proxy auth key" } }, 401);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const body = await c.req.text();
|
|
51
|
+
const headers: Record<string, string> = {};
|
|
52
|
+
for (const key of ["anthropic-version", "anthropic-beta", "content-type"]) {
|
|
53
|
+
const val = c.req.header(key);
|
|
54
|
+
if (val) headers[key] = val;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return proxyService.forward("/v1/messages", "POST", headers, body);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** POST /proxy/v1/messages/count_tokens — token counting proxy */
|
|
61
|
+
proxyRoutes.post("/v1/messages/count_tokens", async (c) => {
|
|
62
|
+
if (!proxyService.isEnabled()) {
|
|
63
|
+
return c.json({ type: "error", error: { type: "api_error", message: "Proxy is disabled" } }, 503);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const authHeader = c.req.header("authorization") || c.req.header("x-api-key");
|
|
67
|
+
if (!validateProxyAuth(authHeader)) {
|
|
68
|
+
return c.json({ type: "error", error: { type: "authentication_error", message: "Invalid proxy auth key" } }, 401);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const body = await c.req.text();
|
|
72
|
+
const headers: Record<string, string> = {};
|
|
73
|
+
for (const key of ["anthropic-version", "anthropic-beta", "content-type"]) {
|
|
74
|
+
const val = c.req.header(key);
|
|
75
|
+
if (val) headers[key] = val;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return proxyService.forward("/v1/messages/count_tokens", "POST", headers, body);
|
|
79
|
+
});
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type ThemeConfig,
|
|
11
11
|
} from "../../types/config.ts";
|
|
12
12
|
import { ok, err } from "../../types/api.ts";
|
|
13
|
+
import { proxyService } from "../../services/proxy.service.ts";
|
|
13
14
|
|
|
14
15
|
export const settingsRoutes = new Hono();
|
|
15
16
|
|
|
@@ -17,7 +18,12 @@ export const settingsRoutes = new Hono();
|
|
|
17
18
|
function stripSensitiveFields(ai: { providers: Record<string, unknown> }) {
|
|
18
19
|
const clone = structuredClone(ai);
|
|
19
20
|
for (const provider of Object.values(clone.providers)) {
|
|
20
|
-
|
|
21
|
+
const p = provider as Record<string, unknown>;
|
|
22
|
+
delete p.api_key_env;
|
|
23
|
+
// Mask api_key: show only that it's set, not the value
|
|
24
|
+
if (p.api_key && typeof p.api_key === "string" && p.api_key.length > 0) {
|
|
25
|
+
p.api_key = "••••" + (p.api_key as string).slice(-4);
|
|
26
|
+
}
|
|
21
27
|
}
|
|
22
28
|
return clone;
|
|
23
29
|
}
|
|
@@ -120,6 +126,10 @@ settingsRoutes.put("/ai", async (c) => {
|
|
|
120
126
|
if (body.providers) {
|
|
121
127
|
updated.providers = { ...currentAi.providers };
|
|
122
128
|
for (const [name, config] of Object.entries(body.providers)) {
|
|
129
|
+
// Don't overwrite api_key with the masked value from UI
|
|
130
|
+
if (config.api_key && config.api_key.startsWith("••••")) {
|
|
131
|
+
delete config.api_key;
|
|
132
|
+
}
|
|
123
133
|
updated.providers[name] = {
|
|
124
134
|
...currentAi.providers[name],
|
|
125
135
|
...config,
|
|
@@ -227,3 +237,45 @@ settingsRoutes.post("/telegram/test", async (c) => {
|
|
|
227
237
|
return c.json(err((e as Error).message), 500);
|
|
228
238
|
}
|
|
229
239
|
});
|
|
240
|
+
|
|
241
|
+
// ── Proxy ────────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
/** GET /settings/proxy — proxy status */
|
|
244
|
+
settingsRoutes.get("/proxy", async (c) => {
|
|
245
|
+
const { tunnelService } = await import("../../services/tunnel.service.ts");
|
|
246
|
+
const tunnelUrl = tunnelService.getTunnelUrl();
|
|
247
|
+
const authKey = proxyService.getAuthKey();
|
|
248
|
+
return c.json(ok({
|
|
249
|
+
enabled: proxyService.isEnabled(),
|
|
250
|
+
authKey: authKey ?? null,
|
|
251
|
+
requestCount: proxyService.getRequestCount(),
|
|
252
|
+
tunnelUrl: tunnelUrl ?? null,
|
|
253
|
+
proxyEndpoint: tunnelUrl ? `${tunnelUrl}/proxy/v1/messages` : null,
|
|
254
|
+
}));
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
/** PUT /settings/proxy — update proxy settings */
|
|
258
|
+
settingsRoutes.put("/proxy", async (c) => {
|
|
259
|
+
try {
|
|
260
|
+
const body = await c.req.json<{ enabled?: boolean; authKey?: string; generateKey?: boolean }>();
|
|
261
|
+
if (body.enabled !== undefined) {
|
|
262
|
+
proxyService.setEnabled(body.enabled);
|
|
263
|
+
}
|
|
264
|
+
if (body.generateKey) {
|
|
265
|
+
proxyService.generateAuthKey();
|
|
266
|
+
} else if (body.authKey !== undefined) {
|
|
267
|
+
proxyService.setAuthKey(body.authKey);
|
|
268
|
+
}
|
|
269
|
+
const { tunnelService } = await import("../../services/tunnel.service.ts");
|
|
270
|
+
const tunnelUrl = tunnelService.getTunnelUrl();
|
|
271
|
+
return c.json(ok({
|
|
272
|
+
enabled: proxyService.isEnabled(),
|
|
273
|
+
authKey: proxyService.getAuthKey() ?? null,
|
|
274
|
+
requestCount: proxyService.getRequestCount(),
|
|
275
|
+
tunnelUrl: tunnelUrl ?? null,
|
|
276
|
+
proxyEndpoint: tunnelUrl ? `${tunnelUrl}/proxy/v1/messages` : null,
|
|
277
|
+
}));
|
|
278
|
+
} catch (e) {
|
|
279
|
+
return c.json(err((e as Error).message), 400);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { getConfigValue, setConfigValue } from "./db.service.ts";
|
|
2
|
+
import { accountSelector } from "./account-selector.service.ts";
|
|
3
|
+
import { accountService } from "./account.service.ts";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
|
|
6
|
+
const PROXY_ENABLED_KEY = "proxy_enabled";
|
|
7
|
+
const PROXY_AUTH_KEY = "proxy_auth_key";
|
|
8
|
+
|
|
9
|
+
const ANTHROPIC_API_BASE = "https://api.anthropic.com";
|
|
10
|
+
|
|
11
|
+
class ProxyService {
|
|
12
|
+
private requestCount = 0;
|
|
13
|
+
|
|
14
|
+
isEnabled(): boolean {
|
|
15
|
+
return getConfigValue(PROXY_ENABLED_KEY) === "true";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setEnabled(enabled: boolean): void {
|
|
19
|
+
setConfigValue(PROXY_ENABLED_KEY, String(enabled));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getAuthKey(): string | null {
|
|
23
|
+
return getConfigValue(PROXY_AUTH_KEY);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Generate a new random auth key */
|
|
27
|
+
generateAuthKey(): string {
|
|
28
|
+
const key = `ppm-proxy-${randomBytes(16).toString("hex")}`;
|
|
29
|
+
setConfigValue(PROXY_AUTH_KEY, key);
|
|
30
|
+
return key;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Set a custom auth key */
|
|
34
|
+
setAuthKey(key: string): void {
|
|
35
|
+
setConfigValue(PROXY_AUTH_KEY, key);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getRequestCount(): number {
|
|
39
|
+
return this.requestCount;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Forward a request to Anthropic API using account rotation.
|
|
44
|
+
* Returns a Response object (may be streaming SSE).
|
|
45
|
+
*/
|
|
46
|
+
async forward(
|
|
47
|
+
path: string,
|
|
48
|
+
method: string,
|
|
49
|
+
headers: Record<string, string>,
|
|
50
|
+
body: string | null,
|
|
51
|
+
): Promise<Response> {
|
|
52
|
+
// Pick account via rotation
|
|
53
|
+
const account = accountSelector.next();
|
|
54
|
+
if (!account) {
|
|
55
|
+
return new Response(
|
|
56
|
+
JSON.stringify({ type: "error", error: { type: "authentication_error", message: "No active accounts available" } }),
|
|
57
|
+
{ status: 401, headers: { "Content-Type": "application/json" } },
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Ensure token is fresh for OAuth accounts
|
|
62
|
+
let token = account.accessToken;
|
|
63
|
+
if (token.startsWith("sk-ant-oat")) {
|
|
64
|
+
const fresh = await accountService.ensureFreshToken(account.id);
|
|
65
|
+
if (fresh) token = fresh.accessToken;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Build upstream headers — forward relevant Anthropic headers
|
|
69
|
+
const upstreamHeaders: Record<string, string> = {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"User-Agent": "ppm-proxy/1.0",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Set auth based on token type
|
|
75
|
+
if (token.startsWith("sk-ant-oat")) {
|
|
76
|
+
upstreamHeaders["Authorization"] = `Bearer ${token}`;
|
|
77
|
+
upstreamHeaders["anthropic-beta"] = headers["anthropic-beta"] || "oauth-2025-04-20";
|
|
78
|
+
} else {
|
|
79
|
+
upstreamHeaders["x-api-key"] = token;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Forward anthropic-version header
|
|
83
|
+
if (headers["anthropic-version"]) {
|
|
84
|
+
upstreamHeaders["anthropic-version"] = headers["anthropic-version"];
|
|
85
|
+
}
|
|
86
|
+
// Forward anthropic-beta if present from client
|
|
87
|
+
if (headers["anthropic-beta"]) {
|
|
88
|
+
upstreamHeaders["anthropic-beta"] = headers["anthropic-beta"];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = `${ANTHROPIC_API_BASE}${path}`;
|
|
92
|
+
console.log(`[proxy] ${method} ${path} → account ${account.email ?? account.id}`);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const upstream = await fetch(url, {
|
|
96
|
+
method,
|
|
97
|
+
headers: upstreamHeaders,
|
|
98
|
+
body: body || undefined,
|
|
99
|
+
signal: AbortSignal.timeout(300_000), // 5min timeout for long streaming
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
this.requestCount++;
|
|
103
|
+
|
|
104
|
+
// Handle rate limit / auth errors for account rotation
|
|
105
|
+
if (upstream.status === 429) {
|
|
106
|
+
accountSelector.onRateLimit(account.id);
|
|
107
|
+
console.log(`[proxy] 429 from Anthropic — account ${account.email ?? account.id} rate limited`);
|
|
108
|
+
} else if (upstream.status === 401) {
|
|
109
|
+
accountSelector.onAuthError(account.id);
|
|
110
|
+
console.log(`[proxy] 401 from Anthropic — account ${account.email ?? account.id} auth error`);
|
|
111
|
+
} else if (upstream.status >= 200 && upstream.status < 300) {
|
|
112
|
+
accountSelector.onSuccess(account.id);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Stream response back as-is (preserves SSE for streaming)
|
|
116
|
+
const responseHeaders = new Headers();
|
|
117
|
+
// Forward key response headers
|
|
118
|
+
for (const key of ["content-type", "x-request-id", "request-id"]) {
|
|
119
|
+
const val = upstream.headers.get(key);
|
|
120
|
+
if (val) responseHeaders.set(key, val);
|
|
121
|
+
}
|
|
122
|
+
// CORS for external tools
|
|
123
|
+
responseHeaders.set("Access-Control-Allow-Origin", "*");
|
|
124
|
+
|
|
125
|
+
return new Response(upstream.body, {
|
|
126
|
+
status: upstream.status,
|
|
127
|
+
headers: responseHeaders,
|
|
128
|
+
});
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.error(`[proxy] Error forwarding to Anthropic:`, (e as Error).message);
|
|
131
|
+
return new Response(
|
|
132
|
+
JSON.stringify({ type: "error", error: { type: "api_error", message: (e as Error).message } }),
|
|
133
|
+
{ status: 502, headers: { "Content-Type": "application/json" } },
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export const proxyService = new ProxyService();
|
package/src/types/config.ts
CHANGED
|
@@ -46,6 +46,7 @@ export type PermissionMode = typeof VALID_PERMISSION_MODES[number];
|
|
|
46
46
|
export interface AIProviderConfig {
|
|
47
47
|
type: "agent-sdk" | "mock";
|
|
48
48
|
api_key_env?: string;
|
|
49
|
+
api_key?: string;
|
|
49
50
|
base_url?: string;
|
|
50
51
|
// Agent SDK-specific settings (ignored by mock provider)
|
|
51
52
|
model?: string;
|
|
@@ -72,71 +72,15 @@ export function MessageList({
|
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// Track which user message is pinned (scrolled above viewport) + push-out offset
|
|
76
|
-
const [pinnedContent, setPinnedContent] = useState<string | null>(null);
|
|
77
|
-
const [pushOffset, setPushOffset] = useState(0);
|
|
78
|
-
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
79
|
-
const pinnedRef = useRef<HTMLDivElement>(null);
|
|
80
|
-
|
|
81
75
|
const filtered = useMemo(() => messages.filter((msg) => {
|
|
82
76
|
const hasContent = msg.content && msg.content.trim().length > 0;
|
|
83
77
|
const hasEvents = msg.events && msg.events.length > 0;
|
|
84
78
|
return hasContent || hasEvents;
|
|
85
79
|
}), [messages]);
|
|
86
80
|
|
|
87
|
-
// Observe user message elements to track which one is pinned + push-out transition
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
const wrapper = wrapperRef.current;
|
|
90
|
-
if (!wrapper) return;
|
|
91
|
-
const scrollEl = wrapper.querySelector("[data-stick-to-bottom-scroll]") as HTMLElement
|
|
92
|
-
?? wrapper.firstElementChild as HTMLElement;
|
|
93
|
-
if (!scrollEl || scrollEl.scrollHeight <= scrollEl.clientHeight) return;
|
|
94
|
-
|
|
95
|
-
const handleScroll = () => {
|
|
96
|
-
const userEls = wrapper.querySelectorAll<HTMLElement>("[data-user-content]");
|
|
97
|
-
const scrollRect = scrollEl.getBoundingClientRect();
|
|
98
|
-
const pinnedH = pinnedRef.current?.offsetHeight ?? 0;
|
|
99
|
-
|
|
100
|
-
let lastAbove: string | null = null;
|
|
101
|
-
let nextTop = Infinity;
|
|
102
|
-
|
|
103
|
-
for (let i = 0; i < userEls.length; i++) {
|
|
104
|
-
const rect = userEls[i]!.getBoundingClientRect();
|
|
105
|
-
if (rect.top < scrollRect.top + 4) {
|
|
106
|
-
lastAbove = userEls[i]!.getAttribute("data-user-content");
|
|
107
|
-
// Find the next user message after this one
|
|
108
|
-
const nextEl = userEls[i + 1];
|
|
109
|
-
nextTop = nextEl ? nextEl.getBoundingClientRect().top - scrollRect.top : Infinity;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
setPinnedContent(lastAbove);
|
|
114
|
-
// Push-out: when next header enters the pinned area, offset upward
|
|
115
|
-
if (pinnedH > 0 && nextTop < pinnedH) {
|
|
116
|
-
setPushOffset(nextTop - pinnedH);
|
|
117
|
-
} else {
|
|
118
|
-
setPushOffset(0);
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
scrollEl.addEventListener("scroll", handleScroll, { passive: true });
|
|
123
|
-
handleScroll();
|
|
124
|
-
return () => scrollEl.removeEventListener("scroll", handleScroll);
|
|
125
|
-
}, [filtered]);
|
|
126
|
-
|
|
127
81
|
return (
|
|
128
82
|
<div className="relative flex-1 overflow-hidden flex flex-col min-h-0">
|
|
129
|
-
|
|
130
|
-
{pinnedContent && (
|
|
131
|
-
<div
|
|
132
|
-
ref={pinnedRef}
|
|
133
|
-
className="absolute top-0 left-0 right-0 z-20 bg-background"
|
|
134
|
-
style={pushOffset < 0 ? { transform: `translateY(${pushOffset}px)` } : undefined}
|
|
135
|
-
>
|
|
136
|
-
<PinnedUserMessage content={pinnedContent} projectName={projectName} />
|
|
137
|
-
</div>
|
|
138
|
-
)}
|
|
139
|
-
<StickToBottom ref={wrapperRef} className="flex-1 overflow-y-auto overflow-x-hidden" resize="smooth" initial="instant">
|
|
83
|
+
<StickToBottom className="flex-1 overflow-y-auto overflow-x-hidden" resize="smooth" initial="instant">
|
|
140
84
|
<StickToBottom.Content className="p-4 space-y-4">
|
|
141
85
|
{filtered.map((msg) => (
|
|
142
86
|
<MessageBubble
|
|
@@ -162,73 +106,6 @@ export function MessageList({
|
|
|
162
106
|
);
|
|
163
107
|
}
|
|
164
108
|
|
|
165
|
-
/** Compact pinned bar showing the current user message at the top of chat */
|
|
166
|
-
function PinnedUserMessage({ content, projectName }: { content: string; projectName?: string }) {
|
|
167
|
-
const { files, text } = useMemo(() => {
|
|
168
|
-
const parsed = parseUserAttachments(content);
|
|
169
|
-
const { cleanText } = extractSystemTags(parsed.text);
|
|
170
|
-
return { files: parsed.files, text: cleanText };
|
|
171
|
-
}, [content]);
|
|
172
|
-
|
|
173
|
-
const [expanded, setExpanded] = useState(false);
|
|
174
|
-
const [isOverflowing, setIsOverflowing] = useState(false);
|
|
175
|
-
const contentRef = useRef<HTMLDivElement>(null);
|
|
176
|
-
|
|
177
|
-
// Reset expanded state when pinned message changes
|
|
178
|
-
useEffect(() => { setExpanded(false); }, [content]);
|
|
179
|
-
|
|
180
|
-
useEffect(() => {
|
|
181
|
-
const el = contentRef.current;
|
|
182
|
-
if (!el) return;
|
|
183
|
-
const check = () => setIsOverflowing(el.scrollHeight > el.clientHeight + 2);
|
|
184
|
-
check();
|
|
185
|
-
const ro = new ResizeObserver(check);
|
|
186
|
-
ro.observe(el);
|
|
187
|
-
return () => ro.disconnect();
|
|
188
|
-
}, [text]);
|
|
189
|
-
|
|
190
|
-
if (!text && files.length === 0) return null;
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
<div className="shrink-0 px-4 pt-3 pb-2">
|
|
194
|
-
<div className="rounded-lg bg-primary/10 px-3 py-2 text-sm text-text-primary space-y-2 border border-primary/15 shadow-sm">
|
|
195
|
-
{files.length > 0 && (
|
|
196
|
-
<div className="flex flex-wrap gap-1.5">
|
|
197
|
-
{files.map((filePath, i) => (
|
|
198
|
-
<div key={i} className="flex items-center gap-1 rounded-md border border-border/60 bg-background/40 px-1.5 py-0.5 text-[11px] text-text-secondary">
|
|
199
|
-
{isImagePath(filePath) ? <ImageIcon className="size-3 shrink-0" /> : <FileText className="size-3 shrink-0" />}
|
|
200
|
-
<span className="truncate max-w-32">{basename(filePath)}</span>
|
|
201
|
-
</div>
|
|
202
|
-
))}
|
|
203
|
-
</div>
|
|
204
|
-
)}
|
|
205
|
-
{text && (
|
|
206
|
-
<div>
|
|
207
|
-
<div
|
|
208
|
-
ref={contentRef}
|
|
209
|
-
className={cn(
|
|
210
|
-
"whitespace-pre-wrap break-words transition-all duration-200",
|
|
211
|
-
!expanded && "line-clamp-2",
|
|
212
|
-
expanded && "max-h-[40vh] overflow-y-auto",
|
|
213
|
-
)}
|
|
214
|
-
>
|
|
215
|
-
{text}
|
|
216
|
-
</div>
|
|
217
|
-
{(isOverflowing || expanded) && (
|
|
218
|
-
<button
|
|
219
|
-
onClick={() => setExpanded(!expanded)}
|
|
220
|
-
className="flex items-center gap-1 text-xs text-primary/70 hover:text-primary mt-1 transition-colors"
|
|
221
|
-
>
|
|
222
|
-
{expanded ? <><ChevronUp className="size-3" />Show less</> : <><ChevronDown className="size-3" />Show more</>}
|
|
223
|
-
</button>
|
|
224
|
-
)}
|
|
225
|
-
</div>
|
|
226
|
-
)}
|
|
227
|
-
</div>
|
|
228
|
-
</div>
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
109
|
/** Floating button to scroll back to bottom when user has scrolled up */
|
|
233
110
|
function ScrollToBottomButton() {
|
|
234
111
|
const { isAtBottom, scrollToBottom } = useStickToBottomContext();
|
|
@@ -374,7 +251,7 @@ function UserBubble({ content, projectName, onFork }: { content: string; project
|
|
|
374
251
|
}, [text]);
|
|
375
252
|
|
|
376
253
|
return (
|
|
377
|
-
<div
|
|
254
|
+
<div className="group/user relative rounded-lg bg-primary/10 px-3 py-2 text-sm text-text-primary border border-primary/15 shadow-sm">
|
|
378
255
|
{/* System tags as badges */}
|
|
379
256
|
{tags.length > 0 && <SystemTagBadges tags={tags} />}
|
|
380
257
|
|
|
@@ -117,6 +117,27 @@ export function AISettingsSection({ compact }: { compact?: boolean } = {}) {
|
|
|
117
117
|
/>
|
|
118
118
|
</div>
|
|
119
119
|
|
|
120
|
+
<div className={fieldGap}>
|
|
121
|
+
<Label htmlFor="ai-api-key" className={compact ? labelSize : undefined}>API Key / Token</Label>
|
|
122
|
+
<Input
|
|
123
|
+
key={`apikey-${revision}`}
|
|
124
|
+
id="ai-api-key"
|
|
125
|
+
type="password"
|
|
126
|
+
defaultValue={config?.api_key ?? ""}
|
|
127
|
+
placeholder="sk-ant-... (optional, overrides accounts)"
|
|
128
|
+
className={compact ? "h-7 text-[11px] font-mono" : "font-mono"}
|
|
129
|
+
onBlur={(e) => {
|
|
130
|
+
const val = e.target.value.trim();
|
|
131
|
+
// Don't save if it's the masked value
|
|
132
|
+
if (val.startsWith("••••")) return;
|
|
133
|
+
handleSave("api_key", val || undefined);
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
<p className={`${compact ? "text-[9px]" : "text-[11px]"} text-muted-foreground`}>
|
|
137
|
+
Direct API key or OAuth token. Leave empty to use connected accounts.
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
120
141
|
<div className={fieldGap}>
|
|
121
142
|
<Label htmlFor="ai-effort" className={compact ? labelSize : undefined}>Effort</Label>
|
|
122
143
|
<Select
|