mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Fit the panel to Codex's and Claude's panel rules
Images and stylesheet files load as data: URLs, which hosts allow where some refuse blob: images; the scene-asset source gets an https app address the panel serves, since Codex pages use their own scheme; and a panel whose 3D context the browser takes back says so.
This commit is contained in:
1 parent
996b95eb45
commit
62468c547f
3 files changed
+63
-17
No files matched your search
@@ -130,9 +130,12 @@ export function panelRuntime(config) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The app's own paths: relative ones, and absolute ones on this page's
|
// The app's own paths: relative ones, absolute ones on this page's site
|
||||||
// site, matched by scheme and host since a host may use its own scheme.
|
// (matched by scheme and host, since a host may use its own scheme), and
|
||||||
|
// ones under the app's address in the page, an https address for code
|
||||||
|
// that needs one (`config.appBaseUrl`).
|
||||||
const page = new URL(document.baseURI);
|
const page = new URL(document.baseURI);
|
||||||
|
const appOrigin = new URL(config.appBaseUrl).origin;
|
||||||
function appPath(value) {
|
function appPath(value) {
|
||||||
let url;
|
let url;
|
||||||
try {
|
try {
|
||||||
@@ -140,20 +143,33 @@ export function panelRuntime(config) {
|
|||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (url.protocol !== page.protocol || url.host !== page.host) return null;
|
const own =
|
||||||
return url.pathname + url.search;
|
url.origin === appOrigin ||
|
||||||
|
(url.protocol === page.protocol && url.host === page.host);
|
||||||
|
return own ? url.pathname + url.search : null;
|
||||||
}
|
}
|
||||||
const blobUrls = new Map();
|
const fileUrls = new Map();
|
||||||
/** A blob: URL holding an app file, for elements that load by URL. */
|
/**
|
||||||
|
* A data: URL holding an app file, for elements that load by URL. Hosts
|
||||||
|
* allow data: images where some refuse blob: ones.
|
||||||
|
*/
|
||||||
function appObjectUrl(path) {
|
function appObjectUrl(path) {
|
||||||
if (!blobUrls.has(path))
|
if (!fileUrls.has(path))
|
||||||
blobUrls.set(
|
fileUrls.set(
|
||||||
path,
|
path,
|
||||||
appFetch(path)
|
appFetch(path)
|
||||||
.then((response) => response.blob())
|
.then((response) => response.blob())
|
||||||
.then((blob) => URL.createObjectURL(blob)),
|
.then(
|
||||||
|
(blob) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = () => reject(reader.error);
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
}),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return blobUrls.get(path);
|
return fileUrls.get(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function installRequests() {
|
function installRequests() {
|
||||||
@@ -311,12 +327,15 @@ export function panelRuntime(config) {
|
|||||||
URL.createObjectURL(new Blob([BOOT], { type: 'text/javascript' })),
|
URL.createObjectURL(new Blob([BOOT], { type: 'text/javascript' })),
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
appObjectUrl(path).then((script) =>
|
appFetch(path)
|
||||||
worker.postMessage({
|
.then((response) => response.blob())
|
||||||
__gevScript: script,
|
.then((blob) => URL.createObjectURL(blob))
|
||||||
module: options?.type === 'module',
|
.then((script) =>
|
||||||
}),
|
worker.postMessage({
|
||||||
);
|
__gevScript: script,
|
||||||
|
module: options?.type === 'module',
|
||||||
|
}),
|
||||||
|
);
|
||||||
return worker;
|
return worker;
|
||||||
};
|
};
|
||||||
window.Worker.prototype = NativeWorker.prototype;
|
window.Worker.prototype = NativeWorker.prototype;
|
||||||
@@ -398,6 +417,7 @@ export function panelRuntime(config) {
|
|||||||
*/
|
*/
|
||||||
async function startApp(url) {
|
async function startApp(url) {
|
||||||
window.GEV_EMBED_INLINE = true;
|
window.GEV_EMBED_INLINE = true;
|
||||||
|
window.GEV_APP_BASE_URL = config.appBaseUrl;
|
||||||
try {
|
try {
|
||||||
history.replaceState(
|
history.replaceState(
|
||||||
null,
|
null,
|
||||||
@@ -452,6 +472,21 @@ export function panelRuntime(config) {
|
|||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
for (const { type, text } of scripts) await runScript(text, type);
|
for (const { type, text } of scripts) await runScript(text, type);
|
||||||
|
// Browsers cap live 3D contexts across a conversation's panels and take
|
||||||
|
// one back when there are too many; say so rather than show no map.
|
||||||
|
document.addEventListener(
|
||||||
|
'webglcontextlost',
|
||||||
|
() => {
|
||||||
|
const notice = document.createElement('div');
|
||||||
|
notice.id = 'status';
|
||||||
|
notice.textContent =
|
||||||
|
"This panel lost its 3D graphics, likely because other God's Eye " +
|
||||||
|
"View panels in this conversation hold them. Use Open in God's " +
|
||||||
|
'Eye View above, or show it in a new conversation.';
|
||||||
|
document.body.appendChild(notice);
|
||||||
|
},
|
||||||
|
{ capture: true, once: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tool results: the first view starts the app, later ones move it.
|
// Tool results: the first view starts the app, later ones move it.
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ export function createStandaloneTools(options) {
|
|||||||
sceneDataPacks: {
|
sceneDataPacks: {
|
||||||
sources: {
|
sources: {
|
||||||
assets: createAssetDirectorySource({
|
assets: createAssetDirectorySource({
|
||||||
baseUrl: new URL('/scene-assets/', document.baseURI).href,
|
// A panel names the app's address; see src/tools/globePanel.js.
|
||||||
|
baseUrl: new URL(
|
||||||
|
'/scene-assets/',
|
||||||
|
globalThis.GEV_APP_BASE_URL ?? document.baseURI,
|
||||||
|
).href,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ export const MCP_APP_MIME_TYPE = 'text/html;profile=mcp-app';
|
|||||||
const MCP_APPS_PROTOCOL_VERSION = '2026-01-26';
|
const MCP_APPS_PROTOCOL_VERSION = '2026-01-26';
|
||||||
const PANEL_HEIGHT_PX = 520;
|
const PANEL_HEIGHT_PX = 520;
|
||||||
const LOAD_TIMEOUT_MS = 90_000;
|
const LOAD_TIMEOUT_MS = 90_000;
|
||||||
|
/**
|
||||||
|
* The app's address inside the panel, for code that needs an https address
|
||||||
|
* (the page's own may use a host's scheme). The panel loads its paths
|
||||||
|
* through the MCP server; the name never resolves.
|
||||||
|
*/
|
||||||
|
const PANEL_APP_BASE_URL = 'https://app.gods-eye-view.invalid/';
|
||||||
/** The panel-only tool the panel loads the app through. */
|
/** The panel-only tool the panel loads the app through. */
|
||||||
const PANEL_REQUEST_TOOL = 'panel_request';
|
const PANEL_REQUEST_TOOL = 'panel_request';
|
||||||
|
|
||||||
@@ -51,6 +57,7 @@ const PANEL_REQUEST_TOOL = 'panel_request';
|
|||||||
*/
|
*/
|
||||||
function panelHtml(runtime) {
|
function panelHtml(runtime) {
|
||||||
const config = {
|
const config = {
|
||||||
|
appBaseUrl: PANEL_APP_BASE_URL,
|
||||||
loadTimeoutMs: LOAD_TIMEOUT_MS,
|
loadTimeoutMs: LOAD_TIMEOUT_MS,
|
||||||
panelBase: PANEL_BASE,
|
panelBase: PANEL_BASE,
|
||||||
panelHeight: PANEL_HEIGHT_PX,
|
panelHeight: PANEL_HEIGHT_PX,
|
||||||
|
|||||||
Reference in new issue
Block a user