@mgamadeus/ddd-agents 0.1.0-alpha.23 → 0.1.0-alpha.25
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/chat.js +71 -827
- package/dist/chat.js.map +1 -1
- package/dist/chunks/ConversationMessageInputComposerWidgets-BxyaFBA6.js +631 -0
- package/dist/chunks/ConversationMessageInputComposerWidgets-BxyaFBA6.js.map +1 -0
- package/dist/chunks/{WebSearchResultsConversationWidget-DRBQvycE.js → WebSearchResultsConversationWidget-as3p5bHw.js} +92 -85
- package/dist/chunks/WebSearchResultsConversationWidget-as3p5bHw.js.map +1 -0
- package/dist/chunks/{decide-canvas-open-DBWoWtAC.js → canvas-behaviours-AvzKSYyv.js} +3 -19
- package/dist/chunks/canvas-behaviours-AvzKSYyv.js.map +1 -0
- package/dist/chunks/canvas-reducers-Bfp_i7lc.js +88 -0
- package/dist/chunks/canvas-reducers-Bfp_i7lc.js.map +1 -0
- package/dist/chunks/{canvasBodies-DLjDkviP.js → canvasBodies-Cvu3l38p.js} +6 -3
- package/dist/chunks/{canvasBodies-DLjDkviP.js.map → canvasBodies-Cvu3l38p.js.map} +1 -1
- package/dist/chunks/canvasStorage-EvQsGdto.js +65 -0
- package/dist/chunks/canvasStorage-EvQsGdto.js.map +1 -0
- package/dist/chunks/contract-BxmMxYPF.js +96 -0
- package/dist/chunks/contract-BxmMxYPF.js.map +1 -0
- package/dist/chunks/conversation-BEF0aesf.js +31 -0
- package/dist/chunks/conversation-BEF0aesf.js.map +1 -0
- package/dist/chunks/{ConversationMarkdown-CJhAnuVa.js → widget-click-signal-sources-DulRkNvZ.js} +22 -77
- package/dist/chunks/widget-click-signal-sources-DulRkNvZ.js.map +1 -0
- package/dist/chunks/{widget-name-resolver-C8SfpiR5.js → widget-name-resolver-BGN3HpoG.js} +61 -30
- package/dist/chunks/widget-name-resolver-BGN3HpoG.js.map +1 -0
- package/dist/chunks/{withPollingChangeSubscription-BLDAWF-o.js → withPollingChangeSubscription-B16htQGd.js} +2 -2
- package/dist/chunks/{withPollingChangeSubscription-BLDAWF-o.js.map → withPollingChangeSubscription-B16htQGd.js.map} +1 -1
- package/dist/client-testing.js +28 -6
- package/dist/client-testing.js.map +1 -1
- package/dist/client.js +1 -1
- package/dist/contract.js +8 -27
- package/dist/contract.js.map +1 -1
- package/dist/image-render.js +8 -8
- package/dist/index.js +6 -5
- package/dist/kit.js +2 -2
- package/dist/preview.js +44 -16
- package/dist/preview.js.map +1 -1
- package/dist/renderer.js +62 -57
- package/dist/renderer.js.map +1 -1
- package/dist/styles.css +55 -22
- package/dist/types/client/testing/widget-hydration-stand-ins.d.ts +12 -3
- package/dist/types/entries/chat.d.ts +2 -0
- package/dist/types/entries/client-testing.d.ts +2 -0
- package/dist/types/entries/contract.d.ts +1 -0
- package/dist/types/entries/index.d.ts +1 -1
- package/dist/types/entries/preview.d.ts +1 -0
- package/dist/types/entries/renderer.d.ts +3 -1
- package/dist/types/kit/components/data/chart-model.d.ts +2 -2
- package/dist/types/kit/components/data/use-chart-palette.d.ts +2 -2
- package/dist/types/kit/components/map/use-google-maps-auth-failure.d.ts +18 -0
- package/dist/types/kit/entity-detail/entity-detail-bodies.d.ts +2 -0
- package/dist/types/renderer/canvas/entity-detail-instance.d.ts +5 -3
- package/dist/types/renderer/widget-hydration/use-widget-hydration.d.ts +5 -0
- package/dist/types/widgets/EntityMentionConversationWidget/stories/EntityMentionAskFlowStory.d.ts +8 -0
- package/dist/widgets.js +33 -33
- package/package.json +1 -1
- package/public-api.json +19 -1
- package/dist/chunks/ConversationMarkdown-CJhAnuVa.js.map +0 -1
- package/dist/chunks/WebSearchResultsConversationWidget-DRBQvycE.js.map +0 -1
- package/dist/chunks/decide-canvas-open-DBWoWtAC.js.map +0 -1
- package/dist/chunks/widget-name-resolver-C8SfpiR5.js.map +0 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { K as getFurtherCanvasBehaviours } from "./index-7Qc_3GbG.js";
|
|
3
|
-
import { s as sameCanvasIdentity } from "./WebSearchResultsConversationWidget-DRBQvycE.js";
|
|
4
3
|
const DEFAULT_CANVAS_WIDTH_PERCENT = 50;
|
|
5
4
|
const CANVAS_MIN_WIDTH_PX = 420;
|
|
6
5
|
const CHAT_COLUMN_MIN_WIDTH_PX = 360;
|
|
@@ -31,31 +30,16 @@ const getDeclaredCanvasIds = () => {
|
|
|
31
30
|
};
|
|
32
31
|
const isCanvasId = (value) => typeof value === "string" && canvasBehaviourOf(value) !== void 0;
|
|
33
32
|
const canvasWidthPercentOf = (behaviour) => behaviour.layout === "canvasFillsScreen" ? CANVAS_FILLS_SCREEN_WIDTH_PERCENT : behaviour.widthPercent ?? DEFAULT_CANVAS_WIDTH_PERCENT;
|
|
34
|
-
const ACCEPT = { kind: "accept" };
|
|
35
|
-
const ignore = (reason) => ({ kind: "ignore", reason });
|
|
36
|
-
const decideCanvasOpen = (behaviour, openCanvas, lastClosedCanvas, request) => {
|
|
37
|
-
if (behaviour === void 0) return ignore("unknownCanvas");
|
|
38
|
-
if (!request.hostHasCanvasSurface) return ignore("noSurface");
|
|
39
|
-
if (request.origin !== "deliveredInMessage") return ACCEPT;
|
|
40
|
-
if (!request.justDelivered) return ignore("replay");
|
|
41
|
-
if (openCanvas !== null && sameCanvasIdentity(openCanvas, request)) return ignore("alreadyOpen");
|
|
42
|
-
if (lastClosedCanvas !== null && lastClosedCanvas.closedByCustomer && sameCanvasIdentity(lastClosedCanvas, request)) {
|
|
43
|
-
return ignore("customerClosedIt");
|
|
44
|
-
}
|
|
45
|
-
if (!request.viewportViable && behaviour.narrowViewport === "none") return ignore("noTakeoverOnNarrowViewport");
|
|
46
|
-
return ACCEPT;
|
|
47
|
-
};
|
|
48
33
|
export {
|
|
49
34
|
CANVAS_BOXED_INSET_PX as C,
|
|
50
35
|
DEFAULT_CANVAS_WIDTH_PERCENT as D,
|
|
51
36
|
CANVAS_FILLS_SCREEN_WIDTH_PERCENT as a,
|
|
52
37
|
CANVAS_MIN_WIDTH_PX as b,
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
canvasBehaviourOf as c,
|
|
39
|
+
CHAT_COLUMN_MIN_WIDTH_PX as d,
|
|
55
40
|
canvasWidthPercentOf as e,
|
|
56
|
-
decideCanvasOpen as f,
|
|
57
41
|
getDeclaredCanvasIds as g,
|
|
58
42
|
isCanvasId as i,
|
|
59
43
|
packageCanvasBehaviours as p
|
|
60
44
|
};
|
|
61
|
-
//# sourceMappingURL=
|
|
45
|
+
//# sourceMappingURL=canvas-behaviours-AvzKSYyv.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-behaviours-AvzKSYyv.js","sources":["../../src/renderer/canvas/canvas-behaviours.ts"],"sourcesContent":["import { getFurtherCanvasBehaviours } from \"../../provider/environment\";\n\n/**\n * The CANVAS BEHAVIOURS — one declaration per canvas of how it lives on the surface: how long it stays (`lifetime`),\n * how it takes the screen (`layout`), what happens on a narrow viewport and whether its body starts flush at the top.\n *\n * Keyed on a CANVAS id, not a widget name: one body may have several openers, and one widget can be inline with a\n * detail canvas. The widget registry lists which canvases a widget may open (`opensCanvases`); the canvas BODIES (React\n * components) live in `canvasBodies.tsx`, imported only by the shell. The package declares the canvases of its own\n * widgets here (`packageCanvasBehaviours`); an app declares the canvases of its widgets on its agents environment\n * (`AgentsEnvironment.furtherCanvasBehaviours`), read at every lookup. This module imports nothing but the environment\n * module (which has no runtime imports), so the conversation slice and its specs can read it during store\n * initialisation (the widget registry is downstream of the store; a slice → registry import forms a temporal-dead-zone\n * cycle).\n */\n\n/** The canvases of the package's own widgets. */\nexport type PackageCanvasId = \"conversationSearchResults\" | \"webSearchResults\" | \"entityDetail\" | \"mapFullScreen\";\n\n/**\n * The id of a canvas: one of the package's (`PackageCanvasId`) or one an app declares\n * (`AgentsEnvironment.furtherCanvasBehaviours`). Append-only while a stored pointer (`canvasStorage.ts`) may still name\n * the id. A string, because the app's ids are not known to the package; `isCanvasId` checks a value against both.\n */\nexport type CanvasId = PackageCanvasId | (string & {});\n\n/**\n * `turn`: the canvas belongs to one question — the next SEND closes it for good (the card in the transcript is the\n * way back). `pinned`: a work surface for the whole conversation — it stays through sends and stays re-openable after\n * the customer closes it. Lifetime says nothing about a reload: the canvas open at reload time is restored for BOTH.\n */\nexport type CanvasLifetime = \"turn\" | \"pinned\";\n\n/**\n * `split`: the canvas is the right pane beside the chat. `canvasFillsScreen`: the canvas takes the screen and the chat\n * becomes the narrow companion (a full work surface such as an editor); the host's compact mode is overridden while it\n * is open.\n */\nexport type CanvasLayout = \"split\" | \"canvasFillsScreen\";\n\n/**\n * Below the viable split width (`CANVAS_VIABLE_MIN_WIDTH`): `none` — a live delivery does not take the phone screen\n * over (the card stays, the customer taps it), and the wide → narrow crossing closes an open canvas; `overlay` — the\n * canvas is the widget's only surface, so it opens as the full-screen overlay and survives the crossing.\n */\nexport type CanvasNarrowViewport = \"none\" | \"overlay\";\n\nexport interface CanvasBehaviour {\n readonly lifetime: CanvasLifetime;\n readonly layout: CanvasLayout;\n /** Width of the canvas pane as a percentage of the split. Only read for `layout: \"split\"`; default 50. */\n readonly widthPercent?: number;\n readonly narrowViewport: CanvasNarrowViewport;\n /**\n * The body starts flush at its top edge (a hero image, an iframe) — the pane keeps its gap under the close row, unless\n * `overlayCloseButton` floats the row over the body.\n */\n readonly fullBleed: boolean;\n /**\n * The close button floats over the body's top edge instead of reserving a row of its own, and the pane drops its top\n * and side padding — for a body whose top (a hero image) may sit under the button. The button is then muted\n * `inverted` + `onDark`, since it sits on the dark image. Default false: the row stays in flow so it never covers\n * content.\n */\n readonly overlayCloseButton?: boolean;\n}\n\n/** Width of the canvas pane (% of the split) when a behaviour declares none. */\nexport const DEFAULT_CANVAS_WIDTH_PERCENT = 50;\n\n/** The narrowest the customer can pull the canvas (px); pulling further and holding folds the tab into \"close\". */\nexport const CANVAS_MIN_WIDTH_PX = 420;\n\n/** What the chat column keeps beside the widest canvas (px). */\nexport const CHAT_COLUMN_MIN_WIDTH_PX = 360;\n\n/**\n * The boxed canvas card's inset from its pane edge (px) — the pull tab sits on the card's edge, not the pane's. The\n * pane's padding reads it as `--agents-canvas-inset` (`.splitViewPaneBoxed`), so this is the one place it is set.\n */\nexport const CANVAS_BOXED_INSET_PX = 8;\n\n/** Width of the canvas pane for `layout: \"canvasFillsScreen\"` — the chat keeps a narrow companion column. */\nexport const CANVAS_FILLS_SCREEN_WIDTH_PERCENT = 72;\n\n/** The canvases of the package's own widgets. */\nexport const packageCanvasBehaviours: Readonly<Record<PackageCanvasId, CanvasBehaviour>> = {\n conversationSearchResults: { lifetime: \"turn\", layout: \"split\", narrowViewport: \"none\", fullBleed: false },\n webSearchResults: { lifetime: \"turn\", layout: \"split\", narrowViewport: \"none\", fullBleed: false },\n // One entity's detail, opened from a ComposedView row (`openEntity`) — a click, never a live delivery.\n entityDetail: { lifetime: \"turn\", layout: \"split\", widthPercent: 40, narrowViewport: \"none\", fullBleed: false },\n // A ComposedView `Map` node, full screen — the widest split a turn canvas takes, the map flush.\n mapFullScreen: { lifetime: \"turn\", layout: \"split\", widthPercent: 65, narrowViewport: \"none\", fullBleed: true },\n};\n\nconst hasOwnCanvasBehaviour = (behaviours: Readonly<Record<string, CanvasBehaviour>>, canvasId: string): boolean =>\n Object.prototype.hasOwnProperty.call(behaviours, canvasId);\n\n/**\n * The behaviour of a canvas: the package's own, else the one the app declares\n * (`AgentsEnvironment.furtherCanvasBehaviours`); undefined for an id nobody declared (a stale stored pointer, a typo —\n * the opening rule ignores it). An app cannot replace a package canvas's behaviour.\n */\nexport const canvasBehaviourOf = (canvasId: CanvasId): CanvasBehaviour | undefined => {\n if (hasOwnCanvasBehaviour(packageCanvasBehaviours, canvasId)) {\n return packageCanvasBehaviours[canvasId as PackageCanvasId];\n }\n const furtherCanvasBehaviours = getFurtherCanvasBehaviours();\n return hasOwnCanvasBehaviour(furtherCanvasBehaviours, canvasId) ? furtherCanvasBehaviours[canvasId] : undefined;\n};\n\n/** Every declared canvas id: the package's own first, then the app's further ones. */\nexport const getDeclaredCanvasIds = (): readonly CanvasId[] => {\n const packageCanvasIds = Object.keys(packageCanvasBehaviours);\n const furtherCanvasIds = Object.keys(getFurtherCanvasBehaviours()).filter(\n (canvasId) => !packageCanvasIds.includes(canvasId),\n );\n return [...packageCanvasIds, ...furtherCanvasIds];\n};\n\n/** Type guard for a value read from untrusted input (`sessionStorage`, a URL): is it a declared canvas id? */\nexport const isCanvasId = (value: unknown): value is CanvasId =>\n typeof value === \"string\" && canvasBehaviourOf(value) !== undefined;\n\nexport const canvasWidthPercentOf = (behaviour: CanvasBehaviour): number =>\n behaviour.layout === \"canvasFillsScreen\"\n ? CANVAS_FILLS_SCREEN_WIDTH_PERCENT\n : (behaviour.widthPercent ?? DEFAULT_CANVAS_WIDTH_PERCENT);\n"],"names":[],"mappings":";;AAoEO,MAAM,+BAA+B;AAGrC,MAAM,sBAAsB;AAG5B,MAAM,2BAA2B;AAMjC,MAAM,wBAAwB;AAG9B,MAAM,oCAAoC;AAG1C,MAAM,0BAA8E;AAAA,EACzF,2BAA2B,EAAE,UAAU,QAAQ,QAAQ,SAAS,gBAAgB,QAAQ,WAAW,MAAA;AAAA,EACnG,kBAAkB,EAAE,UAAU,QAAQ,QAAQ,SAAS,gBAAgB,QAAQ,WAAW,MAAA;AAAA;AAAA,EAE1F,cAAc,EAAE,UAAU,QAAQ,QAAQ,SAAS,cAAc,IAAI,gBAAgB,QAAQ,WAAW,MAAA;AAAA;AAAA,EAExG,eAAe,EAAE,UAAU,QAAQ,QAAQ,SAAS,cAAc,IAAI,gBAAgB,QAAQ,WAAW,KAAA;AAC3G;AAEA,MAAM,wBAAwB,CAAC,YAAuD,aACpF,OAAO,UAAU,eAAe,KAAK,YAAY,QAAQ;AAOpD,MAAM,oBAAoB,CAAC,aAAoD;AACpF,MAAI,sBAAsB,yBAAyB,QAAQ,GAAG;AAC5D,WAAO,wBAAwB,QAA2B;AAAA,EAC5D;AACA,QAAM,0BAA0B,2BAAA;AAChC,SAAO,sBAAsB,yBAAyB,QAAQ,IAAI,wBAAwB,QAAQ,IAAI;AACxG;AAGO,MAAM,uBAAuB,MAA2B;AAC7D,QAAM,mBAAmB,OAAO,KAAK,uBAAuB;AAC5D,QAAM,mBAAmB,OAAO,KAAK,2BAAA,CAA4B,EAAE;AAAA,IACjE,CAAC,aAAa,CAAC,iBAAiB,SAAS,QAAQ;AAAA,EAAA;AAEnD,SAAO,CAAC,GAAG,kBAAkB,GAAG,gBAAgB;AAClD;AAGO,MAAM,aAAa,CAAC,UACzB,OAAO,UAAU,YAAY,kBAAkB,KAAK,MAAM;AAErD,MAAM,uBAAuB,CAAC,cACnC,UAAU,WAAW,sBACjB,oCACC,UAAU,gBAAgB;"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { c as canvasBehaviourOf } from "./canvas-behaviours-AvzKSYyv.js";
|
|
3
|
+
import { s as sameCanvasIdentity } from "./WebSearchResultsConversationWidget-as3p5bHw.js";
|
|
4
|
+
const ACCEPT = { kind: "accept" };
|
|
5
|
+
const ignore = (reason) => ({ kind: "ignore", reason });
|
|
6
|
+
const decideCanvasOpen = (behaviour, openCanvas, lastClosedCanvas, request) => {
|
|
7
|
+
if (behaviour === void 0) return ignore("unknownCanvas");
|
|
8
|
+
if (!request.hostHasCanvasSurface) return ignore("noSurface");
|
|
9
|
+
if (request.origin !== "deliveredInMessage") return ACCEPT;
|
|
10
|
+
if (!request.justDelivered) return ignore("replay");
|
|
11
|
+
if (openCanvas !== null && sameCanvasIdentity(openCanvas, request)) return ignore("alreadyOpen");
|
|
12
|
+
if (lastClosedCanvas !== null && lastClosedCanvas.closedByCustomer && sameCanvasIdentity(lastClosedCanvas, request)) {
|
|
13
|
+
return ignore("customerClosedIt");
|
|
14
|
+
}
|
|
15
|
+
if (!request.viewportViable && behaviour.narrowViewport === "none") return ignore("noTakeoverOnNarrowViewport");
|
|
16
|
+
return ACCEPT;
|
|
17
|
+
};
|
|
18
|
+
const toLastClosed = (canvas, closedByCustomer) => ({
|
|
19
|
+
canvasId: canvas.canvasId,
|
|
20
|
+
instanceId: canvas.instanceId,
|
|
21
|
+
anchor: canvas.anchor,
|
|
22
|
+
mountGeneration: canvas.mountGeneration,
|
|
23
|
+
closedByCustomer,
|
|
24
|
+
...canvas.createdEntityId !== void 0 ? { createdEntityId: canvas.createdEntityId } : {}
|
|
25
|
+
});
|
|
26
|
+
const nextMountGeneration = (state) => {
|
|
27
|
+
state.canvasMountCount += 1;
|
|
28
|
+
return state.canvasMountCount;
|
|
29
|
+
};
|
|
30
|
+
const applyOpenCanvas = (state, request) => {
|
|
31
|
+
const decision = decideCanvasOpen(canvasBehaviourOf(request.canvasId), state.canvas, state.lastClosedCanvas, request);
|
|
32
|
+
if (decision.kind === "ignore") return decision;
|
|
33
|
+
const previous = state.canvas;
|
|
34
|
+
const mountGeneration = nextMountGeneration(state);
|
|
35
|
+
const carriedCreatedEntityId = request.createdEntityId ?? (previous !== null && sameCanvasIdentity(previous, request) ? previous.createdEntityId : void 0) ?? (state.lastClosedCanvas !== null && sameCanvasIdentity(state.lastClosedCanvas, request) ? state.lastClosedCanvas.createdEntityId : void 0);
|
|
36
|
+
if (previous !== null && !sameCanvasIdentity(previous, request)) {
|
|
37
|
+
state.lastClosedCanvas = toLastClosed(previous, false);
|
|
38
|
+
} else if (state.lastClosedCanvas !== null && sameCanvasIdentity(state.lastClosedCanvas, request)) {
|
|
39
|
+
state.lastClosedCanvas = null;
|
|
40
|
+
}
|
|
41
|
+
state.canvas = {
|
|
42
|
+
canvasId: request.canvasId,
|
|
43
|
+
instanceId: request.instanceId,
|
|
44
|
+
anchor: request.anchor,
|
|
45
|
+
origin: request.origin,
|
|
46
|
+
mountGeneration,
|
|
47
|
+
...carriedCreatedEntityId !== void 0 ? { createdEntityId: carriedCreatedEntityId } : {}
|
|
48
|
+
};
|
|
49
|
+
return decision;
|
|
50
|
+
};
|
|
51
|
+
const applyCloseCanvas = (state, byCustomer) => {
|
|
52
|
+
if (state.canvas === null) return;
|
|
53
|
+
state.lastClosedCanvas = toLastClosed(state.canvas, byCustomer);
|
|
54
|
+
state.canvas = null;
|
|
55
|
+
};
|
|
56
|
+
const applyResetCanvasForNewTurn = (state) => {
|
|
57
|
+
if (state.canvas !== null && canvasBehaviourOf(state.canvas.canvasId)?.lifetime === "turn") {
|
|
58
|
+
state.canvas = null;
|
|
59
|
+
}
|
|
60
|
+
if (state.lastClosedCanvas !== null) {
|
|
61
|
+
if (canvasBehaviourOf(state.lastClosedCanvas.canvasId)?.lifetime === "turn") {
|
|
62
|
+
state.lastClosedCanvas = null;
|
|
63
|
+
} else if (state.lastClosedCanvas.closedByCustomer) {
|
|
64
|
+
state.lastClosedCanvas = { ...state.lastClosedCanvas, closedByCustomer: false };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const applyClearCanvas = (state) => {
|
|
69
|
+
state.canvas = null;
|
|
70
|
+
state.lastClosedCanvas = null;
|
|
71
|
+
};
|
|
72
|
+
const applySetCanvasCreatedEntityId = (state, created) => {
|
|
73
|
+
const canvas = state.canvas;
|
|
74
|
+
if (canvas === null || !sameCanvasIdentity(canvas, created) || canvas.mountGeneration !== created.mountGeneration) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
state.canvas = { ...canvas, createdEntityId: created.createdEntityId };
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
export {
|
|
81
|
+
applyClearCanvas as a,
|
|
82
|
+
applyCloseCanvas as b,
|
|
83
|
+
applyOpenCanvas as c,
|
|
84
|
+
applyResetCanvasForNewTurn as d,
|
|
85
|
+
applySetCanvasCreatedEntityId as e,
|
|
86
|
+
decideCanvasOpen as f
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=canvas-reducers-Bfp_i7lc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-reducers-Bfp_i7lc.js","sources":["../../src/renderer/canvas/decide-canvas-open.ts","../../src/renderer/canvas/canvas-reducers.ts"],"sourcesContent":["import type { CanvasBehaviour } from \"./canvas-behaviours\";\nimport { CanvasOpenRequest, CanvasState, LastClosedCanvas, sameCanvasIdentity } from \"./canvas-state\";\n\n/**\n * Why an open request is ignored. Every reason is a sentence the shell could log:\n * - `unknownCanvas` no behaviour is declared for the id (a stale stored pointer, a typo)\n * - `noSurface` the host renders no split (`disableSplitView`), so there is nowhere to open\n * - `replay` a delivery-origin request for a message that was NOT just delivered — history\n * replay, a reload, scroll-back; rejected BEFORE anything is read or written\n * - `alreadyOpen` a delivery-origin request for the identity that is already open (the agent re-emits\n * an editor widget after a rebuild) — the open canvas is kept, never remounted\n * - `customerClosedIt` the customer closed exactly this identity and nothing has cleared the veto yet\n * - `noTakeoverOnNarrowViewport` a live delivery on a narrow viewport of a canvas that declares no overlay — the\n * card stays, the customer taps it\n */\nexport type CanvasOpenIgnoreReason =\n | \"unknownCanvas\"\n | \"noSurface\"\n | \"replay\"\n | \"alreadyOpen\"\n | \"customerClosedIt\"\n | \"noTakeoverOnNarrowViewport\";\n\nexport type CanvasOpenDecision =\n | { readonly kind: \"accept\" }\n | { readonly kind: \"ignore\"; readonly reason: CanvasOpenIgnoreReason };\n\nconst ACCEPT: CanvasOpenDecision = { kind: \"accept\" };\n\nconst ignore = (reason: CanvasOpenIgnoreReason): CanvasOpenDecision => ({ kind: \"ignore\", reason });\n\n/**\n * THE opening rule — the one place that answers \"may this canvas open now?\". Pure: it reads the behaviour, the open\n * canvas, the last closed one and the request, and returns Accept or Ignore. It never mutates; the reducer\n * (`canvas-reducers.ts`) applies an Accept. There is no merge rung: the same identity opened again from a card click\n * is a fresh Accept (remount), and the refresh contract of a pinned work surface is a value the body derives from the\n * message list, not a reducer merge.\n *\n * A `cardClick`, an `openedByPage` and a `restoredAfterReload` request always open (the customer, the page or the\n * stored pointer decided); only `deliveredInMessage` is judged.\n */\nexport const decideCanvasOpen = (\n behaviour: CanvasBehaviour | undefined,\n openCanvas: CanvasState | null,\n lastClosedCanvas: LastClosedCanvas | null,\n request: CanvasOpenRequest,\n): CanvasOpenDecision => {\n if (behaviour === undefined) return ignore(\"unknownCanvas\");\n if (!request.hostHasCanvasSurface) return ignore(\"noSurface\");\n if (request.origin !== \"deliveredInMessage\") return ACCEPT;\n\n // Replay is rejected first: a replayed message must not touch the open canvas in any way.\n if (!request.justDelivered) return ignore(\"replay\");\n if (openCanvas !== null && sameCanvasIdentity(openCanvas, request)) return ignore(\"alreadyOpen\");\n if (lastClosedCanvas !== null && lastClosedCanvas.closedByCustomer && sameCanvasIdentity(lastClosedCanvas, request)) {\n return ignore(\"customerClosedIt\");\n }\n if (!request.viewportViable && behaviour.narrowViewport === \"none\") return ignore(\"noTakeoverOnNarrowViewport\");\n return ACCEPT;\n};\n","import { canvasBehaviourOf } from \"./canvas-behaviours\";\nimport { CanvasOpenRequest, CanvasState, LastClosedCanvas, sameCanvasIdentity } from \"./canvas-state\";\nimport { CanvasOpenDecision, decideCanvasOpen } from \"./decide-canvas-open\";\n\n/**\n * The canvas part of a chat slice partition — the two fields every transition reads and writes. Both chat doors carry\n * them (`ConversationState`); the shell is shared, so the state must have the same shape in both partitions.\n */\nexport interface CanvasSliceState {\n canvas: CanvasState | null;\n lastClosedCanvas: LastClosedCanvas | null;\n /**\n * How many canvases this partition has mounted so far — the source of `mountGeneration`. Never reset by a close,\n * a send or a clear (and exempt from `resetState`), so a late asynchronous write from a body that was replaced,\n * cleared or belonged to the previous conversation can never match a new canvas's generation.\n */\n canvasMountCount: number;\n}\n\n/**\n * The lifetime state machine as REDUCERS. Hand-written, not generated setters:\n * a generated setter is a blind overwrite of a snapshot the caller read a render earlier, and two opens in one commit\n * would both see the same stale state. Each function mutates the Immer draft it receives and returns the decision it\n * applied, so the slice can expose them as case reducers and a spec can assert every transition on a plain object.\n */\n\nconst toLastClosed = (canvas: CanvasState, closedByCustomer: boolean): LastClosedCanvas => ({\n canvasId: canvas.canvasId,\n instanceId: canvas.instanceId,\n anchor: canvas.anchor,\n mountGeneration: canvas.mountGeneration,\n closedByCustomer,\n ...(canvas.createdEntityId !== undefined ? { createdEntityId: canvas.createdEntityId } : {}),\n});\n\n/** Next render key: strictly increasing for the partition's lifetime, so no two mounts ever share a generation. */\nconst nextMountGeneration = (state: CanvasSliceState): number => {\n state.canvasMountCount += 1;\n return state.canvasMountCount;\n};\n\n/**\n * Open a canvas — the ONE way to open, for all four origins. On Accept: the replaced canvas (a different identity)\n * becomes `lastClosedCanvas`; a `lastClosedCanvas` of the accepted identity is consumed (its veto is cleared — the\n * customer, the page or the restore decided to have it back); the created entity survives a re-open of the same\n * identity so a restored editor keeps its draft.\n */\nexport const applyOpenCanvas = (state: CanvasSliceState, request: CanvasOpenRequest): CanvasOpenDecision => {\n const decision = decideCanvasOpen(canvasBehaviourOf(request.canvasId), state.canvas, state.lastClosedCanvas, request);\n if (decision.kind === \"ignore\") return decision;\n\n const previous = state.canvas;\n const mountGeneration = nextMountGeneration(state);\n const carriedCreatedEntityId =\n request.createdEntityId ??\n (previous !== null && sameCanvasIdentity(previous, request) ? previous.createdEntityId : undefined) ??\n (state.lastClosedCanvas !== null && sameCanvasIdentity(state.lastClosedCanvas, request)\n ? state.lastClosedCanvas.createdEntityId\n : undefined);\n\n if (previous !== null && !sameCanvasIdentity(previous, request)) {\n state.lastClosedCanvas = toLastClosed(previous, false);\n } else if (state.lastClosedCanvas !== null && sameCanvasIdentity(state.lastClosedCanvas, request)) {\n state.lastClosedCanvas = null;\n }\n state.canvas = {\n canvasId: request.canvasId,\n instanceId: request.instanceId,\n anchor: request.anchor,\n origin: request.origin,\n mountGeneration,\n ...(carriedCreatedEntityId !== undefined ? { createdEntityId: carriedCreatedEntityId } : {}),\n };\n return decision;\n};\n\n/**\n * Close the open canvas but keep it re-openable: it becomes `lastClosedCanvas`. `byCustomer` marks the customer's own\n * close (the canvas close control) — a live delivery of the same identity is then ignored until a send, a card click\n * or a page hand-over. A shell-driven close (compact mode, the side panel opening, a narrow crossing) passes false.\n */\nexport const applyCloseCanvas = (state: CanvasSliceState, byCustomer: boolean): void => {\n if (state.canvas === null) return;\n state.lastClosedCanvas = toLastClosed(state.canvas, byCustomer);\n state.canvas = null;\n};\n\n/**\n * The SEND boundary: a new user message starts a new turn. A `turn` canvas is over for good — it leaves the open slot\n * AND `lastClosedCanvas` (the card in the transcript is the way back). A `pinned` canvas stays open,\n * and a closed pinned one stays re-openable with its customer veto cleared (the send is a clearing edge).\n */\nexport const applyResetCanvasForNewTurn = (state: CanvasSliceState): void => {\n if (state.canvas !== null && canvasBehaviourOf(state.canvas.canvasId)?.lifetime === \"turn\") {\n state.canvas = null;\n }\n if (state.lastClosedCanvas !== null) {\n if (canvasBehaviourOf(state.lastClosedCanvas.canvasId)?.lifetime === \"turn\") {\n state.lastClosedCanvas = null;\n } else if (state.lastClosedCanvas.closedByCustomer) {\n state.lastClosedCanvas = { ...state.lastClosedCanvas, closedByCustomer: false };\n }\n }\n};\n\n/** Forget the canvas entirely — a conversation switch, `closeAgent`, the modal unmount. Nothing is re-openable. */\nexport const applyClearCanvas = (state: CanvasSliceState): void => {\n state.canvas = null;\n state.lastClosedCanvas = null;\n};\n\nexport interface CanvasCreatedEntity {\n readonly canvasId: CanvasState[\"canvasId\"];\n readonly instanceId: string;\n /** The generation the body was mounted with — a late write from a body that was replaced meanwhile is dropped. */\n readonly mountGeneration: number;\n readonly createdEntityId: number;\n}\n\n/** A body created its entity (the post editor's draft): remember the id on the canvas it was created for. */\nexport const applySetCanvasCreatedEntityId = (state: CanvasSliceState, created: CanvasCreatedEntity): boolean => {\n const canvas = state.canvas;\n if (canvas === null || !sameCanvasIdentity(canvas, created) || canvas.mountGeneration !== created.mountGeneration) {\n return false;\n }\n state.canvas = { ...canvas, createdEntityId: created.createdEntityId };\n return true;\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo, useCallback, useContext, createContext, useState } from "react";
|
|
4
|
-
import { p as payloadHashOfInstanceId,
|
|
5
|
-
import {
|
|
4
|
+
import { p as payloadHashOfInstanceId, E as useConversationWidgetContext, aq as parseConversationSearchResultsPayload, ar as WebSearchAnswer, as as WebSearchSourcesList, at as parseWebSearchResultsPayload, au as ENTITY_MENTION_WIDGET_NAME, av as buildEntityMentionFields, aw as normalizeEntityMentionFields, ax as entityMentionKey, ay as MAX_ENTITY_MENTIONS_PER_MESSAGE, az as toEntityMentionWireFields, F as useIsEmailWidgetRender, aA as ConversationWidgetButton } from "./WebSearchResultsConversationWidget-as3p5bHw.js";
|
|
5
|
+
import { m as findWidgetFenceByPayloadHash, n as findWidgetFencesInMarkdown, K as readEntityDetailInstanceId, L as readMapNodeKeyOfInstanceId, M as findMapNodeInMarkdown, D as readOpenEntityActionTarget, N as canvasInstanceIdForEntityDetail, O as mapStyles, P as MapPresentationContext, e as ComposedViewRenderer } from "./widget-name-resolver-BGN3HpoG.js";
|
|
6
6
|
import { q as Text, a as ConversationSearchResultsList } from "./index-7Qc_3GbG.js";
|
|
7
7
|
import { E as EntityDetailNameReportContext } from "./entity-detail-name-3A82gDAp.js";
|
|
8
8
|
const useAnchorWidgetPayload = (canvas, anchorMessageMarkdown, widgetName) => useMemo(() => {
|
|
@@ -177,6 +177,7 @@ const registerEntityDetailBodies = (definitions) => {
|
|
|
177
177
|
buildEntityDetailBodyRegistry([...Object.values(entityDetailBodies), ...definitions])
|
|
178
178
|
);
|
|
179
179
|
};
|
|
180
|
+
const getEntityDetailBody = (entityType) => Object.prototype.hasOwnProperty.call(entityDetailBodies, entityType) ? entityDetailBodies[entityType] : void 0;
|
|
180
181
|
const EntityDetailBodiesContext = createContext(entityDetailBodies);
|
|
181
182
|
const useEntityDetailBodies = () => useContext(EntityDetailBodiesContext);
|
|
182
183
|
const entityDetail = "agents_entityDetail_QEy9QH";
|
|
@@ -283,10 +284,12 @@ const registerCanvasBodies = (bodies) => {
|
|
|
283
284
|
Object.assign(canvasBodies, bodies);
|
|
284
285
|
};
|
|
285
286
|
export {
|
|
287
|
+
EntityMentionAskButton as E,
|
|
286
288
|
registerCanvasBodies as a,
|
|
287
289
|
registerEntityDetailBodies as b,
|
|
288
290
|
canvasBodies as c,
|
|
291
|
+
getEntityDetailBody as g,
|
|
289
292
|
readEntityDetailBodyDefinitions as r,
|
|
290
293
|
useAnchorWidgetPayload as u
|
|
291
294
|
};
|
|
292
|
-
//# sourceMappingURL=canvasBodies-
|
|
295
|
+
//# sourceMappingURL=canvasBodies-Cvu3l38p.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvasBodies-DLjDkviP.js","sources":["../../src/renderer/canvas/use-anchor-widget-payload.ts","../../src/widgets/ConversationSearchResultsConversationWidget/ConversationSearchResultsCanvas.tsx","../../src/widgets/ConversationSearchResultsConversationWidget/ConversationSearchResultsCanvasBody.tsx","../../src/widgets/WebSearchResultsConversationWidget/WebSearchResultsCanvas.tsx","../../src/widgets/WebSearchResultsConversationWidget/WebSearchResultsCanvasBody.tsx","../../src/widgets/EntityMentionConversationWidget/shared/use-entity-mention-composer.ts","../../src/widgets/EntityMentionConversationWidget/EntityMentionAskButton/EntityMentionAskButton.tsx","../../src/kit/entity-detail/entity-detail-bodies.ts","../../src/kit/entity-detail/EntityDetailCanvasBody.tsx","../../src/kit/map-full-screen/MapFullScreenCanvasBody.tsx","../../src/renderer/canvas/canvasBodies.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { CanvasState } from \"./canvas-state\";\nimport { payloadHashOfInstanceId } from \"./canvas-state\";\nimport { findWidgetFenceByPayloadHash, findWidgetFencesInMarkdown } from \"./find-widget-fences-in-markdown\";\n\n/**\n * The payload string of the widget a message-anchored canvas was opened from: the fence of `widgetName` in the anchor\n * message whose payload hash the canvas instance id carries (`canvasInstanceIdForMessagePayload`); when the instance\n * id carries no hash (one canvas per message), the first fence of that name. Undefined for a page anchor, a missing\n * anchor message, or a message that no longer carries the fence — the body then renders nothing.\n */\nexport const useAnchorWidgetPayload = (\n canvas: CanvasState,\n anchorMessageMarkdown: string | null,\n widgetName: string,\n): string | undefined =>\n useMemo(() => {\n if (canvas.anchor.kind !== \"message\" || anchorMessageMarkdown === null) return undefined;\n const payloadHash = payloadHashOfInstanceId(canvas.instanceId);\n if (payloadHash !== undefined) {\n return findWidgetFenceByPayloadHash(anchorMessageMarkdown, widgetName, payloadHash)?.payload;\n }\n return findWidgetFencesInMarkdown(anchorMessageMarkdown).find((fence) => fence.name === widgetName)?.payload;\n }, [canvas.anchor, canvas.instanceId, anchorMessageMarkdown, widgetName]);\n","import { ConversationSearchResultsList, Text } from \"../../provider/components\";\nimport React from \"react\";\nimport { parseConversationSearchResultsPayload } from \"./shared/conversation-search-results-widget-payload\";\n\ntype ConversationSearchResultsCanvasProps = {\n query: string;\n groups: ReturnType<typeof parseConversationSearchResultsPayload>[\"groups\"];\n onOpenConversation: (conversationId: number, messageId?: number) => void;\n};\n\n// Canvas body styles — the same as every list canvas body, so the full-screen panel behaves the same:\n// a full-height flex column whose inner area SCROLLS (scrollStyle: flex 1 + overflowY auto — without it the content\n// overflows the panel with no scrollbar), plus a horizontal inset (contentStyle) because the results list is built\n// for the narrow inline chat column where the bubble provides the gutter — in the canvas there is no bubble, so it would\n// otherwise render edge-to-edge.\nconst canvasStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n minHeight: 0,\n background: \"var(--agents-bg)\",\n};\nconst scrollStyle: React.CSSProperties = {\n flex: 1,\n minHeight: 0,\n overflowY: \"auto\",\n};\nconst contentStyle: React.CSSProperties = {\n padding: \"16px 24px 24px\",\n};\n\n/** The canvas body — a titled wrapper around the same grouped results, scrollable + inset like the other canvas widgets. */\nexport const ConversationSearchResultsCanvas = (props: ConversationSearchResultsCanvasProps) => (\n <div style={canvasStyle} data-testid=\"conversation-search-results-widget-canvas\">\n <div style={scrollStyle}>\n <div style={contentStyle} data-testid=\"conversation-search-results-widget-canvas-results\">\n <Text\n role=\"title\"\n replacements={{ query: props.query }}\n testId=\"conversation-search-results-widget-canvas-title\"\n >\n {'Search results for \"%query%\"'}\n </Text>\n <ConversationSearchResultsList groups={props.groups} query={props.query} onOpenConversation={props.onOpenConversation} />\n </div>\n </div>\n </div>\n);\n","import React, { useMemo } from \"react\";\nimport type { CanvasBodyProps } from \"../../renderer/canvas/canvas-body-props\";\nimport { useConversationWidgetContext } from \"../../renderer/ConversationWidgetCallbacksContext\";\nimport { useAnchorWidgetPayload } from \"../../renderer/canvas/use-anchor-widget-payload\";\nimport { parseConversationSearchResultsPayload } from \"./shared/conversation-search-results-widget-payload\";\nimport { ConversationSearchResultsCanvas } from \"./ConversationSearchResultsCanvas\";\n\n/**\n * The `conversationSearchResults` CANVAS BODY (`canvasBodies.tsx`): re-reads the `ConversationSearchResults` fence of\n * the anchor message — the same payload the inline widget rendered from — and shows the grouped results. Nothing was\n * stored for it but the pointer. Renders nothing when the anchor message no longer carries the fence.\n */\nexport const ConversationSearchResultsCanvasBody = ({ canvas, anchorMessageMarkdown }: CanvasBodyProps) => {\n const { onOpenConversation } = useConversationWidgetContext();\n const payload = useAnchorWidgetPayload(canvas, anchorMessageMarkdown, \"ConversationSearchResults\");\n const parsed = useMemo(\n () => (payload === undefined ? null : parseConversationSearchResultsPayload(payload)),\n [payload],\n );\n if (parsed === null || parsed.groups.length === 0) return null;\n return (\n <ConversationSearchResultsCanvas\n query={parsed.query}\n groups={parsed.groups}\n onOpenConversation={(conversationId, messageId) => onOpenConversation?.(conversationId, messageId)}\n />\n );\n};\n","import { Text } from \"../../provider/components\";\nimport React from \"react\";\nimport { WebSearchResultSource } from \"./shared/web-search-results-widget-payload\";\nimport { WebSearchAnswer } from \"./WebSearchAnswer\";\nimport { WebSearchSourcesList } from \"./WebSearchSourcesList\";\n\ntype WebSearchResultsCanvasProps = {\n query: string;\n answer: string;\n sources: WebSearchResultSource[];\n provider?: string;\n};\n\n// Canvas body styles — mirror the ConversationSearchResults / listReviews drawer widgets: a full-height flex column\n// whose inner area SCROLLS (flex 1 + overflowY auto) with a horizontal inset (there is no chat bubble gutter in the canvas).\nconst canvasStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n minHeight: 0,\n background: \"var(--agents-bg)\",\n};\nconst scrollStyle: React.CSSProperties = { flex: 1, minHeight: 0, overflowY: \"auto\" };\nconst contentStyle: React.CSSProperties = { padding: \"16px 24px 24px\" };\n\n/** The canvas body — the full answer + sources, scrollable + inset like the other canvas widgets. */\nexport const WebSearchResultsCanvas = (props: WebSearchResultsCanvasProps) => (\n <div style={canvasStyle} data-testid=\"web-search-results-widget-canvas\">\n <div style={scrollStyle}>\n <div style={contentStyle}>\n {props.query ? (\n <Text\n role=\"title\"\n replacements={{ query: props.query }}\n testId=\"web-search-results-widget-canvas-title\"\n >\n {'\"%query%\"'}\n </Text>\n ) : (\n <Text role=\"title\" testId=\"web-search-results-widget-canvas-title\">\n Web search\n </Text>\n )}\n <div style={{ marginTop: 12 }}>\n <WebSearchAnswer answer={props.answer} />\n </div>\n <WebSearchSourcesList\n sources={props.sources}\n provider={props.provider}\n testId=\"web-search-results-widget-canvas\"\n />\n </div>\n </div>\n </div>\n);\n","import React, { useMemo } from \"react\";\nimport type { CanvasBodyProps } from \"../../renderer/canvas/canvas-body-props\";\nimport { useAnchorWidgetPayload } from \"../../renderer/canvas/use-anchor-widget-payload\";\nimport { parseWebSearchResultsPayload } from \"./shared/web-search-results-widget-payload\";\nimport { WebSearchResultsCanvas } from \"./WebSearchResultsCanvas\";\n\n/**\n * The `webSearchResults` CANVAS BODY (`canvasBodies.tsx`): re-reads the `WebSearchResults` fence of the anchor message\n * — the answer and its cited sources the inline widget rendered from — and shows them in the canvas. Renders nothing\n * when the anchor message no longer carries the fence.\n */\nexport const WebSearchResultsCanvasBody = ({ canvas, anchorMessageMarkdown }: CanvasBodyProps) => {\n const payload = useAnchorWidgetPayload(canvas, anchorMessageMarkdown, \"WebSearchResults\");\n const parsed = useMemo(() => (payload === undefined ? null : parseWebSearchResultsPayload(payload)), [payload]);\n if (parsed === null || (parsed.answer.trim() === \"\" && parsed.sources.length === 0)) return null;\n return (\n <WebSearchResultsCanvas\n query={parsed.query}\n answer={parsed.answer}\n sources={parsed.sources}\n provider={parsed.provider}\n />\n );\n};\n","import { useCallback } from \"react\";\nimport { useConversationWidgetContext } from \"../../../renderer/ConversationWidgetCallbacksContext\";\nimport {\n buildEntityMentionFields,\n ENTITY_MENTION_WIDGET_NAME,\n entityMentionKey,\n EntityMentionTarget,\n MAX_ENTITY_MENTIONS_PER_MESSAGE,\n normalizeEntityMentionFields,\n toEntityMentionWireFields,\n} from \"./entity-mention\";\n\nexport interface UseEntityMentionComposerResult {\n /** Whether this entity is already mentioned in the composer. */\n isMentioned: (target: EntityMentionTarget) => boolean;\n /**\n * Puts the entity into the composer as an `EntityMention` chip — once per entity (a second click is a no-op); past\n * {@link MAX_ENTITY_MENTIONS_PER_MESSAGE} the new entity takes the place of the oldest mention (one write: a remove\n * and an add in one event would both read the list before either). Sends nothing: the owner writes the question.\n * Returns false for a target that names no entity.\n */\n mentionEntity: (target: EntityMentionTarget) => boolean;\n}\n\n/**\n * The opener side of the `EntityMention` composer widget: what the detail panel's \"Ask about this\"\n * button calls. Reads and writes the composer widgets of the chat's widget context; the composer strip renders them.\n */\nexport const useEntityMentionComposer = (): UseEntityMentionComposerResult => {\n const { composerWidgets, addComposerWidget, replaceComposerWidgetFields } = useConversationWidgetContext();\n\n const findMentionWidgets = useCallback(\n () => composerWidgets.filter((composerWidget) => composerWidget.widgetName === ENTITY_MENTION_WIDGET_NAME),\n [composerWidgets],\n );\n\n const isMentioned = useCallback(\n (target: EntityMentionTarget) => {\n const mention = buildEntityMentionFields(target);\n if (mention === null) return false;\n return findMentionWidgets().some((composerWidget) => {\n const heldMention = normalizeEntityMentionFields(composerWidget.fields);\n return heldMention !== null && entityMentionKey(heldMention) === entityMentionKey(mention);\n });\n },\n [findMentionWidgets],\n );\n\n const mentionEntity = useCallback(\n (target: EntityMentionTarget) => {\n const mention = buildEntityMentionFields(target);\n if (mention === null) return false;\n if (isMentioned(target)) return true;\n const mentionWidgets = findMentionWidgets();\n if (mentionWidgets.length >= MAX_ENTITY_MENTIONS_PER_MESSAGE) {\n replaceComposerWidgetFields(mentionWidgets[0].composerWidgetId, toEntityMentionWireFields(mention));\n } else {\n addComposerWidget(ENTITY_MENTION_WIDGET_NAME, toEntityMentionWireFields(mention));\n }\n return true;\n },\n [isMentioned, findMentionWidgets, replaceComposerWidgetFields, addComposerWidget],\n );\n\n return { isMentioned, mentionEntity };\n};\n","import { useIsEmailWidgetRender } from \"../../../renderer/ConversationWidgetRenderContext\";\nimport { ConversationWidgetButton } from \"../../_shared/ConversationWidgetButton\";\nimport type { EntityMentionTarget } from \"../shared/entity-mention\";\nimport { useEntityMentionComposer } from \"../shared/use-entity-mention-composer\";\n\nexport interface EntityMentionAskButtonProps extends EntityMentionTarget {\n testId?: string;\n}\n\n/**\n * The detail panel's \"Ask about this\" button (ChatGPT's \"Anschlussfrage stellen\"):\n * puts the entity into the chat input as an `EntityMention` chip and sends NOTHING — the owner writes the question,\n * the chip rides in that message as a fence, and the backend reads which entity the question is about. Not a\n * next-step CTA: it posts no prompt. A second click on the same entity changes nothing; the\n * label turns into \"Added to your message\". Mounted by the `entityDetail` canvas (the shell or a domain body) with\n * the entity the panel shows; renders nothing in the e-mail capture or for a target that names no entity.\n */\nexport const EntityMentionAskButton = (props: EntityMentionAskButtonProps) => {\n const { entityType, entityId, label, agentSpaceKey, testId = \"entity-mention-ask\" } = props;\n const isEmailRender = useIsEmailWidgetRender();\n const { isMentioned, mentionEntity } = useEntityMentionComposer();\n const target = { entityType, entityId, label, agentSpaceKey };\n if (isEmailRender) return null;\n const isAlreadyMentioned = isMentioned(target);\n return (\n <ConversationWidgetButton\n variant=\"primary\"\n size=\"medium\"\n icon={isAlreadyMentioned ? \"check\" : \"message\"}\n disabled={isAlreadyMentioned}\n onClick={() => mentionEntity(target)}\n testId={testId}\n >\n {isAlreadyMentioned ? \"Added to your message\" : \"Ask about this\"}\n </ConversationWidgetButton>\n );\n};\n","import type React from \"react\";\nimport { createContext, useContext } from \"react\";\n\n/**\n * The bodies of the `entityDetail` canvas, one per entity type: every `*.entity-detail-body.tsx` below\n * `widgets/` that exports `entityDetailBody` registers itself by folder glob — an entity type is added with\n * the backend `<Type>EntityReferenceResolver` and this one file, and edits no list. A second body for one entity type\n * is a build error (thrown at module load).\n */\n\nexport interface EntityDetailBodyProps {\n entityType: string;\n /** The id as the backend resolved it (a row id or an external id). */\n entityId: string;\n /** The key of the agent space the chat is scoped to, for a body whose read endpoint is agent-space-scoped. */\n agentSpaceKey?: number;\n}\n\nexport interface EntityDetailBodyDefinition {\n /** The backend resolver's `ENTITY_TYPE`. */\n entityType: string;\n /** Self-hydrates the entity by id through its own read endpoint (the endpoint's entity rights apply). */\n component: React.ComponentType<EntityDetailBodyProps>;\n}\n\nconst bodyModules = import.meta.glob<{ entityDetailBody?: EntityDetailBodyDefinition }>(\n [\"../../widgets/**/*.entity-detail-body.tsx\", \"../**/*.entity-detail-body.tsx\"],\n { eager: true },\n);\n\nexport const buildEntityDetailBodyRegistry = (\n definitions: EntityDetailBodyDefinition[],\n): Readonly<Record<string, EntityDetailBodyDefinition>> => {\n const registry: Record<string, EntityDetailBodyDefinition> = {};\n for (const definition of definitions) {\n if (Object.prototype.hasOwnProperty.call(registry, definition.entityType)) {\n throw new Error(`Two entity detail bodies register the entity type \"${definition.entityType}\".`);\n }\n registry[definition.entityType] = definition;\n }\n return registry;\n};\n\n/** The body definitions a set of `*.entity-detail-body.tsx` modules export (the package's or an app's glob). */\nexport const readEntityDetailBodyDefinitions = (\n modules: Record<string, { entityDetailBody?: EntityDetailBodyDefinition }>,\n): EntityDetailBodyDefinition[] =>\n Object.values(modules)\n .map((bodyModule) => bodyModule.entityDetailBody)\n .filter((definition): definition is EntityDetailBodyDefinition => definition !== undefined);\n\n/** Entity type → body: the package's folder, then what an app registers (`registerEntityDetailBodies`). */\nexport const entityDetailBodies: Record<string, EntityDetailBodyDefinition> = {\n ...buildEntityDetailBodyRegistry(readEntityDetailBodyDefinitions(bodyModules)),\n};\n\n/** Adds an app's entity detail bodies. Runs outside React, before the first render; an entity type registers once. */\nexport const registerEntityDetailBodies = (definitions: readonly EntityDetailBodyDefinition[]): void => {\n Object.assign(\n entityDetailBodies,\n buildEntityDetailBodyRegistry([...Object.values(entityDetailBodies), ...definitions]),\n );\n};\n\n/** The registry the canvas body uses — the folder's by default; a spec or a story may provide stubs. */\nexport const EntityDetailBodiesContext =\n createContext<Readonly<Record<string, EntityDetailBodyDefinition>>>(entityDetailBodies);\n\nexport const useEntityDetailBodies = () => useContext(EntityDetailBodiesContext);\n","import { Text } from \"../../provider/components\";\nimport { useCallback, useState } from \"react\";\nimport type { CanvasBodyProps } from \"../../renderer/canvas/canvas-body-props\";\nimport { readEntityDetailInstanceId } from \"../../renderer/canvas/entity-detail-instance\";\nimport { EntityMentionAskButton } from \"../../widgets/EntityMentionConversationWidget/EntityMentionAskButton/EntityMentionAskButton\";\nimport { useEntityDetailBodies } from \"./entity-detail-bodies\";\nimport { EntityDetailNameReportContext } from \"./entity-detail-name\";\nimport styles from \"./EntityDetailCanvasBody.module.scss\";\n\n/**\n * The `entityDetail` CANVAS BODY (`canvasBodies.tsx`): the shell around one entity's detail. It reads\n * WHICH entity from the canvas instance id (`entityDetail:<entityType>:<id>`) and mounts the body registered for that\n * entity type; the body self-hydrates by id. An entity type without a body renders a short notice instead of nothing,\n * so a click never lands on an empty pane. \"Ask about this\" stays at the pane's foot while the detail scrolls and\n * names the entity in its chip once the body reported the name (`useReportEntityDetailName`).\n */\nexport const EntityDetailCanvasBody = ({ canvas, agentSpaceKey }: CanvasBodyProps) => {\n const bodies = useEntityDetailBodies();\n const [reportedEntity, setReportedEntity] = useState<{ instanceId: string; name: string | undefined }>();\n const reportEntityName = useCallback(\n (entityName: string | undefined) => setReportedEntity({ instanceId: canvas.instanceId, name: entityName }),\n [canvas.instanceId],\n );\n const target = readEntityDetailInstanceId(canvas.instanceId);\n if (target === undefined) return null;\n const definition = Object.prototype.hasOwnProperty.call(bodies, target.entityType)\n ? bodies[target.entityType]\n : undefined;\n if (!definition) {\n return (\n <div className={styles.entityDetail} data-testid=\"entity-detail-canvas\">\n <Text role=\"help\" color=\"var(--agents-fg-light)\" testId=\"entity-detail-canvas-unsupported\">\n {\"This detail cannot be shown here yet.\"}\n </Text>\n </div>\n );\n }\n const Body = definition.component;\n // A name reported for the previous entity never labels the next one's chip.\n const entityName = reportedEntity?.instanceId === canvas.instanceId ? reportedEntity.name : undefined;\n return (\n <div className={styles.entityDetail} data-testid=\"entity-detail-canvas\">\n <EntityDetailNameReportContext.Provider value={reportEntityName}>\n <Body entityType={target.entityType} entityId={target.entityId} agentSpaceKey={agentSpaceKey} />\n </EntityDetailNameReportContext.Provider>\n <div className={styles.entityDetailFooter}>\n <EntityMentionAskButton\n entityType={target.entityType}\n entityId={target.entityId}\n label={entityName}\n agentSpaceKey={agentSpaceKey}\n />\n </div>\n </div>\n );\n};\n","import { Text } from \"../../provider/components\";\nimport { useCallback, useMemo } from \"react\";\nimport type { CanvasBodyProps } from \"../../renderer/canvas/canvas-body-props\";\nimport { useConversationWidgetContext } from \"../../renderer/ConversationWidgetCallbacksContext\";\nimport {\n canvasInstanceIdForEntityDetail,\n readOpenEntityActionTarget,\n} from \"../../renderer/canvas/entity-detail-instance\";\nimport type {\n ComposedViewAction,\n ComposedViewTree,\n} from \"../../renderer/widget-tree/composed-view-tree\";\nimport type { ExpressionValue } from \"../../renderer/widget-tree/expression/evaluate-expression\";\nimport { ComposedViewRenderer } from \"../ComposedViewRenderer\";\nimport { findMapNodeInMarkdown, readMapNodeKeyOfInstanceId } from \"../components/map/map-full-screen-instance\";\nimport { MapPresentationContext } from \"../components/map/map-presentation-context\";\nimport mapStyles from \"../components/map/Map.module.scss\";\n\n/** The body reports nothing of its own: the inline block already reported this map to the model. */\nconst ignoreRenderReport = () => {};\n\n/**\n * The `mapFullScreen` CANVAS BODY (`canvasBodies.tsx`): one `Map` node of the anchor\n * message, full screen — the map filling the pane beside the list of its points. It finds the node through the\n * message's `ComposedView` fences and the node key its instance id carries (`map-full-screen-instance.ts`), then\n * renders it through the same `ComposedViewRenderer` with the node's own tree, so its bindings (`$data`, `$from`,\n * `$source`, `$computed`, `$state` at its initial value) and its actions resolve exactly as inline; the\n * `MapPresentationContext` switches the node to its full-screen layout. A point's `openEntity` action opens the\n * `entityDetail` canvas on the same anchor message (it replaces this canvas, as a click on an inline point would).\n */\nexport const MapFullScreenCanvasBody = (props: CanvasBodyProps) => {\n const { canvas, anchorMessageMarkdown, agentSpaceKey } = props;\n const { openCanvas } = useConversationWidgetContext();\n const mapNodeKey = readMapNodeKeyOfInstanceId(canvas.instanceId);\n\n const mapTree = useMemo<ComposedViewTree | undefined>(() => {\n if (anchorMessageMarkdown === null || mapNodeKey === undefined) return undefined;\n const mapNodeInComposedView = findMapNodeInMarkdown(anchorMessageMarkdown, mapNodeKey);\n if (!mapNodeInComposedView) return undefined;\n // The map alone, with its tree's state, data, computed values and actions; the tree's findings were the inline\n // block's to report.\n return { ...mapNodeInComposedView.tree, title: null, view: mapNodeInComposedView.mapNode, findings: [] };\n }, [anchorMessageMarkdown, mapNodeKey]);\n\n const anchor = canvas.anchor;\n const onOpenEntity = useCallback(\n (action: ComposedViewAction, currentRow?: ExpressionValue) => {\n const target = readOpenEntityActionTarget(action, currentRow);\n if (target === undefined || anchor.kind !== \"message\") return;\n openCanvas({\n canvasId: \"entityDetail\",\n instanceId: canvasInstanceIdForEntityDetail(target),\n anchor,\n origin: \"cardClick\",\n justDelivered: false,\n });\n },\n [openCanvas, anchor],\n );\n\n if (!mapTree) {\n return (\n <div\n className={mapStyles.fullScreenNotice}\n data-canvas-instance={canvas.instanceId}\n data-testid=\"map-full-screen-canvas-unavailable\"\n >\n <Text role=\"help\" color=\"var(--agents-fg-light)\">\n {\"This map is no longer available.\"}\n </Text>\n </div>\n );\n }\n return (\n <div\n className={mapStyles.fullScreenBody}\n data-canvas-instance={canvas.instanceId}\n data-testid=\"map-full-screen-canvas\"\n >\n <MapPresentationContext.Provider value=\"fullScreen\">\n <ComposedViewRenderer\n tree={mapTree}\n interactive\n isEmailRender={false}\n messageId={anchor.kind === \"message\" ? anchor.messageId : undefined}\n agentSpaceKey={agentSpaceKey}\n onReport={ignoreRenderReport}\n onOpenEntity={onOpenEntity}\n testId=\"map-full-screen-canvas-view\"\n />\n </MapPresentationContext.Provider>\n </div>\n );\n};\n","import type React from \"react\";\nimport type { CanvasId } from \"./canvas-behaviours\";\nimport type { CanvasBodyProps } from \"./canvas-body-props\";\nimport { ConversationSearchResultsCanvasBody } from \"../../widgets/ConversationSearchResultsConversationWidget/ConversationSearchResultsCanvasBody\";\nimport { WebSearchResultsCanvasBody } from \"../../widgets/WebSearchResultsConversationWidget/WebSearchResultsCanvasBody\";\nimport { EntityDetailCanvasBody } from \"../../kit/entity-detail/EntityDetailCanvasBody\";\nimport { MapFullScreenCanvasBody } from \"../../kit/map-full-screen/MapFullScreenCanvasBody\";\n\nexport type { CanvasBodyProps } from \"./canvas-body-props\";\nexport { canvasFeatureFlagOf } from \"./canvas-feature-flags\";\n\n/**\n * THE CANVAS BODIES — canvas id → the React component the shell mounts in the canvas pane. The one module that knows\n * both the canvas ids and React components, and it is imported ONLY by `CanvasPane` (the shell), never by\n * the slice, the behaviours or a widget — that is the import layering that keeps the store-init temporal-dead-zone\n * cycle away. The bodies themselves are small: a body that hosts a heavy page (an app's list page or editor)\n * lazy-loads that page, exactly as its widget did. Every canvas has a body; nothing\n * else hands the pane a rendered node.\n *\n * The package's own bodies are listed here; an app adds the bodies of its canvases with `registerCanvasBodies` before\n * the first render. The object is filled in place, so the shell reads every registration.\n */\nexport const canvasBodies = {\n conversationSearchResults: ConversationSearchResultsCanvasBody,\n webSearchResults: WebSearchResultsCanvasBody,\n entityDetail: EntityDetailCanvasBody,\n mapFullScreen: MapFullScreenCanvasBody,\n} as Record<CanvasId, React.ComponentType<CanvasBodyProps>>;\n\n/** Adds an app's canvas bodies (canvas id → body). Runs outside React, before the first render. */\nexport const registerCanvasBodies = (\n bodies: Readonly<Partial<Record<CanvasId, React.ComponentType<CanvasBodyProps>>>>,\n): void => {\n Object.assign(canvasBodies, bodies);\n};\n"],"names":["canvasStyle","scrollStyle","contentStyle","entityName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"canvasBodies-Cvu3l38p.js","sources":["../../src/renderer/canvas/use-anchor-widget-payload.ts","../../src/widgets/ConversationSearchResultsConversationWidget/ConversationSearchResultsCanvas.tsx","../../src/widgets/ConversationSearchResultsConversationWidget/ConversationSearchResultsCanvasBody.tsx","../../src/widgets/WebSearchResultsConversationWidget/WebSearchResultsCanvas.tsx","../../src/widgets/WebSearchResultsConversationWidget/WebSearchResultsCanvasBody.tsx","../../src/widgets/EntityMentionConversationWidget/shared/use-entity-mention-composer.ts","../../src/widgets/EntityMentionConversationWidget/EntityMentionAskButton/EntityMentionAskButton.tsx","../../src/kit/entity-detail/entity-detail-bodies.ts","../../src/kit/entity-detail/EntityDetailCanvasBody.tsx","../../src/kit/map-full-screen/MapFullScreenCanvasBody.tsx","../../src/renderer/canvas/canvasBodies.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { CanvasState } from \"./canvas-state\";\nimport { payloadHashOfInstanceId } from \"./canvas-state\";\nimport { findWidgetFenceByPayloadHash, findWidgetFencesInMarkdown } from \"./find-widget-fences-in-markdown\";\n\n/**\n * The payload string of the widget a message-anchored canvas was opened from: the fence of `widgetName` in the anchor\n * message whose payload hash the canvas instance id carries (`canvasInstanceIdForMessagePayload`); when the instance\n * id carries no hash (one canvas per message), the first fence of that name. Undefined for a page anchor, a missing\n * anchor message, or a message that no longer carries the fence — the body then renders nothing.\n */\nexport const useAnchorWidgetPayload = (\n canvas: CanvasState,\n anchorMessageMarkdown: string | null,\n widgetName: string,\n): string | undefined =>\n useMemo(() => {\n if (canvas.anchor.kind !== \"message\" || anchorMessageMarkdown === null) return undefined;\n const payloadHash = payloadHashOfInstanceId(canvas.instanceId);\n if (payloadHash !== undefined) {\n return findWidgetFenceByPayloadHash(anchorMessageMarkdown, widgetName, payloadHash)?.payload;\n }\n return findWidgetFencesInMarkdown(anchorMessageMarkdown).find((fence) => fence.name === widgetName)?.payload;\n }, [canvas.anchor, canvas.instanceId, anchorMessageMarkdown, widgetName]);\n","import { ConversationSearchResultsList, Text } from \"../../provider/components\";\nimport React from \"react\";\nimport { parseConversationSearchResultsPayload } from \"./shared/conversation-search-results-widget-payload\";\n\ntype ConversationSearchResultsCanvasProps = {\n query: string;\n groups: ReturnType<typeof parseConversationSearchResultsPayload>[\"groups\"];\n onOpenConversation: (conversationId: number, messageId?: number) => void;\n};\n\n// Canvas body styles — the same as every list canvas body, so the full-screen panel behaves the same:\n// a full-height flex column whose inner area SCROLLS (scrollStyle: flex 1 + overflowY auto — without it the content\n// overflows the panel with no scrollbar), plus a horizontal inset (contentStyle) because the results list is built\n// for the narrow inline chat column where the bubble provides the gutter — in the canvas there is no bubble, so it would\n// otherwise render edge-to-edge.\nconst canvasStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n minHeight: 0,\n background: \"var(--agents-bg)\",\n};\nconst scrollStyle: React.CSSProperties = {\n flex: 1,\n minHeight: 0,\n overflowY: \"auto\",\n};\nconst contentStyle: React.CSSProperties = {\n padding: \"16px 24px 24px\",\n};\n\n/** The canvas body — a titled wrapper around the same grouped results, scrollable + inset like the other canvas widgets. */\nexport const ConversationSearchResultsCanvas = (props: ConversationSearchResultsCanvasProps) => (\n <div style={canvasStyle} data-testid=\"conversation-search-results-widget-canvas\">\n <div style={scrollStyle}>\n <div style={contentStyle} data-testid=\"conversation-search-results-widget-canvas-results\">\n <Text\n role=\"title\"\n replacements={{ query: props.query }}\n testId=\"conversation-search-results-widget-canvas-title\"\n >\n {'Search results for \"%query%\"'}\n </Text>\n <ConversationSearchResultsList groups={props.groups} query={props.query} onOpenConversation={props.onOpenConversation} />\n </div>\n </div>\n </div>\n);\n","import React, { useMemo } from \"react\";\nimport type { CanvasBodyProps } from \"../../renderer/canvas/canvas-body-props\";\nimport { useConversationWidgetContext } from \"../../renderer/ConversationWidgetCallbacksContext\";\nimport { useAnchorWidgetPayload } from \"../../renderer/canvas/use-anchor-widget-payload\";\nimport { parseConversationSearchResultsPayload } from \"./shared/conversation-search-results-widget-payload\";\nimport { ConversationSearchResultsCanvas } from \"./ConversationSearchResultsCanvas\";\n\n/**\n * The `conversationSearchResults` CANVAS BODY (`canvasBodies.tsx`): re-reads the `ConversationSearchResults` fence of\n * the anchor message — the same payload the inline widget rendered from — and shows the grouped results. Nothing was\n * stored for it but the pointer. Renders nothing when the anchor message no longer carries the fence.\n */\nexport const ConversationSearchResultsCanvasBody = ({ canvas, anchorMessageMarkdown }: CanvasBodyProps) => {\n const { onOpenConversation } = useConversationWidgetContext();\n const payload = useAnchorWidgetPayload(canvas, anchorMessageMarkdown, \"ConversationSearchResults\");\n const parsed = useMemo(\n () => (payload === undefined ? null : parseConversationSearchResultsPayload(payload)),\n [payload],\n );\n if (parsed === null || parsed.groups.length === 0) return null;\n return (\n <ConversationSearchResultsCanvas\n query={parsed.query}\n groups={parsed.groups}\n onOpenConversation={(conversationId, messageId) => onOpenConversation?.(conversationId, messageId)}\n />\n );\n};\n","import { Text } from \"../../provider/components\";\nimport React from \"react\";\nimport { WebSearchResultSource } from \"./shared/web-search-results-widget-payload\";\nimport { WebSearchAnswer } from \"./WebSearchAnswer\";\nimport { WebSearchSourcesList } from \"./WebSearchSourcesList\";\n\ntype WebSearchResultsCanvasProps = {\n query: string;\n answer: string;\n sources: WebSearchResultSource[];\n provider?: string;\n};\n\n// Canvas body styles — mirror the ConversationSearchResults / listReviews drawer widgets: a full-height flex column\n// whose inner area SCROLLS (flex 1 + overflowY auto) with a horizontal inset (there is no chat bubble gutter in the canvas).\nconst canvasStyle: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n flexDirection: \"column\",\n minHeight: 0,\n background: \"var(--agents-bg)\",\n};\nconst scrollStyle: React.CSSProperties = { flex: 1, minHeight: 0, overflowY: \"auto\" };\nconst contentStyle: React.CSSProperties = { padding: \"16px 24px 24px\" };\n\n/** The canvas body — the full answer + sources, scrollable + inset like the other canvas widgets. */\nexport const WebSearchResultsCanvas = (props: WebSearchResultsCanvasProps) => (\n <div style={canvasStyle} data-testid=\"web-search-results-widget-canvas\">\n <div style={scrollStyle}>\n <div style={contentStyle}>\n {props.query ? (\n <Text\n role=\"title\"\n replacements={{ query: props.query }}\n testId=\"web-search-results-widget-canvas-title\"\n >\n {'\"%query%\"'}\n </Text>\n ) : (\n <Text role=\"title\" testId=\"web-search-results-widget-canvas-title\">\n Web search\n </Text>\n )}\n <div style={{ marginTop: 12 }}>\n <WebSearchAnswer answer={props.answer} />\n </div>\n <WebSearchSourcesList\n sources={props.sources}\n provider={props.provider}\n testId=\"web-search-results-widget-canvas\"\n />\n </div>\n </div>\n </div>\n);\n","import React, { useMemo } from \"react\";\nimport type { CanvasBodyProps } from \"../../renderer/canvas/canvas-body-props\";\nimport { useAnchorWidgetPayload } from \"../../renderer/canvas/use-anchor-widget-payload\";\nimport { parseWebSearchResultsPayload } from \"./shared/web-search-results-widget-payload\";\nimport { WebSearchResultsCanvas } from \"./WebSearchResultsCanvas\";\n\n/**\n * The `webSearchResults` CANVAS BODY (`canvasBodies.tsx`): re-reads the `WebSearchResults` fence of the anchor message\n * — the answer and its cited sources the inline widget rendered from — and shows them in the canvas. Renders nothing\n * when the anchor message no longer carries the fence.\n */\nexport const WebSearchResultsCanvasBody = ({ canvas, anchorMessageMarkdown }: CanvasBodyProps) => {\n const payload = useAnchorWidgetPayload(canvas, anchorMessageMarkdown, \"WebSearchResults\");\n const parsed = useMemo(() => (payload === undefined ? null : parseWebSearchResultsPayload(payload)), [payload]);\n if (parsed === null || (parsed.answer.trim() === \"\" && parsed.sources.length === 0)) return null;\n return (\n <WebSearchResultsCanvas\n query={parsed.query}\n answer={parsed.answer}\n sources={parsed.sources}\n provider={parsed.provider}\n />\n );\n};\n","import { useCallback } from \"react\";\nimport { useConversationWidgetContext } from \"../../../renderer/ConversationWidgetCallbacksContext\";\nimport {\n buildEntityMentionFields,\n ENTITY_MENTION_WIDGET_NAME,\n entityMentionKey,\n EntityMentionTarget,\n MAX_ENTITY_MENTIONS_PER_MESSAGE,\n normalizeEntityMentionFields,\n toEntityMentionWireFields,\n} from \"./entity-mention\";\n\nexport interface UseEntityMentionComposerResult {\n /** Whether this entity is already mentioned in the composer. */\n isMentioned: (target: EntityMentionTarget) => boolean;\n /**\n * Puts the entity into the composer as an `EntityMention` chip — once per entity (a second click is a no-op); past\n * {@link MAX_ENTITY_MENTIONS_PER_MESSAGE} the new entity takes the place of the oldest mention (one write: a remove\n * and an add in one event would both read the list before either). Sends nothing: the owner writes the question.\n * Returns false for a target that names no entity.\n */\n mentionEntity: (target: EntityMentionTarget) => boolean;\n}\n\n/**\n * The opener side of the `EntityMention` composer widget: what the detail panel's \"Ask about this\"\n * button calls. Reads and writes the composer widgets of the chat's widget context; the composer strip renders them.\n */\nexport const useEntityMentionComposer = (): UseEntityMentionComposerResult => {\n const { composerWidgets, addComposerWidget, replaceComposerWidgetFields } = useConversationWidgetContext();\n\n const findMentionWidgets = useCallback(\n () => composerWidgets.filter((composerWidget) => composerWidget.widgetName === ENTITY_MENTION_WIDGET_NAME),\n [composerWidgets],\n );\n\n const isMentioned = useCallback(\n (target: EntityMentionTarget) => {\n const mention = buildEntityMentionFields(target);\n if (mention === null) return false;\n return findMentionWidgets().some((composerWidget) => {\n const heldMention = normalizeEntityMentionFields(composerWidget.fields);\n return heldMention !== null && entityMentionKey(heldMention) === entityMentionKey(mention);\n });\n },\n [findMentionWidgets],\n );\n\n const mentionEntity = useCallback(\n (target: EntityMentionTarget) => {\n const mention = buildEntityMentionFields(target);\n if (mention === null) return false;\n if (isMentioned(target)) return true;\n const mentionWidgets = findMentionWidgets();\n if (mentionWidgets.length >= MAX_ENTITY_MENTIONS_PER_MESSAGE) {\n replaceComposerWidgetFields(mentionWidgets[0].composerWidgetId, toEntityMentionWireFields(mention));\n } else {\n addComposerWidget(ENTITY_MENTION_WIDGET_NAME, toEntityMentionWireFields(mention));\n }\n return true;\n },\n [isMentioned, findMentionWidgets, replaceComposerWidgetFields, addComposerWidget],\n );\n\n return { isMentioned, mentionEntity };\n};\n","import { useIsEmailWidgetRender } from \"../../../renderer/ConversationWidgetRenderContext\";\nimport { ConversationWidgetButton } from \"../../_shared/ConversationWidgetButton\";\nimport type { EntityMentionTarget } from \"../shared/entity-mention\";\nimport { useEntityMentionComposer } from \"../shared/use-entity-mention-composer\";\n\nexport interface EntityMentionAskButtonProps extends EntityMentionTarget {\n testId?: string;\n}\n\n/**\n * The detail panel's \"Ask about this\" button (ChatGPT's \"Anschlussfrage stellen\"):\n * puts the entity into the chat input as an `EntityMention` chip and sends NOTHING — the owner writes the question,\n * the chip rides in that message as a fence, and the backend reads which entity the question is about. Not a\n * next-step CTA: it posts no prompt. A second click on the same entity changes nothing; the\n * label turns into \"Added to your message\". Mounted by the `entityDetail` canvas (the shell or a domain body) with\n * the entity the panel shows; renders nothing in the e-mail capture or for a target that names no entity.\n */\nexport const EntityMentionAskButton = (props: EntityMentionAskButtonProps) => {\n const { entityType, entityId, label, agentSpaceKey, testId = \"entity-mention-ask\" } = props;\n const isEmailRender = useIsEmailWidgetRender();\n const { isMentioned, mentionEntity } = useEntityMentionComposer();\n const target = { entityType, entityId, label, agentSpaceKey };\n if (isEmailRender) return null;\n const isAlreadyMentioned = isMentioned(target);\n return (\n <ConversationWidgetButton\n variant=\"primary\"\n size=\"medium\"\n icon={isAlreadyMentioned ? \"check\" : \"message\"}\n disabled={isAlreadyMentioned}\n onClick={() => mentionEntity(target)}\n testId={testId}\n >\n {isAlreadyMentioned ? \"Added to your message\" : \"Ask about this\"}\n </ConversationWidgetButton>\n );\n};\n","import type React from \"react\";\nimport { createContext, useContext } from \"react\";\n\n/**\n * The bodies of the `entityDetail` canvas, one per entity type: every `*.entity-detail-body.tsx` below\n * `widgets/` that exports `entityDetailBody` registers itself by folder glob — an entity type is added with\n * the backend `<Type>EntityReferenceResolver` and this one file, and edits no list. A second body for one entity type\n * is a build error (thrown at module load).\n */\n\nexport interface EntityDetailBodyProps {\n entityType: string;\n /** The id as the backend resolved it (a row id or an external id). */\n entityId: string;\n /** The key of the agent space the chat is scoped to, for a body whose read endpoint is agent-space-scoped. */\n agentSpaceKey?: number;\n}\n\nexport interface EntityDetailBodyDefinition {\n /** The backend resolver's `ENTITY_TYPE`. */\n entityType: string;\n /** Self-hydrates the entity by id through its own read endpoint (the endpoint's entity rights apply). */\n component: React.ComponentType<EntityDetailBodyProps>;\n}\n\nconst bodyModules = import.meta.glob<{ entityDetailBody?: EntityDetailBodyDefinition }>(\n [\"../../widgets/**/*.entity-detail-body.tsx\", \"../**/*.entity-detail-body.tsx\"],\n { eager: true },\n);\n\nexport const buildEntityDetailBodyRegistry = (\n definitions: EntityDetailBodyDefinition[],\n): Readonly<Record<string, EntityDetailBodyDefinition>> => {\n const registry: Record<string, EntityDetailBodyDefinition> = {};\n for (const definition of definitions) {\n if (Object.prototype.hasOwnProperty.call(registry, definition.entityType)) {\n throw new Error(`Two entity detail bodies register the entity type \"${definition.entityType}\".`);\n }\n registry[definition.entityType] = definition;\n }\n return registry;\n};\n\n/** The body definitions a set of `*.entity-detail-body.tsx` modules export (the package's or an app's glob). */\nexport const readEntityDetailBodyDefinitions = (\n modules: Record<string, { entityDetailBody?: EntityDetailBodyDefinition }>,\n): EntityDetailBodyDefinition[] =>\n Object.values(modules)\n .map((bodyModule) => bodyModule.entityDetailBody)\n .filter((definition): definition is EntityDetailBodyDefinition => definition !== undefined);\n\n/** Entity type → body: the package's folder, then what an app registers (`registerEntityDetailBodies`). */\nexport const entityDetailBodies: Record<string, EntityDetailBodyDefinition> = {\n ...buildEntityDetailBodyRegistry(readEntityDetailBodyDefinitions(bodyModules)),\n};\n\n/** Adds an app's entity detail bodies. Runs outside React, before the first render; an entity type registers once. */\nexport const registerEntityDetailBodies = (definitions: readonly EntityDetailBodyDefinition[]): void => {\n Object.assign(\n entityDetailBodies,\n buildEntityDetailBodyRegistry([...Object.values(entityDetailBodies), ...definitions]),\n );\n};\n\n/** The body registered for one entity type (the package's or an app's), or undefined when none is. */\nexport const getEntityDetailBody = (entityType: string): EntityDetailBodyDefinition | undefined =>\n Object.prototype.hasOwnProperty.call(entityDetailBodies, entityType) ? entityDetailBodies[entityType] : undefined;\n\n/** The registry the canvas body uses — the folder's by default; a spec or a story may provide stubs. */\nexport const EntityDetailBodiesContext =\n createContext<Readonly<Record<string, EntityDetailBodyDefinition>>>(entityDetailBodies);\n\nexport const useEntityDetailBodies = () => useContext(EntityDetailBodiesContext);\n","import { Text } from \"../../provider/components\";\nimport { useCallback, useState } from \"react\";\nimport type { CanvasBodyProps } from \"../../renderer/canvas/canvas-body-props\";\nimport { readEntityDetailInstanceId } from \"../../renderer/canvas/entity-detail-instance\";\nimport { EntityMentionAskButton } from \"../../widgets/EntityMentionConversationWidget/EntityMentionAskButton/EntityMentionAskButton\";\nimport { useEntityDetailBodies } from \"./entity-detail-bodies\";\nimport { EntityDetailNameReportContext } from \"./entity-detail-name\";\nimport styles from \"./EntityDetailCanvasBody.module.scss\";\n\n/**\n * The `entityDetail` CANVAS BODY (`canvasBodies.tsx`): the shell around one entity's detail. It reads\n * WHICH entity from the canvas instance id (`entityDetail:<entityType>:<id>`) and mounts the body registered for that\n * entity type; the body self-hydrates by id. An entity type without a body renders a short notice instead of nothing,\n * so a click never lands on an empty pane. \"Ask about this\" stays at the pane's foot while the detail scrolls and\n * names the entity in its chip once the body reported the name (`useReportEntityDetailName`).\n */\nexport const EntityDetailCanvasBody = ({ canvas, agentSpaceKey }: CanvasBodyProps) => {\n const bodies = useEntityDetailBodies();\n const [reportedEntity, setReportedEntity] = useState<{ instanceId: string; name: string | undefined }>();\n const reportEntityName = useCallback(\n (entityName: string | undefined) => setReportedEntity({ instanceId: canvas.instanceId, name: entityName }),\n [canvas.instanceId],\n );\n const target = readEntityDetailInstanceId(canvas.instanceId);\n if (target === undefined) return null;\n const definition = Object.prototype.hasOwnProperty.call(bodies, target.entityType)\n ? bodies[target.entityType]\n : undefined;\n if (!definition) {\n return (\n <div className={styles.entityDetail} data-testid=\"entity-detail-canvas\">\n <Text role=\"help\" color=\"var(--agents-fg-light)\" testId=\"entity-detail-canvas-unsupported\">\n {\"This detail cannot be shown here yet.\"}\n </Text>\n </div>\n );\n }\n const Body = definition.component;\n // A name reported for the previous entity never labels the next one's chip.\n const entityName = reportedEntity?.instanceId === canvas.instanceId ? reportedEntity.name : undefined;\n return (\n <div className={styles.entityDetail} data-testid=\"entity-detail-canvas\">\n <EntityDetailNameReportContext.Provider value={reportEntityName}>\n <Body entityType={target.entityType} entityId={target.entityId} agentSpaceKey={agentSpaceKey} />\n </EntityDetailNameReportContext.Provider>\n <div className={styles.entityDetailFooter}>\n <EntityMentionAskButton\n entityType={target.entityType}\n entityId={target.entityId}\n label={entityName}\n agentSpaceKey={agentSpaceKey}\n />\n </div>\n </div>\n );\n};\n","import { Text } from \"../../provider/components\";\nimport { useCallback, useMemo } from \"react\";\nimport type { CanvasBodyProps } from \"../../renderer/canvas/canvas-body-props\";\nimport { useConversationWidgetContext } from \"../../renderer/ConversationWidgetCallbacksContext\";\nimport {\n canvasInstanceIdForEntityDetail,\n readOpenEntityActionTarget,\n} from \"../../renderer/canvas/entity-detail-instance\";\nimport type {\n ComposedViewAction,\n ComposedViewTree,\n} from \"../../renderer/widget-tree/composed-view-tree\";\nimport type { ExpressionValue } from \"../../renderer/widget-tree/expression/evaluate-expression\";\nimport { ComposedViewRenderer } from \"../ComposedViewRenderer\";\nimport { findMapNodeInMarkdown, readMapNodeKeyOfInstanceId } from \"../components/map/map-full-screen-instance\";\nimport { MapPresentationContext } from \"../components/map/map-presentation-context\";\nimport mapStyles from \"../components/map/Map.module.scss\";\n\n/** The body reports nothing of its own: the inline block already reported this map to the model. */\nconst ignoreRenderReport = () => {};\n\n/**\n * The `mapFullScreen` CANVAS BODY (`canvasBodies.tsx`): one `Map` node of the anchor\n * message, full screen — the map filling the pane beside the list of its points. It finds the node through the\n * message's `ComposedView` fences and the node key its instance id carries (`map-full-screen-instance.ts`), then\n * renders it through the same `ComposedViewRenderer` with the node's own tree, so its bindings (`$data`, `$from`,\n * `$source`, `$computed`, `$state` at its initial value) and its actions resolve exactly as inline; the\n * `MapPresentationContext` switches the node to its full-screen layout. A point's `openEntity` action opens the\n * `entityDetail` canvas on the same anchor message (it replaces this canvas, as a click on an inline point would).\n */\nexport const MapFullScreenCanvasBody = (props: CanvasBodyProps) => {\n const { canvas, anchorMessageMarkdown, agentSpaceKey } = props;\n const { openCanvas } = useConversationWidgetContext();\n const mapNodeKey = readMapNodeKeyOfInstanceId(canvas.instanceId);\n\n const mapTree = useMemo<ComposedViewTree | undefined>(() => {\n if (anchorMessageMarkdown === null || mapNodeKey === undefined) return undefined;\n const mapNodeInComposedView = findMapNodeInMarkdown(anchorMessageMarkdown, mapNodeKey);\n if (!mapNodeInComposedView) return undefined;\n // The map alone, with its tree's state, data, computed values and actions; the tree's findings were the inline\n // block's to report.\n return { ...mapNodeInComposedView.tree, title: null, view: mapNodeInComposedView.mapNode, findings: [] };\n }, [anchorMessageMarkdown, mapNodeKey]);\n\n const anchor = canvas.anchor;\n const onOpenEntity = useCallback(\n (action: ComposedViewAction, currentRow?: ExpressionValue) => {\n const target = readOpenEntityActionTarget(action, currentRow);\n if (target === undefined || anchor.kind !== \"message\") return;\n openCanvas({\n canvasId: \"entityDetail\",\n instanceId: canvasInstanceIdForEntityDetail(target),\n anchor,\n origin: \"cardClick\",\n justDelivered: false,\n });\n },\n [openCanvas, anchor],\n );\n\n if (!mapTree) {\n return (\n <div\n className={mapStyles.fullScreenNotice}\n data-canvas-instance={canvas.instanceId}\n data-testid=\"map-full-screen-canvas-unavailable\"\n >\n <Text role=\"help\" color=\"var(--agents-fg-light)\">\n {\"This map is no longer available.\"}\n </Text>\n </div>\n );\n }\n return (\n <div\n className={mapStyles.fullScreenBody}\n data-canvas-instance={canvas.instanceId}\n data-testid=\"map-full-screen-canvas\"\n >\n <MapPresentationContext.Provider value=\"fullScreen\">\n <ComposedViewRenderer\n tree={mapTree}\n interactive\n isEmailRender={false}\n messageId={anchor.kind === \"message\" ? anchor.messageId : undefined}\n agentSpaceKey={agentSpaceKey}\n onReport={ignoreRenderReport}\n onOpenEntity={onOpenEntity}\n testId=\"map-full-screen-canvas-view\"\n />\n </MapPresentationContext.Provider>\n </div>\n );\n};\n","import type React from \"react\";\nimport type { CanvasId } from \"./canvas-behaviours\";\nimport type { CanvasBodyProps } from \"./canvas-body-props\";\nimport { ConversationSearchResultsCanvasBody } from \"../../widgets/ConversationSearchResultsConversationWidget/ConversationSearchResultsCanvasBody\";\nimport { WebSearchResultsCanvasBody } from \"../../widgets/WebSearchResultsConversationWidget/WebSearchResultsCanvasBody\";\nimport { EntityDetailCanvasBody } from \"../../kit/entity-detail/EntityDetailCanvasBody\";\nimport { MapFullScreenCanvasBody } from \"../../kit/map-full-screen/MapFullScreenCanvasBody\";\n\nexport type { CanvasBodyProps } from \"./canvas-body-props\";\nexport { canvasFeatureFlagOf } from \"./canvas-feature-flags\";\n\n/**\n * THE CANVAS BODIES — canvas id → the React component the shell mounts in the canvas pane. The one module that knows\n * both the canvas ids and React components, and it is imported ONLY by `CanvasPane` (the shell), never by\n * the slice, the behaviours or a widget — that is the import layering that keeps the store-init temporal-dead-zone\n * cycle away. The bodies themselves are small: a body that hosts a heavy page (an app's list page or editor)\n * lazy-loads that page, exactly as its widget did. Every canvas has a body; nothing\n * else hands the pane a rendered node.\n *\n * The package's own bodies are listed here; an app adds the bodies of its canvases with `registerCanvasBodies` before\n * the first render. The object is filled in place, so the shell reads every registration.\n */\nexport const canvasBodies = {\n conversationSearchResults: ConversationSearchResultsCanvasBody,\n webSearchResults: WebSearchResultsCanvasBody,\n entityDetail: EntityDetailCanvasBody,\n mapFullScreen: MapFullScreenCanvasBody,\n} as Record<CanvasId, React.ComponentType<CanvasBodyProps>>;\n\n/** Adds an app's canvas bodies (canvas id → body). Runs outside React, before the first render. */\nexport const registerCanvasBodies = (\n bodies: Readonly<Partial<Record<CanvasId, React.ComponentType<CanvasBodyProps>>>>,\n): void => {\n Object.assign(canvasBodies, bodies);\n};\n"],"names":["canvasStyle","scrollStyle","contentStyle","entityName"],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { i as isCanvasId } from "./canvas-behaviours-AvzKSYyv.js";
|
|
3
|
+
const CANVAS_STORAGE_KEY_PREFIX = "agent.canvas.";
|
|
4
|
+
const canvasStorageKey = (conversationId) => `${CANVAS_STORAGE_KEY_PREFIX}${conversationId}`;
|
|
5
|
+
const isPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
6
|
+
const parseAnchor = (value) => {
|
|
7
|
+
if (typeof value !== "object" || value === null) return null;
|
|
8
|
+
const candidate = value;
|
|
9
|
+
if (candidate.kind === "message" && isPositiveInteger(candidate.messageId)) {
|
|
10
|
+
return { kind: "message", messageId: candidate.messageId };
|
|
11
|
+
}
|
|
12
|
+
if (candidate.kind === "page" && isPositiveInteger(candidate.agentSpaceKey)) {
|
|
13
|
+
return { kind: "page", agentSpaceKey: candidate.agentSpaceKey };
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
};
|
|
17
|
+
const parseStoredCanvasPointer = (value) => {
|
|
18
|
+
if (typeof value !== "object" || value === null) return null;
|
|
19
|
+
const candidate = value;
|
|
20
|
+
const anchor = parseAnchor(candidate.anchor);
|
|
21
|
+
if (!isCanvasId(candidate.canvasId) || anchor === null) return null;
|
|
22
|
+
if (typeof candidate.instanceId !== "string" || candidate.instanceId.length === 0) return null;
|
|
23
|
+
return {
|
|
24
|
+
canvasId: candidate.canvasId,
|
|
25
|
+
anchor,
|
|
26
|
+
instanceId: candidate.instanceId,
|
|
27
|
+
...isPositiveInteger(candidate.createdEntityId) ? { createdEntityId: candidate.createdEntityId } : {}
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
const storeOpenCanvas = (conversationId, canvas) => {
|
|
31
|
+
if (conversationId === void 0) return;
|
|
32
|
+
const pointer = {
|
|
33
|
+
canvasId: canvas.canvasId,
|
|
34
|
+
anchor: canvas.anchor,
|
|
35
|
+
instanceId: canvas.instanceId,
|
|
36
|
+
...canvas.createdEntityId !== void 0 ? { createdEntityId: canvas.createdEntityId } : {}
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
window.sessionStorage.setItem(canvasStorageKey(conversationId), JSON.stringify(pointer));
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const readStoredCanvas = (conversationId) => {
|
|
44
|
+
if (conversationId === void 0) return null;
|
|
45
|
+
try {
|
|
46
|
+
const raw = window.sessionStorage.getItem(canvasStorageKey(conversationId));
|
|
47
|
+
if (raw === null) return null;
|
|
48
|
+
return parseStoredCanvasPointer(JSON.parse(raw));
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const clearStoredCanvas = (conversationId) => {
|
|
54
|
+
if (conversationId === void 0) return;
|
|
55
|
+
try {
|
|
56
|
+
window.sessionStorage.removeItem(canvasStorageKey(conversationId));
|
|
57
|
+
} catch {
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
export {
|
|
61
|
+
clearStoredCanvas as c,
|
|
62
|
+
readStoredCanvas as r,
|
|
63
|
+
storeOpenCanvas as s
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=canvasStorage-EvQsGdto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvasStorage-EvQsGdto.js","sources":["../../src/renderer/canvas/canvasStorage.ts"],"sourcesContent":["import { isCanvasId } from \"./canvas-behaviours\";\nimport type { CanvasAnchor, CanvasState } from \"./canvas-state\";\n\n/**\n * Where the open canvas of a conversation is remembered across a reload: `sessionStorage`, per conversation, holding\n * the POINTER only — which canvas, which anchor, which instance, and the entity a body created. Never\n * content: the anchor message's fence supplies the body's data on restore. Tab-local by design (the canvas is viewing\n * state of this tab); a new tab starts with no canvas.\n *\n * Written on every change of the open canvas for ANY lifetime, cleared when the canvas closes; on read the pointer is\n * validated shape by shape — a stale id or a mangled entry reads as \"nothing stored\". Every accessor is try/catch-\n * guarded: a private window, blocked site data or a preview context can throw on the accessor itself.\n */\nexport interface StoredCanvasPointer {\n readonly canvasId: CanvasState[\"canvasId\"];\n readonly anchor: CanvasAnchor;\n readonly instanceId: string;\n readonly createdEntityId?: number;\n}\n\nconst CANVAS_STORAGE_KEY_PREFIX = \"agent.canvas.\";\n\nconst canvasStorageKey = (conversationId: number): string => `${CANVAS_STORAGE_KEY_PREFIX}${conversationId}`;\n\nconst isPositiveInteger = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isInteger(value) && value > 0;\n\nconst parseAnchor = (value: unknown): CanvasAnchor | null => {\n if (typeof value !== \"object\" || value === null) return null;\n const candidate = value as { kind?: unknown; messageId?: unknown; agentSpaceKey?: unknown };\n if (candidate.kind === \"message\" && isPositiveInteger(candidate.messageId)) {\n return { kind: \"message\", messageId: candidate.messageId };\n }\n if (candidate.kind === \"page\" && isPositiveInteger(candidate.agentSpaceKey)) {\n return { kind: \"page\", agentSpaceKey: candidate.agentSpaceKey };\n }\n return null;\n};\n\n/** Validate an untrusted value into a pointer, or null. Exported for the spec and for the shell's restore path. */\nexport const parseStoredCanvasPointer = (value: unknown): StoredCanvasPointer | null => {\n if (typeof value !== \"object\" || value === null) return null;\n const candidate = value as { canvasId?: unknown; anchor?: unknown; instanceId?: unknown; createdEntityId?: unknown };\n const anchor = parseAnchor(candidate.anchor);\n if (!isCanvasId(candidate.canvasId) || anchor === null) return null;\n if (typeof candidate.instanceId !== \"string\" || candidate.instanceId.length === 0) return null;\n return {\n canvasId: candidate.canvasId,\n anchor,\n instanceId: candidate.instanceId,\n ...(isPositiveInteger(candidate.createdEntityId) ? { createdEntityId: candidate.createdEntityId } : {}),\n };\n};\n\n/** Remember the canvas open in this conversation. No-op without a conversation id (a cold chat has none yet). */\nexport const storeOpenCanvas = (conversationId: number | undefined, canvas: CanvasState): void => {\n if (conversationId === undefined) return;\n const pointer: StoredCanvasPointer = {\n canvasId: canvas.canvasId,\n anchor: canvas.anchor,\n instanceId: canvas.instanceId,\n ...(canvas.createdEntityId !== undefined ? { createdEntityId: canvas.createdEntityId } : {}),\n };\n try {\n window.sessionStorage.setItem(canvasStorageKey(conversationId), JSON.stringify(pointer));\n } catch {\n // Storage unavailable — the canvas then simply does not survive the reload.\n }\n};\n\n/** The canvas that was open in this conversation, validated, or null (nothing stored, stale, or storage unusable). */\nexport const readStoredCanvas = (conversationId: number | undefined): StoredCanvasPointer | null => {\n if (conversationId === undefined) return null;\n try {\n const raw = window.sessionStorage.getItem(canvasStorageKey(conversationId));\n if (raw === null) return null;\n return parseStoredCanvasPointer(JSON.parse(raw));\n } catch {\n return null;\n }\n};\n\n/** Forget the canvas of this conversation — it closed, or its anchor is no longer among the loaded messages. */\nexport const clearStoredCanvas = (conversationId: number | undefined): void => {\n if (conversationId === undefined) return;\n try {\n window.sessionStorage.removeItem(canvasStorageKey(conversationId));\n } catch {\n // Storage unavailable — nothing was stored either.\n }\n};\n"],"names":[],"mappings":";;AAoBA,MAAM,4BAA4B;AAElC,MAAM,mBAAmB,CAAC,mBAAmC,GAAG,yBAAyB,GAAG,cAAc;AAE1G,MAAM,oBAAoB,CAAC,UACzB,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ;AAElE,MAAM,cAAc,CAAC,UAAwC;AAC3D,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,YAAY;AAClB,MAAI,UAAU,SAAS,aAAa,kBAAkB,UAAU,SAAS,GAAG;AAC1E,WAAO,EAAE,MAAM,WAAW,WAAW,UAAU,UAAA;AAAA,EACjD;AACA,MAAI,UAAU,SAAS,UAAU,kBAAkB,UAAU,aAAa,GAAG;AAC3E,WAAO,EAAE,MAAM,QAAQ,eAAe,UAAU,cAAA;AAAA,EAClD;AACA,SAAO;AACT;AAGO,MAAM,2BAA2B,CAAC,UAA+C;AACtF,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,YAAY;AAClB,QAAM,SAAS,YAAY,UAAU,MAAM;AAC3C,MAAI,CAAC,WAAW,UAAU,QAAQ,KAAK,WAAW,KAAM,QAAO;AAC/D,MAAI,OAAO,UAAU,eAAe,YAAY,UAAU,WAAW,WAAW,EAAG,QAAO;AAC1F,SAAO;AAAA,IACL,UAAU,UAAU;AAAA,IACpB;AAAA,IACA,YAAY,UAAU;AAAA,IACtB,GAAI,kBAAkB,UAAU,eAAe,IAAI,EAAE,iBAAiB,UAAU,oBAAoB,CAAA;AAAA,EAAC;AAEzG;AAGO,MAAM,kBAAkB,CAAC,gBAAoC,WAA8B;AAChG,MAAI,mBAAmB,OAAW;AAClC,QAAM,UAA+B;AAAA,IACnC,UAAU,OAAO;AAAA,IACjB,QAAQ,OAAO;AAAA,IACf,YAAY,OAAO;AAAA,IACnB,GAAI,OAAO,oBAAoB,SAAY,EAAE,iBAAiB,OAAO,oBAAoB,CAAA;AAAA,EAAC;AAE5F,MAAI;AACF,WAAO,eAAe,QAAQ,iBAAiB,cAAc,GAAG,KAAK,UAAU,OAAO,CAAC;AAAA,EACzF,QAAQ;AAAA,EAER;AACF;AAGO,MAAM,mBAAmB,CAAC,mBAAmE;AAClG,MAAI,mBAAmB,OAAW,QAAO;AACzC,MAAI;AACF,UAAM,MAAM,OAAO,eAAe,QAAQ,iBAAiB,cAAc,CAAC;AAC1E,QAAI,QAAQ,KAAM,QAAO;AACzB,WAAO,yBAAyB,KAAK,MAAM,GAAG,CAAC;AAAA,EACjD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,MAAM,oBAAoB,CAAC,mBAA6C;AAC7E,MAAI,mBAAmB,OAAW;AAClC,MAAI;AACF,WAAO,eAAe,WAAW,iBAAiB,cAAc,CAAC;AAAA,EACnE,QAAQ;AAAA,EAER;AACF;"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./conversation-BEF0aesf.js";
|
|
3
|
+
const DELEGATE_TOOL_NAME = "delegate";
|
|
4
|
+
const RESOLVED_STATUSES = ["COMPLETED", "FAILED", "CANCELLED"];
|
|
5
|
+
const isResolved = (status) => status != null && RESOLVED_STATUSES.includes(status);
|
|
6
|
+
const TERMINAL_RUN_STATES = ["COMPLETED", "FAILED", "CANCELLED"];
|
|
7
|
+
const UNRESOLVED_RUN_STATUSES = ["QUEUED", "RUNNING"];
|
|
8
|
+
const isEffectivelyResolved = (message) => {
|
|
9
|
+
if (message == null) return false;
|
|
10
|
+
if (isResolved(message.toolCall?.status)) return true;
|
|
11
|
+
if (message.toolCall?.name !== DELEGATE_TOOL_NAME) return false;
|
|
12
|
+
const children = message.delegatedConversations ?? [];
|
|
13
|
+
if (children.length === 0) return false;
|
|
14
|
+
const declaredTasks = message.toolCall?.arguments?.tasks;
|
|
15
|
+
if (Array.isArray(declaredTasks) && declaredTasks.length > children.length) return false;
|
|
16
|
+
return children.every((child) => TERMINAL_RUN_STATES.includes(child.runState ?? ""));
|
|
17
|
+
};
|
|
18
|
+
const countOutstandingChildren = (message) => {
|
|
19
|
+
const children = message.delegatedConversations ?? [];
|
|
20
|
+
if (children.length > 0) {
|
|
21
|
+
return children.filter((child) => !TERMINAL_RUN_STATES.includes(child.runState ?? "")).length;
|
|
22
|
+
}
|
|
23
|
+
if (message.toolCall?.name !== DELEGATE_TOOL_NAME) {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
const subAgent = message.subAgent;
|
|
27
|
+
if (subAgent != null) {
|
|
28
|
+
const currentRun = (subAgent.runs ?? []).find((run) => run.runNumber === subAgent.currentRunNumber);
|
|
29
|
+
return currentRun != null && UNRESOLVED_RUN_STATUSES.includes(currentRun.status ?? "") ? 1 : 0;
|
|
30
|
+
}
|
|
31
|
+
if (isResolved(message.toolCall?.status)) {
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
const declaredTasks = message.toolCall?.arguments?.tasks;
|
|
35
|
+
return Array.isArray(declaredTasks) && declaredTasks.length > 0 ? declaredTasks.length : 1;
|
|
36
|
+
};
|
|
37
|
+
const hasUnresolvedToolCall = (message) => message.toolCall != null && !isEffectivelyResolved(message);
|
|
38
|
+
const hasDelegationInFlight = (message) => countOutstandingChildren(message) > 0 || message.toolCall?.name === DELEGATE_TOOL_NAME && !isEffectivelyResolved(message);
|
|
39
|
+
const deriveAgentActivity = (messages, runStatus) => {
|
|
40
|
+
if (runStatus === "failed") {
|
|
41
|
+
return { active: false, label: "" };
|
|
42
|
+
}
|
|
43
|
+
const sorted = [...messages ?? []].sort(
|
|
44
|
+
(a, b) => (a.indexInConversation ?? Number.POSITIVE_INFINITY) - (b.indexInConversation ?? Number.POSITIVE_INFINITY)
|
|
45
|
+
);
|
|
46
|
+
const lastMessage = sorted[sorted.length - 1];
|
|
47
|
+
if (lastMessage?.role === "USER") {
|
|
48
|
+
return { active: true, label: "" };
|
|
49
|
+
}
|
|
50
|
+
const unresolvedDelegateCount = sorted.reduce((count, message) => count + countOutstandingChildren(message), 0);
|
|
51
|
+
if (runStatus === "idle" && unresolvedDelegateCount === 0) {
|
|
52
|
+
return { active: false, label: "" };
|
|
53
|
+
}
|
|
54
|
+
if (unresolvedDelegateCount > 0) {
|
|
55
|
+
return {
|
|
56
|
+
active: true,
|
|
57
|
+
label: "Waiting for %count% delegations...",
|
|
58
|
+
replacements: { count: unresolvedDelegateCount },
|
|
59
|
+
context: unresolvedDelegateCount === 1 ? "one" : "other"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (lastMessage?.messageType === "TOOL_CALL" && !isEffectivelyResolved(lastMessage)) {
|
|
63
|
+
return {
|
|
64
|
+
active: true,
|
|
65
|
+
label: "Running: %tool%...",
|
|
66
|
+
replacements: { tool: lastMessage.toolCall?.name ?? "Tool" },
|
|
67
|
+
toolRunning: true
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (runStatus === "running") {
|
|
71
|
+
return { active: true, label: "" };
|
|
72
|
+
}
|
|
73
|
+
return { active: false, label: "" };
|
|
74
|
+
};
|
|
75
|
+
const CHANGING_STATUSES = ["PENDING", "RUNNING", "CANCEL_REQUESTED"];
|
|
76
|
+
const canStillChange = (message) => CHANGING_STATUSES.includes(message.status ?? "") || hasUnresolvedToolCall(message) || hasDelegationInFlight(message);
|
|
77
|
+
const buildChangedMessagesRequest = (messages, pageSize) => {
|
|
78
|
+
const loaded = (messages ?? []).filter((message) => message.indexInConversation != null);
|
|
79
|
+
if (loaded.length === 0) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const indexes = (list) => list.map((message) => message.indexInConversation);
|
|
83
|
+
const highestIndex = Math.max(...indexes(loaded));
|
|
84
|
+
const changing = loaded.filter(canStillChange);
|
|
85
|
+
const fromIndex = Math.max(
|
|
86
|
+
changing.length > 0 ? Math.min(...indexes(changing)) : highestIndex + 1,
|
|
87
|
+
highestIndex - pageSize + 1
|
|
88
|
+
);
|
|
89
|
+
const highestKnownId = Math.max(...loaded.map((message) => message.id ?? 0));
|
|
90
|
+
return { highestKnownId, fromIndex, pageSize };
|
|
91
|
+
};
|
|
92
|
+
export {
|
|
93
|
+
buildChangedMessagesRequest as b,
|
|
94
|
+
deriveAgentActivity as d
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=contract-BxmMxYPF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-BxmMxYPF.js","sources":["../../src/chat/controller/derive-agent-activity.ts","../../src/chat/controller/changed-messages-request.ts"],"sourcesContent":["import type { ConversationMessageStatus } from \"../../contract/conversation\";\nimport type { ChatMessage } from \"../chat-message\";\n\n/** Mirrors App\\Domain\\AI\\Entities\\Conversations\\Agent\\Tools\\DelegateTool::NAME. */\nconst DELEGATE_TOOL_NAME = \"delegate\";\n\n/** Terminal tool-call statuses — mirrors ToolCall::isResolved (COMPLETED / FAILED / CANCELLED). */\nconst RESOLVED_STATUSES: string[] = [\"COMPLETED\", \"FAILED\", \"CANCELLED\"] satisfies ConversationMessageStatus[];\nconst isResolved = (status?: string | null): boolean => status != null && RESOLVED_STATUSES.includes(status);\n\n/** Terminal child run-states — mirrors AIConversationRunState::TERMINAL (COMPLETED / FAILED / CANCELLED). */\nconst TERMINAL_RUN_STATES: string[] = [\"COMPLETED\", \"FAILED\", \"CANCELLED\"];\n\n/** A sub-agent run still waiting to start or actively working (mirrors DelegationSubAgentRun::STATUS_*). */\nconst UNRESOLVED_RUN_STATUSES: string[] = [\"QUEUED\", \"RUNNING\"];\n\n/**\n * A tool call is EFFECTIVELY resolved when its stored status says so — or, for a `delegate` call, when every\n * expanded child already reached a terminal run state: the children are the truth of a fan-out, the stored\n * status is a mirror that a write race can leave stale (a delegate call left RUNNING kept the whole customer\n * chat \"thinking\" for the rest of a setup). An empty or missing expansion proves nothing and falls back to the status.\n */\nconst isEffectivelyResolved = (message: ChatMessage | undefined): boolean => {\n if (message == null) return false;\n if (isResolved(message.toolCall?.status)) return true;\n if (message.toolCall?.name !== DELEGATE_TOOL_NAME) return false;\n const children = message.delegatedConversations ?? [];\n if (children.length === 0) return false;\n // A partial expansion (page cap) proves nothing: the call declared N tasks, N children must be visible.\n const declaredTasks = (message.toolCall?.arguments as { tasks?: unknown[] } | undefined)?.tasks;\n if (Array.isArray(declaredTasks) && declaredTasks.length > children.length) return false;\n return children.every((child) => TERMINAL_RUN_STATES.includes(child.runState ?? \"\"));\n};\n\n/** How many sub-agents spawned by this message are still working. */\nexport const countOutstandingChildren = (message: ChatMessage): number => {\n const children = message.delegatedConversations ?? [];\n if (children.length > 0) {\n return children.filter((child) => !TERMINAL_RUN_STATES.includes(child.runState ?? \"\")).length;\n }\n if (message.toolCall?.name !== DELEGATE_TOOL_NAME) {\n return 0;\n }\n // A NEW row (one `delegate` call = one sub-agent) carries its own run record instead of\n // `delegatedConversations`; no child expanded, so the record's CURRENT run is the truth of whether it is done.\n const subAgent = message.subAgent;\n if (subAgent != null) {\n const currentRun = (subAgent.runs ?? []).find((run) => run.runNumber === subAgent.currentRunNumber);\n return currentRun != null && UNRESOLVED_RUN_STATUSES.includes(currentRun.status ?? \"\") ? 1 : 0;\n }\n if (isResolved(message.toolCall?.status)) {\n return 0;\n }\n const declaredTasks = (message.toolCall?.arguments as { tasks?: unknown[] } | undefined)?.tasks;\n return Array.isArray(declaredTasks) && declaredTasks.length > 0 ? declaredTasks.length : 1;\n};\n\n/** A tool call whose result is still to come — its progress lives in `toolCall.status`, never in the row's status. */\nexport const hasUnresolvedToolCall = (message: ChatMessage): boolean =>\n message.toolCall != null && !isEffectivelyResolved(message);\n\n/** Whether this message's delegation can still change: a child is still working, or the call is not resolved yet. */\nexport const hasDelegationInFlight = (message: ChatMessage): boolean =>\n countOutstandingChildren(message) > 0 ||\n (message.toolCall?.name === DELEGATE_TOOL_NAME && !isEffectivelyResolved(message));\n\nexport type AgentActivity = {\n /** Whether a turn is in flight — drives the indicator's visibility (ConversationView aiThinking). */\n active: boolean;\n /**\n * Translation source string for \"what the agent is doing right now\" (empty when nothing to show). Rendered through\n * the chat's `Text`, so this is the translatable key with %placeholders% — NOT pre-interpolated. The dynamic values\n * arrive via {@link AgentActivity.replacements} and plural form via {@link AgentActivity.context}.\n */\n label: string;\n /** Dynamic values substituted into the %placeholders% of {@link AgentActivity.label} by <Text>. */\n replacements?: Record<string, string | number>;\n /** Plural selector for <Text> when the label is count-dependent. */\n context?: \"one\" | \"other\";\n /** A non-delegate tool is running → show the ASCII wrench spinner instead of a text label. */\n toolRunning?: boolean;\n};\n\n/**\n * Derive \"what the agent is doing right now\" for the agent activity indicator PURELY from the\n * live conversation data: the message tree (refreshed on each realtime-signal tick / on open) plus the signal's\n * coarse runStatus (\"running\" | \"idle\" | \"failed\" | null). No network. Mirrors the backend truth —\n * AIConversation.runState (IDLE/RUNNING/AWAITING_DELEGATES/…) and per-message status/messageType/toolCall — but reads\n * it off the messages the FE already holds, so the indicator is correct even before the first signal emission.\n */\nexport const deriveAgentActivity = (\n messages: ChatMessage[] | null,\n runStatus: string | null,\n): AgentActivity => {\n if (runStatus === \"failed\") {\n // Failure surfaces via ConversationView's error state, not the activity line.\n return { active: false, label: \"\" };\n }\n\n // An optimistic (just-sent, not yet server-assigned) row carries no index — it belongs at the TAIL, the newest\n // thing in the conversation, never at the front, where `?? 0` would park it ahead of every real message.\n const sorted = [...(messages ?? [])].sort(\n (a, b) => (a.indexInConversation ?? Number.POSITIVE_INFINITY) - (b.indexInConversation ?? Number.POSITIVE_INFINITY),\n );\n const lastMessage = sorted[sorted.length - 1];\n\n // The owner just sent — the optimistic bubble, or a saved row still awaiting the first response — starts the face\n // thinking immediately, before the server's \"running\" ping arrives (which can lag the send by about a second);\n // checked before the `idle` short-circuit below, whose `runStatus` can still report the PREVIOUS turn's rest state.\n // A rejected POST resets `aiThinking` itself (conversations-message-post.case.ts), so no separate reset is needed.\n if (lastMessage?.role === \"USER\") {\n return { active: true, label: \"\" };\n }\n\n // Sub-agents still working → the orchestrator is parked on its children (backend runState AWAITING_DELEGATES).\n // Counted per CHILD, not per delegate message: a fan-out (`delegate` with N tasks, or a tool result carrying\n // `delegationTasks`, e.g. a LARGE website edit's page packets) is ONE message with N children. The expanded\n // `delegatedConversations` carry each child's runState; without the expand, a `delegate` call still awaiting its\n // verdict counts its declared tasks, else one.\n const unresolvedDelegateCount = sorted.reduce((count, message) => count + countOutstandingChildren(message), 0);\n\n // The turn is at rest and nothing is outstanding → idle, no indicator.\n if (runStatus === \"idle\" && unresolvedDelegateCount === 0) {\n return { active: false, label: \"\" };\n }\n\n if (unresolvedDelegateCount > 0) {\n return {\n active: true,\n label: \"Waiting for %count% delegations...\",\n replacements: { count: unresolvedDelegateCount },\n context: unresolvedDelegateCount === 1 ? \"one\" : \"other\",\n };\n }\n\n // A tool-call that hasn't resolved yet → that tool is running now (a delegate call whose children all finished\n // is resolved in effect, whatever its stored status says).\n if (lastMessage?.messageType === \"TOOL_CALL\" && !isEffectivelyResolved(lastMessage)) {\n return {\n active: true,\n label: \"Running: %tool%...\",\n replacements: { tool: lastMessage.toolCall?.name ?? \"Tool\" },\n toolRunning: true,\n };\n }\n\n // The model is producing a turn (signal running) — a trailing owner row was already caught above.\n if (runStatus === \"running\") {\n return { active: true, label: \"\" };\n }\n\n return { active: false, label: \"\" };\n};\n","import type { ChangedMessagesRequest, ConversationMessageStatus } from \"../../contract/conversation\";\nimport type { ChatMessage } from \"../chat-message\";\nimport { hasDelegationInFlight, hasUnresolvedToolCall } from \"./derive-agent-activity\";\n\n/** Row statuses that are not final yet (today only a waiting signal row carries one, PENDING until a turn reads it). */\nconst CHANGING_STATUSES: string[] = [\"PENDING\", \"RUNNING\", \"CANCEL_REQUESTED\"] satisfies ConversationMessageStatus[];\n\nconst canStillChange = (message: ChatMessage): boolean =>\n CHANGING_STATUSES.includes(message.status ?? \"\") || hasUnresolvedToolCall(message) || hasDelegationInFlight(message);\n\n/**\n * What a live update reads, given the loaded messages: every row written since the newest loaded one, plus every\n * message from the lowest one that can still change. \"Written since\" goes by id, not index: ids grow with every write,\n * while an index can be taken below the newest loaded one (a call row persisted after the row its execution emitted)\n * or reused (a re-drive after an admin delete-from). The index start never lies below the newest page (a newest-first\n * page of `pageSize` cannot return older rows anyway), so a row a dead worker left pending cannot pin it. Nothing\n * loaded yet → null: the caller reads the newest page as on open. How the request reaches the wire (the filter string)\n * is the binding's matter (`AgentsConversationClient.getChangedMessages`).\n */\nexport const buildChangedMessagesRequest = (\n messages: ChatMessage[] | null,\n pageSize: number,\n): ChangedMessagesRequest | null => {\n const loaded = (messages ?? []).filter((message) => message.indexInConversation != null);\n if (loaded.length === 0) {\n return null;\n }\n const indexes = (list: ChatMessage[]) => list.map((message) => message.indexInConversation as number);\n const highestIndex = Math.max(...indexes(loaded));\n const changing = loaded.filter(canStillChange);\n const fromIndex = Math.max(\n changing.length > 0 ? Math.min(...indexes(changing)) : highestIndex + 1,\n highestIndex - pageSize + 1,\n );\n const highestKnownId = Math.max(...loaded.map((message) => message.id ?? 0));\n return { highestKnownId, fromIndex, pageSize };\n};\n"],"names":[],"mappings":"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"}
|