@rho-workspace/rho 0.5.5 → 0.5.7
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/dist/agent-connect.d.ts +42 -0
- package/dist/agent-connect.js +68 -0
- package/dist/answer-board.js +3 -3
- package/dist/bridge.js +6 -1
- package/dist/browser-access.d.ts +1 -0
- package/dist/browser-access.js +5 -3
- package/dist/capabilities.d.ts +21 -5
- package/dist/capabilities.js +6 -4
- package/dist/cli.js +113 -18
- package/dist/composition.d.ts +1 -1
- package/dist/composition.js +13 -3
- package/dist/connect-cli.d.ts +50 -0
- package/dist/connect-cli.js +128 -0
- package/dist/contract.generated.d.ts +1437 -381
- package/dist/contract.generated.js +1 -1
- package/dist/contracts/owners.generated.js +1160 -267
- package/dist/conversation-presentation.d.ts +9 -2
- package/dist/conversation-presentation.js +3 -1
- package/dist/conversations.d.ts +306 -7
- package/dist/conversations.js +208 -22
- package/dist/discovery.js +3 -3
- package/dist/extension-contract.d.ts +19 -0
- package/dist/extension-contract.js +15 -6
- package/dist/extension-markets.d.ts +74 -0
- package/dist/extension-markets.js +166 -0
- package/dist/extensions.d.ts +29 -1
- package/dist/extensions.js +13 -4
- package/dist/external-work.d.ts +22 -0
- package/dist/external-work.js +91 -0
- package/dist/plugin-cli.js +75 -3
- package/dist/plugin-inventory.d.ts +56 -0
- package/dist/plugin-inventory.js +41 -0
- package/dist/plugin-inventory.json +1581 -102
- package/dist/plugin-presentation.d.ts +105 -2
- package/dist/plugin-presentation.js +59 -5
- package/dist/plugins.d.ts +14 -3
- package/dist/plugins.js +12 -6
- package/dist/project-notes.js +1 -1
- package/dist/protocol.d.ts +7 -0
- package/dist/protocol.js +40 -15
- package/dist/replay.js +4 -0
- package/dist/response-contracts.js +26 -11
- package/dist/running.d.ts +10 -0
- package/dist/running.js +45 -0
- package/dist/server.d.ts +4 -10
- package/dist/server.js +140 -14
- package/dist/state.d.ts +8 -0
- package/dist/state.js +8 -0
- package/dist/studio-layout.js +3 -3
- package/dist/task-frame.d.ts +1 -1
- package/dist/timeline-events.d.ts +1 -0
- package/dist/timeline-events.js +8 -1
- package/dist/ui-context.d.ts +73 -0
- package/dist/ui-context.js +98 -0
- package/dist/ui-contract.d.ts +23 -0
- package/dist/web/.vite/manifest.json +135 -76
- package/dist/web/assets/answer-board-CeYS8k8y.js +1 -0
- package/dist/web/assets/choices-D8vGwsl2.js +1 -0
- package/dist/web/assets/{console-BSjLGe0T.js → console-Dyuo1q4N.js} +4 -4
- package/dist/web/assets/{dist-Tzoe_zm-.js → dist-CSiCyBml.js} +1 -1
- package/dist/web/assets/{dist-Cw8hRIlT.js → dist-D9FLb9Hm.js} +1 -1
- package/dist/web/assets/{dist-foH9IKc6.js → dist-FXbTBwML.js} +1 -1
- package/dist/web/assets/{dist-Buk0lwzI.js → dist-l3DUhbw5.js} +1 -1
- package/dist/web/assets/{explore-in-BVUy3ZW_.js → explore-in-BfSFGRh2.js} +1 -1
- package/dist/web/assets/graph-BU2_o5uA.js +2 -0
- package/dist/web/assets/index-C16mXVvk.css +1 -0
- package/dist/web/assets/index-CWg9XYXx.js +952 -0
- package/dist/web/assets/markdown.reader-Ct2-e2Zt.png +0 -0
- package/dist/web/assets/{notes-meaning-pVbbQUGP.js → notes-meaning-T2m5zQfv.js} +1 -1
- package/dist/web/assets/overview-PzfLJCo-.js +6 -0
- package/dist/web/assets/preview-BguEdsza.js +1 -0
- package/dist/web/assets/previews-pWNik-2g.js +3 -0
- package/dist/web/assets/{review-CMxQlQ-C.js → review-Dy_85lcH.js} +1 -1
- package/dist/web/assets/rho.editor-CCJeyHma.png +0 -0
- package/dist/web/assets/rho.flow-WkMj6fRS.png +0 -0
- package/dist/web/assets/rho.media-Cc6amodO.png +0 -0
- package/dist/web/assets/rho.notebook-6vsZKGDq.png +0 -0
- package/dist/web/assets/rho.object-BGoeXjte.png +0 -0
- package/dist/web/assets/rho.plots-yIiISCkb.png +0 -0
- package/dist/web/assets/rho.scatter-DwHxesCs.png +0 -0
- package/dist/web/assets/rho.servers-DKf_mnNq.png +0 -0
- package/dist/web/assets/rho.table-DWOGaWDa.png +0 -0
- package/dist/web/assets/rho.variables-CKcWczhQ.png +0 -0
- package/dist/web/assets/{skills-BFNSyrnD.js → skills-Dyz7mBlc.js} +1 -1
- package/dist/web/assets/{skills-work-BGPSnF_I.js → skills-work-BEGqlCkx.js} +1 -1
- package/dist/web/assets/studio-process-D-ykbC4D.js +2 -0
- package/dist/web/assets/timeline-events-DKTho9fU.js +1 -0
- package/dist/web/assets/work-turns-DSabCogR.js +1 -0
- package/dist/web/assets/{workspace-CWlkjLNx.js → workspace-DfjBL--r.js} +2 -2
- package/dist/web/composition.json +135 -80
- package/dist/web/index.html +3 -3
- package/dist/web/renderer.json +1 -1
- package/dist/web-assets.js +2 -2
- package/dist/work-graph.d.ts +11 -1
- package/dist/work-graph.js +16 -5
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.js +8 -7
- package/docs/FRONTEND.md +71 -21
- package/docs/HEADLESS.md +127 -24
- package/docs/THIRD-PARTY-PLUGINS.md +75 -3
- package/node_modules/@rho-workspace/plugin-agent/README.md +34 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.d.ts +32 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-command.js +54 -11
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/acp-conversation.js +29 -3
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session-contract.js +11 -5
- package/node_modules/@rho-workspace/plugin-agent/dist/external-session.d.ts +12 -1
- package/node_modules/@rho-workspace/plugin-agent/dist/index.d.ts +8 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/index.js +3 -2
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.d.ts +13 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp-entry.js +13 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/mcp.js +16 -8
- package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.d.ts +70 -0
- package/node_modules/@rho-workspace/plugin-agent/dist/native-sessions.js +538 -0
- package/node_modules/@rho-workspace/plugin-agent/package.json +2 -1
- package/node_modules/@rho-workspace/plugin-figures/LICENSE +661 -0
- package/node_modules/@rho-workspace/plugin-figures/README.md +191 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/index.d.ts +236 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/index.js +165 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/operations.d.ts +44 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/operations.js +39 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/presentation.d.ts +19 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/presentation.js +26 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/record.d.ts +188 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/record.js +143 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui-styles.js +68 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui.d.ts +56 -0
- package/node_modules/@rho-workspace/plugin-figures/dist/ui.js +363 -0
- package/node_modules/@rho-workspace/plugin-figures/package.json +41 -0
- package/node_modules/@rho-workspace/plugin-figures/py/figures.py +856 -0
- package/node_modules/@rho-workspace/plugin-figures/r/figures.R +775 -0
- package/node_modules/@rho-workspace/plugin-kernel/README.md +27 -3
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.d.ts +12 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/contract.js +8 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.d.ts +61 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/index.js +125 -9
- package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.d.ts +14 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/session-scope.js +9 -0
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-base.d.ts +23 -5
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook-work.js +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.d.ts +6 -4
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-notebook.js +44 -17
- package/node_modules/@rho-workspace/plugin-kernel/dist/ui-styles.js +6 -0
- package/node_modules/@rho-workspace/plugin-kernel/package.json +1 -1
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.R +144 -4
- package/node_modules/@rho-workspace/plugin-kernel/runners/runner.py +106 -1
- package/node_modules/@rho-workspace/plugin-tables/README.md +5 -0
- package/node_modules/@rho-workspace/plugin-tables/dist/index.d.ts +1 -0
- package/node_modules/@rho-workspace/plugin-tables/dist/index.js +19 -3
- package/node_modules/@rho-workspace/plugin-tables/dist/ui.js +1 -1
- package/node_modules/@rho-workspace/plugin-tables/package.json +1 -1
- package/node_modules/hono/dist/adapter/aws-lambda/handler.js +1 -1
- package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +1 -1
- package/node_modules/hono/dist/cjs/adapter/bun/serve-static.js +1 -1
- package/node_modules/hono/dist/cjs/adapter/bun/websocket.js +1 -1
- package/node_modules/hono/dist/cjs/client/fetch-result-please.js +1 -1
- package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +2 -1
- package/node_modules/hono/dist/cjs/helper/ssg/ssg.js +5 -3
- package/node_modules/hono/dist/cjs/jsx/base.js +49 -30
- package/node_modules/hono/dist/cjs/jsx/components.js +2 -2
- package/node_modules/hono/dist/cjs/jsx/constants.js +2 -0
- package/node_modules/hono/dist/cjs/jsx/jsx-runtime.js +13 -17
- package/node_modules/hono/dist/cjs/middleware/etag/index.js +3 -1
- package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +1 -0
- package/node_modules/hono/dist/cjs/middleware/timing/timing.js +4 -3
- package/node_modules/hono/dist/cjs/utils/cookie.js +2 -2
- package/node_modules/hono/dist/cjs/utils/mime.js +1 -1
- package/node_modules/hono/dist/cjs/utils/url.js +1 -1
- package/node_modules/hono/dist/client/fetch-result-please.js +1 -1
- package/node_modules/hono/dist/helper/accepts/accepts.js +2 -1
- package/node_modules/hono/dist/helper/ssg/ssg.js +5 -3
- package/node_modules/hono/dist/jsx/base.js +49 -32
- package/node_modules/hono/dist/jsx/components.js +2 -2
- package/node_modules/hono/dist/jsx/constants.js +2 -1
- package/node_modules/hono/dist/jsx/jsx-runtime.js +15 -19
- package/node_modules/hono/dist/middleware/etag/index.js +3 -1
- package/node_modules/hono/dist/middleware/language/language.js +1 -1
- package/node_modules/hono/dist/middleware/pretty-json/index.js +1 -0
- package/node_modules/hono/dist/middleware/timing/timing.js +4 -3
- package/node_modules/hono/dist/utils/cookie.js +2 -2
- package/node_modules/hono/dist/utils/mime.js +1 -1
- package/node_modules/hono/dist/utils/url.js +1 -1
- package/node_modules/hono/package.json +1 -1
- package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +3 -2
- package/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
- package/node_modules/jose/dist/webapi/jws/general/verify.js +2 -8
- package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +11 -8
- package/node_modules/jose/dist/webapi/lib/key_management.js +0 -1
- package/node_modules/jose/package.json +1 -1
- package/package.json +6 -4
- package/protocol/index.json +106 -64
- package/protocol/operations/agent.sessions.list.json +57 -3
- package/protocol/operations/agent.sessions.read.json +61 -4
- package/protocol/operations/capability.list.json +382 -6
- package/protocol/operations/composition.apply.json +10 -2
- package/protocol/operations/composition.delete.json +845 -0
- package/protocol/operations/composition.read.json +10 -2
- package/protocol/operations/composition.undo.json +10 -2
- package/protocol/operations/conversation.agents.add.json +346 -0
- package/protocol/operations/conversation.agents.remove.json +340 -0
- package/protocol/operations/conversation.configure.json +29 -0
- package/protocol/operations/conversation.decide.json +225 -0
- package/protocol/operations/conversation.list.json +29 -0
- package/protocol/operations/conversation.permission.json +225 -0
- package/protocol/operations/conversation.read.json +225 -0
- package/protocol/operations/conversation.restore.json +29 -0
- package/protocol/operations/conversation.send.json +294 -0
- package/protocol/operations/conversation.settings.check.json +226 -416
- package/protocol/operations/conversation.settings.read.json +224 -411
- package/protocol/operations/conversation.settings.update.json +248 -475
- package/protocol/operations/conversation.start.json +313 -6
- package/protocol/operations/conversation.stop.json +225 -0
- package/protocol/operations/conversation.trash.json +29 -0
- package/protocol/operations/extension.describe.json +39 -0
- package/protocol/operations/extension.install.json +62 -0
- package/protocol/operations/extension.list.json +164 -1
- package/protocol/operations/extension.market.change.json +210 -0
- package/protocol/operations/figure.read.json +265 -0
- package/protocol/operations/figure.select.json +157 -0
- package/protocol/operations/kernel.interrupt.json +119 -0
- package/protocol/operations/kernel.read.json +119 -0
- package/protocol/operations/kernel.run.json +119 -0
- package/protocol/operations/observation.snapshot.json +445 -8
- package/protocol/operations/project.openFile.json +10 -2
- package/protocol/operations/session.grant.json +4 -0
- package/protocol/operations/session.instructions.json +43 -0
- package/protocol/operations/shared.json +1 -0
- package/protocol/operations/view.close.json +10 -2
- package/protocol/operations/view.protection.set.json +10 -2
- package/vendor/composition.json +174 -109
- package/vendor/rho-workspace-plugin-agent-0.4.33.tgz +0 -0
- package/vendor/rho-workspace-plugin-figures-0.5.1.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.13.tgz +0 -0
- package/vendor/rho-workspace-plugin-tables-0.5.2.tgz +0 -0
- package/web/theme.css +17 -0
- package/dist/web/assets/answer-board-CBkGbSLD.js +0 -1
- package/dist/web/assets/canvas-D8rDdfDz.js +0 -1
- package/dist/web/assets/choices-b0XhrGQV.js +0 -1
- package/dist/web/assets/graph-1OdIJ9l7.js +0 -2
- package/dist/web/assets/index-CsC3sTRq.css +0 -1
- package/dist/web/assets/index-CxmItXqa.js +0 -891
- package/dist/web/assets/overview-B9UtVJ32.js +0 -6
- package/dist/web/assets/preview-CksD_5KS.js +0 -1
- package/dist/web/assets/studio-process-CsRoTHOh.js +0 -2
- package/dist/web/assets/timeline-events-BbuuFWHV.js +0 -1
- package/dist/web/assets/work-turns-BvJlIMmA.js +0 -1
- package/vendor/rho-workspace-plugin-agent-0.4.29.tgz +0 -0
- package/vendor/rho-workspace-plugin-kernel-0.5.5.tgz +0 -0
- package/vendor/rho-workspace-plugin-tables-0.5.0.tgz +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { statements, unfinished } from './statements.js';
|
|
4
4
|
import { cellRelations } from './lineage.js';
|
|
5
5
|
import { CellScriptFlow } from './ui-flow.js';
|
|
6
6
|
import { Icon } from './ui-icons.js';
|
|
7
7
|
import { PlaceChip, usePlaces, whereOf } from './ui-servers.js';
|
|
8
8
|
import { takeCell } from './ui-notebook-target.js';
|
|
9
|
+
import { scopedCells } from './session-scope.js';
|
|
9
10
|
import { call, refused, agentName, clock, durationText, useShell, withShell, versionOf } from './ui-base.js';
|
|
10
11
|
export { versionOf };
|
|
11
12
|
export const languages = ['r', 'python'];
|
|
@@ -69,13 +70,30 @@ export function usePlot(client, projectId, cellId, plot) {
|
|
|
69
70
|
}, [client, projectId, cellId, plot]);
|
|
70
71
|
return { src, problem };
|
|
71
72
|
}
|
|
73
|
+
// What a figure on this page needs from it: who ran what, and the page's event cursor.
|
|
74
|
+
const FigureFacts = createContext({});
|
|
75
|
+
function WithFacts({ snapshot, children }) {
|
|
76
|
+
const who = useAuthors(snapshot), cursor = snapshot.cursor;
|
|
77
|
+
return _jsx(FigureFacts.Provider, { value: useMemo(() => ({ who, cursor }), [who, cursor]), children: children });
|
|
78
|
+
}
|
|
79
|
+
// A plot an extension described is drawn by the page's figure, with the picture standing in until
|
|
80
|
+
// then; one it could not describe says why under its picture.
|
|
81
|
+
function useFigure(client, projectId, cell, output, label, picture, fit = false) {
|
|
82
|
+
const { figure } = useShell(), facts = useContext(FigureFacts);
|
|
83
|
+
if (!output.figure || !figure)
|
|
84
|
+
return null;
|
|
85
|
+
return figure({ client, projectId, cellId: cell.cellId, plot: output.plot, width: output.width || 1008, height: output.height || 720, label, name: `${languageName[cell.language]} cell ${cell.number}, plot ${output.plot}`, fit, cursor: facts.cursor, fallback: picture,
|
|
86
|
+
author: facts.who && (by => ({ name: facts.who(by).name, mine: by === me })) });
|
|
87
|
+
}
|
|
72
88
|
function Plot({ client, projectId, cell, output, label }) {
|
|
73
89
|
const { src, problem } = usePlot(client, projectId, cell.cellId, output.plot);
|
|
74
90
|
// Plots are drawn at twice the size they are shown at, for sharp lines on dense screens.
|
|
75
91
|
const width = Math.round((output.width || 1008) / 2), height = Math.round((output.height || 720) / 2);
|
|
92
|
+
const picture = _jsx("figure", { className: "nb-plot", style: { maxWidth: width }, children: src ? _jsx("img", { src: src, width: width, height: height, alt: label }) : _jsx("div", { className: "nb-plot-wait", style: { aspectRatio: `${width} / ${height}` }, "aria-label": "Reading the plot" }) });
|
|
93
|
+
const described = useFigure(client, projectId, cell, output, label, picture);
|
|
76
94
|
if (problem)
|
|
77
95
|
return _jsxs("p", { className: "nb-note", children: ["This plot could not be read. ", problem] });
|
|
78
|
-
return
|
|
96
|
+
return _jsxs(_Fragment, { children: [described ?? picture, output.figureProblem && _jsxs("p", { className: "nb-note", children: ["Its marks are not selectable: ", output.figureProblem] })] });
|
|
79
97
|
}
|
|
80
98
|
export function Code({ code, language }) {
|
|
81
99
|
const lines = code.replace(/\n+$/, '').split('\n');
|
|
@@ -100,12 +118,23 @@ function Printed({ output }) {
|
|
|
100
118
|
const long = output.kind !== 'error' && lines.length > notebookViewLimits.outputLines + 4;
|
|
101
119
|
return _jsxs(_Fragment, { children: [_jsx("pre", { className: 'nb-out ' + output.kind, children: long && !whole ? lines.slice(0, notebookViewLimits.outputLines).join('\n') : lines.join('\n') }), long && _jsx("button", { className: "text-button nb-more nb-out-more", onClick: () => setWhole(!whole), children: whole ? 'Show less' : `Show all ${lines.length} lines` })] });
|
|
102
120
|
}
|
|
121
|
+
function Saved({ client, projectId, cell, saved }) {
|
|
122
|
+
const { figure, openPath } = useShell(), facts = useContext(FigureFacts);
|
|
123
|
+
const name = saved.path.split('/').at(-1);
|
|
124
|
+
const head = _jsxs("p", { className: "nb-saved", children: [_jsx("span", { children: "Saved" }), openPath ? _jsx("button", { className: "text-button", title: 'Show ' + saved.path, onClick: () => openPath(saved.path), children: saved.path }) : _jsx("code", { children: saved.path }), saved.figure ? _jsxs("span", { className: "nb-saved-what", children: [saved.figure.bound.toLocaleString(), " ", saved.figure.bound === 1 ? 'mark' : 'marks', " bound to their rows"] })
|
|
125
|
+
: saved.figureProblem ? _jsxs("span", { className: "nb-saved-what", children: ["kept as a picture: ", saved.figureProblem] }) : null] });
|
|
126
|
+
if (!saved.figure || !figure)
|
|
127
|
+
return head;
|
|
128
|
+
return _jsxs(_Fragment, { children: [head, figure({ client, projectId, file: { path: saved.path, sha256: saved.sha256 }, width: 1008, label: `${name}, saved by ${cell.purpose ?? 'this cell'}`, name: `${name} (${languageName[cell.language]} cell ${cell.number})`,
|
|
129
|
+
cursor: facts.cursor, fallback: _jsx("div", { className: "nb-plot-wait", style: { maxWidth: 504, aspectRatio: '7 / 5' }, "aria-label": "Reading the figure" }),
|
|
130
|
+
author: facts.who && (by => ({ name: facts.who(by).name, mine: by === me })) })] });
|
|
131
|
+
}
|
|
103
132
|
function Outputs({ cell, client, projectId }) {
|
|
104
133
|
let plots = 0;
|
|
105
134
|
const purpose = cell.purpose ?? 'this cell';
|
|
106
135
|
return _jsxs(_Fragment, { children: [cell.outputs.map((output, i) => output.kind === 'plot'
|
|
107
136
|
? _jsx(Plot, { client: client, projectId: projectId, cell: cell, output: output, label: `Plot ${++plots} drawn by ${purpose}` }, i)
|
|
108
|
-
: _jsx(Printed, { output: output }, i)), cell.cut && _jsxs("p", { className: "nb-note", children: ["Only the first ", sizeText(cell.kept), " of the ", sizeText(cell.bytes), " it printed were kept."] })] });
|
|
137
|
+
: _jsx(Printed, { output: output }, i)), (cell.saved ?? []).map(saved => _jsx(Saved, { client: client, projectId: projectId, cell: cell, saved: saved }, saved.number)), cell.cut && _jsxs("p", { className: "nb-note", children: ["Only the first ", sizeText(cell.kept), " of the ", sizeText(cell.bytes), " it printed were kept."] })] });
|
|
109
138
|
}
|
|
110
139
|
const sizeText = (bytes) => bytes < 1024 ? bytes + ' bytes' : bytes < 1024 * 1024 ? Math.round(bytes / 1024) + ' KB' : (bytes / 1024 / 1024).toFixed(1) + ' MB';
|
|
111
140
|
function Ending({ cell, now }) {
|
|
@@ -286,7 +315,7 @@ function ConsoleCell({ cell, author, who, client, projectId, now, plotsBeside, p
|
|
|
286
315
|
const interrupted = cell.interrupt ? `Interrupted by ${who(cell.interrupt.by).name === 'You' ? 'you' : who(cell.interrupt.by).name}` : null;
|
|
287
316
|
return _jsxs("div", { className: 'nbc-cell ' + cell.state + ' ' + cell.language, id: 'cell-' + cell.cellId, children: [author.name !== 'You' && _jsxs("p", { className: "nbc-who", children: ["# ", author.conversationId ? _jsx("button", { className: "text-button", title: author.title ? 'Open ' + author.title : undefined, onClick: () => onConversation(author.conversationId), children: author.name }) : author.name, cell.purpose ? ' · ' + cell.purpose : ''] }), _jsx(PromptCode, { code: cell.code, language: cell.language }), cell.outputs.map((output, i) => output.kind === 'plot'
|
|
288
317
|
? _jsx(ConsolePlot, { client: client, projectId: projectId, cell: cell, output: output, number: plotNumber(cell.cellId, output.plot), beside: plotsBeside }, i)
|
|
289
|
-
: _jsx("pre", { className: 'nbc-out ' + output.kind, children: (output.text ?? '').replace(/\n$/, '') }, i)), cell.cut && _jsxs("p", { className: "nbc-quiet", children: ["Only the first ", sizeText(cell.kept), " of the ", sizeText(cell.bytes), " it printed were kept."] }), cell.state === 'running' && _jsxs("p", { className: "nbc-state running", children: [_jsx("span", { className: "pulse-dot", "aria-hidden": "true" }), "Running ", durationText(now - Date.parse(cell.startedAt ?? cell.queuedAt))] }), cell.state === 'queued' && _jsx("p", { className: "nbc-state", children: "Waiting for the cell before it" }), (interrupted || cell.state === 'not-run' || cell.state === 'unknown' || (cell.problem && cell.state !== 'error' && cell.state !== 'interrupted')) &&
|
|
318
|
+
: _jsx("pre", { className: 'nbc-out ' + output.kind, children: (output.text ?? '').replace(/\n$/, '') }, i)), (cell.saved ?? []).map(saved => _jsxs("p", { className: "nbc-quiet", children: ["# Saved ", saved.path, saved.figure ? ` · ${saved.figure.bound.toLocaleString()} marks bound` : saved.figureProblem ? ` · kept as a picture: ${saved.figureProblem}` : ''] }, saved.number)), cell.cut && _jsxs("p", { className: "nbc-quiet", children: ["Only the first ", sizeText(cell.kept), " of the ", sizeText(cell.bytes), " it printed were kept."] }), cell.state === 'running' && _jsxs("p", { className: "nbc-state running", children: [_jsx("span", { className: "pulse-dot", "aria-hidden": "true" }), "Running ", durationText(now - Date.parse(cell.startedAt ?? cell.queuedAt))] }), cell.state === 'queued' && _jsx("p", { className: "nbc-state", children: "Waiting for the cell before it" }), (interrupted || cell.state === 'not-run' || cell.state === 'unknown' || (cell.problem && cell.state !== 'error' && cell.state !== 'interrupted')) &&
|
|
290
319
|
_jsx("p", { className: "nbc-state", children: interrupted ?? (cell.state === 'not-run' ? 'Not run' : cell.state === 'unknown' ? 'Outcome unknown' : cell.problem) })] });
|
|
291
320
|
}
|
|
292
321
|
function ConsoleMark({ session, edge, who }) {
|
|
@@ -422,26 +451,19 @@ export function SessionPane({ client, snapshot, prepared, onTaken, onConversatio
|
|
|
422
451
|
observer.observe(element);
|
|
423
452
|
return () => observer.disconnect();
|
|
424
453
|
}, []);
|
|
425
|
-
return withShell(shell, _jsx("div", { className: "nb-view", ref: box, children: _jsx(NotebookBody, { client: client, snapshot: snapshot, onConversation: onConversation ?? (() => { }), compact: !wide, inView: true, shown: shown, prepared: prepared, onTaken: onTaken, onObject: onObject, beside: beside, form: form, scope: scope }) }));
|
|
454
|
+
return withShell(shell, _jsx(WithFacts, { snapshot: snapshot, children: _jsx("div", { className: "nb-view", ref: box, children: _jsx(NotebookBody, { client: client, snapshot: snapshot, onConversation: onConversation ?? (() => { }), compact: !wide, inView: true, shown: shown, prepared: prepared, onTaken: onTaken, onObject: onObject, beside: beside, form: form, scope: scope }) }) }));
|
|
426
455
|
}
|
|
427
456
|
// The Notebook as a page of its own, or inside a view. The page that shows it may give it the
|
|
428
457
|
// application's code highlighting.
|
|
429
458
|
export function NotebookPage({ shell, ...props }) {
|
|
430
|
-
return withShell(shell, _jsx(NotebookBody, { ...props }));
|
|
459
|
+
return withShell(shell, _jsx(WithFacts, { snapshot: props.snapshot, children: _jsx(NotebookBody, { ...props }) }));
|
|
431
460
|
}
|
|
432
461
|
function NotebookBody({ client, snapshot, onConversation, onObject, scope, compact = false, inView = false, shown = 0, prepared, onTaken, beside = {}, form = 'notebook' }) {
|
|
433
462
|
const projectId = snapshot.project.id;
|
|
434
463
|
const { read, off, problem, again } = useKernel(client, projectId, snapshot.cursor);
|
|
435
464
|
const { places, setPlaces } = usePlaces(client, projectId, snapshot.cursor);
|
|
436
465
|
const who = useAuthors(snapshot);
|
|
437
|
-
const cells = useMemo(() =>
|
|
438
|
-
const all = read?.cells ?? [];
|
|
439
|
-
if (!scope)
|
|
440
|
-
return all;
|
|
441
|
-
const theirs = new Set(all.filter(c => c.by === scope.principal).map(c => c.sessionId));
|
|
442
|
-
return all.filter(c => (theirs.has(c.sessionId) || (c.by === me && scope.since !== null && Date.parse(c.queuedAt) >= scope.since))
|
|
443
|
-
&& (scope.at === null || Date.parse(c.queuedAt) <= scope.at));
|
|
444
|
-
}, [read, scope?.principal, scope?.since, scope?.at]);
|
|
466
|
+
const cells = useMemo(() => scopedCells(read?.cells ?? [], scope), [read, scope?.principal, scope?.since, scope?.at, scope?.cellIds]);
|
|
445
467
|
const latest = useMemo(() => {
|
|
446
468
|
const out = {};
|
|
447
469
|
for (const s of read?.sessions ?? [])
|
|
@@ -679,7 +701,10 @@ export function VariablesPane({ client, snapshot, onObject }) {
|
|
|
679
701
|
// the ones before it to step back through. A new plot comes forward as it is drawn. Plots of an
|
|
680
702
|
// ended session stay with their cells in the Notebook.
|
|
681
703
|
export const plotsPaneLimits = { plots: 24 };
|
|
682
|
-
export function PlotsPane({ client, snapshot }) {
|
|
704
|
+
export function PlotsPane({ client, snapshot, shell }) {
|
|
705
|
+
return withShell(shell, _jsx(WithFacts, { snapshot: snapshot, children: _jsx(Plots, { client: client, snapshot: snapshot }) }));
|
|
706
|
+
}
|
|
707
|
+
function Plots({ client, snapshot }) {
|
|
683
708
|
const projectId = snapshot.project.id;
|
|
684
709
|
const { read, off, problem } = useKernel(client, projectId, snapshot.cursor);
|
|
685
710
|
const who = useAuthors(snapshot);
|
|
@@ -711,9 +736,11 @@ export function PlotsPane({ client, snapshot }) {
|
|
|
711
736
|
}
|
|
712
737
|
function PlotFit({ client, projectId, cell, output, label }) {
|
|
713
738
|
const { src, problem } = usePlot(client, projectId, cell.cellId, output.plot);
|
|
739
|
+
const picture = _jsx("figure", { className: "nb-plots-fit", children: src ? _jsx("img", { src: src, alt: label }) : _jsx("span", { className: "nb-plot-wait", "aria-label": "Reading the plot" }) });
|
|
740
|
+
const described = useFigure(client, projectId, cell, output, label, picture, true);
|
|
714
741
|
if (problem)
|
|
715
742
|
return _jsxs("p", { className: "nb-note", children: ["This plot could not be read. ", problem] });
|
|
716
|
-
return _jsx("
|
|
743
|
+
return described ? _jsx("div", { className: "nb-plots-figure", children: described }) : picture;
|
|
717
744
|
}
|
|
718
745
|
function PlotThumb({ client, projectId, cell, output, current, label, onShow }) {
|
|
719
746
|
const { src } = usePlot(client, projectId, cell.cellId, output.plot);
|
|
@@ -40,6 +40,10 @@ const styles = `/* The Notebook: cells in the order they ran beside the live ses
|
|
|
40
40
|
.nb-plot img { display: block; width: 100%; height: auto; border: 1px solid var(--color-hairline); border-radius: 6px; background: #fff; }
|
|
41
41
|
.nb-plot-wait { display: block; width: 100%; border-radius: 6px; background: var(--color-well); }
|
|
42
42
|
.nb-note { color: var(--color-meta); font-size: 12px; }
|
|
43
|
+
.nb-saved { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; margin: 6px 0; font-size: 12px; color: var(--color-meta); }
|
|
44
|
+
.nb-saved > span:first-child { font-weight: 600; color: var(--color-ink); }
|
|
45
|
+
.nb-saved .text-button { font-family: var(--font-mono, ui-monospace, monospace); font-size: 12px; overflow-wrap: anywhere; }
|
|
46
|
+
.nb-saved-what { overflow-wrap: anywhere; }
|
|
43
47
|
.nb-body > .nb-note { padding-left: 12px; }
|
|
44
48
|
.nb-problem { padding-left: 12px; color: var(--color-amber-text); font-size: 12.5px; }
|
|
45
49
|
.nb-actions { display: flex; gap: 16px; padding-left: 12px; }
|
|
@@ -188,6 +192,8 @@ button.nb-cell-link:hover { background: var(--color-hover); text-decoration: und
|
|
|
188
192
|
.nb-plots-fit { flex: 1; display: flex; align-items: center; justify-content: center; min-height: 0; margin: 0; }
|
|
189
193
|
.nb-plots-fit img { max-width: 100%; max-height: 100%; object-fit: contain; border: 1px solid var(--color-hairline); border-radius: 6px; background: #fff; }
|
|
190
194
|
.nb-plots-fit .nb-plot-wait { width: min(100%, 504px); aspect-ratio: 7 / 5; }
|
|
195
|
+
.nb-plots-figure { flex: 1; min-height: 0; display: flex; flex-direction: column; }
|
|
196
|
+
.nb-plots-figure > .fig { flex: 1; min-height: 0; }
|
|
191
197
|
.nb-plots-strip { display: flex; flex: none; gap: 6px; overflow-x: auto; padding: 2px; }
|
|
192
198
|
.nb-plots-thumb { flex: none; width: 72px; min-height: 0; padding: 2px; border: 1px solid var(--color-hairline); border-radius: 6px; background: var(--color-surface); }
|
|
193
199
|
.nb-plots-thumb[aria-current="true"] { border-color: var(--color-accent); box-shadow: 0 0 0 1px var(--color-accent); }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rho-workspace/plugin-kernel",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rho live session plugin: an R and a Python session the Agent and the person run code in, kept with each cell's output, plots and the session's variables",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -161,7 +161,98 @@ call_text <- function(condition) {
|
|
|
161
161
|
# that draws several keeps each; a cell reports the pages it drew or changed, drawn to files at
|
|
162
162
|
# its end. A device the code opens itself is its own.
|
|
163
163
|
device <- NULL; seen <- NULL; plots <- list()
|
|
164
|
+
# Plugins can describe what a page draws (RHO_KERNEL_EXTENSIONS names their R files). Each file is
|
|
165
|
+
# sourced into an environment of its own; page_object() names the object a page starts drawing,
|
|
166
|
+
# and describe_page() writes a record beside the page's picture. One that fails is left out, and a
|
|
167
|
+
# page whose object no extension describes stays a picture.
|
|
168
|
+
extensions <- list()
|
|
169
|
+
for (file in strsplit(Sys.getenv("RHO_KERNEL_EXTENSIONS"), .Platform$path.sep, fixed = TRUE)[[1]]) {
|
|
170
|
+
if (!nzchar(file)) next
|
|
171
|
+
env <- new.env(parent = baseenv())
|
|
172
|
+
if (!inherits(tryCatch(sys.source(file, envir = env), error = function(e) e), "error")) extensions[[length(extensions) + 1]] <- env
|
|
173
|
+
}
|
|
174
|
+
page_object <- NULL
|
|
175
|
+
# Pages the code saved to files of its own (ggsave): an extension names the plot, the file and the
|
|
176
|
+
# size, and the page is described when the cell ends, once the file is written. A file saved twice
|
|
177
|
+
# keeps its last page.
|
|
178
|
+
saving <- list()
|
|
179
|
+
absolute <- function(file) if (grepl("^(/|~|[A-Za-z]:[/\\\\])", file)) path.expand(file) else file.path(getwd(), file)
|
|
180
|
+
# A device the code opens on a file (png(), pdf(), svg() and the others grDevices names) is noted
|
|
181
|
+
# with its file. Each page drawn there is recorded as it is drawn: before the next page starts,
|
|
182
|
+
# after each top-level expression and before dev.off() closes it. When it closes, its last page (or
|
|
183
|
+
# each page, for a file name numbered with %d) is described as a figure of its file, as a page
|
|
184
|
+
# ggsave() saved is. The runner's own devices are not noted. A device opened through a function a
|
|
185
|
+
# package imported before the session started is not seen; its file stays a picture.
|
|
186
|
+
drawing <- list(); closed <- list(); own_devices <- FALSE
|
|
187
|
+
numbered <- function(file) grepl("%[#0 +-]*[0-9]*d", file)
|
|
188
|
+
file_snapshot <- function(d) {
|
|
189
|
+
key <- as.character(d); f <- drawing[[key]]
|
|
190
|
+
if (is.null(f) || !length(f$pages) || !(d %in% grDevices::dev.list())) return()
|
|
191
|
+
current <- grDevices::dev.cur(); if (current != d) grDevices::dev.set(d)
|
|
192
|
+
p <- tryCatch(grDevices::recordPlot(), error = function(e) NULL); size <- grDevices::dev.size("in")
|
|
193
|
+
if (current != d) grDevices::dev.set(current)
|
|
194
|
+
if (!is.null(p) && length(drawn(p[[1]]))) drawing[[key]]$pages[[length(f$pages)]] <<- c(f$pages[[length(f$pages)]][c("object")], list(recorded = p, width = size[1], height = size[2]))
|
|
195
|
+
}
|
|
196
|
+
file_page <- function(d, grid) {
|
|
197
|
+
key <- as.character(d)
|
|
198
|
+
if (is.null(drawing[[key]])) return()
|
|
199
|
+
file_snapshot(d)
|
|
200
|
+
grDevices::dev.control("enable")
|
|
201
|
+
# A file that is not numbered keeps only its last page: earlier ones are counted, not kept.
|
|
202
|
+
n <- length(drawing[[key]]$pages)
|
|
203
|
+
if (n && !numbered(drawing[[key]]$file)) drawing[[key]]$pages[[n]] <<- list(object = NULL)
|
|
204
|
+
object <- NULL
|
|
205
|
+
if (grid) for (env in extensions) if (is.function(env$page_object)) {
|
|
206
|
+
found <- tryCatch(env$page_object(), error = function(e) NULL)
|
|
207
|
+
if (!is.null(found)) { object <- list(env = env, object = found); break }
|
|
208
|
+
}
|
|
209
|
+
drawing[[key]]$pages[[n + 1L]] <<- list(object = object)
|
|
210
|
+
}
|
|
211
|
+
device_opening <- function(env) assign(".rho_devices", grDevices::dev.list(), envir = env)
|
|
212
|
+
device_opened <- function(env) tryCatch({
|
|
213
|
+
d <- grDevices::dev.cur(); before <- get0(".rho_devices", envir = env, inherits = FALSE)
|
|
214
|
+
if (d == 1L || d %in% before) return(invisible())
|
|
215
|
+
# A device number is used again once its device is closed; the runner's own device is never noted.
|
|
216
|
+
drawing[[as.character(d)]] <<- NULL
|
|
217
|
+
file <- get0("filename", envir = env, inherits = FALSE) %||% get0("file", envir = env, inherits = FALSE)
|
|
218
|
+
if (own_devices || identical(d, device) || !is.character(file) || length(file) != 1L || !nzchar(file)) return(invisible())
|
|
219
|
+
drawing[[as.character(d)]] <<- list(file = absolute(file), pages = list())
|
|
220
|
+
invisible()
|
|
221
|
+
}, error = function(e) invisible())
|
|
222
|
+
device_closing <- function(which) tryCatch({
|
|
223
|
+
d <- as.integer(which); key <- as.character(d)
|
|
224
|
+
if (length(d) != 1L || is.na(d) || is.null(drawing[[key]])) return(invisible())
|
|
225
|
+
file_snapshot(d)
|
|
226
|
+
closed[[length(closed) + 1L]] <<- drawing[[key]]; drawing[[key]] <<- NULL
|
|
227
|
+
invisible()
|
|
228
|
+
}, error = function(e) invisible())
|
|
229
|
+
invisible(utils::capture.output(suppressMessages(tryCatch(
|
|
230
|
+
for (where in list(asNamespace("grDevices"), as.environment("package:grDevices"))) {
|
|
231
|
+
for (f in intersect(c("png", "jpeg", "tiff", "bmp", "svg", "pdf", "cairo_pdf"), ls(where)))
|
|
232
|
+
trace(f, tracer = substitute(F(environment()), list(F = device_opening)), exit = substitute(F(environment()), list(F = device_opened)), where = where, print = FALSE)
|
|
233
|
+
trace("dev.off", tracer = substitute(F(which), list(F = device_closing)), where = where, print = FALSE)
|
|
234
|
+
}, error = function(e) NULL))))
|
|
235
|
+
started_page <- function(grid) {
|
|
236
|
+
current <- grDevices::dev.cur()
|
|
237
|
+
if (!is.null(device) && current != device) {
|
|
238
|
+
if (grid) for (env in extensions) if (is.function(env$saved_page)) {
|
|
239
|
+
found <- tryCatch(env$saved_page(), error = function(e) NULL)
|
|
240
|
+
if (!is.null(found)) { found$env <- env; saving[[absolute(found$file)]] <<- found; drawing[[as.character(current)]] <<- NULL; return() }
|
|
241
|
+
}
|
|
242
|
+
file_page(current, grid)
|
|
243
|
+
return()
|
|
244
|
+
}
|
|
245
|
+
if (is.null(device) || grDevices::dev.cur() != device) return()
|
|
246
|
+
page_object <<- NULL
|
|
247
|
+
if (!grid) return()
|
|
248
|
+
for (env in extensions) if (is.function(env$page_object)) {
|
|
249
|
+
found <- tryCatch(env$page_object(), error = function(e) NULL)
|
|
250
|
+
if (!is.null(found)) { page_object <<- list(env = env, object = found); return() }
|
|
251
|
+
}
|
|
252
|
+
}
|
|
164
253
|
recording <- function() {
|
|
254
|
+
# A file the code opened and has not closed stays where it draws, as in the R console.
|
|
255
|
+
if (!is.null(drawing[[as.character(grDevices::dev.cur())]])) return()
|
|
165
256
|
if (!is.null(device) && device %in% grDevices::dev.list()) { grDevices::dev.set(device); return() }
|
|
166
257
|
grDevices::pdf(NULL, width = 7, height = 5); grDevices::dev.control("enable")
|
|
167
258
|
device <<- grDevices::dev.cur(); seen <<- NULL
|
|
@@ -171,13 +262,17 @@ snapshot <- function() {
|
|
|
171
262
|
current <- grDevices::dev.cur(); if (current != device) grDevices::dev.set(device)
|
|
172
263
|
p <- grDevices::recordPlot()
|
|
173
264
|
if (current != device) grDevices::dev.set(current)
|
|
174
|
-
if (length(drawn(p[[1]])) && !(length(seen) && same(drawn(p[[1]]), drawn(seen)))) { plots[[length(plots) + 1]] <<- p; seen <<- p[[1]] }
|
|
265
|
+
if (length(drawn(p[[1]])) && !(length(seen) && same(drawn(p[[1]]), drawn(seen)))) { attr(p, "rho_page") <- page_object; plots[[length(plots) + 1]] <<- p; seen <<- p[[1]] }
|
|
175
266
|
}
|
|
176
|
-
setHook("before.plot.new", function() snapshot())
|
|
177
|
-
setHook("before.grid.newpage", function() snapshot())
|
|
267
|
+
setHook("before.plot.new", function() { snapshot(); started_page(FALSE) })
|
|
268
|
+
setHook("before.grid.newpage", function() { snapshot(); started_page(TRUE) })
|
|
178
269
|
|
|
270
|
+
# A page drawn on a file, ready to be described as that file: the object an extension named when
|
|
271
|
+
# the page started, else the page as it was recorded.
|
|
272
|
+
drawn_page <- function(p) list(env = p$object$env %||% Find(function(env) is.function(env$describe_page), extensions),
|
|
273
|
+
object = p$object$object %||% p$recorded, width = p$width, height = p$height, recorded = p$recorded, quiet = TRUE)
|
|
179
274
|
run <- function(msg) {
|
|
180
|
-
id <- msg$id; plots <<- list(); ok <- TRUE; interrupted <- FALSE
|
|
275
|
+
id <- msg$id; plots <<- list(); saving <<- list(); closed <<- list(); own_devices <<- FALSE; ok <- TRUE; interrupted <- FALSE
|
|
181
276
|
known <- ls(globalenv()); seen <- lineage_of(msg$code); files <- project_paths(seen$strings)
|
|
182
277
|
recording()
|
|
183
278
|
tryCatch({
|
|
@@ -193,10 +288,13 @@ run <- function(msg) {
|
|
|
193
288
|
warning = function(w) { say(list(id = id, kind = "warning", text = paste0("Warning", call_text(w), ": ", conditionMessage(w), "\n"))); invokeRestart("muffleWarning") },
|
|
194
289
|
message = function(m) { say(list(id = id, kind = "stderr", text = conditionMessage(m))); invokeRestart("muffleMessage") })
|
|
195
290
|
snapshot()
|
|
291
|
+
for (key in names(drawing)) file_snapshot(as.integer(key))
|
|
196
292
|
if (!went) break
|
|
197
293
|
}
|
|
198
294
|
}, interrupt = function(e) { interrupted <<- TRUE; ok <<- FALSE; cat("\n") })
|
|
199
295
|
# A session on a server draws in its own temporary folder and sends each plot with its answer.
|
|
296
|
+
# The devices drawn on from here are the runner's own.
|
|
297
|
+
own_devices <<- TRUE
|
|
200
298
|
shown <- final_plots(plots); plots <<- list()
|
|
201
299
|
for (i in seq_along(shown)) {
|
|
202
300
|
if (i > msg$maxPlots) break
|
|
@@ -205,9 +303,51 @@ run <- function(msg) {
|
|
|
205
303
|
tryCatch(grDevices::replayPlot(shown[[i]]), error = function(e) NULL)
|
|
206
304
|
grDevices::dev.off()
|
|
207
305
|
plot <- list(id = id, kind = "plot", file = file, width = 1008, height = 720)
|
|
306
|
+
# A page no extension named an object for (base graphics) is offered to them as it was recorded.
|
|
307
|
+
page <- attr(shown[[i]], "rho_page")
|
|
308
|
+
if (is.null(page)) for (env in extensions) if (is.function(env$describe_page)) { page <- list(env = env, object = shown[[i]]); break }
|
|
309
|
+
if (!is.null(page) && !isTRUE(msg$inline) && is.function(page$env$describe_page)) {
|
|
310
|
+
described <- tryCatch(page$env$describe_page(page$object, file, 7, 5, recorded = shown[[i]]), error = function(e) list(problem = conditionMessage(e)))
|
|
311
|
+
if (!is.null(described$problem)) plot$figureProblem <- as.character(described$problem)[1] else if (!is.null(described)) plot$figure <- described
|
|
312
|
+
}
|
|
208
313
|
if (isTRUE(msg$inline)) { plot$data <- jsonlite::base64_enc(readBin(file, "raw", file.info(file)$size)); unlink(file) }
|
|
209
314
|
say(plot)
|
|
210
315
|
}
|
|
316
|
+
# A page saved to a file in the project is described at the size it was saved at; its record is
|
|
317
|
+
# kept with the cell's plots. A session on a server keeps none: its files are not here.
|
|
318
|
+
# Files the code drew to and closed, or whose device is gone, join them: a file numbered with %d
|
|
319
|
+
# holds each page, a PDF of several pages is not described, any other file holds its last page.
|
|
320
|
+
for (key in names(drawing)) if (!(as.integer(key) %in% grDevices::dev.list())) { closed[[length(closed) + 1L]] <<- drawing[[key]]; drawing[[key]] <<- NULL }
|
|
321
|
+
for (f in closed) {
|
|
322
|
+
n <- length(f$pages)
|
|
323
|
+
if (!n) next
|
|
324
|
+
if (numbered(f$file)) { for (j in seq_len(n)) if (!is.null(f$pages[[j]]$recorded)) saving[[sprintf(f$file, j)]] <<- drawn_page(f$pages[[j]]) }
|
|
325
|
+
else if (n > 1L && grepl("[.]pdf$", f$file, ignore.case = TRUE)) saving[[f$file]] <<- list(problem = sprintf("The file holds %d pages; only a file of one page is described.", n))
|
|
326
|
+
else if (!is.null(f$pages[[n]]$recorded)) saving[[f$file]] <<- drawn_page(f$pages[[n]])
|
|
327
|
+
}
|
|
328
|
+
closed <<- list()
|
|
329
|
+
saved <- saving; saving <<- list()
|
|
330
|
+
if (!isTRUE(msg$inline) && !is.null(msg$plots)) {
|
|
331
|
+
root <- normalizePath(getwd(), winslash = "/")
|
|
332
|
+
k <- 0L
|
|
333
|
+
for (named in names(saved)) {
|
|
334
|
+
if (k >= msg$maxPlots) break
|
|
335
|
+
page <- saved[[named]]
|
|
336
|
+
if (!file.exists(named)) next
|
|
337
|
+
full <- normalizePath(named, winslash = "/")
|
|
338
|
+
if (!startsWith(full, paste0(root, "/"))) next
|
|
339
|
+
described <- if (!is.null(page$problem)) list(problem = page$problem) else if (is.function(page$env$describe_page))
|
|
340
|
+
tryCatch(page$env$describe_page(page$object, full, page$width, page$height, file.path(msg$plots, paste0(id, "-s", k + 1L, ".figure.json")), recorded = page$recorded),
|
|
341
|
+
error = function(e) list(problem = conditionMessage(e)))
|
|
342
|
+
# A page drawn on a file that no extension describes (a lattice page) stays a picture without a word.
|
|
343
|
+
if (is.null(described) && isTRUE(page$quiet)) next
|
|
344
|
+
k <- k + 1L
|
|
345
|
+
out <- list(id = id, kind = "saved", path = substring(full, nchar(root) + 2), number = k)
|
|
346
|
+
if (!is.null(described$problem)) out$figureProblem <- as.character(described$problem)[1] else if (!is.null(described)) out$figure <- described
|
|
347
|
+
say(out)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
own_devices <<- FALSE
|
|
211
351
|
say(list(id = id, kind = "done", ok = ok, interrupted = interrupted, variables = variables(msg$maxVariables),
|
|
212
352
|
used = I(intersect(seen$mentioned, known)), assigned = I(seen$assigned), files = touched(files)))
|
|
213
353
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# that value, as a notebook does. Figures made with matplotlib are saved after each cell. An
|
|
5
5
|
# interrupt (SIGINT; on Windows a Ctrl+C or Ctrl+Break on the session's console) stops the cell
|
|
6
6
|
# that is running and leaves the session and its variables.
|
|
7
|
-
import ast, base64, io, json, linecache, os, platform, re, signal, sys, tempfile, traceback, types
|
|
7
|
+
import ast, base64, functools, io, json, linecache, os, platform, re, signal, sys, tempfile, traceback, types
|
|
8
8
|
|
|
9
9
|
root = sys.argv[1]
|
|
10
10
|
os.chdir(root)
|
|
@@ -67,6 +67,27 @@ def variables(most):
|
|
|
67
67
|
items.append({'name': name, 'type': type(value).__name__, 'size': size, 'bytes': bytes_, 'summary': summary})
|
|
68
68
|
return {'items': items, 'omitted': max(0, len(names) - most)}
|
|
69
69
|
|
|
70
|
+
# Plugins can describe what a figure draws (RHO_KERNEL_EXTENSIONS names their Python files). Each file
|
|
71
|
+
# runs in a namespace of its own; describe_figure() writes a record beside the figure's picture. One
|
|
72
|
+
# that fails to load is left out, and a figure no extension describes stays a picture.
|
|
73
|
+
extensions = []
|
|
74
|
+
for file in os.environ.get('RHO_KERNEL_EXTENSIONS', '').split(os.pathsep):
|
|
75
|
+
if not file: continue
|
|
76
|
+
try:
|
|
77
|
+
with open(file, encoding='utf-8') as f: scope = {'__name__': 'rho_extension', '__file__': file}; exec(compile(f.read(), file, 'exec'), scope)
|
|
78
|
+
if callable(scope.get('describe_figure')): extensions.append(scope)
|
|
79
|
+
except Exception: pass
|
|
80
|
+
|
|
81
|
+
# The names the running cell's code assigns, then those it reads: a describer compares those tables
|
|
82
|
+
# first, a table the cell made before one it read.
|
|
83
|
+
cell_names = []
|
|
84
|
+
def describe(figure, into, **options):
|
|
85
|
+
for scope in extensions:
|
|
86
|
+
try: out = scope['describe_figure'](figure, into, namespace, prefer=cell_names, **options)
|
|
87
|
+
except Exception as error: return {'problem': f'{type(error).__name__}: {error}'}
|
|
88
|
+
if out is not None: return out
|
|
89
|
+
return None
|
|
90
|
+
|
|
70
91
|
# A session on a server draws in its own temporary folder and sends each figure with its answer.
|
|
71
92
|
def save_figures(cell, folder, most, inline=False):
|
|
72
93
|
plt = sys.modules.get('matplotlib.pyplot')
|
|
@@ -81,9 +102,85 @@ def save_figures(cell, folder, most, inline=False):
|
|
|
81
102
|
if inline:
|
|
82
103
|
with open(file, 'rb') as f: plot['data'] = base64.b64encode(f.read()).decode('ascii')
|
|
83
104
|
os.remove(file)
|
|
105
|
+
elif extensions:
|
|
106
|
+
described = describe(figure, file[:-4] + '.figure.json', bbox_inches='tight')
|
|
107
|
+
if described and described.get('problem'): plot['figureProblem'] = str(described['problem'])[:500]
|
|
108
|
+
elif described: plot['figure'] = described
|
|
84
109
|
emit(plot)
|
|
85
110
|
plt.close('all')
|
|
86
111
|
|
|
112
|
+
# Figures the code saved to files in the project (savefig): each is described as savefig wrote it,
|
|
113
|
+
# and named with its file when the cell ends, once the file is as the cell left it. A file saved twice
|
|
114
|
+
# keeps its last figure. Nothing is kept for a session on a server, or for a file outside the project.
|
|
115
|
+
saving = {'cell': None, 'folder': None, 'files': {}, 'most': 0}
|
|
116
|
+
def saved_figure(figure, fname, kwargs):
|
|
117
|
+
import matplotlib
|
|
118
|
+
cell, folder, files = saving['cell'], saving['folder'], saving['files']
|
|
119
|
+
if not cell or not folder or not isinstance(fname, (str, os.PathLike)): return
|
|
120
|
+
full = os.path.realpath(os.path.join(os.getcwd(), os.fspath(fname)))
|
|
121
|
+
# savefig adds the format's extension to a name that has none.
|
|
122
|
+
fmt = kwargs.get('format') or matplotlib.rcParams['savefig.format']
|
|
123
|
+
if not os.path.isfile(full) and os.path.isfile(f'{full}.{fmt}'): full = f'{full}.{fmt}'
|
|
124
|
+
root = os.path.realpath(os.getcwd())
|
|
125
|
+
if not full.startswith(root + os.sep) or full.startswith(os.path.realpath(folder) + os.sep) or not os.path.isfile(full): return
|
|
126
|
+
if full not in files and len(files) >= saving['most']: return
|
|
127
|
+
pending = files[full][0] if full in files else os.path.join(folder, f'{cell}-p{len(files) + 1}.figure.json')
|
|
128
|
+
bbox = kwargs.get('bbox_inches', matplotlib.rcParams['savefig.bbox'])
|
|
129
|
+
options = {'bbox_inches': None if bbox == 'standard' else bbox, 'pad_inches': kwargs.get('pad_inches', matplotlib.rcParams['savefig.pad_inches'])}
|
|
130
|
+
files[full] = (pending, describe(figure, pending, **options))
|
|
131
|
+
|
|
132
|
+
def watch_savefig(module):
|
|
133
|
+
Figure = getattr(module, 'Figure', None)
|
|
134
|
+
if Figure is None or getattr(Figure.savefig, '_rho', False): return
|
|
135
|
+
original = Figure.savefig
|
|
136
|
+
@functools.wraps(original)
|
|
137
|
+
def savefig(self, fname, *args, **kwargs):
|
|
138
|
+
result = original(self, fname, *args, **kwargs)
|
|
139
|
+
try: saved_figure(self, fname, kwargs)
|
|
140
|
+
except Exception: pass
|
|
141
|
+
return result
|
|
142
|
+
savefig._rho = True
|
|
143
|
+
Figure.savefig = savefig
|
|
144
|
+
|
|
145
|
+
# matplotlib.figure is watched as it is first imported, whichever cell imports it.
|
|
146
|
+
class WatchFigures:
|
|
147
|
+
def find_spec(self, name, path=None, target=None):
|
|
148
|
+
if name != 'matplotlib.figure': return None
|
|
149
|
+
for finder in sys.meta_path:
|
|
150
|
+
if finder is self or not hasattr(finder, 'find_spec'): continue
|
|
151
|
+
spec = finder.find_spec(name, path, target)
|
|
152
|
+
if spec is None: continue
|
|
153
|
+
loader = spec.loader
|
|
154
|
+
if loader is not None and hasattr(loader, 'exec_module'):
|
|
155
|
+
run = loader.exec_module
|
|
156
|
+
def exec_module(module, run=run):
|
|
157
|
+
run(module)
|
|
158
|
+
try: watch_savefig(module)
|
|
159
|
+
except Exception: pass
|
|
160
|
+
loader.exec_module = exec_module
|
|
161
|
+
return spec
|
|
162
|
+
return None
|
|
163
|
+
if extensions:
|
|
164
|
+
if 'matplotlib.figure' in sys.modules: watch_savefig(sys.modules['matplotlib.figure'])
|
|
165
|
+
else: sys.meta_path.insert(0, WatchFigures())
|
|
166
|
+
|
|
167
|
+
def emit_saved(cell):
|
|
168
|
+
root, k = os.path.realpath(os.getcwd()), 0
|
|
169
|
+
for full, (pending, described) in saving['files'].items():
|
|
170
|
+
if not os.path.isfile(full): continue
|
|
171
|
+
k += 1
|
|
172
|
+
out = {'id': cell, 'kind': 'saved', 'path': os.path.relpath(full, root).replace(os.sep, '/'), 'number': k}
|
|
173
|
+
if described and described.get('problem'): out['figureProblem'] = str(described['problem'])[:500]
|
|
174
|
+
elif described and os.path.isfile(pending):
|
|
175
|
+
record = os.path.join(saving['folder'], f'{cell}-s{k}.figure.json')
|
|
176
|
+
os.replace(pending, record)
|
|
177
|
+
out['figure'] = dict(described, file=os.path.basename(record))
|
|
178
|
+
emit(out)
|
|
179
|
+
for pending, _ in saving['files'].values():
|
|
180
|
+
if os.path.isfile(pending):
|
|
181
|
+
try: os.remove(pending)
|
|
182
|
+
except OSError: pass
|
|
183
|
+
|
|
87
184
|
# What a cell's code reads and assigns, from its parsed text before it runs: the names it loads,
|
|
88
185
|
# the names it binds (also through df[...] = or obj.attr =, def, class and import), and its
|
|
89
186
|
# strings. What a function, lambda or comprehension binds is its own, and so is a name the cell
|
|
@@ -210,6 +307,8 @@ def run(msg):
|
|
|
210
307
|
name = f"<cell {msg.get('number', '')}>"
|
|
211
308
|
linecache.cache[name] = (len(msg['code']), None, msg['code'].splitlines(True), name)
|
|
212
309
|
sys.stdout, sys.stderr = Stream(cell, 'stdout'), Stream(cell, 'stderr')
|
|
310
|
+
if extensions and not msg.get('inline') and msg.get('plots'): saving.update(cell=cell, folder=msg['plots'], files={}, most=msg['maxPlots'])
|
|
311
|
+
cell_names[:] = list(assigned) + sorted(n for n in mentioned if n not in assigned)
|
|
213
312
|
try:
|
|
214
313
|
tree = ast.parse(msg['code'], name, 'exec')
|
|
215
314
|
last = tree.body.pop() if tree.body and isinstance(tree.body[-1], ast.Expr) else None
|
|
@@ -227,8 +326,14 @@ def run(msg):
|
|
|
227
326
|
emit({'id': cell, 'kind': 'error', 'text': 'Traceback (most recent call last):\n' + text if frames else text})
|
|
228
327
|
finally:
|
|
229
328
|
sys.stdout, sys.stderr = real_out, real_err
|
|
329
|
+
# The cell's own pictures are not files it saved.
|
|
330
|
+
saving['cell'] = None
|
|
230
331
|
try: save_figures(cell, msg['plots'], msg['maxPlots'], msg.get('inline', False))
|
|
231
332
|
except Exception as error: emit({'id': cell, 'kind': 'stderr', 'text': f'The figures could not be saved: {error}\n'})
|
|
333
|
+
if saving['files']:
|
|
334
|
+
try: emit_saved(cell)
|
|
335
|
+
except Exception: pass
|
|
336
|
+
saving.update(cell=None, folder=None, files={}, most=0)
|
|
232
337
|
emit({'id': cell, 'kind': 'done', 'ok': ok, 'interrupted': interrupted, 'variables': variables(msg['maxVariables']),
|
|
233
338
|
'used': sorted(mentioned & known), 'assigned': assigned, 'files': touched(files)})
|
|
234
339
|
|
|
@@ -13,6 +13,11 @@ Selected services: Only the root, bytes, resolve and register ports of Files; no
|
|
|
13
13
|
|
|
14
14
|
Only this plugin depends on csv-parse. No scientific column assumptions or chart renderer.
|
|
15
15
|
|
|
16
|
+
A CSV opened as observed is read lexically, every value as text. A column its header leaves
|
|
17
|
+
unnamed is named: R's `write.csv()` and pandas' `to_csv()` write row names or the index under an
|
|
18
|
+
empty header, which becomes `row`; another unnamed column becomes `column N`, and a name the
|
|
19
|
+
header already uses is not taken twice.
|
|
20
|
+
|
|
16
21
|
The package depends on `@rho-workspace/plugin-contracts` 0.4.0 for generic envelopes and service ports
|
|
17
22
|
and ordinary file primitives; that package contains no capability implementation.
|
|
18
23
|
Service ports do not install or start other plugins. Rho explicitly selects and
|
|
@@ -5,6 +5,7 @@ export declare const tableLimits: {
|
|
|
5
5
|
maxRows: number;
|
|
6
6
|
maxPageRows: number;
|
|
7
7
|
};
|
|
8
|
+
export declare function csvColumnNames(header: string[]): string[];
|
|
8
9
|
export declare function lexicalCsvDeclaration(data: Buffer, relative: string): Declaration;
|
|
9
10
|
export declare function validateTableDeclaration(d: Declaration): void;
|
|
10
11
|
export declare const declarationValidator: {
|
|
@@ -4,12 +4,28 @@ import { parse } from 'csv-parse/sync';
|
|
|
4
4
|
import { atomicJson, canonical, confined, digest, publicResource, requireThat, stateDirectory, validateDeclarationEnvelope as validateEnvelope } from '@rho-workspace/plugin-contracts';
|
|
5
5
|
const declaredSkill = (id, content, version = '1.0') => ({ id, version, content, digest: digest(content) });
|
|
6
6
|
export const tableLimits = { maxRows: 100000, maxPageRows: 1000 };
|
|
7
|
+
// A column a CSV leaves unnamed is named: R's write.csv() and pandas' to_csv() write row names
|
|
8
|
+
// or the index under an empty header, which is "row"; another unnamed column is "column N". A name
|
|
9
|
+
// the header already uses is not taken twice. The row reader names the header the same way.
|
|
10
|
+
export function csvColumnNames(header) {
|
|
11
|
+
const taken = new Set(header.filter(Boolean));
|
|
12
|
+
return header.map((name, i) => {
|
|
13
|
+
if (name)
|
|
14
|
+
return name;
|
|
15
|
+
const base = i === 0 ? 'row' : `column ${i + 1}`;
|
|
16
|
+
let named = base;
|
|
17
|
+
for (let k = 2; taken.has(named); k++)
|
|
18
|
+
named = `${base} ${k}`;
|
|
19
|
+
taken.add(named);
|
|
20
|
+
return named;
|
|
21
|
+
});
|
|
22
|
+
}
|
|
7
23
|
// Opening an observed CSV supplies a lexical view, with no guessed numeric,
|
|
8
24
|
// missing-value or entity semantics. The usual table reader validates all rows.
|
|
9
25
|
export function lexicalCsvDeclaration(data, relative) {
|
|
10
26
|
requireThat(data.length <= 8 * 1024 * 1024, 'limit', 'CSV preview exceeds 8 MiB.');
|
|
11
27
|
const records = parse(data, { bom: true, skip_empty_lines: true, max_record_size: 1024 * 1024, to: 1 });
|
|
12
|
-
const declaration = { path: relative, kind: 'table', format: { encoding: 'csv', columns: (records[0] ?? []).map(name => ({ name, type: 'string', nullValues: [] })), key: null } };
|
|
28
|
+
const declaration = { path: relative, kind: 'table', format: { encoding: 'csv', columns: csvColumnNames(records[0] ?? []).map(name => ({ name, type: 'string', nullValues: [] })), key: null } };
|
|
13
29
|
validateTableDeclaration(declaration);
|
|
14
30
|
return declaration;
|
|
15
31
|
}
|
|
@@ -50,7 +66,7 @@ export class Tables {
|
|
|
50
66
|
else {
|
|
51
67
|
const records = parse(data, { bom: true, skip_empty_lines: true, max_record_size: 1024 * 1024 });
|
|
52
68
|
const header = records.shift() ?? [];
|
|
53
|
-
requireThat(canonical(header) === canonical(f.columns.map(c => c.name)), 'condition', 'CSV headers do not match the declared columns.');
|
|
69
|
+
requireThat(canonical(csvColumnNames(header)) === canonical(f.columns.map(c => c.name)), 'condition', 'CSV headers do not match the declared columns.');
|
|
54
70
|
requireThat(records.length <= this.limits.maxRows, 'limit', 'Table exceeds the row reader budget.', { limit: 'maxRows' });
|
|
55
71
|
rows = records.map(values => Object.fromEntries(f.columns.map((c, i) => {
|
|
56
72
|
const value = values[i];
|
|
@@ -160,6 +176,6 @@ const tablesSkill = {
|
|
|
160
176
|
],
|
|
161
177
|
verify: { command: 'npm run test:tables', description: 'Declared types keep leading zeros and literal NA, joins retain nulls, namespaces and both input versions, and unkeyed tables never fabricate keys.' }
|
|
162
178
|
};
|
|
163
|
-
export const plugin = { entryVersion: '1.0', id: 'tables', package: '@rho-workspace/plugin-tables', version: '0.5.
|
|
179
|
+
export const plugin = { entryVersion: '1.0', id: 'tables', package: '@rho-workspace/plugin-tables', version: '0.5.2', requires: ['files'], capabilities: ['tables'], presentation,
|
|
164
180
|
operations: Object.entries(operationContracts).map(([method, contract]) => ({ method, presentation: operationPresentation[method], version: '2.0', ...structuredClone(contract), requires: [...contract.requires] })), skills: [{ ...tablesSkill, presentation: skillPresentation }],
|
|
165
181
|
components: [{ id: 'rho.table', version: '0.2.0', owner: 'rho-plugins.table', bindings: { data: 'table' }, ui: './ui', presentation: viewPresentation, propsSchema: { type: 'object', additionalProperties: false, properties: { title: { type: 'string', minLength: 1 } }, required: ['title'] } }] };
|
|
@@ -230,7 +230,7 @@ function BoundTable({ view, projectId, client, selectable = true, active, render
|
|
|
230
230
|
return _jsxs("div", { ref: root, style: { display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }, children: [_jsxs("div", { style: bar, role: "toolbar", "aria-label": "Table", children: [_jsx("span", { children: count }), selected > 0 && _jsxs("span", { children: ["\u00B7 ", selected, " selected"] }), selected > 0 && requestFeedback && _jsx("button", { style: control, onClick: () => { void selectionQueue.current.then(() => { if (selectionConfirmed.current)
|
|
231
231
|
requestFeedback();
|
|
232
232
|
else
|
|
233
|
-
setFailure('Confirm the selected rows before asking
|
|
233
|
+
setFailure('Confirm the selected rows before asking the Agent.'); }); }, children: "Ask Agent" }), filter && _jsxs("span", { style: { ...control, display: 'inline-flex', gap: 6, alignItems: 'center' }, children: [filter.column, " ", opLabel[filter.op], filter.op !== 'isNull' && ' ' + String(filter.value), _jsx("button", { "aria-label": "Remove filter", style: { border: 'none', background: 'none', cursor: 'pointer', padding: 0, color: 'inherit' }, onClick: () => applyFilter(null), children: "\u00D7" })] }), !filter && meta && !draft && _jsx("button", { style: control, onClick: () => setDraft({ column: meta.columns[0].name, op: 'eq', value: '' }), children: "Filter" }), draft && meta && _jsxs("form", { style: { display: 'flex', gap: 6, flexWrap: 'wrap', maxWidth: '100%' }, onSubmit: e => { e.preventDefault(); submitDraft(); }, children: [_jsx("select", { "aria-label": "Column", style: control, value: draft.column, onChange: e => setDraft({ ...draft, column: e.target.value, op: 'eq', value: '' }), children: meta.columns.map(c => _jsx("option", { children: c.name }, c.name)) }), _jsx("select", { "aria-label": "Condition", style: control, value: draft.op, onChange: e => setDraft({ ...draft, op: e.target.value }), children: Object.keys(opLabel).filter(op => !(['lt', 'gt'].includes(op) && meta.columns.find(c => c.name === draft.column)?.type === 'boolean')).map(op => _jsx("option", { value: op, children: opLabel[op] }, op)) }), draft.op !== 'isNull' && _jsx("input", { "aria-label": "Value", style: control, value: draft.value, onChange: e => setDraft({ ...draft, value: e.target.value }) }), _jsx("button", { style: control, type: "submit", children: "Apply" }), _jsx("button", { style: control, type: "button", onClick: () => setDraft(null), children: "Cancel" })] }), meta && !meta.entityKey && selectable && _jsx("span", { style: { color: 'var(--text-muted, #5f6873)' }, children: "This table has no row key; row selection is unavailable." })] }), failure && _jsxs("div", { role: "alert", style: { ...bar, color: 'var(--danger, #cf222e)' }, children: [failure, _jsx("button", { style: control, onClick: () => { setAttempt(n => n + 1); api.current?.purgeInfiniteCache(); }, children: "Try again" })] }), observationFailure && _jsx("div", { role: "status", style: bar, children: observationFailure }), _jsx("div", { style: { flex: 1, minHeight: 0, maskImage: more ? 'linear-gradient(to right, #000 calc(100% - 48px), transparent)' : undefined }, children: meta && _jsx(AgGridReact, { theme: theme, rowModelType: "infinite", datasource: datasource, cacheBlockSize: pageRows, maxBlocksInCache: 5, suppressMultiSort: true, maxConcurrentDatasourceRequests: 1, columnDefs: columnDefs,
|
|
234
234
|
// Columns take their contents' width, headers included, so a number is never cut to
|
|
235
235
|
// an ellipsis that hides its exponent; a wide table scrolls sideways instead.
|
|
236
236
|
defaultColDef: { resizable: true, minWidth: 64 }, suppressColumnVirtualisation: (meta?.columns.length ?? 0) <= 40, onFirstDataRendered: fitColumns, getRowId: meta?.entityKey && meta.namespace ? p => meta.namespace + ':' + String(p.data[meta.entityKey]) : undefined, rowSelection: meta?.entityKey && meta.namespace && selectable ? { mode: 'multiRow', checkboxes: true, headerCheckbox: false, enableClickSelection: true } : undefined, onModelUpdated: e => { if (meta.entityKey)
|
|
@@ -199,7 +199,7 @@ var EventV2Processor = class extends EventProcessor {
|
|
|
199
199
|
const headers = new Headers();
|
|
200
200
|
this.getCookies(event, headers);
|
|
201
201
|
if (event.headers) {
|
|
202
|
-
for (const [k, v] of Object.entries(event.headers)) if (v) headers.set(k, v);
|
|
202
|
+
for (const [k, v] of Object.entries(event.headers)) if (v) headers.set(k, sanitizeHeaderValue(v));
|
|
203
203
|
}
|
|
204
204
|
return headers;
|
|
205
205
|
}
|