@nextclaw/ui 0.27.5-beta.0 → 0.27.5-beta.1
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 +10 -0
- package/dist/.vite/manifest.json +99 -99
- package/dist/assets/{account-panel-DgqicWMm.js → account-panel-C83GDFuy.js} +1 -1
- package/dist/assets/agents-CJA15zzv.js +1 -0
- package/dist/assets/{agents-CTSR4ZKb.js → agents-Dr2KkEp2.js} +1 -1
- package/dist/assets/{app-presenter-provider-DdD2Q7jZ.js → app-presenter-provider-Cx9MwMtb.js} +12 -12
- package/dist/assets/{appearance-settings-page-CSWYLrsR.js → appearance-settings-page-3IfIzjic.js} +1 -1
- package/dist/assets/apps-CvIlb9Rn.js +1 -0
- package/dist/assets/{confirm-dialog-jz2cQKId.js → confirm-dialog-D30HJBXN.js} +1 -1
- package/dist/assets/{context-menu-C0qywo73.js → context-menu-C6m-WNpl.js} +65 -65
- package/dist/assets/cron-BWZWu80t.js +1 -0
- package/dist/assets/{dialog-DMKYZDFu.js → dialog-DB_l9LK0.js} +1 -1
- package/dist/assets/doc-browser-DQDsmRlr.js +1 -0
- package/dist/assets/inbox-CIsD65lW.js +1 -0
- package/dist/assets/{keyboard-shortcuts-settings-page-BCxB9m69.js → keyboard-shortcuts-settings-page-L4kLGuB8.js} +1 -1
- package/dist/assets/{main-C6VGoYNO.js → main-BNWRrWc_.js} +2 -2
- package/dist/assets/{marketplace-CXCLbyho.js → marketplace-BH1Sq8dL.js} +1 -1
- package/dist/assets/mcp-marketplace-page-BA-InAbJ.js +1 -0
- package/dist/assets/{model-config-page-CeVvZ29o.js → model-config-page-zqVWyIT0.js} +1 -1
- package/dist/assets/panel-apps-CMX4iWwQ.js +1 -0
- package/dist/assets/{panelStandalone-DomappXm.js → panelStandalone-C38o5B4Q.js} +1 -1
- package/dist/assets/{projects-BWM-oEo0.js → projects-DNe43T2d.js} +1 -1
- package/dist/assets/{provider-models-D8WOdsdu.js → provider-models-zGsDUBHG.js} +1 -1
- package/dist/assets/{provider-scoped-model-input-BOPkUOtD.js → provider-scoped-model-input-DpEyP1h8.js} +1 -1
- package/dist/assets/{providers-config-page-BilPL0kF.js → providers-config-page-J-1auWPu.js} +1 -1
- package/dist/assets/{pwa-Cp7pPvsZ.js → pwa-jmuWbrqH.js} +1 -1
- package/dist/assets/remote-C7H2o46-.js +1 -0
- package/dist/assets/{runtime-config-page-Dx6lWn3_.js → runtime-config-page-CJhqeDcu.js} +1 -1
- package/dist/assets/{service-apps-panel-9khaUSFI.js → service-apps-panel-Dj9okUgp.js} +1 -1
- package/dist/assets/{theme-provider-BsJ6HxeX.js → theme-provider-D4rd-tCP.js} +1 -1
- package/dist/index.html +10 -10
- package/dist/panel-standalone.html +4 -4
- package/package.json +7 -7
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +2 -0
- package/src/features/panel-apps/components/panel-app-runtime-surface.tsx +3 -1
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +25 -10
- package/src/features/right-panel-resources/components/__tests__/system-object-resource.test.tsx +25 -3
- package/src/features/right-panel-resources/components/native-object-resource.tsx +3 -1
- package/src/features/right-panel-resources/components/system-object-resource.tsx +15 -1
- package/src/features/right-panel-resources/pages/resource-page.tsx +29 -2
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +16 -13
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +3 -0
- package/src/shared/components/doc-browser/doc-browser.tsx +7 -1
- package/src/shared/components/ui/context-menu/context-menu-items.tsx +1 -0
- package/src/shared/components/ui/context-menu/context-menu.tsx +9 -0
- package/dist/assets/agents-i7167DcF.js +0 -1
- package/dist/assets/apps-DUP3aPpu.js +0 -1
- package/dist/assets/cron-DPBUHn7j.js +0 -1
- package/dist/assets/doc-browser-C7kvXGc_.js +0 -1
- package/dist/assets/inbox-C09MQO9k.js +0 -1
- package/dist/assets/mcp-marketplace-page-ZAm380SB.js +0 -1
- package/dist/assets/panel-apps-BQlyeAId.js +0 -1
- package/dist/assets/remote-DngM8iep.js +0 -1
- package/src/features/panel-apps/components/__tests__/panel-app-toolbar.test.tsx +0 -108
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +0 -89
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i as e}from"./chunk-DF37yw5_.js";import{Lt as t,Qn as n,Sn as r,Xn as i,Yt as a,Zn as o,fn as s,hn as c,lr as l,ur as ee,xr as u}from"./api-CXWxExzS.js";import{t as d}from"./createLucideIcon-C3LSyx-e.js";import{J as te}from"./context-menu-C0qywo73.js";import{d as ne,f as re,l as f,p,s as m}from"./i18n-ByVUrt9w.js";import{a as ie,i as ae,n as oe,o as h,r as se,t as ce}from"./dialog-DMKYZDFu.js";import{n as g}from"./navigation-history-CCnxsAKR.js";var _=`nextclaw.ui.theme`,v=`default`,le=`plain-paper`;function y(e){return e===`default`?le:e}var b=[{value:`default`,labelKey:`themeDefault`,appearance:`light`},{value:`plain-paper`,labelKey:`themePlainPaper`,appearance:`light`},{value:`paper-ink`,labelKey:`themePaperInk`,appearance:`light`},{value:`work`,labelKey:`themeWork`,appearance:`light`},{value:`night`,labelKey:`themeNight`,appearance:`dark`},{value:`charcoal`,labelKey:`themeCharcoal`,appearance:`dark`},{value:`island`,labelKey:`themeIsland`,appearance:`light`},{value:`natural`,labelKey:`themeNatural`,appearance:`light`},{value:`minimal`,labelKey:`themeMinimal`,appearance:`light`},{value:`warm`,labelKey:`themeWarm`,appearance:`light`},{value:`cool`,labelKey:`themeCool`,appearance:`light`},{value:`dawn`,labelKey:`themeDawn`,appearance:`light`},{value:`graphite`,labelKey:`themeGraphite`,appearance:`light`},{value:`probe`,labelKey:`themeProbe`,appearance:`light`}],ue=b.map(({value:e})=>e),de=b.map(({value:e,labelKey:t})=>({value:e,labelKey:t}));function x(e){return typeof e==`string`?ue.includes(e)?e:e===`leaf`?`warm`:null:null}function S(e){return b.find(t=>t.value===y(e))?.appearance??`light`}var C=new class{constructor(){this.activeTheme=v,this.initialized=!1,this.listeners=new Set,this.resolveInitialTheme=()=>{if(typeof window>`u`)return v;try{let e=x(window.localStorage.getItem(_));if(e)return e}catch{}return v},this.initializeTheme=()=>(this.initialized||=(this.activeTheme=this.resolveInitialTheme(),this.applyThemeAttribute(this.activeTheme),!0),this.activeTheme),this.getTheme=()=>this.initialized?this.activeTheme:this.initializeTheme(),this.setTheme=e=>{this.initializeTheme(),e!==this.activeTheme&&(this.activeTheme=e,this.applyThemeAttribute(this.activeTheme),this.saveTheme(this.activeTheme),this.listeners.forEach(e=>e(this.activeTheme)))},this.subscribeThemeChange=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)}),this.applyThemeAttribute=e=>{if(typeof document>`u`)return;let t=S(e);document.documentElement.setAttribute(`data-theme`,y(e)),document.documentElement.setAttribute(`data-theme-appearance`,t),document.documentElement.style.colorScheme=t},this.saveTheme=e=>{if(!(typeof window>`u`))try{window.localStorage.setItem(_,e)}catch{}}}};function w(){return C.initializeTheme()}function fe(){return C.getTheme()}function pe(e){C.setTheme(e)}function me(e){return C.subscribeThemeChange(e)}var T=d(`Boxes`,[[`path`,{d:`M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z`,key:`lc1i9w`}],[`path`,{d:`m7 16.5-4.74-2.85`,key:`1o9zyk`}],[`path`,{d:`m7 16.5 5-3`,key:`va8pkn`}],[`path`,{d:`M7 16.5v5.17`,key:`jnp8gn`}],[`path`,{d:`M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z`,key:`8zsnat`}],[`path`,{d:`m17 16.5-5-3`,key:`8arw3v`}],[`path`,{d:`m17 16.5 4.74-2.85`,key:`8rfmw`}],[`path`,{d:`M17 16.5v5.17`,key:`k6z78m`}],[`path`,{d:`M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z`,key:`1xygjf`}],[`path`,{d:`M12 8 7.26 5.15`,key:`1vbdud`}],[`path`,{d:`m12 8 4.74-2.85`,key:`3rx089`}],[`path`,{d:`M12 13.5V8`,key:`1io7kd`}]]),E=d(`ShieldAlert`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`M12 8v4`,key:`1got3b`}],[`path`,{d:`M12 16h.01`,key:`1drbdi`}]]),D=class{constructor(e){this.authorizationManager=e,this.handleIframeMessage=({event:e,iframe:t})=>{this.isBridgeRequest(e.data)&&(!t?.contentWindow||e.source!==t.contentWindow||this.handleBridgeRequest({event:e,iframe:t},e.data))},this.handleBridgeRequest=async(e,t)=>{try{let n=this.getBridgeContext(t),r=await this.dispatchRequest(n,t);this.postResponse(e,{type:`nextclaw:panel-app-service-actions:response`,requestId:t.requestId,ok:!0,data:r})}catch(n){this.postResponse(e,{type:`nextclaw:panel-app-service-actions:response`,requestId:t.requestId,ok:!1,error:this.toBridgeError(n)})}},this.dispatchRequest=async(e,n)=>{switch(n.method){case`list`:return await t.serviceApps.listServiceActions({bridgeSessionToken:e.token});case`requestGrant`:return await this.confirmAndGrant(e,this.requireActionId(n));case`revokeGrant`:return await t.serviceApps.revokeServiceAction(this.requireActionId(n),{bridgeSessionToken:e.token});case`invoke`:return await this.invokeWithAuthorization(e,n);case`agent.send`:return await this.sendAgentMessageWithAuthorization(e,n);case`agent.generateObject`:return await this.generateAgentObjectWithAuthorization(e,n);case`verification.list`:return await t.serviceApps.listVerificationRecords({acceptanceId:n.payload?.acceptanceId,appId:n.payload?.appId,limit:n.payload?.limit,bridgeSessionToken:e.token});case`acceptance.status`:return await t.serviceApps.getPortableRuntimeAcceptanceStatus({appId:n.payload?.appId,locale:n.payload?.locale,bridgeSessionToken:e.token});case`acceptance.export`:return await t.serviceApps.exportPortableRuntimeAcceptance({appId:n.payload?.appId,locale:n.payload?.locale,bridgeSessionToken:e.token});default:throw Error(`Unsupported panel bridge method: ${String(n.method)}`)}},this.invokeWithAuthorization=async(e,n)=>{let r=this.requireActionId(n);try{return await t.serviceApps.invokeServiceAction(r,this.readOptionalRecord(n.payload?.input),{bridgeSessionToken:e.token})}catch(i){if(!(i instanceof c)||i.code!==`AUTHORIZATION_REQUIRED`)throw i;return await this.confirmAndGrant(e,r,this.readOptionalRecord(n.payload?.input)),await t.serviceApps.invokeServiceAction(r,this.readOptionalRecord(n.payload?.input),{bridgeSessionToken:e.token})}},this.sendAgentMessageWithAuthorization=async(e,n)=>{try{return await t.panelApps.sendAgentMessage(this.readAgentSendRequest(n),{bridgeSessionToken:e.token})}catch(r){if(!(r instanceof c)||r.code!==`AUTHORIZATION_REQUIRED`)throw r;return await this.confirmAndGrantAgentCapability(e,`agent:send`),await t.panelApps.sendAgentMessage(this.readAgentSendRequest(n),{bridgeSessionToken:e.token})}},this.generateAgentObjectWithAuthorization=async(e,n)=>{try{return await t.panelApps.generateAgentObject(this.readGenerateObjectRequest(n),{bridgeSessionToken:e.token})}catch(r){if(!(r instanceof c)||r.code!==`AUTHORIZATION_REQUIRED`)throw r;return await this.confirmAndGrantAgentCapability(e,`agent:generateObject`),await t.panelApps.generateAgentObject(this.readGenerateObjectRequest(n),{bridgeSessionToken:e.token})}},this.confirmAndGrantAgentCapability=async(e,n)=>{if(!await this.authorizationManager.requestAuthorization({panelAppId:e.appId,actions:[{actionId:n,actionTitle:n===`agent:send`?`Send agent message`:`Generate object`,actionDescription:n===`agent:send`?`Send a message to a NextClaw Agent session.`:`Send context to a NextClaw Agent session and receive a structured object.`,risk:`write`}]}))throw new c({code:`AUTHORIZATION_REJECTED`,message:`Permission rejected for ${n}.`});return await t.panelApps.grantAgentCapability(n,{bridgeSessionToken:e.token})},this.confirmAndGrant=async(e,n,r)=>{let i=await this.listGrantCandidateActions(e,n);if(!await this.authorizationManager.requestAuthorization({panelAppId:e.appId,actions:i.map(e=>({actionId:e.id,actionTitle:e.title,actionDescription:e.description,risk:e.risk})),inputPreview:this.createInputPreview(r)}))throw new c({code:`AUTHORIZATION_REJECTED`,message:`Permission rejected for ${n}.`});let a=await t.serviceApps.grantServiceActions(i.map(e=>e.id),{bridgeSessionToken:e.token}),o=a.grants.find(e=>e.actionId===n)??a.grants[0];if(!o)throw Error(`No permission grant returned for ${n}.`);return o},this.listGrantCandidateActions=async(e,n)=>{let r=await t.serviceApps.listServiceActions({bridgeSessionToken:e.token}),i=r.actions.find(e=>e.id===n);if(i&&i.grantState!==`not-granted`)return[i];let a=r.actions.filter(e=>e.grantState===`not-granted`),o=[...a.filter(e=>e.id===n),...a.filter(e=>e.id!==n)];return o.length>0?o:[{appId:i?.appId??n.split(`.`)[0]??n,description:i?.description,id:n,name:i?.name??n,risk:i?.risk??`dangerous`,title:i?.title}]},this.createInputPreview=e=>{if(!(!e||Object.keys(e).length===0))try{let t=JSON.stringify(e,null,2);return t.length>500?`${t.slice(0,500)}...`:t}catch{return`[unserializable input]`}},this.getBridgeContext=e=>({appId:e.appId,token:e.runtimeToken}),this.requireActionId=e=>{let t=e.payload?.actionId?.trim();if(!t)throw Error(`Panel bridge actionId is required.`);return t},this.readOptionalRecord=e=>{if(e!==void 0)return this.requireRecord(e,`input`)},this.readAgentSendRequest=e=>({payload:this.requireRecord(e.payload?.request,`agent.send request`)}),this.readGenerateObjectRequest=e=>({input:this.requireRecord(e.payload?.input,`generateObject input`)}),this.requireRecord=(e,t)=>{if(!e||typeof e!=`object`||Array.isArray(e))throw Error(`${t} is required.`);return e},this.postResponse=(e,t)=>{e.iframe?.contentWindow?.postMessage(t,`*`)},this.toBridgeError=e=>e instanceof c?{code:e.code,message:e.message,details:e.details}:{message:e instanceof Error?e.message:String(e)},this.isBridgeRequest=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return t.type===`nextclaw:panel-app-service-actions:request`&&typeof t.requestId==`string`&&typeof t.runtimeToken==`string`&&typeof t.appId==`string`&&(t.method===`invoke`||t.method===`agent.send`||t.method===`agent.generateObject`||t.method===`acceptance.export`||t.method===`acceptance.status`||t.method===`list`||t.method===`requestGrant`||t.method===`revokeGrant`||t.method===`verification.list`)}}};function he(){return`service-action-auth-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}var O=s((e,t)=>({pending:null,requestAuthorization:async n=>await new Promise(r=>{t().pending?.resolve(!1),e({pending:{id:he(),...n,resolve:r}})}),resolveAuthorization:n=>{let{pending:r}=t();r&&(r.resolve(n),e({pending:null}))}})),ge=class{constructor(){this.requestAuthorization=async e=>await O.getState().requestAuthorization(e)}},k=class{constructor(){this.serviceActionAuthorizationManager=new ge,this.panelAppBridgeManager=new D(this.serviceActionAuthorizationManager)}},A=e(u(),1),j=ee(),M=globalThis,N=M.__NEXTCLAW_PANEL_APP_HOST_CONTEXT__??(0,A.createContext)(null);M.__NEXTCLAW_PANEL_APP_HOST_CONTEXT__=N;function P({children:e,presenter:t}){let n=(0,A.useMemo)(()=>t??new k,[t]);return(0,j.jsx)(N.Provider,{value:n,children:e})}function F(){let e=(0,A.useContext)(N);if(!e)throw Error(`usePanelAppHostPresenter must be used inside PanelAppHostProvider`);return e}var I=[`panel-apps`],L=[`panel-app`];function R(e,t){return e.id===t||e.appId===t}function z(e,t,n){return e&&{...e,entries:e.entries.map(e=>R(e,t)?n(e):e)}}function B(e,t){return z(e,t.id,()=>t)}function V(e,t){let n=e?.entries.reduce((e,t)=>Math.max(e,t.mainSidebarOrder??-1),-1)??-1;return z(e,t.id,e=>({...e,...typeof t.preferences.favorite==`boolean`?{favorite:t.preferences.favorite}:{},...typeof t.preferences.mainSidebar==`boolean`?t.preferences.mainSidebar?{mainSidebar:!0,mainSidebarOrder:e.mainSidebar?e.mainSidebarOrder:n+1}:{mainSidebar:!1,mainSidebarOrder:void 0}:{}}))}function _e(e={}){return n({queryKey:I,queryFn:()=>t.panelApps.listPanelApps(),enabled:e.enabled??!0,staleTime:0})}function H(e,r={}){return n({queryKey:[...L,e],queryFn:()=>t.panelApps.getPanelApp(e),enabled:!!e&&(r.enabled??!0),staleTime:5e3})}function ve(){let e=l();return o({mutationFn:({id:e,preferences:n})=>t.panelApps.updatePanelAppPreferences(e,n),onMutate:async t=>{await e.cancelQueries({queryKey:I});let n=e.getQueryData(I)?.entries.find(e=>R(e,t.id));return e.setQueryData(I,e=>V(e,t)),{previousEntry:n}},onSuccess:t=>{e.setQueryData(I,e=>B(e,t)),e.setQueryData([...L,t.appId],t)},onError:(t,n,r)=>{let a=r?.previousEntry;a&&e.setQueryData(I,e=>z(e,n.id,()=>a));let o=t instanceof Error?t.message:String(t);i.error(`${m(`panelAppsPreferenceUpdateFailed`)}: ${o}`)}})}function ye(){let e=l();return o({mutationFn:e=>t.panelApps.recordPanelAppOpened(e),onSuccess:t=>{e.setQueryData(I,e=>B(e,t)),e.setQueryData([...L,t.appId],t)}})}function U(){let e=l();return o({mutationFn:e=>t.panelApps.grantClient(e),onSuccess:()=>{e.invalidateQueries({queryKey:I}),e.invalidateQueries({queryKey:L})}})}function be(){let e=l();return o({mutationFn:e=>t.panelApps.deletePanelApp(e),onSuccess:()=>{e.invalidateQueries({queryKey:I}),e.invalidateQueries({queryKey:L})}})}function W(){let e=F(),{isPending:t,mutateAsync:n}=U(),r=(0,A.useRef)(new Map);return{ensurePanelAppClientGrant:(0,A.useCallback)(async t=>{if(!t.clientDeclared||t.clientGranted)return!0;let i=r.current.get(t.appId);if(i)return await i;let a=(async()=>await e.serviceActionAuthorizationManager.requestAuthorization({panelAppId:t.appId,actions:[{actionId:`nextclaw.client`,actionTitle:m(`panelAppsClientGrantTitle`),actionDescription:m(`panelAppsClientGrantDescription`),risk:`dangerous`}]})?(await n(t.appId),!0):!1)();r.current.set(t.appId,a);try{return await a}finally{r.current.delete(t.appId)}},[n,e]),isPending:t}}function xe(e){let r=l(),i=(0,A.useId)(),{ensurePanelAppClientGrant:a}=W(),o=!!(e?.clientDeclared&&!e.clientGranted),s=n({queryKey:[`panel-app-client-grant`,e?.appId??`none`,e?.clientGranted??!1],queryFn:async()=>await a(e),enabled:o,gcTime:0,retry:!1,staleTime:1/0}),c=e?o?s.isError?`error`:s.isPending?`checking`:s.data?`allowed`:`denied`:`allowed`:`idle`;return n({queryKey:[`panel-app-open`,i,e?.appId??`none`],queryFn:async()=>{let n=await t.panelApps.recordPanelAppOpened(e.id);return r.setQueryData(I,e=>B(e,n)),r.setQueryData([...L,n.appId],n),n},enabled:!!(e&&c===`allowed`),retry:!1,staleTime:1/0}),{retryClientGrant:s.refetch,state:c}}var G=`allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads allow-pointer-lock allow-presentation`;function K(e){e?.focus({preventScroll:!0}),e?.contentWindow?.focus()}function q(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.kind===`document`?!0:t.kind===`element`&&Array.isArray(t.path)&&t.path.length>0&&t.path.length<=30&&t.path.every(e=>typeof e?.index==`number`&&Number.isInteger(e.index)&&e.index>=0&&e.index<=1e3&&typeof e.tagName==`string`&&e.tagName.length>0&&e.tagName.length<=32)}function Se(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.currentUrl==`string`&&q(t.target)}function Ce(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.type===r.scrollMessageType&&t.version===r.version&&q(t.target)&&typeof t.x==`number`&&Number.isFinite(t.x)&&t.x>=0&&typeof t.y==`number`&&Number.isFinite(t.y)&&t.y>=0}function J({currentUrl:e,iframeRef:t,isEnabled:n,restorationKey:i}){let a=i&&e?JSON.stringify([i,e]):null;return(0,A.useEffect)(()=>{if(!e||!n||!a)return;let r=a,i=n=>{n.source!==t.current?.contentWindow||!Ce(n.data)||g.save(r,{x:n.data.x,y:n.data.y,payload:{currentUrl:e,target:n.data.target}})};return window.addEventListener(`message`,i),()=>window.removeEventListener(`message`,i)},[e,t,n,a]),(0,A.useCallback)(()=>{if(!e||!n||!a)return;let i=g.read(a);!i||!Se(i.payload)||i.payload.currentUrl!==e||t.current?.contentWindow?.postMessage({type:r.restoreScrollMessageType,version:r.version,target:i.payload.target,x:i.x,y:i.y},`*`)},[e,t,n,a])}function Y({appId:e,restorationScope:t,unavailableAction:n}){let r=F(),i=H(e),a=(0,A.useRef)(null),o=(0,A.useMemo)(()=>i.data,[i.data]),s=xe(o),c=J({currentUrl:o?.contentPath??null,iframeRef:a,isEnabled:!!o,restorationKey:o?`panel-app:${t}:${o.appId}`:null});return(0,A.useEffect)(()=>{let e=e=>{r.panelAppBridgeManager.handleIframeMessage({event:e,iframe:a.current})};return window.addEventListener(`message`,e),()=>window.removeEventListener(`message`,e)},[r]),i.isLoading?(0,j.jsx)(X,{message:m(`panelAppsLoading`)}):i.isError?(0,j.jsx)(X,{message:i.error instanceof Error?i.error.message:m(`panelAppsLoadFailed`),actionLabel:m(`panelAppsTryAgain`),onAction:()=>void i.refetch()}):o?s.state===`checking`||s.state===`idle`?(0,j.jsx)(X,{message:m(`panelAppsCheckingPermission`)}):s.state===`denied`||s.state===`error`?(0,j.jsx)(X,{message:s.state===`denied`?m(`panelAppsPermissionDenied`):m(`panelAppsPermissionFailed`),actionLabel:m(`panelAppsTryAgain`),onAction:()=>void s.retryClientGrant()}):(0,j.jsx)(`div`,{className:`h-full min-h-0 bg-background`,children:(0,j.jsx)(`iframe`,{ref:a,src:o.contentPath,title:o.title,sandbox:G,className:`block h-full w-full border-0 bg-background`,onLoad:c,onPointerOver:e=>K(e.currentTarget)},o.appId)}):(0,j.jsx)(X,{message:m(`panelAppsMainUnavailable`),actionLabel:n?.label,onAction:n?.onAction})}function X({actionLabel:e,message:t,onAction:n}){return(0,j.jsx)(`div`,{className:`flex h-full min-h-0 items-center justify-center bg-background px-6 text-center`,children:(0,j.jsxs)(`div`,{className:`max-w-sm`,children:[(0,j.jsx)(`div`,{className:`mx-auto flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground`,children:n?(0,j.jsx)(te,{className:`h-5 w-5`}):(0,j.jsx)(T,{className:`h-5 w-5`})}),(0,j.jsx)(`p`,{className:`mt-3 text-sm leading-6 text-muted-foreground`,children:t}),e&&n?(0,j.jsx)(`button`,{type:`button`,className:`mt-4 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border`,onClick:n,children:e}):null]})})}var Z=(0,A.createContext)(null);function we({children:e}){let[t,n]=(0,A.useState)(()=>ne());(0,A.useEffect)(()=>p(e=>{n(e)}),[]);let r=(0,A.useCallback)(e=>{re(e),n(f())},[]),i=(0,A.useCallback)(()=>{r(t===`en`?`zh`:`en`)},[t,r]),a=(0,A.useMemo)(()=>({language:t,setLanguage:r,toggleLanguage:i}),[t,r,i]);return(0,j.jsx)(Z.Provider,{value:a,children:e})}function Te(){let e=(0,A.useContext)(Z);if(!e)throw Error(`useI18n must be used within I18nProvider`);return{...e,t:m}}function Ee({appId:e}){let t=H(e),n=(0,A.useMemo)(()=>t.data,[t.data]);return(0,A.useEffect)(()=>{let e=document.title;return document.title=`${n?.title??m(`panelAppsTitle`)} · NextClaw`,()=>{document.title=e}},[n?.title]),(0,j.jsx)(`main`,{className:`h-dvh min-h-0 w-full overflow-hidden bg-background`,children:(0,j.jsx)(Y,{appId:e,restorationScope:`standalone`})})}function De(){let e=O(e=>e.pending),t=O(e=>e.resolveAuthorization);return(0,j.jsx)(ce,{open:!!e,onOpenChange:e=>{e||t(!1)},children:(0,j.jsxs)(oe,{children:[(0,j.jsxs)(ie,{showClose:!1,children:[(0,j.jsxs)(h,{className:`flex items-center gap-2`,children:[(0,j.jsx)(E,{className:`h-4 w-4 text-amber-600`}),m(`serviceActionAuthorizationTitle`)]}),(0,j.jsx)(se,{children:m(`serviceActionAuthorizationDescription`)})]}),e?(0,j.jsxs)(`div`,{className:`space-y-3 rounded-md border border-border bg-muted/60 p-3 text-sm`,children:[(0,j.jsx)(Q,{label:m(`serviceActionAuthorizationSource`),value:e.panelAppId}),(0,j.jsxs)(`div`,{className:`space-y-2`,children:[(0,j.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:m(`serviceActionAuthorizationActions`)}),(0,j.jsx)(`div`,{className:`max-h-64 space-y-2 overflow-auto`,children:e.actions.map(e=>(0,j.jsx)(Oe,{action:e},e.actionId))})]}),e.inputPreview?(0,j.jsx)(Q,{label:m(`serviceActionAuthorizationInput`),value:e.inputPreview}):null]}):null,(0,j.jsxs)(ae,{className:`gap-2 sm:gap-0`,children:[(0,j.jsx)(a,{type:`button`,variant:`outline`,onClick:()=>t(!1),children:m(`serviceActionAuthorizationReject`)}),(0,j.jsx)(a,{type:`button`,onClick:()=>t(!0),children:m(`serviceActionAuthorizationAllow`)})]})]})})}function Oe({action:e}){return(0,j.jsxs)(`div`,{className:`space-y-1 rounded border border-border bg-card px-3 py-2`,children:[(0,j.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,j.jsx)(`div`,{className:`min-w-0 break-words text-xs font-medium text-foreground`,children:e.actionTitle??e.actionId}),(0,j.jsx)(`div`,{className:`shrink-0 rounded bg-amber-50 px-1.5 py-0.5 text-[11px] font-medium text-amber-700`,children:e.risk??`dangerous`})]}),(0,j.jsx)(`div`,{className:`break-words text-[11px] text-muted-foreground`,children:e.actionId}),e.actionDescription?(0,j.jsx)(`div`,{className:`break-words text-xs text-muted-foreground`,children:e.actionDescription}):null]})}function Q({label:e,value:t}){return(0,j.jsxs)(`div`,{className:`grid grid-cols-[5rem_minmax(0,1fr)] gap-3`,children:[(0,j.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:e}),(0,j.jsx)(`div`,{className:`min-w-0 break-words text-xs text-foreground`,children:t})]})}var $=(0,A.createContext)(null);function ke({children:e}){let[t,n]=(0,A.useState)(()=>w());(0,A.useEffect)(()=>me(e=>{n(e)}),[]);let r=(0,A.useCallback)(e=>{pe(e),n(fe())},[]),i=(0,A.useMemo)(()=>({theme:t,setTheme:r}),[t,r]);return(0,j.jsx)($.Provider,{value:i,children:e})}function Ae(){let e=(0,A.useContext)($);if(!e)throw Error(`useTheme must be used within ThemeProvider`);return e}export{v as C,x as D,w as E,y as O,T as S,S as T,ve as _,we as a,D as b,J as c,W as d,be as f,ye as g,_e as h,Ee as i,G as l,H as m,Ae as n,Te as o,U as p,De as r,Y as s,ke as t,K as u,P as v,de as w,E as x,k as y};
|
|
1
|
+
import{i as e}from"./chunk-DF37yw5_.js";import{Lt as t,Qn as n,Sn as r,Xn as i,Yt as a,Zn as o,fn as s,hn as c,lr as l,ur as ee,xr as u}from"./api-CXWxExzS.js";import{t as d}from"./createLucideIcon-C3LSyx-e.js";import{J as te}from"./context-menu-C6m-WNpl.js";import{d as ne,f as re,l as f,p,s as m}from"./i18n-ByVUrt9w.js";import{a as ie,i as ae,n as oe,o as h,r as se,t as ce}from"./dialog-DB_l9LK0.js";import{n as g}from"./navigation-history-CCnxsAKR.js";var _=`nextclaw.ui.theme`,v=`default`,le=`plain-paper`;function y(e){return e===`default`?le:e}var b=[{value:`default`,labelKey:`themeDefault`,appearance:`light`},{value:`plain-paper`,labelKey:`themePlainPaper`,appearance:`light`},{value:`paper-ink`,labelKey:`themePaperInk`,appearance:`light`},{value:`work`,labelKey:`themeWork`,appearance:`light`},{value:`night`,labelKey:`themeNight`,appearance:`dark`},{value:`charcoal`,labelKey:`themeCharcoal`,appearance:`dark`},{value:`island`,labelKey:`themeIsland`,appearance:`light`},{value:`natural`,labelKey:`themeNatural`,appearance:`light`},{value:`minimal`,labelKey:`themeMinimal`,appearance:`light`},{value:`warm`,labelKey:`themeWarm`,appearance:`light`},{value:`cool`,labelKey:`themeCool`,appearance:`light`},{value:`dawn`,labelKey:`themeDawn`,appearance:`light`},{value:`graphite`,labelKey:`themeGraphite`,appearance:`light`},{value:`probe`,labelKey:`themeProbe`,appearance:`light`}],ue=b.map(({value:e})=>e),de=b.map(({value:e,labelKey:t})=>({value:e,labelKey:t}));function x(e){return typeof e==`string`?ue.includes(e)?e:e===`leaf`?`warm`:null:null}function S(e){return b.find(t=>t.value===y(e))?.appearance??`light`}var C=new class{constructor(){this.activeTheme=v,this.initialized=!1,this.listeners=new Set,this.resolveInitialTheme=()=>{if(typeof window>`u`)return v;try{let e=x(window.localStorage.getItem(_));if(e)return e}catch{}return v},this.initializeTheme=()=>(this.initialized||=(this.activeTheme=this.resolveInitialTheme(),this.applyThemeAttribute(this.activeTheme),!0),this.activeTheme),this.getTheme=()=>this.initialized?this.activeTheme:this.initializeTheme(),this.setTheme=e=>{this.initializeTheme(),e!==this.activeTheme&&(this.activeTheme=e,this.applyThemeAttribute(this.activeTheme),this.saveTheme(this.activeTheme),this.listeners.forEach(e=>e(this.activeTheme)))},this.subscribeThemeChange=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)}),this.applyThemeAttribute=e=>{if(typeof document>`u`)return;let t=S(e);document.documentElement.setAttribute(`data-theme`,y(e)),document.documentElement.setAttribute(`data-theme-appearance`,t),document.documentElement.style.colorScheme=t},this.saveTheme=e=>{if(!(typeof window>`u`))try{window.localStorage.setItem(_,e)}catch{}}}};function w(){return C.initializeTheme()}function fe(){return C.getTheme()}function pe(e){C.setTheme(e)}function me(e){return C.subscribeThemeChange(e)}var T=d(`Boxes`,[[`path`,{d:`M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z`,key:`lc1i9w`}],[`path`,{d:`m7 16.5-4.74-2.85`,key:`1o9zyk`}],[`path`,{d:`m7 16.5 5-3`,key:`va8pkn`}],[`path`,{d:`M7 16.5v5.17`,key:`jnp8gn`}],[`path`,{d:`M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z`,key:`8zsnat`}],[`path`,{d:`m17 16.5-5-3`,key:`8arw3v`}],[`path`,{d:`m17 16.5 4.74-2.85`,key:`8rfmw`}],[`path`,{d:`M17 16.5v5.17`,key:`k6z78m`}],[`path`,{d:`M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z`,key:`1xygjf`}],[`path`,{d:`M12 8 7.26 5.15`,key:`1vbdud`}],[`path`,{d:`m12 8 4.74-2.85`,key:`3rx089`}],[`path`,{d:`M12 13.5V8`,key:`1io7kd`}]]),E=d(`ShieldAlert`,[[`path`,{d:`M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z`,key:`oel41y`}],[`path`,{d:`M12 8v4`,key:`1got3b`}],[`path`,{d:`M12 16h.01`,key:`1drbdi`}]]),D=class{constructor(e){this.authorizationManager=e,this.handleIframeMessage=({event:e,iframe:t})=>{this.isBridgeRequest(e.data)&&(!t?.contentWindow||e.source!==t.contentWindow||this.handleBridgeRequest({event:e,iframe:t},e.data))},this.handleBridgeRequest=async(e,t)=>{try{let n=this.getBridgeContext(t),r=await this.dispatchRequest(n,t);this.postResponse(e,{type:`nextclaw:panel-app-service-actions:response`,requestId:t.requestId,ok:!0,data:r})}catch(n){this.postResponse(e,{type:`nextclaw:panel-app-service-actions:response`,requestId:t.requestId,ok:!1,error:this.toBridgeError(n)})}},this.dispatchRequest=async(e,n)=>{switch(n.method){case`list`:return await t.serviceApps.listServiceActions({bridgeSessionToken:e.token});case`requestGrant`:return await this.confirmAndGrant(e,this.requireActionId(n));case`revokeGrant`:return await t.serviceApps.revokeServiceAction(this.requireActionId(n),{bridgeSessionToken:e.token});case`invoke`:return await this.invokeWithAuthorization(e,n);case`agent.send`:return await this.sendAgentMessageWithAuthorization(e,n);case`agent.generateObject`:return await this.generateAgentObjectWithAuthorization(e,n);case`verification.list`:return await t.serviceApps.listVerificationRecords({acceptanceId:n.payload?.acceptanceId,appId:n.payload?.appId,limit:n.payload?.limit,bridgeSessionToken:e.token});case`acceptance.status`:return await t.serviceApps.getPortableRuntimeAcceptanceStatus({appId:n.payload?.appId,locale:n.payload?.locale,bridgeSessionToken:e.token});case`acceptance.export`:return await t.serviceApps.exportPortableRuntimeAcceptance({appId:n.payload?.appId,locale:n.payload?.locale,bridgeSessionToken:e.token});default:throw Error(`Unsupported panel bridge method: ${String(n.method)}`)}},this.invokeWithAuthorization=async(e,n)=>{let r=this.requireActionId(n);try{return await t.serviceApps.invokeServiceAction(r,this.readOptionalRecord(n.payload?.input),{bridgeSessionToken:e.token})}catch(i){if(!(i instanceof c)||i.code!==`AUTHORIZATION_REQUIRED`)throw i;return await this.confirmAndGrant(e,r,this.readOptionalRecord(n.payload?.input)),await t.serviceApps.invokeServiceAction(r,this.readOptionalRecord(n.payload?.input),{bridgeSessionToken:e.token})}},this.sendAgentMessageWithAuthorization=async(e,n)=>{try{return await t.panelApps.sendAgentMessage(this.readAgentSendRequest(n),{bridgeSessionToken:e.token})}catch(r){if(!(r instanceof c)||r.code!==`AUTHORIZATION_REQUIRED`)throw r;return await this.confirmAndGrantAgentCapability(e,`agent:send`),await t.panelApps.sendAgentMessage(this.readAgentSendRequest(n),{bridgeSessionToken:e.token})}},this.generateAgentObjectWithAuthorization=async(e,n)=>{try{return await t.panelApps.generateAgentObject(this.readGenerateObjectRequest(n),{bridgeSessionToken:e.token})}catch(r){if(!(r instanceof c)||r.code!==`AUTHORIZATION_REQUIRED`)throw r;return await this.confirmAndGrantAgentCapability(e,`agent:generateObject`),await t.panelApps.generateAgentObject(this.readGenerateObjectRequest(n),{bridgeSessionToken:e.token})}},this.confirmAndGrantAgentCapability=async(e,n)=>{if(!await this.authorizationManager.requestAuthorization({panelAppId:e.appId,actions:[{actionId:n,actionTitle:n===`agent:send`?`Send agent message`:`Generate object`,actionDescription:n===`agent:send`?`Send a message to a NextClaw Agent session.`:`Send context to a NextClaw Agent session and receive a structured object.`,risk:`write`}]}))throw new c({code:`AUTHORIZATION_REJECTED`,message:`Permission rejected for ${n}.`});return await t.panelApps.grantAgentCapability(n,{bridgeSessionToken:e.token})},this.confirmAndGrant=async(e,n,r)=>{let i=await this.listGrantCandidateActions(e,n);if(!await this.authorizationManager.requestAuthorization({panelAppId:e.appId,actions:i.map(e=>({actionId:e.id,actionTitle:e.title,actionDescription:e.description,risk:e.risk})),inputPreview:this.createInputPreview(r)}))throw new c({code:`AUTHORIZATION_REJECTED`,message:`Permission rejected for ${n}.`});let a=await t.serviceApps.grantServiceActions(i.map(e=>e.id),{bridgeSessionToken:e.token}),o=a.grants.find(e=>e.actionId===n)??a.grants[0];if(!o)throw Error(`No permission grant returned for ${n}.`);return o},this.listGrantCandidateActions=async(e,n)=>{let r=await t.serviceApps.listServiceActions({bridgeSessionToken:e.token}),i=r.actions.find(e=>e.id===n);if(i&&i.grantState!==`not-granted`)return[i];let a=r.actions.filter(e=>e.grantState===`not-granted`),o=[...a.filter(e=>e.id===n),...a.filter(e=>e.id!==n)];return o.length>0?o:[{appId:i?.appId??n.split(`.`)[0]??n,description:i?.description,id:n,name:i?.name??n,risk:i?.risk??`dangerous`,title:i?.title}]},this.createInputPreview=e=>{if(!(!e||Object.keys(e).length===0))try{let t=JSON.stringify(e,null,2);return t.length>500?`${t.slice(0,500)}...`:t}catch{return`[unserializable input]`}},this.getBridgeContext=e=>({appId:e.appId,token:e.runtimeToken}),this.requireActionId=e=>{let t=e.payload?.actionId?.trim();if(!t)throw Error(`Panel bridge actionId is required.`);return t},this.readOptionalRecord=e=>{if(e!==void 0)return this.requireRecord(e,`input`)},this.readAgentSendRequest=e=>({payload:this.requireRecord(e.payload?.request,`agent.send request`)}),this.readGenerateObjectRequest=e=>({input:this.requireRecord(e.payload?.input,`generateObject input`)}),this.requireRecord=(e,t)=>{if(!e||typeof e!=`object`||Array.isArray(e))throw Error(`${t} is required.`);return e},this.postResponse=(e,t)=>{e.iframe?.contentWindow?.postMessage(t,`*`)},this.toBridgeError=e=>e instanceof c?{code:e.code,message:e.message,details:e.details}:{message:e instanceof Error?e.message:String(e)},this.isBridgeRequest=e=>{if(!e||typeof e!=`object`)return!1;let t=e;return t.type===`nextclaw:panel-app-service-actions:request`&&typeof t.requestId==`string`&&typeof t.runtimeToken==`string`&&typeof t.appId==`string`&&(t.method===`invoke`||t.method===`agent.send`||t.method===`agent.generateObject`||t.method===`acceptance.export`||t.method===`acceptance.status`||t.method===`list`||t.method===`requestGrant`||t.method===`revokeGrant`||t.method===`verification.list`)}}};function he(){return`service-action-auth-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`}var O=s((e,t)=>({pending:null,requestAuthorization:async n=>await new Promise(r=>{t().pending?.resolve(!1),e({pending:{id:he(),...n,resolve:r}})}),resolveAuthorization:n=>{let{pending:r}=t();r&&(r.resolve(n),e({pending:null}))}})),ge=class{constructor(){this.requestAuthorization=async e=>await O.getState().requestAuthorization(e)}},k=class{constructor(){this.serviceActionAuthorizationManager=new ge,this.panelAppBridgeManager=new D(this.serviceActionAuthorizationManager)}},A=e(u(),1),j=ee(),M=globalThis,N=M.__NEXTCLAW_PANEL_APP_HOST_CONTEXT__??(0,A.createContext)(null);M.__NEXTCLAW_PANEL_APP_HOST_CONTEXT__=N;function P({children:e,presenter:t}){let n=(0,A.useMemo)(()=>t??new k,[t]);return(0,j.jsx)(N.Provider,{value:n,children:e})}function F(){let e=(0,A.useContext)(N);if(!e)throw Error(`usePanelAppHostPresenter must be used inside PanelAppHostProvider`);return e}var I=[`panel-apps`],L=[`panel-app`];function R(e,t){return e.id===t||e.appId===t}function z(e,t,n){return e&&{...e,entries:e.entries.map(e=>R(e,t)?n(e):e)}}function B(e,t){return z(e,t.id,()=>t)}function V(e,t){let n=e?.entries.reduce((e,t)=>Math.max(e,t.mainSidebarOrder??-1),-1)??-1;return z(e,t.id,e=>({...e,...typeof t.preferences.favorite==`boolean`?{favorite:t.preferences.favorite}:{},...typeof t.preferences.mainSidebar==`boolean`?t.preferences.mainSidebar?{mainSidebar:!0,mainSidebarOrder:e.mainSidebar?e.mainSidebarOrder:n+1}:{mainSidebar:!1,mainSidebarOrder:void 0}:{}}))}function _e(e={}){return n({queryKey:I,queryFn:()=>t.panelApps.listPanelApps(),enabled:e.enabled??!0,staleTime:0})}function H(e,r={}){return n({queryKey:[...L,e],queryFn:()=>t.panelApps.getPanelApp(e),enabled:!!e&&(r.enabled??!0),staleTime:5e3})}function ve(){let e=l();return o({mutationFn:({id:e,preferences:n})=>t.panelApps.updatePanelAppPreferences(e,n),onMutate:async t=>{await e.cancelQueries({queryKey:I});let n=e.getQueryData(I)?.entries.find(e=>R(e,t.id));return e.setQueryData(I,e=>V(e,t)),{previousEntry:n}},onSuccess:t=>{e.setQueryData(I,e=>B(e,t)),e.setQueryData([...L,t.appId],t)},onError:(t,n,r)=>{let a=r?.previousEntry;a&&e.setQueryData(I,e=>z(e,n.id,()=>a));let o=t instanceof Error?t.message:String(t);i.error(`${m(`panelAppsPreferenceUpdateFailed`)}: ${o}`)}})}function ye(){let e=l();return o({mutationFn:e=>t.panelApps.recordPanelAppOpened(e),onSuccess:t=>{e.setQueryData(I,e=>B(e,t)),e.setQueryData([...L,t.appId],t)}})}function U(){let e=l();return o({mutationFn:e=>t.panelApps.grantClient(e),onSuccess:()=>{e.invalidateQueries({queryKey:I}),e.invalidateQueries({queryKey:L})}})}function be(){let e=l();return o({mutationFn:e=>t.panelApps.deletePanelApp(e),onSuccess:()=>{e.invalidateQueries({queryKey:I}),e.invalidateQueries({queryKey:L})}})}function W(){let e=F(),{isPending:t,mutateAsync:n}=U(),r=(0,A.useRef)(new Map);return{ensurePanelAppClientGrant:(0,A.useCallback)(async t=>{if(!t.clientDeclared||t.clientGranted)return!0;let i=r.current.get(t.appId);if(i)return await i;let a=(async()=>await e.serviceActionAuthorizationManager.requestAuthorization({panelAppId:t.appId,actions:[{actionId:`nextclaw.client`,actionTitle:m(`panelAppsClientGrantTitle`),actionDescription:m(`panelAppsClientGrantDescription`),risk:`dangerous`}]})?(await n(t.appId),!0):!1)();r.current.set(t.appId,a);try{return await a}finally{r.current.delete(t.appId)}},[n,e]),isPending:t}}function xe(e){let r=l(),i=(0,A.useId)(),{ensurePanelAppClientGrant:a}=W(),o=!!(e?.clientDeclared&&!e.clientGranted),s=n({queryKey:[`panel-app-client-grant`,e?.appId??`none`,e?.clientGranted??!1],queryFn:async()=>await a(e),enabled:o,gcTime:0,retry:!1,staleTime:1/0}),c=e?o?s.isError?`error`:s.isPending?`checking`:s.data?`allowed`:`denied`:`allowed`:`idle`;return n({queryKey:[`panel-app-open`,i,e?.appId??`none`],queryFn:async()=>{let n=await t.panelApps.recordPanelAppOpened(e.id);return r.setQueryData(I,e=>B(e,n)),r.setQueryData([...L,n.appId],n),n},enabled:!!(e&&c===`allowed`),retry:!1,staleTime:1/0}),{retryClientGrant:s.refetch,state:c}}var G=`allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-downloads allow-pointer-lock allow-presentation`;function K(e){e?.focus({preventScroll:!0}),e?.contentWindow?.focus()}function q(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.kind===`document`?!0:t.kind===`element`&&Array.isArray(t.path)&&t.path.length>0&&t.path.length<=30&&t.path.every(e=>typeof e?.index==`number`&&Number.isInteger(e.index)&&e.index>=0&&e.index<=1e3&&typeof e.tagName==`string`&&e.tagName.length>0&&e.tagName.length<=32)}function Se(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.currentUrl==`string`&&q(t.target)}function Ce(e){if(!e||typeof e!=`object`)return!1;let t=e;return t.type===r.scrollMessageType&&t.version===r.version&&q(t.target)&&typeof t.x==`number`&&Number.isFinite(t.x)&&t.x>=0&&typeof t.y==`number`&&Number.isFinite(t.y)&&t.y>=0}function J({currentUrl:e,iframeRef:t,isEnabled:n,restorationKey:i}){let a=i&&e?JSON.stringify([i,e]):null;return(0,A.useEffect)(()=>{if(!e||!n||!a)return;let r=a,i=n=>{n.source!==t.current?.contentWindow||!Ce(n.data)||g.save(r,{x:n.data.x,y:n.data.y,payload:{currentUrl:e,target:n.data.target}})};return window.addEventListener(`message`,i),()=>window.removeEventListener(`message`,i)},[e,t,n,a]),(0,A.useCallback)(()=>{if(!e||!n||!a)return;let i=g.read(a);!i||!Se(i.payload)||i.payload.currentUrl!==e||t.current?.contentWindow?.postMessage({type:r.restoreScrollMessageType,version:r.version,target:i.payload.target,x:i.x,y:i.y},`*`)},[e,t,n,a])}function Y({appId:e,restorationScope:t,refreshVersion:n=0,unavailableAction:r}){let i=F(),a=H(e),o=(0,A.useRef)(null),s=(0,A.useMemo)(()=>a.data,[a.data]),c=xe(s),l=J({currentUrl:s?.contentPath??null,iframeRef:o,isEnabled:!!s,restorationKey:s?`panel-app:${t}:${s.appId}`:null});return(0,A.useEffect)(()=>{let e=e=>{i.panelAppBridgeManager.handleIframeMessage({event:e,iframe:o.current})};return window.addEventListener(`message`,e),()=>window.removeEventListener(`message`,e)},[i]),a.isLoading?(0,j.jsx)(X,{message:m(`panelAppsLoading`)}):a.isError?(0,j.jsx)(X,{message:a.error instanceof Error?a.error.message:m(`panelAppsLoadFailed`),actionLabel:m(`panelAppsTryAgain`),onAction:()=>void a.refetch()}):s?c.state===`checking`||c.state===`idle`?(0,j.jsx)(X,{message:m(`panelAppsCheckingPermission`)}):c.state===`denied`||c.state===`error`?(0,j.jsx)(X,{message:c.state===`denied`?m(`panelAppsPermissionDenied`):m(`panelAppsPermissionFailed`),actionLabel:m(`panelAppsTryAgain`),onAction:()=>void c.retryClientGrant()}):(0,j.jsx)(`div`,{className:`h-full min-h-0 bg-background`,children:(0,j.jsx)(`iframe`,{ref:o,src:s.contentPath,title:s.title,sandbox:G,className:`block h-full w-full border-0 bg-background`,onLoad:l,onPointerOver:e=>K(e.currentTarget)},`${s.appId}:${n}`)}):(0,j.jsx)(X,{message:m(`panelAppsMainUnavailable`),actionLabel:r?.label,onAction:r?.onAction})}function X({actionLabel:e,message:t,onAction:n}){return(0,j.jsx)(`div`,{className:`flex h-full min-h-0 items-center justify-center bg-background px-6 text-center`,children:(0,j.jsxs)(`div`,{className:`max-w-sm`,children:[(0,j.jsx)(`div`,{className:`mx-auto flex h-10 w-10 items-center justify-center rounded-lg bg-muted text-muted-foreground`,children:n?(0,j.jsx)(te,{className:`h-5 w-5`}):(0,j.jsx)(T,{className:`h-5 w-5`})}),(0,j.jsx)(`p`,{className:`mt-3 text-sm leading-6 text-muted-foreground`,children:t}),e&&n?(0,j.jsx)(`button`,{type:`button`,className:`mt-4 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border`,onClick:n,children:e}):null]})})}var Z=(0,A.createContext)(null);function we({children:e}){let[t,n]=(0,A.useState)(()=>ne());(0,A.useEffect)(()=>p(e=>{n(e)}),[]);let r=(0,A.useCallback)(e=>{re(e),n(f())},[]),i=(0,A.useCallback)(()=>{r(t===`en`?`zh`:`en`)},[t,r]),a=(0,A.useMemo)(()=>({language:t,setLanguage:r,toggleLanguage:i}),[t,r,i]);return(0,j.jsx)(Z.Provider,{value:a,children:e})}function Te(){let e=(0,A.useContext)(Z);if(!e)throw Error(`useI18n must be used within I18nProvider`);return{...e,t:m}}function Ee({appId:e}){let t=H(e),n=(0,A.useMemo)(()=>t.data,[t.data]);return(0,A.useEffect)(()=>{let e=document.title;return document.title=`${n?.title??m(`panelAppsTitle`)} · NextClaw`,()=>{document.title=e}},[n?.title]),(0,j.jsx)(`main`,{className:`h-dvh min-h-0 w-full overflow-hidden bg-background`,children:(0,j.jsx)(Y,{appId:e,restorationScope:`standalone`})})}function De(){let e=O(e=>e.pending),t=O(e=>e.resolveAuthorization);return(0,j.jsx)(ce,{open:!!e,onOpenChange:e=>{e||t(!1)},children:(0,j.jsxs)(oe,{children:[(0,j.jsxs)(ie,{showClose:!1,children:[(0,j.jsxs)(h,{className:`flex items-center gap-2`,children:[(0,j.jsx)(E,{className:`h-4 w-4 text-amber-600`}),m(`serviceActionAuthorizationTitle`)]}),(0,j.jsx)(se,{children:m(`serviceActionAuthorizationDescription`)})]}),e?(0,j.jsxs)(`div`,{className:`space-y-3 rounded-md border border-border bg-muted/60 p-3 text-sm`,children:[(0,j.jsx)(Q,{label:m(`serviceActionAuthorizationSource`),value:e.panelAppId}),(0,j.jsxs)(`div`,{className:`space-y-2`,children:[(0,j.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:m(`serviceActionAuthorizationActions`)}),(0,j.jsx)(`div`,{className:`max-h-64 space-y-2 overflow-auto`,children:e.actions.map(e=>(0,j.jsx)(Oe,{action:e},e.actionId))})]}),e.inputPreview?(0,j.jsx)(Q,{label:m(`serviceActionAuthorizationInput`),value:e.inputPreview}):null]}):null,(0,j.jsxs)(ae,{className:`gap-2 sm:gap-0`,children:[(0,j.jsx)(a,{type:`button`,variant:`outline`,onClick:()=>t(!1),children:m(`serviceActionAuthorizationReject`)}),(0,j.jsx)(a,{type:`button`,onClick:()=>t(!0),children:m(`serviceActionAuthorizationAllow`)})]})]})})}function Oe({action:e}){return(0,j.jsxs)(`div`,{className:`space-y-1 rounded border border-border bg-card px-3 py-2`,children:[(0,j.jsxs)(`div`,{className:`flex items-start justify-between gap-3`,children:[(0,j.jsx)(`div`,{className:`min-w-0 break-words text-xs font-medium text-foreground`,children:e.actionTitle??e.actionId}),(0,j.jsx)(`div`,{className:`shrink-0 rounded bg-amber-50 px-1.5 py-0.5 text-[11px] font-medium text-amber-700`,children:e.risk??`dangerous`})]}),(0,j.jsx)(`div`,{className:`break-words text-[11px] text-muted-foreground`,children:e.actionId}),e.actionDescription?(0,j.jsx)(`div`,{className:`break-words text-xs text-muted-foreground`,children:e.actionDescription}):null]})}function Q({label:e,value:t}){return(0,j.jsxs)(`div`,{className:`grid grid-cols-[5rem_minmax(0,1fr)] gap-3`,children:[(0,j.jsx)(`div`,{className:`text-xs font-medium text-muted-foreground`,children:e}),(0,j.jsx)(`div`,{className:`min-w-0 break-words text-xs text-foreground`,children:t})]})}var $=(0,A.createContext)(null);function ke({children:e}){let[t,n]=(0,A.useState)(()=>w());(0,A.useEffect)(()=>me(e=>{n(e)}),[]);let r=(0,A.useCallback)(e=>{pe(e),n(fe())},[]),i=(0,A.useMemo)(()=>({theme:t,setTheme:r}),[t,r]);return(0,j.jsx)($.Provider,{value:i,children:e})}function Ae(){let e=(0,A.useContext)($);if(!e)throw Error(`useTheme must be used within ThemeProvider`);return e}export{v as C,x as D,w as E,y as O,T as S,S as T,ve as _,we as a,D as b,J as c,W as d,be as f,ye as g,_e as h,Ee as i,G as l,H as m,Ae as n,Te as o,U as p,De as r,Y as s,ke as t,K as u,P as v,de as w,E as x,k as y};
|
package/dist/index.html
CHANGED
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
})();
|
|
193
193
|
</script>
|
|
194
194
|
<title>NextClaw</title>
|
|
195
|
-
<script type="module" crossorigin src="/assets/main-
|
|
195
|
+
<script type="module" crossorigin src="/assets/main-BNWRrWc_.js"></script>
|
|
196
196
|
<link rel="modulepreload" crossorigin href="/assets/chunk-DF37yw5_.js">
|
|
197
197
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-DWTEM3RW.js">
|
|
198
198
|
<link rel="modulepreload" crossorigin href="/assets/i18n-ByVUrt9w.js">
|
|
@@ -206,12 +206,12 @@
|
|
|
206
206
|
<link rel="modulepreload" crossorigin href="/assets/inbox-CI8ATevA.js">
|
|
207
207
|
<link rel="modulepreload" crossorigin href="/assets/x-waP-WLuT.js">
|
|
208
208
|
<link rel="modulepreload" crossorigin href="/assets/plus-C42o6xrD.js">
|
|
209
|
-
<link rel="modulepreload" crossorigin href="/assets/context-menu-
|
|
209
|
+
<link rel="modulepreload" crossorigin href="/assets/context-menu-C6m-WNpl.js">
|
|
210
210
|
<link rel="modulepreload" crossorigin href="/assets/puzzle-DmKVREoh.js">
|
|
211
211
|
<link rel="modulepreload" crossorigin href="/assets/book-open-B3Ac0voD.js">
|
|
212
|
-
<link rel="modulepreload" crossorigin href="/assets/dialog-
|
|
212
|
+
<link rel="modulepreload" crossorigin href="/assets/dialog-DB_l9LK0.js">
|
|
213
213
|
<link rel="modulepreload" crossorigin href="/assets/navigation-history-CCnxsAKR.js">
|
|
214
|
-
<link rel="modulepreload" crossorigin href="/assets/theme-provider-
|
|
214
|
+
<link rel="modulepreload" crossorigin href="/assets/theme-provider-D4rd-tCP.js">
|
|
215
215
|
<link rel="modulepreload" crossorigin href="/assets/download-C2gYe8n1.js">
|
|
216
216
|
<link rel="modulepreload" crossorigin href="/assets/middleware-B--6mAUM.js">
|
|
217
217
|
<link rel="modulepreload" crossorigin href="/assets/use-viewport-layout-Bnm5W5Qz.js">
|
|
@@ -224,15 +224,15 @@
|
|
|
224
224
|
<link rel="modulepreload" crossorigin href="/assets/doc-browser-context-CoVMIiU8.js">
|
|
225
225
|
<link rel="modulepreload" crossorigin href="/assets/tag-chip-DFzDSvcF.js">
|
|
226
226
|
<link rel="modulepreload" crossorigin href="/assets/card-eFBmxYiI.js">
|
|
227
|
-
<link rel="modulepreload" crossorigin href="/assets/confirm-dialog-
|
|
228
|
-
<link rel="modulepreload" crossorigin href="/assets/provider-models-
|
|
227
|
+
<link rel="modulepreload" crossorigin href="/assets/confirm-dialog-D30HJBXN.js">
|
|
228
|
+
<link rel="modulepreload" crossorigin href="/assets/provider-models-zGsDUBHG.js">
|
|
229
229
|
<link rel="modulepreload" crossorigin href="/assets/select-BJ-EjmCz.js">
|
|
230
230
|
<link rel="modulepreload" crossorigin href="/assets/use-config-M1L4xn8c.js">
|
|
231
|
-
<link rel="modulepreload" crossorigin href="/assets/app-presenter-provider-
|
|
232
|
-
<link rel="modulepreload" crossorigin href="/assets/provider-scoped-model-input-
|
|
231
|
+
<link rel="modulepreload" crossorigin href="/assets/app-presenter-provider-Cx9MwMtb.js">
|
|
232
|
+
<link rel="modulepreload" crossorigin href="/assets/provider-scoped-model-input-DpEyP1h8.js">
|
|
233
233
|
<link rel="modulepreload" crossorigin href="/assets/settings-2-D3-m8a3v.js">
|
|
234
|
-
<link rel="modulepreload" crossorigin href="/assets/agents-
|
|
235
|
-
<link rel="modulepreload" crossorigin href="/assets/pwa-
|
|
234
|
+
<link rel="modulepreload" crossorigin href="/assets/agents-Dr2KkEp2.js">
|
|
235
|
+
<link rel="modulepreload" crossorigin href="/assets/pwa-jmuWbrqH.js">
|
|
236
236
|
<link rel="modulepreload" crossorigin href="/assets/login-page--Njgbmpm.js">
|
|
237
237
|
<link rel="stylesheet" crossorigin href="/assets/context-menu-DEcVZfaU.css">
|
|
238
238
|
<link rel="stylesheet" crossorigin href="/assets/app-presenter-provider-D3BuuOyZ.css">
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#ffffff" />
|
|
7
7
|
<title>Panel App · NextClaw</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/panelStandalone-
|
|
8
|
+
<script type="module" crossorigin src="/assets/panelStandalone-C38o5B4Q.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/chunk-DF37yw5_.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-DWTEM3RW.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/i18n-ByVUrt9w.js">
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
<link rel="modulepreload" crossorigin href="/assets/inbox-CI8ATevA.js">
|
|
20
20
|
<link rel="modulepreload" crossorigin href="/assets/x-waP-WLuT.js">
|
|
21
21
|
<link rel="modulepreload" crossorigin href="/assets/plus-C42o6xrD.js">
|
|
22
|
-
<link rel="modulepreload" crossorigin href="/assets/context-menu-
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/dialog-
|
|
22
|
+
<link rel="modulepreload" crossorigin href="/assets/context-menu-C6m-WNpl.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/dialog-DB_l9LK0.js">
|
|
24
24
|
<link rel="modulepreload" crossorigin href="/assets/navigation-history-CCnxsAKR.js">
|
|
25
|
-
<link rel="modulepreload" crossorigin href="/assets/theme-provider-
|
|
25
|
+
<link rel="modulepreload" crossorigin href="/assets/theme-provider-D4rd-tCP.js">
|
|
26
26
|
<link rel="stylesheet" crossorigin href="/assets/context-menu-DEcVZfaU.css">
|
|
27
27
|
<link rel="stylesheet" crossorigin href="/assets/panelStandalone-XRlWxbRS.css">
|
|
28
28
|
</head>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ui",
|
|
3
|
-
"version": "0.27.5-beta.
|
|
3
|
+
"version": "0.27.5-beta.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
|
35
35
|
"zod": "^3.23.8",
|
|
36
36
|
"zustand": "^5.0.2",
|
|
37
|
-
"@nextclaw/
|
|
38
|
-
"@nextclaw/
|
|
37
|
+
"@nextclaw/agent-chat-ui": "0.12.4-beta.0",
|
|
38
|
+
"@nextclaw/ncp-http-agent-client": "0.4.27",
|
|
39
39
|
"@nextclaw/ncp": "0.11.3",
|
|
40
|
+
"@nextclaw/client-sdk": "0.12.14-beta.1",
|
|
41
|
+
"@nextclaw/shared": "0.8.4",
|
|
40
42
|
"@nextclaw/ncp-toolkit": "0.6.28",
|
|
41
|
-
"@nextclaw/
|
|
42
|
-
"@nextclaw/
|
|
43
|
-
"@nextclaw/agent-chat": "0.3.14",
|
|
44
|
-
"@nextclaw/ncp-react": "0.5.31"
|
|
43
|
+
"@nextclaw/ncp-react": "0.5.31",
|
|
44
|
+
"@nextclaw/agent-chat": "0.3.14"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@testing-library/react": "^16.3.0",
|
package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx
CHANGED
|
@@ -37,6 +37,7 @@ function renderDetail(detailId: string) {
|
|
|
37
37
|
open: vi.fn(),
|
|
38
38
|
openTarget: vi.fn(),
|
|
39
39
|
refreshIframe: vi.fn(),
|
|
40
|
+
refreshVersion: 0,
|
|
40
41
|
tab: {
|
|
41
42
|
id: "tab-1",
|
|
42
43
|
kind: MARKETPLACE_DETAIL_TAB_KIND,
|
|
@@ -131,6 +132,7 @@ describe("Marketplace detail doc renderer", () => {
|
|
|
131
132
|
open: vi.fn(),
|
|
132
133
|
openTarget: vi.fn(),
|
|
133
134
|
refreshIframe: vi.fn(),
|
|
135
|
+
refreshVersion: 0,
|
|
134
136
|
tab: {
|
|
135
137
|
id: "tab-1",
|
|
136
138
|
kind: MARKETPLACE_DETAIL_TAB_KIND,
|
|
@@ -18,10 +18,12 @@ type PanelAppUnavailableAction = {
|
|
|
18
18
|
export function PanelAppRuntimeSurface({
|
|
19
19
|
appId,
|
|
20
20
|
restorationScope,
|
|
21
|
+
refreshVersion = 0,
|
|
21
22
|
unavailableAction,
|
|
22
23
|
}: {
|
|
23
24
|
appId: string;
|
|
24
25
|
restorationScope: "main" | "standalone";
|
|
26
|
+
refreshVersion?: number;
|
|
25
27
|
unavailableAction?: PanelAppUnavailableAction;
|
|
26
28
|
}) {
|
|
27
29
|
const presenter = usePanelAppHostPresenter();
|
|
@@ -97,7 +99,7 @@ export function PanelAppRuntimeSurface({
|
|
|
97
99
|
return (
|
|
98
100
|
<div className="h-full min-h-0 bg-background">
|
|
99
101
|
<iframe
|
|
100
|
-
key={entry.appId}
|
|
102
|
+
key={`${entry.appId}:${refreshVersion}`}
|
|
101
103
|
ref={iframeRef}
|
|
102
104
|
src={entry.contentPath}
|
|
103
105
|
title={entry.title}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { AppWindow, Boxes } from 'lucide-react';
|
|
1
|
+
import { AppWindow, Boxes, ExternalLink, RefreshCw } from 'lucide-react';
|
|
2
2
|
import type { DocBrowserContextValue } from '@/shared/components/doc-browser/doc-browser-context';
|
|
3
3
|
import type { DocBrowserCustomTabRenderers } from '@/shared/components/doc-browser/doc-browser-renderer.types';
|
|
4
4
|
import { getPresenter } from '@/app/presenters/app.presenter';
|
|
5
5
|
import { AppsPanel } from '@/features/apps';
|
|
6
|
-
import { PanelAppDocBrowserToolbar } from '@/features/panel-apps/components/panel-app-toolbar';
|
|
7
6
|
import {
|
|
8
7
|
createPanelAppRightPanelResourceTarget,
|
|
9
8
|
RIGHT_PANEL_APPS_TAB_KIND,
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
RIGHT_PANEL_HOME_TAB_KIND,
|
|
13
12
|
RIGHT_PANEL_PANEL_APP_TAB_KIND,
|
|
14
13
|
RightPanelResourceHomePage,
|
|
14
|
+
readPanelAppIdFromTab,
|
|
15
15
|
} from '@/features/right-panel-resources';
|
|
16
16
|
import { t } from '@/shared/lib/i18n';
|
|
17
17
|
import { PANEL_APP_IFRAME_SANDBOX, focusPanelAppIframe } from './panel-app-iframe.utils';
|
|
@@ -56,14 +56,29 @@ export const PANEL_APPS_DOC_BROWSER_RENDERERS: DocBrowserCustomTabRenderers = {
|
|
|
56
56
|
onIframeMessage: (params) => getPresenter().panelAppBridgeManager.handleIframeMessage(params),
|
|
57
57
|
onIframePointerOver: (event) => focusPanelAppIframe(event.currentTarget),
|
|
58
58
|
renderIcon: () => <AppWindow className="w-4 h-4 text-primary shrink-0" />,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
getTabMenuGroups: ({ refreshIframe, tab }) => {
|
|
60
|
+
const appId = readPanelAppIdFromTab(tab);
|
|
61
|
+
return [{
|
|
62
|
+
key: 'panel-app-actions',
|
|
63
|
+
items: [
|
|
64
|
+
{
|
|
65
|
+
key: 'refresh',
|
|
66
|
+
icon: <RefreshCw className="h-4 w-4" />,
|
|
67
|
+
label: t('panelAppsRefreshCurrent'),
|
|
68
|
+
onSelect: refreshIframe,
|
|
69
|
+
},
|
|
70
|
+
...(appId ? [{
|
|
71
|
+
key: 'open-standalone',
|
|
72
|
+
icon: <ExternalLink className="h-4 w-4" />,
|
|
73
|
+
label: t(typeof window !== 'undefined' && window.nextclawDesktop ? 'panelAppsOpenInBrowser' : 'panelAppsOpenInNewTab'),
|
|
74
|
+
href: `/apps/panel/${encodeURIComponent(appId)}/standalone`,
|
|
75
|
+
target: '_blank' as const,
|
|
76
|
+
rel: 'opener',
|
|
77
|
+
referrerPolicy: 'no-referrer' as const,
|
|
78
|
+
}] : []),
|
|
79
|
+
],
|
|
80
|
+
}];
|
|
81
|
+
},
|
|
67
82
|
supportsScrollRestoration: true,
|
|
68
83
|
},
|
|
69
84
|
};
|
package/src/features/right-panel-resources/components/__tests__/system-object-resource.test.tsx
CHANGED
|
@@ -17,7 +17,7 @@ vi.mock("@/features/inbox", () => ({ InboxPage: ({ resourceId }: { resourceId: s
|
|
|
17
17
|
vi.mock("@/features/apps", () => ({ AppsPanel: ({ serviceResourceId }: { serviceResourceId: string }) => <div>service-app:{serviceResourceId}</div> }));
|
|
18
18
|
vi.mock("@/features/marketplace", () => ({ McpMarketplacePage: ({ resourceId }: { resourceId: string }) => <div>mcp-server:{resourceId}</div> }));
|
|
19
19
|
vi.mock("@/features/panel-apps", () => ({
|
|
20
|
-
PanelAppRuntimeSurface: ({ appId }: { appId: string }) => <div>panel-app:{appId}</div>,
|
|
20
|
+
PanelAppRuntimeSurface: ({ appId, refreshVersion }: { appId: string; refreshVersion?: number }) => <div>panel-app:{appId}:{refreshVersion}</div>,
|
|
21
21
|
PanelAppHostProvider: ({ children }: { children: ReactNode }) => children,
|
|
22
22
|
}));
|
|
23
23
|
|
|
@@ -61,7 +61,7 @@ afterEach(cleanup);
|
|
|
61
61
|
describe("system object reading identity", () => {
|
|
62
62
|
it.each(["agent", "cron-job", "project", "inbox-delivery", "service-app", "mcp-server", "panel-app"])("opens %s with its native renderer and unchanged identity", async (objectType) => {
|
|
63
63
|
render(<NativeObjectResource objectType={objectType} objectId="raw:id" openTarget={vi.fn()} />);
|
|
64
|
-
expect(await screen.findByText(`${objectType}:raw:id`)).toBeTruthy();
|
|
64
|
+
expect(await screen.findByText(objectType === "panel-app" ? `${objectType}:raw:id:` : `${objectType}:raw:id`)).toBeTruthy();
|
|
65
65
|
expect(screen.queryByTestId("preview")).toBeNull();
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -70,6 +70,27 @@ describe("system object reading identity", () => {
|
|
|
70
70
|
expect(await screen.findByText("project:project/work")).toBeTruthy();
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
it("adds refresh to the host tab menu and passes its version to panel app content", async () => {
|
|
74
|
+
const uri = "nextclaw://objects/panel-app/raw%3Aid";
|
|
75
|
+
const refreshIframe = vi.fn();
|
|
76
|
+
const params = (refreshVersion: number) => ({
|
|
77
|
+
currentUrl: uri,
|
|
78
|
+
tab: { id: uri, kind: "system-object", title: "Panel App", currentUrl: uri, resourceUri: uri, history: [uri], historyIndex: 0, navVersion: 0 },
|
|
79
|
+
open: vi.fn(), openTarget: vi.fn(), refreshIframe, refreshVersion,
|
|
80
|
+
} satisfies DocBrowserCustomTabRenderParams);
|
|
81
|
+
const renderer = SYSTEM_OBJECT_RESOURCE_RENDERERS["system-object"];
|
|
82
|
+
expect(renderer.renderToolbar).toBeUndefined();
|
|
83
|
+
const refreshAction = renderer.getTabMenuGroups!(params(0))[0]?.items[0];
|
|
84
|
+
expect(refreshAction?.label).toBe("Refresh current panel app");
|
|
85
|
+
if (refreshAction && "onSelect" in refreshAction) refreshAction.onSelect?.();
|
|
86
|
+
expect(refreshIframe).toHaveBeenCalledTimes(1);
|
|
87
|
+
const view = render(renderer.renderContent!(params(0)));
|
|
88
|
+
expect(await screen.findByText("panel-app:raw:id:0")).toBeTruthy();
|
|
89
|
+
view.rerender(renderer.renderContent!(params(1)));
|
|
90
|
+
expect(await screen.findByText("panel-app:raw:id:1")).toBeTruthy();
|
|
91
|
+
expect(renderer.getTabMenuGroups!({ ...params(0), tab: { ...params(0).tab, currentUrl: "nextclaw://objects/agent/test", resourceUri: "nextclaw://objects/agent/test" } })).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
73
94
|
it("retains a global file's source context when quoting into a different conversation", () => {
|
|
74
95
|
const target = createWorkspaceFilePanelTarget({
|
|
75
96
|
key: "source-file", path: "agent.md", label: "agent.md", parentSessionKey: "source-session", viewMode: "preview",
|
|
@@ -77,7 +98,7 @@ describe("system object reading identity", () => {
|
|
|
77
98
|
render(WORKSPACE_FILE_PANEL_RENDERERS["workspace-file"].renderContent!({
|
|
78
99
|
currentUrl: target.url,
|
|
79
100
|
tab: { ...target, id: target.url, currentUrl: target.url, history: [target.url], historyIndex: 0, navVersion: 0 },
|
|
80
|
-
open: vi.fn(), openTarget: vi.fn(), refreshIframe: vi.fn(),
|
|
101
|
+
open: vi.fn(), openTarget: vi.fn(), refreshIframe: vi.fn(), refreshVersion: 0,
|
|
81
102
|
}));
|
|
82
103
|
fireEvent.click(screen.getByRole("button", { name: "Add selection" }));
|
|
83
104
|
expect(actions.addExcerptToChat).toHaveBeenLastCalledWith({
|
|
@@ -92,6 +113,7 @@ describe("system object reading identity", () => {
|
|
|
92
113
|
open: vi.fn(),
|
|
93
114
|
openTarget: vi.fn(),
|
|
94
115
|
refreshIframe: vi.fn(),
|
|
116
|
+
refreshVersion: 0,
|
|
95
117
|
} satisfies DocBrowserCustomTabRenderParams);
|
|
96
118
|
const first = "nextclaw://objects/skill/first";
|
|
97
119
|
const second = "nextclaw://objects/skill/second";
|
|
@@ -65,10 +65,12 @@ export function NativeObjectResource({
|
|
|
65
65
|
objectType,
|
|
66
66
|
objectId,
|
|
67
67
|
openTarget,
|
|
68
|
+
refreshVersion,
|
|
68
69
|
}: {
|
|
69
70
|
objectType: string;
|
|
70
71
|
objectId: string;
|
|
71
72
|
openTarget: DocBrowserCustomTabRenderParams["openTarget"];
|
|
73
|
+
refreshVersion?: number;
|
|
72
74
|
}) {
|
|
73
75
|
let content;
|
|
74
76
|
switch (objectType) {
|
|
@@ -111,7 +113,7 @@ export function NativeObjectResource({
|
|
|
111
113
|
case "panel-app":
|
|
112
114
|
content = (
|
|
113
115
|
<PanelAppHostProvider>
|
|
114
|
-
<PanelAppRuntimeSurface appId={objectId} restorationScope="main" />
|
|
116
|
+
<PanelAppRuntimeSurface appId={objectId} restorationScope="main" refreshVersion={refreshVersion} />
|
|
115
117
|
</PanelAppHostProvider>
|
|
116
118
|
);
|
|
117
119
|
break;
|
|
@@ -13,12 +13,14 @@ import { useAppPresenter } from "@/app/components/app-presenter-provider";
|
|
|
13
13
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
14
14
|
import { parseSystemObjectReferenceUri } from "@nextclaw/shared";
|
|
15
15
|
import { NativeObjectResource } from "./native-object-resource";
|
|
16
|
+
import { RefreshCw } from "lucide-react";
|
|
16
17
|
import { ChatTextSelectionAction } from "@nextclaw/agent-chat-ui";
|
|
17
18
|
import { WORKSPACE_TEXT_EXCERPT_MAX_CHARACTERS } from "@/features/chat";
|
|
18
19
|
|
|
19
20
|
function SystemObjectResource({
|
|
20
21
|
tab,
|
|
21
22
|
openTarget,
|
|
23
|
+
refreshVersion,
|
|
22
24
|
}: DocBrowserCustomTabRenderParams) {
|
|
23
25
|
const app = useAppPresenter();
|
|
24
26
|
const { pathname } = useLocation();
|
|
@@ -52,7 +54,7 @@ function SystemObjectResource({
|
|
|
52
54
|
)
|
|
53
55
|
}
|
|
54
56
|
>
|
|
55
|
-
<NativeObjectResource key={uri} {...identity} openTarget={openTarget} />
|
|
57
|
+
<NativeObjectResource key={uri} {...identity} openTarget={openTarget} refreshVersion={refreshVersion} />
|
|
56
58
|
</ChatTextSelectionAction>
|
|
57
59
|
);
|
|
58
60
|
}
|
|
@@ -160,6 +162,18 @@ function SystemObjectDocument({
|
|
|
160
162
|
export const SYSTEM_OBJECT_RESOURCE_RENDERERS: DocBrowserCustomTabRenderers = {
|
|
161
163
|
"system-object": {
|
|
162
164
|
getTitle: (tab) => tab.title,
|
|
165
|
+
getTabMenuGroups: ({ refreshIframe, tab }) => {
|
|
166
|
+
const identity = parseSystemObjectReferenceUri(tab.resourceUri ?? tab.currentUrl);
|
|
167
|
+
return identity?.objectType === "panel-app" ? [{
|
|
168
|
+
key: "panel-app-refresh",
|
|
169
|
+
items: [{
|
|
170
|
+
key: "refresh",
|
|
171
|
+
label: t("panelAppsRefreshCurrent"),
|
|
172
|
+
icon: <RefreshCw className="h-4 w-4" />,
|
|
173
|
+
onSelect: refreshIframe,
|
|
174
|
+
}],
|
|
175
|
+
}] : [];
|
|
176
|
+
},
|
|
163
177
|
renderContent: (params) => <SystemObjectResource {...params} />,
|
|
164
178
|
},
|
|
165
179
|
};
|
|
@@ -14,6 +14,16 @@ import { pageResourceTab } from "@/features/right-panel-resources/managers/page-
|
|
|
14
14
|
import { pageResourceFromTarget } from "@/features/right-panel-resources/utils/page-resource-identity.utils";
|
|
15
15
|
import type { PageResource } from "@/features/right-panel-resources/types/page-resource.types";
|
|
16
16
|
import { t } from "@/shared/lib/i18n";
|
|
17
|
+
import { parseSystemObjectReferenceUri } from "@nextclaw/shared";
|
|
18
|
+
import { RefreshCw } from "lucide-react";
|
|
19
|
+
import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
|
|
20
|
+
import { usePanelApps } from "@/features/panel-apps/hooks/use-panel-apps";
|
|
21
|
+
|
|
22
|
+
function PanelAppResourceTitle({ appId, fallback }: { appId: string; fallback: string }) {
|
|
23
|
+
const panelApps = usePanelApps();
|
|
24
|
+
const title = panelApps.data?.entries.find((entry) => entry.appId === appId)?.title ?? fallback;
|
|
25
|
+
return <span className="min-w-0 flex-1 truncate text-sm">{title}</span>;
|
|
26
|
+
}
|
|
17
27
|
|
|
18
28
|
function ResourcePageContent({ page }: { page: PageResource }) {
|
|
19
29
|
const app = useAppPresenter();
|
|
@@ -22,6 +32,10 @@ function ResourcePageContent({ page }: { page: PageResource }) {
|
|
|
22
32
|
const [revision, setRevision] = useState(0);
|
|
23
33
|
const tab = useMemo(() => pageResourceTab(page), [page]);
|
|
24
34
|
const renderer = PAGE_RESOURCE_RENDERERS[tab.kind];
|
|
35
|
+
const object = tab.kind === "system-object"
|
|
36
|
+
? parseSystemObjectReferenceUri(page.uri)
|
|
37
|
+
: null;
|
|
38
|
+
const panelAppId = object?.objectType === "panel-app" ? object.objectId : null;
|
|
25
39
|
const iframeInstanceId = `main:${page.uri}:${revision}`;
|
|
26
40
|
const restore = useDocBrowserScrollRestoration({
|
|
27
41
|
currentTab: tab,
|
|
@@ -55,6 +69,7 @@ function ResourcePageContent({ page }: { page: PageResource }) {
|
|
|
55
69
|
tab,
|
|
56
70
|
currentUrl: tab.currentUrl,
|
|
57
71
|
refreshIframe: () => setRevision((value) => value + 1),
|
|
72
|
+
refreshVersion: revision,
|
|
58
73
|
open: (uri?: string) => {
|
|
59
74
|
const next = uri ? app.pageResourceManager.resolve(uri) : null;
|
|
60
75
|
if (next) app.pageResourceManager.open(next, "main", navigate);
|
|
@@ -73,10 +88,22 @@ function ResourcePageContent({ page }: { page: PageResource }) {
|
|
|
73
88
|
>
|
|
74
89
|
<header className="flex min-h-10 shrink-0 items-center gap-2 border-b border-border/60 px-3">
|
|
75
90
|
<PageResourceIcon uri={page.uri} icon={page.target.dockIcon} />
|
|
76
|
-
|
|
91
|
+
{panelAppId ? (
|
|
92
|
+
<PanelAppResourceTitle appId={panelAppId} fallback={page.title} />
|
|
93
|
+
) : (
|
|
94
|
+
<span className="min-w-0 flex-1 truncate text-sm">{page.title}</span>
|
|
95
|
+
)}
|
|
96
|
+
{panelAppId ? (
|
|
97
|
+
<IconActionButton
|
|
98
|
+
icon={<RefreshCw className="h-3.5 w-3.5" />}
|
|
99
|
+
label={t("panelAppsRefreshCurrent")}
|
|
100
|
+
onClick={params.refreshIframe}
|
|
101
|
+
size="sm"
|
|
102
|
+
/>
|
|
103
|
+
) : null}
|
|
77
104
|
<PageResourceActionsMenu page={page} />
|
|
78
105
|
</header>
|
|
79
|
-
{renderer?.renderToolbar?.(params)}
|
|
106
|
+
{!panelAppId ? renderer?.renderToolbar?.(params) : null}
|
|
80
107
|
<DocBrowserFrameContent
|
|
81
108
|
currentTab={tab}
|
|
82
109
|
currentUrl={tab.currentUrl}
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
DocBrowserTab,
|
|
19
19
|
} from "@/shared/components/doc-browser/doc-browser-context";
|
|
20
20
|
import { PANEL_APPS_DOC_BROWSER_RENDERERS } from "@/features/panel-apps";
|
|
21
|
+
import { pageResourceFromTab, usePageResourceActions } from "@/features/right-panel-resources";
|
|
21
22
|
import { PANEL_APP_SCROLL_RESTORATION_CONTRACT } from "@nextclaw/shared";
|
|
22
23
|
|
|
23
24
|
const { navigateMock } = vi.hoisted(() => ({
|
|
@@ -28,6 +29,12 @@ const panelAppHooks = vi.hoisted(() => ({
|
|
|
28
29
|
mutate: vi.fn(),
|
|
29
30
|
}));
|
|
30
31
|
|
|
32
|
+
function DocBrowserWithPageActions() {
|
|
33
|
+
const actions = usePageResourceActions();
|
|
34
|
+
return <DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS}
|
|
35
|
+
getTabMenuGroups={(tab) => actions(pageResourceFromTab(tab), 'global')} />;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
vi.mock("react-router-dom", async () => ({
|
|
32
39
|
...((await vi.importActual("react-router-dom")) as object),
|
|
33
40
|
useNavigate: () => navigateMock,
|
|
@@ -536,21 +543,12 @@ describe("DocBrowser panel app navigation", () => {
|
|
|
536
543
|
docBrowserState.activeHistoryIndex = 1;
|
|
537
544
|
docBrowserState.currentTab = panelAppTab;
|
|
538
545
|
|
|
539
|
-
render(<
|
|
546
|
+
render(<DocBrowserWithPageActions />);
|
|
540
547
|
|
|
541
548
|
expect(screen.getAllByRole("button", { name: "Back" })).toHaveLength(1);
|
|
542
549
|
expect(screen.queryByRole("button", { name: "Apps" })).toBeNull();
|
|
543
550
|
await user.click(screen.getByRole("button", { name: "Back" }));
|
|
544
551
|
expect(docBrowserState.goBack).toHaveBeenCalledTimes(1);
|
|
545
|
-
|
|
546
|
-
expect(screen.queryByRole("button", { name: "Pin to left sidebar" })).toBeNull();
|
|
547
|
-
await user.click(screen.getByRole("button", { name: "More panel app actions" }));
|
|
548
|
-
await user.click(screen.getByRole("menuitem", { name: "Layout and position" }));
|
|
549
|
-
await user.click(screen.getByRole("menuitem", { name: "Pin to left sidebar" }));
|
|
550
|
-
expect(panelAppHooks.mutate).toHaveBeenCalledWith({
|
|
551
|
-
id: "piano",
|
|
552
|
-
preferences: { mainSidebar: true },
|
|
553
|
-
});
|
|
554
552
|
});
|
|
555
553
|
|
|
556
554
|
it("keeps placement actions when a restored shortcut only has a stable current URL", async () => {
|
|
@@ -569,9 +567,9 @@ describe("DocBrowser panel app navigation", () => {
|
|
|
569
567
|
docBrowserState.activeTabId = panelAppTab.id;
|
|
570
568
|
docBrowserState.currentTab = panelAppTab;
|
|
571
569
|
|
|
572
|
-
render(<
|
|
570
|
+
render(<DocBrowserWithPageActions />);
|
|
573
571
|
|
|
574
|
-
await user.click(screen.getByRole("button", { name: "More
|
|
572
|
+
await user.click(screen.getByRole("button", { name: "More tab actions" }));
|
|
575
573
|
await user.click(screen.getByRole("menuitem", { name: "Layout and position" }));
|
|
576
574
|
await user.click(screen.getByRole("menuitem", { name: "Pin to left sidebar" }));
|
|
577
575
|
expect(panelAppHooks.mutate).toHaveBeenCalledWith({
|
|
@@ -623,7 +621,12 @@ describe("DocBrowser scroll restoration", () => {
|
|
|
623
621
|
source: initialWindow,
|
|
624
622
|
}));
|
|
625
623
|
|
|
626
|
-
|
|
624
|
+
expect(screen.queryByRole("button", { name: "Refresh current panel app" })).toBeNull();
|
|
625
|
+
fireEvent.click(screen.getByRole("button", { name: "More tab actions" }));
|
|
626
|
+
const standalone = screen.getByRole("menuitem", { name: "Open in New Tab" });
|
|
627
|
+
expect(standalone.getAttribute("href")).toBe("/apps/panel/piano/standalone");
|
|
628
|
+
expect(standalone.getAttribute("target")).toBe("_blank");
|
|
629
|
+
fireEvent.click(screen.getByRole("menuitem", { name: "Refresh current panel app" }));
|
|
627
630
|
|
|
628
631
|
const refreshedIframe = container.querySelector('iframe[title="Piano"]') as HTMLIFrameElement;
|
|
629
632
|
const refreshedPostMessage = vi.fn();
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { PointerEvent, ReactNode } from 'react';
|
|
2
2
|
import type { DocBrowserContextValue, DocBrowserTab } from './doc-browser-context';
|
|
3
|
+
import type { ContextMenuGroup } from '@/shared/components/ui/context-menu/context-menu';
|
|
3
4
|
|
|
4
5
|
export type DocBrowserCustomTabRenderParams = {
|
|
5
6
|
currentUrl: string;
|
|
6
7
|
open: DocBrowserContextValue['open'];
|
|
7
8
|
openTarget: DocBrowserContextValue['openTarget'];
|
|
8
9
|
refreshIframe: () => void;
|
|
10
|
+
refreshVersion: number;
|
|
9
11
|
tab: DocBrowserTab;
|
|
10
12
|
};
|
|
11
13
|
|
|
@@ -17,6 +19,7 @@ export type DocBrowserIframeMessageParams = {
|
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
export type DocBrowserCustomTabRenderer = {
|
|
22
|
+
getTabMenuGroups?: (params: DocBrowserCustomTabRenderParams) => readonly ContextMenuGroup[];
|
|
20
23
|
getIframeSandbox?: (tab: DocBrowserTab) => string | undefined;
|
|
21
24
|
getTitle?: (tab: DocBrowserTab) => string;
|
|
22
25
|
onIframeMessage?: (params: DocBrowserIframeMessageParams) => void;
|
|
@@ -265,6 +265,7 @@ export function DocBrowser({
|
|
|
265
265
|
open,
|
|
266
266
|
openTarget,
|
|
267
267
|
refreshIframe,
|
|
268
|
+
refreshVersion: iframeReloadVersion,
|
|
268
269
|
tab: currentTab,
|
|
269
270
|
} : undefined;
|
|
270
271
|
const customToolbar = customRenderParams ? customRenderer?.renderToolbar?.(customRenderParams) : null;
|
|
@@ -325,7 +326,12 @@ export function DocBrowser({
|
|
|
325
326
|
onToggleDock={handleToggleDock}
|
|
326
327
|
onSetActiveTab={setActiveTab}
|
|
327
328
|
onCloseTab={closeTab}
|
|
328
|
-
getTabMenuGroups={
|
|
329
|
+
getTabMenuGroups={(tab) => [
|
|
330
|
+
...(getTabMenuGroups?.(tab) ?? []),
|
|
331
|
+
...(tab.id === activeTabId && customRenderParams
|
|
332
|
+
? customRenderer?.getTabMenuGroups?.(customRenderParams) ?? []
|
|
333
|
+
: []),
|
|
334
|
+
]}
|
|
329
335
|
/>
|
|
330
336
|
}>
|
|
331
337
|
{panelContent}
|
|
@@ -164,6 +164,7 @@ export function ContextMenuItems({ groups, onClose }: {
|
|
|
164
164
|
onClose={onClose} className={className}>{content}</ContextSubmenu>;
|
|
165
165
|
const select = () => { item.onSelect?.(); onClose(item.href ? false : item.restoreFocus !== false); };
|
|
166
166
|
return item.href ? <a key={item.key} role="menuitem" className={className} href={item.href} download={item.download}
|
|
167
|
+
target={item.target} rel={item.rel} referrerPolicy={item.referrerPolicy}
|
|
167
168
|
onPointerEnter={() => setOpenKey(null)} onClick={(event) => { event.stopPropagation(); select(); }}>{content}</a>
|
|
168
169
|
: <button key={item.key} type="button" role="menuitem" disabled={item.disabled} aria-pressed={item.pressed}
|
|
169
170
|
data-menu-key={item.key} className={className} onPointerEnter={() => setOpenKey(null)}
|