@datalayer/agent-runtimes 1.3.28 → 1.3.30
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/lib/AgentNode.js +14 -33
- package/lib/chat/ChatFloating.d.ts +12 -2
- package/lib/chat/ChatFloating.js +196 -25
- package/lib/chat/assistant/AssistantStage.d.ts +70 -0
- package/lib/chat/assistant/AssistantStage.js +192 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +26 -0
- package/lib/chat/assistant/SpeechBalloon.js +46 -0
- package/lib/chat/assistant/SpriteCharacter.d.ts +26 -0
- package/lib/chat/assistant/SpriteCharacter.js +73 -0
- package/lib/chat/assistant/characters.d.ts +30 -0
- package/lib/chat/assistant/characters.js +41 -0
- package/lib/chat/assistant/formats/acs.d.ts +88 -0
- package/lib/chat/assistant/formats/acs.js +493 -0
- package/lib/chat/assistant/formats/agentCompression.d.ts +5 -0
- package/lib/chat/assistant/formats/agentCompression.js +92 -0
- package/lib/chat/assistant/formats/blobs.d.ts +6 -0
- package/lib/chat/assistant/formats/blobs.js +52 -0
- package/lib/chat/assistant/formats/clippy.d.ts +21 -0
- package/lib/chat/assistant/formats/clippy.js +167 -0
- package/lib/chat/assistant/formats/index.d.ts +13 -0
- package/lib/chat/assistant/formats/index.js +15 -0
- package/lib/chat/assistant/formats/objectLiteral.d.ts +23 -0
- package/lib/chat/assistant/formats/objectLiteral.js +233 -0
- package/lib/chat/assistant/formats/stateAnimations.d.ts +16 -0
- package/lib/chat/assistant/formats/stateAnimations.js +115 -0
- package/lib/chat/assistant/formats/types.d.ts +56 -0
- package/lib/chat/assistant/formats/types.js +11 -0
- package/lib/chat/assistant/state.d.ts +62 -0
- package/lib/chat/assistant/state.js +116 -0
- package/lib/chat/base/ChatBase.js +41 -32
- package/lib/chat/base/modelChoice.d.ts +13 -0
- package/lib/chat/base/modelChoice.js +13 -0
- package/lib/chat/header/ChatViewModeToggle.js +6 -3
- package/lib/{loop/plugins/chat → chat/presence}/Presence.js +1 -1
- package/lib/{loop/plugins/chat → chat/presence}/presenceStatus.d.ts +7 -1
- package/lib/{loop/plugins/chat → chat/presence}/presenceStatus.js +18 -0
- package/lib/chat/viewModes.d.ts +6 -0
- package/lib/chat/viewModes.js +10 -1
- package/lib/examples/ChatAssistantExample.d.ts +8 -0
- package/lib/examples/ChatAssistantExample.js +59 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/main.js +3 -0
- package/lib/loop/apps/AppRenderer.d.ts +34 -2
- package/lib/loop/apps/AppRenderer.js +96 -29
- package/lib/loop/apps/agent.d.ts +15 -0
- package/lib/loop/apps/agent.js +42 -0
- package/lib/loop/apps/appspec.d.ts +5 -1
- package/lib/loop/apps/appspec.js +21 -1
- package/lib/loop/apps/checks.js +3 -1
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/core/index.d.ts +23 -0
- package/lib/loop/core/index.js +1 -0
- package/lib/loop/embed/AppEmbed.d.ts +42 -0
- package/lib/loop/embed/AppEmbed.js +205 -0
- package/lib/loop/embed/datalayer-app.d.ts +1 -0
- package/lib/loop/embed/datalayer-app.js +16 -0
- package/lib/loop/embed/element.d.ts +57 -0
- package/lib/loop/embed/element.js +405 -0
- package/lib/loop/embed/embedConfig.d.ts +120 -0
- package/lib/loop/embed/embedConfig.js +148 -0
- package/lib/loop/embed/embedTheme.d.ts +24 -0
- package/lib/loop/embed/embedTheme.js +83 -0
- package/lib/loop/index.d.ts +6 -2
- package/lib/loop/index.js +8 -2
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +21 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +34 -6
- package/lib/loop/plugins/agent-capacity/index.d.ts +4 -0
- package/lib/loop/plugins/agent-capacity/index.js +3 -1
- package/lib/loop/plugins/agents/switchable.d.ts +7 -0
- package/lib/loop/plugins/agents/switchable.js +91 -88
- package/lib/loop/plugins/app-page/AppPage.d.ts +16 -0
- package/lib/loop/plugins/app-page/AppPage.js +53 -0
- package/lib/loop/plugins/app-page/appPage.d.ts +180 -0
- package/lib/loop/plugins/app-page/appPage.js +443 -0
- package/lib/loop/plugins/app-page/index.d.ts +8 -0
- package/lib/loop/plugins/app-page/index.js +34 -0
- package/lib/loop/plugins/assistant-characters/index.d.ts +30 -0
- package/lib/loop/plugins/assistant-characters/index.js +41 -0
- package/lib/loop/plugins/chat/ChatView.js +50 -39
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +31 -0
- package/lib/loop/plugins/page-layout/SplitLayout.js +129 -0
- package/lib/loop/plugins/page-layout/index.d.ts +13 -0
- package/lib/loop/plugins/page-layout/index.js +15 -2
- package/lib/loop/presets.d.ts +12 -0
- package/lib/loop/presets.js +5 -3
- package/lib/specs/actions.js +14 -0
- package/lib/specs/agents/agents.js +4 -0
- package/lib/specs/apps.d.ts +13 -1
- package/lib/specs/apps.js +2715 -373
- package/lib/specs/appspecSchema.js +21 -3
- package/lib/types/agentspecs.d.ts +21 -1
- package/lib/types/chat.d.ts +1 -1
- package/lib/types/config.d.ts +12 -0
- package/package.json +2 -1
- package/scripts/codegen/generate_agents.py +50 -2
- package/scripts/codegen/generate_apps.py +36 -6
- /package/lib/{loop/plugins/chat → chat/presence}/Presence.d.ts +0 -0
|
@@ -8,13 +8,13 @@ import { computed, signal } from '@datalayer/reactor';
|
|
|
8
8
|
const NO_STANCE = signal(undefined);
|
|
9
9
|
import { useContributions, useSignalValue, useGate, useReactorPlatform, ReactorLazy, ReactorSlot, useSlotComponents, } from '@datalayer/reactor/react';
|
|
10
10
|
import { ChatBase } from '../../../chat/base/ChatBase';
|
|
11
|
-
import { readServerCatalogue } from '../../../chat/base/modelChoice';
|
|
11
|
+
import { offeredModels as offeredModelsFor, readServerCatalogue, } from '../../../chat/base/modelChoice';
|
|
12
12
|
import { SUGGESTION_CHIP_WIDTH } from '../../../chat/display/EmptyState';
|
|
13
13
|
import { AnonymousKeyExpired } from '@datalayer/core/lib/components/anonymous/AnonymousKeyExpired';
|
|
14
14
|
import { useAnonymousSessionStore } from '../../../runtimes/browser/anonymousToken';
|
|
15
15
|
import { browserProtocolConfig } from '../../../runtimes/browser';
|
|
16
16
|
import { useBrowserInference } from '../../../hooks/useBrowserInference';
|
|
17
|
-
import {
|
|
17
|
+
import { agentInstructions, inPageAgentRefusal, resolveAgentspec, } from '../../apps/agent';
|
|
18
18
|
import { targetRunsAgentInPage, } from '../agents/switchable';
|
|
19
19
|
import { agentIcon } from '../agents/agentIcons';
|
|
20
20
|
import { specSubagents } from '../agents/specSubagents';
|
|
@@ -43,8 +43,8 @@ import { turnWritersOf } from './turnState';
|
|
|
43
43
|
import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView, } from './chatViewTools';
|
|
44
44
|
import { useAgentCommandTools } from '../../../tools/adapters/commands';
|
|
45
45
|
import { NONE_EDITOR, seedEditorChoice, setEditorOptions, } from '../shell/editorChoice';
|
|
46
|
-
import { PresenceFace, PresenceLine } from '
|
|
47
|
-
import { presenceState } from '
|
|
46
|
+
import { PresenceFace, PresenceLine } from '../../../chat/presence/Presence';
|
|
47
|
+
import { presenceState, presenceToolOf, } from '../../../chat/presence/presenceStatus';
|
|
48
48
|
/** No editor: the conversation on its own. */
|
|
49
49
|
const NO_SURFACE = '';
|
|
50
50
|
/**
|
|
@@ -212,18 +212,7 @@ export default function ChatView({ workspace }) {
|
|
|
212
212
|
*/
|
|
213
213
|
const handleDisplayItemsChange = useCallback((items) => {
|
|
214
214
|
// The newest tool call, for an application's line of status (T-08).
|
|
215
|
-
|
|
216
|
-
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
217
|
-
const item = items[index];
|
|
218
|
-
if (typeof item.toolName === 'string' && item.toolCallId) {
|
|
219
|
-
newest = item;
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const open = !!newest && newest.status !== 'complete' && newest.status !== 'error';
|
|
224
|
-
const pendingApproval = open &&
|
|
225
|
-
newest?.result
|
|
226
|
-
?.pending_approval === true;
|
|
215
|
+
const { open, pendingApproval } = presenceToolOf(items);
|
|
227
216
|
setPresenceTool(previous => previous.open === open && previous.pendingApproval === pendingApproval
|
|
228
217
|
? previous
|
|
229
218
|
: { open, pendingApproval });
|
|
@@ -666,9 +655,23 @@ export default function ChatView({ workspace }) {
|
|
|
666
655
|
* icon for want of this lookup — it used the document tools and called
|
|
667
656
|
* nobody.
|
|
668
657
|
*/
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
|
|
658
|
+
const blueprint = useContributions(LoopAgentBlueprint)[0]?.value;
|
|
659
|
+
const blueprintSpecId = blueprint?.specId;
|
|
660
|
+
/*
|
|
661
|
+
* An agent of the catalogue or a Cog's, as a runtime resolves it: an
|
|
662
|
+
* application on a Cog looked up among the agentspecs alone was turned in
|
|
663
|
+
* the page with no instructions at all (LOOP H-01).
|
|
664
|
+
*/
|
|
665
|
+
const spec = resolveAgentspec(member?.specId ?? agentId) ??
|
|
666
|
+
(blueprintSpecId ? resolveAgentspec(blueprintSpecId) : undefined);
|
|
667
|
+
/*
|
|
668
|
+
* The blueprint's words and model — an application's — on top of its
|
|
669
|
+
* spec's, for the agent it contributes; never for a team's member.
|
|
670
|
+
*/
|
|
671
|
+
const blueprintTurn = member ? undefined : blueprint;
|
|
672
|
+
// Turned in the page, an agent that resolves to nothing is refused rather
|
|
673
|
+
// than run bare.
|
|
674
|
+
const inPageRefusal = inPage && !member ? inPageAgentRefusal(blueprintSpecId, spec) : undefined;
|
|
672
675
|
/*
|
|
673
676
|
* The team's name for a member first, then the spec's own.
|
|
674
677
|
*
|
|
@@ -956,12 +959,12 @@ export default function ChatView({ workspace }) {
|
|
|
956
959
|
* for something that was never theirs.
|
|
957
960
|
*/
|
|
958
961
|
const expiredKeyIsTemporary = !signedInUser;
|
|
959
|
-
const chatDisabled = gateBlocked || keyExpired;
|
|
962
|
+
const chatDisabled = gateBlocked || keyExpired || Boolean(inPageRefusal);
|
|
960
963
|
const disabledReason = keyExpired
|
|
961
964
|
? expiredKeyIsTemporary
|
|
962
965
|
? 'This demo runs on a shared key, and its time is up. Sign in to keep going.'
|
|
963
966
|
: 'Your key has expired. Sign in to keep going.'
|
|
964
|
-
: gateReason;
|
|
967
|
+
: (inPageRefusal ?? gateReason);
|
|
965
968
|
/*
|
|
966
969
|
* The prompt's text, held here rather than inside `InputPrompt`.
|
|
967
970
|
*
|
|
@@ -992,7 +995,7 @@ export default function ChatView({ workspace }) {
|
|
|
992
995
|
* the member being addressed changes.
|
|
993
996
|
*/
|
|
994
997
|
const [pickedModel, setPickedModel] = useState();
|
|
995
|
-
const activeModel = pickedModel ?? spec?.model ?? workspace.model ?? '';
|
|
998
|
+
const activeModel = pickedModel ?? blueprintTurn?.model ?? spec?.model ?? workspace.model ?? '';
|
|
996
999
|
/*
|
|
997
1000
|
* The team, in the shape the footer asks for.
|
|
998
1001
|
*
|
|
@@ -1082,7 +1085,7 @@ export default function ChatView({ workspace }) {
|
|
|
1082
1085
|
const protocol = useMemo(() => inPage
|
|
1083
1086
|
? browserProtocolConfig({
|
|
1084
1087
|
agentId: member?.specId ?? agentId,
|
|
1085
|
-
instructions: spec?.
|
|
1088
|
+
instructions: agentInstructions(spec, blueprintTurn?.instructions),
|
|
1086
1089
|
model: activeModel || undefined,
|
|
1087
1090
|
inference,
|
|
1088
1091
|
// Who this member may hand work to, and what they are told. Both
|
|
@@ -1122,6 +1125,7 @@ export default function ChatView({ workspace }) {
|
|
|
1122
1125
|
agentTools,
|
|
1123
1126
|
team,
|
|
1124
1127
|
spec,
|
|
1128
|
+
blueprintTurn,
|
|
1125
1129
|
activeModel,
|
|
1126
1130
|
agentServerUrl,
|
|
1127
1131
|
]);
|
|
@@ -1139,7 +1143,8 @@ export default function ChatView({ workspace }) {
|
|
|
1139
1143
|
const configQuery = useConfig(!inPage, protocol.type === 'ag-ui' ? protocol.configEndpoint : undefined, undefined, agentId);
|
|
1140
1144
|
const skillsQuery = useSkills(true);
|
|
1141
1145
|
const skillActions = useSkillActions(agentId);
|
|
1142
|
-
|
|
1146
|
+
// `undefined` until the runtime answers: its answer is then the list.
|
|
1147
|
+
const [catalogModels, setCatalogModels] = useState(undefined);
|
|
1143
1148
|
useEffect(() => {
|
|
1144
1149
|
/*
|
|
1145
1150
|
* Only where there is a server to ask.
|
|
@@ -1154,23 +1159,31 @@ export default function ChatView({ workspace }) {
|
|
|
1154
1159
|
return undefined;
|
|
1155
1160
|
}
|
|
1156
1161
|
let cancelled = false;
|
|
1157
|
-
|
|
1158
|
-
|
|
1162
|
+
// The agent's own models (its model and model_additionals), each saying
|
|
1163
|
+
// whether its inference serves it.
|
|
1164
|
+
void fetch(`${agentServerUrl}/api/v1/configure/models?agent_id=${encodeURIComponent(agentId)}`)
|
|
1165
|
+
.then(response => (response.ok ? response.json() : null))
|
|
1159
1166
|
.then(payload => {
|
|
1160
1167
|
if (!cancelled) {
|
|
1161
1168
|
// In the chat's shape: the route says `available`, the menu and
|
|
1162
1169
|
// the opening pick read `isAvailable`, and an unmapped row passes
|
|
1163
1170
|
// for usable. See `readServerCatalogue`.
|
|
1164
|
-
|
|
1171
|
+
// A runtime that did not answer offers nothing: the agentspecs
|
|
1172
|
+
// catalogue is not its answer.
|
|
1173
|
+
setCatalogModels(payload ? readServerCatalogue(payload) : []);
|
|
1165
1174
|
}
|
|
1166
1175
|
})
|
|
1167
1176
|
// No catalogue is not an error worth a banner: the menu simply has
|
|
1168
1177
|
// nothing to offer, and the agent answers on whatever it was given.
|
|
1169
|
-
.catch(() =>
|
|
1178
|
+
.catch(() => {
|
|
1179
|
+
if (!cancelled) {
|
|
1180
|
+
setCatalogModels([]);
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1170
1183
|
return () => {
|
|
1171
1184
|
cancelled = true;
|
|
1172
1185
|
};
|
|
1173
|
-
}, [agentServerUrl, inPage]);
|
|
1186
|
+
}, [agentServerUrl, agentId, inPage]);
|
|
1174
1187
|
/*
|
|
1175
1188
|
* What to offer when the server has no catalogue of its own.
|
|
1176
1189
|
*
|
|
@@ -1179,16 +1192,14 @@ export default function ChatView({ workspace }) {
|
|
|
1179
1192
|
* with nothing in it is a control that opens onto an empty list — so the
|
|
1180
1193
|
* agentspecs catalogue stands in, filtered to the models marked available.
|
|
1181
1194
|
*/
|
|
1182
|
-
const offeredModels = useMemo(() => catalogModels
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
provider: model.provider,
|
|
1191
|
-
})), [catalogModels]);
|
|
1195
|
+
const offeredModels = useMemo(() => offeredModelsFor(catalogModels, () => Object.values(AI_MODEL_CATALOGUE)
|
|
1196
|
+
// A typed-judgment model answers typed questions, not a chat.
|
|
1197
|
+
.filter(model => model.available && isChatModel(model))
|
|
1198
|
+
.map(model => ({
|
|
1199
|
+
id: model.id,
|
|
1200
|
+
name: model.name,
|
|
1201
|
+
provider: model.provider,
|
|
1202
|
+
}))), [catalogModels]);
|
|
1192
1203
|
/*
|
|
1193
1204
|
* The tools the agent can call, from both sides.
|
|
1194
1205
|
*
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The split: the conversation and the work side by side, one hairline
|
|
3
|
+
* between them that a person drags (LOOP T-07, an Appspec's `layout: split`).
|
|
4
|
+
*
|
|
5
|
+
* The page layout's other arrangement. Where `page` puts the work on a sheet
|
|
6
|
+
* and the conversation in a panel that opens when wanted, `split` keeps both
|
|
7
|
+
* on screen: the conversation on the left — its transcript with the composer
|
|
8
|
+
* under it — and the work on the right, the way the reference screens show a
|
|
9
|
+
* mail, a document or the agent's computer beside the chat.
|
|
10
|
+
*
|
|
11
|
+
* The hairline is a separator a person can move with the pointer or the
|
|
12
|
+
* keyboard (arrows by a step, Home and End to the bounds); the share it
|
|
13
|
+
* leaves the conversation is kept while the workspace is mounted.
|
|
14
|
+
*
|
|
15
|
+
* @module loop/plugins/page-layout/SplitLayout
|
|
16
|
+
*/
|
|
17
|
+
import type { JSX } from 'react';
|
|
18
|
+
import type { ChatLayoutParts } from '../../core';
|
|
19
|
+
/** The conversation's share of the width when the split opens. */
|
|
20
|
+
export declare const SPLIT_DEFAULT_SHARE = 0.4;
|
|
21
|
+
/** The narrowest and widest the conversation may be made. */
|
|
22
|
+
export declare const SPLIT_MIN_SHARE = 0.2;
|
|
23
|
+
export declare const SPLIT_MAX_SHARE = 0.8;
|
|
24
|
+
/** How far an arrow key moves the hairline. */
|
|
25
|
+
export declare const SPLIT_KEY_STEP = 0.05;
|
|
26
|
+
/** A share, held within the bounds. */
|
|
27
|
+
export declare function clampSplitShare(share: number): number;
|
|
28
|
+
/** Where a key moves the hairline, or `null` for a key it ignores. */
|
|
29
|
+
export declare function splitShareForKey(share: number, key: string): number | null;
|
|
30
|
+
export declare function SplitLayout({ editors, hasEditor, transcript, prompt, chips, picker, transient, }: ChatLayoutParts): JSX.Element;
|
|
31
|
+
export default SplitLayout;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useRef, useState } from 'react';
|
|
3
|
+
import { Box } from '@datalayer/primer-addons';
|
|
4
|
+
/** The conversation's share of the width when the split opens. */
|
|
5
|
+
export const SPLIT_DEFAULT_SHARE = 0.4;
|
|
6
|
+
/** The narrowest and widest the conversation may be made. */
|
|
7
|
+
export const SPLIT_MIN_SHARE = 0.2;
|
|
8
|
+
export const SPLIT_MAX_SHARE = 0.8;
|
|
9
|
+
/** How far an arrow key moves the hairline. */
|
|
10
|
+
export const SPLIT_KEY_STEP = 0.05;
|
|
11
|
+
/** A share, held within the bounds. */
|
|
12
|
+
export function clampSplitShare(share) {
|
|
13
|
+
if (!Number.isFinite(share)) {
|
|
14
|
+
return SPLIT_DEFAULT_SHARE;
|
|
15
|
+
}
|
|
16
|
+
return Math.min(SPLIT_MAX_SHARE, Math.max(SPLIT_MIN_SHARE, share));
|
|
17
|
+
}
|
|
18
|
+
/** Where a key moves the hairline, or `null` for a key it ignores. */
|
|
19
|
+
export function splitShareForKey(share, key) {
|
|
20
|
+
switch (key) {
|
|
21
|
+
case 'ArrowLeft':
|
|
22
|
+
return clampSplitShare(share - SPLIT_KEY_STEP);
|
|
23
|
+
case 'ArrowRight':
|
|
24
|
+
return clampSplitShare(share + SPLIT_KEY_STEP);
|
|
25
|
+
case 'Home':
|
|
26
|
+
return SPLIT_MIN_SHARE;
|
|
27
|
+
case 'End':
|
|
28
|
+
return SPLIT_MAX_SHARE;
|
|
29
|
+
default:
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function SplitLayout({ editors, hasEditor, transcript, prompt, chips, picker, transient, }) {
|
|
34
|
+
const [share, setShare] = useState(SPLIT_DEFAULT_SHARE);
|
|
35
|
+
const row = useRef(null);
|
|
36
|
+
const dragging = useRef(false);
|
|
37
|
+
const shareAt = useCallback((clientX) => {
|
|
38
|
+
const box = row.current?.getBoundingClientRect();
|
|
39
|
+
if (!box || box.width <= 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return clampSplitShare((clientX - box.left) / box.width);
|
|
43
|
+
}, []);
|
|
44
|
+
const onPointerDown = (event) => {
|
|
45
|
+
dragging.current = true;
|
|
46
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
};
|
|
49
|
+
const onPointerMove = (event) => {
|
|
50
|
+
if (!dragging.current) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const next = shareAt(event.clientX);
|
|
54
|
+
if (next !== null) {
|
|
55
|
+
setShare(next);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const onPointerUp = (event) => {
|
|
59
|
+
dragging.current = false;
|
|
60
|
+
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
61
|
+
};
|
|
62
|
+
const onKeyDown = (event) => {
|
|
63
|
+
const next = splitShareForKey(share, event.key);
|
|
64
|
+
if (next !== null) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
setShare(next);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const percent = Math.round(share * 100);
|
|
70
|
+
return (_jsxs(Box, { "data-testid": "loop-split-layout", sx: {
|
|
71
|
+
flex: '1 1 auto',
|
|
72
|
+
minHeight: 0,
|
|
73
|
+
display: 'flex',
|
|
74
|
+
flexDirection: 'column',
|
|
75
|
+
}, children: [picker, _jsxs(Box, { ref: row, sx: {
|
|
76
|
+
flex: '1 1 auto',
|
|
77
|
+
minHeight: 0,
|
|
78
|
+
display: 'flex',
|
|
79
|
+
// The hidden editors position themselves against this row.
|
|
80
|
+
position: 'relative',
|
|
81
|
+
overflow: 'hidden',
|
|
82
|
+
}, children: [_jsxs(Box, { "data-testid": "loop-split-conversation", sx: {
|
|
83
|
+
flex: hasEditor ? `0 0 ${percent}%` : '1 1 auto',
|
|
84
|
+
minWidth: 0,
|
|
85
|
+
minHeight: 0,
|
|
86
|
+
display: 'flex',
|
|
87
|
+
flexDirection: 'column',
|
|
88
|
+
// The transcript draws a rule on its left when an editor is on
|
|
89
|
+
// screen, for the split it sits on the right of. Here it is on
|
|
90
|
+
// the left, and the hairline is the separator: the rule is
|
|
91
|
+
// pushed out of the row, which clips it.
|
|
92
|
+
ml: hasEditor ? '-1px' : 0,
|
|
93
|
+
}, children: [transcript, chips, prompt] }), hasEditor ? (_jsx(Box, { role: "separator", "aria-orientation": "vertical", "aria-label": "Resize the conversation and the page", "aria-valuemin": Math.round(SPLIT_MIN_SHARE * 100), "aria-valuemax": Math.round(SPLIT_MAX_SHARE * 100), "aria-valuenow": percent, tabIndex: 0, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerUp, onKeyDown: onKeyDown, sx: {
|
|
94
|
+
// A hairline to the eye, a few pixels to the pointer.
|
|
95
|
+
flex: '0 0 7px',
|
|
96
|
+
mx: '-3px',
|
|
97
|
+
zIndex: 1,
|
|
98
|
+
cursor: 'col-resize',
|
|
99
|
+
touchAction: 'none',
|
|
100
|
+
display: 'flex',
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
outline: 'none',
|
|
103
|
+
'&::before': {
|
|
104
|
+
content: '""',
|
|
105
|
+
width: '1px',
|
|
106
|
+
height: '100%',
|
|
107
|
+
bg: 'border.default',
|
|
108
|
+
},
|
|
109
|
+
'&:hover::before, &:focus-visible::before': {
|
|
110
|
+
width: '3px',
|
|
111
|
+
bg: 'accent.emphasis',
|
|
112
|
+
},
|
|
113
|
+
} })) : null, _jsx(Box, { "data-testid": "loop-split-page", sx: hasEditor
|
|
114
|
+
? {
|
|
115
|
+
flex: '1 1 0',
|
|
116
|
+
minWidth: 0,
|
|
117
|
+
minHeight: 0,
|
|
118
|
+
display: 'flex',
|
|
119
|
+
}
|
|
120
|
+
: // Mounted but out of sight: the editors keep their place.
|
|
121
|
+
{
|
|
122
|
+
position: 'absolute',
|
|
123
|
+
inset: 0,
|
|
124
|
+
visibility: 'hidden',
|
|
125
|
+
pointerEvents: 'none',
|
|
126
|
+
zIndex: -1,
|
|
127
|
+
}, children: editors })] }), transient] }));
|
|
128
|
+
}
|
|
129
|
+
export default SplitLayout;
|
|
@@ -3,10 +3,23 @@ import type { ChatViewMode } from '../../../types/chat';
|
|
|
3
3
|
import { type TurnPanelFooter } from './TurnPanel';
|
|
4
4
|
export { pageLayoutPanelOpen, pageLayoutSheet, openPagePanel as openConversationPanel, } from '@datalayer/primer-addons/lib/reactor';
|
|
5
5
|
export type { TurnPanelFooter } from './TurnPanel';
|
|
6
|
+
export { SplitLayout, SPLIT_DEFAULT_SHARE, SPLIT_KEY_STEP, SPLIT_MAX_SHARE, SPLIT_MIN_SHARE, clampSplitShare, splitShareForKey, } from './SplitLayout';
|
|
6
7
|
export type { PageSize, PageSizeFormat, } from '@datalayer/primer-addons/lib/reactor';
|
|
7
8
|
export declare const LOOP_PAGE_LAYOUT_PLUGIN_NAME = "@datalayer/loop-plugin-page-layout";
|
|
8
9
|
/** What a host may set on the page layout. */
|
|
9
10
|
export type LoopPageLayoutConfig = {
|
|
11
|
+
/**
|
|
12
|
+
* How the parts are arranged.
|
|
13
|
+
*
|
|
14
|
+
* `page` (the default): the work on a centred sheet, the composer over
|
|
15
|
+
* it, the conversation in a panel that opens when wanted. `split`: the
|
|
16
|
+
* conversation and the work side by side, the conversation on the left
|
|
17
|
+
* with the composer under it, one hairline between them that a person
|
|
18
|
+
* drags (`SplitLayout`) — an Appspec's `layout: split`. The split keeps
|
|
19
|
+
* the conversation on screen, so it has no turn panel, no conversation
|
|
20
|
+
* toggle and no display modes; the other settings are the page's.
|
|
21
|
+
*/
|
|
22
|
+
arrangement?: 'page' | 'split';
|
|
10
23
|
/**
|
|
11
24
|
* Where the turn panel hangs on the composer.
|
|
12
25
|
*
|
|
@@ -6,12 +6,14 @@ import { PageLayout, PagePanelToggle, openPagePanel, } from '@datalayer/primer-a
|
|
|
6
6
|
import { ChatViewModeToggle } from '../../../chat/header/ChatViewModeToggle';
|
|
7
7
|
import { LoopChatLayout, LoopChatTurn, LoopPromptPanel, LoopSlots, } from '../../core';
|
|
8
8
|
import { TurnPanel } from './TurnPanel';
|
|
9
|
+
import { SplitLayout } from './SplitLayout';
|
|
9
10
|
/*
|
|
10
11
|
* The layout's state is primer-addons', re-exported under the names the
|
|
11
12
|
* Loop always had: a host that opened the conversation panel by calling
|
|
12
13
|
* `openConversationPanel()` keeps doing so.
|
|
13
14
|
*/
|
|
14
15
|
export { pageLayoutPanelOpen, pageLayoutSheet, openPagePanel as openConversationPanel, } from '@datalayer/primer-addons/lib/reactor';
|
|
16
|
+
export { SplitLayout, SPLIT_DEFAULT_SHARE, SPLIT_KEY_STEP, SPLIT_MAX_SHARE, SPLIT_MIN_SHARE, clampSplitShare, splitShareForKey, } from './SplitLayout';
|
|
15
17
|
export const LOOP_PAGE_LAYOUT_PLUGIN_NAME = '@datalayer/loop-plugin-page-layout';
|
|
16
18
|
/* A signal to read when no chat contributed a turn: the hook needs one. */
|
|
17
19
|
const NO_TURN = signal({ id: 0, status: 'idle' });
|
|
@@ -35,6 +37,8 @@ const promptStance = signal('floating-top');
|
|
|
35
37
|
* page layout to a page, the assumption that signal already makes.
|
|
36
38
|
*/
|
|
37
39
|
let floatingStance = 'floating-top';
|
|
40
|
+
/** The modes the page layout does not place. */
|
|
41
|
+
const PAGE_LAYOUT_DISABLED_MODES = ['assistant'];
|
|
38
42
|
export function pageLayoutForChatMode(mode) {
|
|
39
43
|
switch (mode) {
|
|
40
44
|
case 'sidebar':
|
|
@@ -60,7 +64,9 @@ export function setPageLayoutChatMode(mode) {
|
|
|
60
64
|
/** The toggle in the floating composer's footer. */
|
|
61
65
|
function ChatModeAction() {
|
|
62
66
|
const mode = useSignalValue(pageLayoutChatMode);
|
|
63
|
-
|
|
67
|
+
// The page layout places the chat itself: the floating assistant, which
|
|
68
|
+
// ChatFloating draws, is offered greyed out with its reason (T-21).
|
|
69
|
+
return (_jsx(ChatViewModeToggle, { value: mode, onChange: setPageLayoutChatMode, disabledModes: PAGE_LAYOUT_DISABLED_MODES }));
|
|
64
70
|
}
|
|
65
71
|
/**
|
|
66
72
|
* The chat view's parts on the page layout.
|
|
@@ -87,6 +93,7 @@ function ConversationToggle() {
|
|
|
87
93
|
export const LoopPageLayoutPlugin = definePlugin({
|
|
88
94
|
name: LOOP_PAGE_LAYOUT_PLUGIN_NAME,
|
|
89
95
|
config: {
|
|
96
|
+
arrangement: 'page',
|
|
90
97
|
turnPanel: 'below',
|
|
91
98
|
turnPanelFooter: 'full',
|
|
92
99
|
prompt: 'docked',
|
|
@@ -94,10 +101,16 @@ export const LoopPageLayoutPlugin = definePlugin({
|
|
|
94
101
|
pageSize: undefined,
|
|
95
102
|
},
|
|
96
103
|
displayName: 'Page layout',
|
|
97
|
-
description: 'The editor — or, in the chat view, the conversation — on a centred sheet, the prompt docked above it at the same width or floating over it as a draggable card, the current turn under the prompt, the conversation in a side panel.',
|
|
104
|
+
description: 'The editor — or, in the chat view, the conversation — on a centred sheet, the prompt docked above it at the same width or floating over it as a draggable card, the current turn under the prompt, the conversation in a side panel; or, split, the conversation and the work side by side with a hairline to drag.',
|
|
98
105
|
octicon: 'file',
|
|
99
106
|
emoji: '\u{1F4C4}',
|
|
100
107
|
build: ({ config, ...ctx }) => {
|
|
108
|
+
if (config.arrangement === 'split') {
|
|
109
|
+
// The conversation is on screen: the composer docked under it, and
|
|
110
|
+
// nothing that stands in for a transcript out of sight.
|
|
111
|
+
ctx.contribute(LoopChatLayout, { id: 'split-layout', prompt: 'docked', Component: SplitLayout }, { id: 'split-layout' });
|
|
112
|
+
return { components: [] };
|
|
113
|
+
}
|
|
101
114
|
/*
|
|
102
115
|
The layout, contributed per build because the composer's stance is
|
|
103
116
|
configuration: `docked-top` means the layout owns the composer's width
|
package/lib/loop/presets.d.ts
CHANGED
|
@@ -34,6 +34,11 @@ export type LoopPresetOptions = {
|
|
|
34
34
|
* A surface id rather than a fixed union, because the surfaces are
|
|
35
35
|
* contributed: an editor plugin the preset has never heard of is still a
|
|
36
36
|
* valid answer, and `'none'` is how a host asks for the chat alone.
|
|
37
|
+
*
|
|
38
|
+
* The notebook when unsaid, and none when `editors` is off. Said with
|
|
39
|
+
* `editors` off, it is honoured: the surface of a plugin the host mounts —
|
|
40
|
+
* an application's page — opens beside the chat without the notebook and
|
|
41
|
+
* the document.
|
|
37
42
|
*/
|
|
38
43
|
defaultEditor?: ChatPluginConfig['defaultSurface'];
|
|
39
44
|
/** Whether the chat offers its surface switcher. */
|
|
@@ -82,6 +87,13 @@ export type LoopPresetOptions = {
|
|
|
82
87
|
* `LoopChatLayout` point.
|
|
83
88
|
*/
|
|
84
89
|
pageLayout?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* How the page layout arranges the parts: `page` (the default), the work
|
|
92
|
+
* on a sheet with the conversation in a panel; or `split`, the
|
|
93
|
+
* conversation and the work side by side with a hairline to drag. Only
|
|
94
|
+
* read with `pageLayout`.
|
|
95
|
+
*/
|
|
96
|
+
pageLayoutArrangement?: 'page' | 'split';
|
|
85
97
|
/**
|
|
86
98
|
* Where the page layout hangs the current turn on the composer: `below`
|
|
87
99
|
* (the default), `above`, or `none`. Only read with `pageLayout`.
|
package/lib/loop/presets.js
CHANGED
|
@@ -52,12 +52,13 @@ import { LoopPageLayoutPlugin } from './plugins/page-layout';
|
|
|
52
52
|
* be installed or signed into, and the switches a demonstration wants are off.
|
|
53
53
|
*/
|
|
54
54
|
export function loopPlugins(options = {}) {
|
|
55
|
-
const { serverUrl, target = 'browser', defaultEditor
|
|
55
|
+
const { serverUrl, target = 'browser', defaultEditor: askedEditor, showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, suggestionLabels = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutArrangement = 'page', pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, datalayerAgentSpecId, datalayerCreatePayload, targetFixed, themeVariant, presence, showTokenUsage, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
|
|
56
|
+
const defaultEditor = askedEditor ?? (editors ? 'notebook' : 'none');
|
|
56
57
|
return [
|
|
57
58
|
// The chat owns the editor beside it, so which one opens is its
|
|
58
59
|
// configuration rather than the workspace's.
|
|
59
60
|
configurePlugin(ChatPlugin, {
|
|
60
|
-
defaultSurface:
|
|
61
|
+
defaultSurface: defaultEditor,
|
|
61
62
|
// The chat's strip stands down when the header selector offers the
|
|
62
63
|
// same choice; see `editorSelector` — and when there is no editor
|
|
63
64
|
// to choose.
|
|
@@ -130,13 +131,14 @@ export function loopPlugins(options = {}) {
|
|
|
130
131
|
// happens to be on is a workspace with a trap in it. The selector itself
|
|
131
132
|
// stays behind the old switch.
|
|
132
133
|
configurePlugin(ShellPlugin, {
|
|
133
|
-
defaultEditor
|
|
134
|
+
defaultEditor,
|
|
134
135
|
showSelector: editors && editorSelector,
|
|
135
136
|
}),
|
|
136
137
|
...(floatingPrompt ? [PromptPlugin] : []),
|
|
137
138
|
...(pageLayout
|
|
138
139
|
? [
|
|
139
140
|
configurePlugin(LoopPageLayoutPlugin, {
|
|
141
|
+
arrangement: pageLayoutArrangement,
|
|
140
142
|
turnPanel: pageLayoutTurnPanel,
|
|
141
143
|
turnPanelFooter: pageLayoutTurnPanelFooter,
|
|
142
144
|
prompt: pageLayoutPrompt,
|
package/lib/specs/actions.js
CHANGED
|
@@ -439,6 +439,8 @@ export const SERVER_ACTIONS = {
|
|
|
439
439
|
* to reproduce.
|
|
440
440
|
*/
|
|
441
441
|
export const APP_BEHAVIOURS = {
|
|
442
|
+
'customer-interview': {},
|
|
443
|
+
'data-quality': {},
|
|
442
444
|
'inbox-triage': {
|
|
443
445
|
'google-workspace.append_table_rows': 'leave_to_me',
|
|
444
446
|
'google-workspace.batch_modify_gmail_message_labels': 'do_it',
|
|
@@ -561,8 +563,13 @@ export const APP_BEHAVIOURS = {
|
|
|
561
563
|
'google-workspace.update_drive_file': 'leave_to_me',
|
|
562
564
|
'google-workspace.update_paragraph_style': 'leave_to_me',
|
|
563
565
|
},
|
|
566
|
+
'model-choice': {},
|
|
567
|
+
'pipeline-report': {},
|
|
564
568
|
'quote-calculator': {},
|
|
569
|
+
'report-from-a-file': {},
|
|
565
570
|
'ship-or-fix': {},
|
|
571
|
+
'supplier-comparison': {},
|
|
572
|
+
'support-desk': {},
|
|
566
573
|
'web-research': {
|
|
567
574
|
'tavily.tavily_crawl': 'do_it',
|
|
568
575
|
'tavily.tavily_extract': 'do_it',
|
|
@@ -576,6 +583,8 @@ export const APP_BEHAVIOURS = {
|
|
|
576
583
|
* application and tool, each condition and the behaviour when it holds.
|
|
577
584
|
*/
|
|
578
585
|
export const APP_ESCALATIONS = {
|
|
586
|
+
'customer-interview': {},
|
|
587
|
+
'data-quality': {},
|
|
579
588
|
'inbox-triage': {
|
|
580
589
|
'google-workspace.batch_modify_gmail_message_labels': [
|
|
581
590
|
{
|
|
@@ -610,7 +619,12 @@ export const APP_ESCALATIONS = {
|
|
|
610
619
|
},
|
|
611
620
|
],
|
|
612
621
|
},
|
|
622
|
+
'model-choice': {},
|
|
623
|
+
'pipeline-report': {},
|
|
613
624
|
'quote-calculator': {},
|
|
625
|
+
'report-from-a-file': {},
|
|
614
626
|
'ship-or-fix': {},
|
|
627
|
+
'supplier-comparison': {},
|
|
628
|
+
'support-desk': {},
|
|
615
629
|
'web-research': {},
|
|
616
630
|
};
|
|
@@ -3380,6 +3380,7 @@ export const EXAMPLE_ONE_TRIGGER_AGENTSPEC_0_0_1 = {
|
|
|
3380
3380
|
domain: undefined,
|
|
3381
3381
|
enabled: true,
|
|
3382
3382
|
model: 'bedrock:us.anthropic.claude-sonnet-4-6',
|
|
3383
|
+
modelAdditionals: ['alibaba:qwen-max'],
|
|
3383
3384
|
mcpServers: [],
|
|
3384
3385
|
skills: [
|
|
3385
3386
|
SKILL_MAP['github:0.0.1']
|
|
@@ -4389,6 +4390,7 @@ export const EXAMPLE_SIMPLE_AGENTSPEC_0_0_1 = {
|
|
|
4389
4390
|
domain: undefined,
|
|
4390
4391
|
enabled: true,
|
|
4391
4392
|
model: 'bedrock:us.anthropic.claude-sonnet-4-6',
|
|
4393
|
+
modelAdditionals: ['alibaba:qwen-max'],
|
|
4392
4394
|
mcpServers: [],
|
|
4393
4395
|
skills: [
|
|
4394
4396
|
SKILL_MAP['events:0.0.1']
|
|
@@ -5021,6 +5023,7 @@ export const JUPYTER_NOTEBOOK_COMPACTOR_AGENTSPEC_0_0_1 = {
|
|
|
5021
5023
|
domain: undefined,
|
|
5022
5024
|
enabled: true,
|
|
5023
5025
|
model: 'bedrock:us.anthropic.claude-sonnet-4-6',
|
|
5026
|
+
modelAdditionals: ['alibaba:qwen-max'],
|
|
5024
5027
|
mcpServers: [],
|
|
5025
5028
|
skills: [].filter(Boolean),
|
|
5026
5029
|
tools: [TOOL_MAP['runtime-echo:0.0.1']],
|
|
@@ -7663,6 +7666,7 @@ export const WORKER_CRAWLER_AGENTSPEC_0_0_1 = {
|
|
|
7663
7666
|
domain: 'market-analyst',
|
|
7664
7667
|
enabled: true,
|
|
7665
7668
|
model: 'bedrock:us.anthropic.claude-sonnet-4-6',
|
|
7669
|
+
modelAdditionals: ['alibaba:qwen-max'],
|
|
7666
7670
|
mcpServers: [MCP_SERVER_MAP['tavily:0.0.1']],
|
|
7667
7671
|
skills: [
|
|
7668
7672
|
SKILL_MAP['github:0.0.1']
|
package/lib/specs/apps.d.ts
CHANGED
|
@@ -7,10 +7,17 @@
|
|
|
7
7
|
* This file is AUTO-GENERATED from YAML specifications.
|
|
8
8
|
* DO NOT EDIT MANUALLY - run 'make specs' to regenerate.
|
|
9
9
|
*/
|
|
10
|
-
import type { AppKind, AppSpec } from '../types/agentspecs';
|
|
10
|
+
import type { AppBuilt, AppKind, AppSpec } from '../types/agentspecs';
|
|
11
|
+
export declare const CUSTOMER_INTERVIEW_APP_0_0_1: AppSpec;
|
|
12
|
+
export declare const DATA_QUALITY_APP_0_0_1: AppSpec;
|
|
11
13
|
export declare const INBOX_TRIAGE_APP_0_0_1: AppSpec;
|
|
14
|
+
export declare const MODEL_CHOICE_APP_0_0_1: AppSpec;
|
|
15
|
+
export declare const PIPELINE_REPORT_APP_0_0_1: AppSpec;
|
|
12
16
|
export declare const QUOTE_CALCULATOR_APP_0_0_1: AppSpec;
|
|
17
|
+
export declare const REPORT_FROM_A_FILE_APP_0_0_1: AppSpec;
|
|
13
18
|
export declare const SHIP_OR_FIX_APP_0_0_1: AppSpec;
|
|
19
|
+
export declare const SUPPLIER_COMPARISON_APP_0_0_1: AppSpec;
|
|
20
|
+
export declare const SUPPORT_DESK_APP_0_0_1: AppSpec;
|
|
14
21
|
export declare const WEB_RESEARCH_APP_0_0_1: AppSpec;
|
|
15
22
|
export declare const APP_CATALOGUE: Record<string, AppSpec>;
|
|
16
23
|
/** An application, by `id` or `id:version`, or undefined. */
|
|
@@ -21,5 +28,10 @@ export declare function getApp(ref: string): AppSpec | undefined;
|
|
|
21
28
|
* Appspec have to give back.
|
|
22
29
|
*/
|
|
23
30
|
export declare const APP_SOURCES: Record<string, Record<string, unknown>>;
|
|
31
|
+
/**
|
|
32
|
+
* How each application was built: `python` (its `app.py`), `canvas` (its
|
|
33
|
+
* page composed on the Canvas) or `written` (its spec written out).
|
|
34
|
+
*/
|
|
35
|
+
export declare const APP_BUILT: Record<string, AppBuilt>;
|
|
24
36
|
/** Every application of the catalogue, or those of a kind. */
|
|
25
37
|
export declare function listApps(kind?: AppKind): AppSpec[];
|