@schlenkr/ragents 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/server/src/plugin-support/language-server/contract.ts +6 -1
- package/apps/server/src/plugin-support/language-server/tools.ts +29 -4
- package/apps/server/src/plugin-support/run-scripts.ts +4 -1
- package/apps/server/src/plugin-support/start-entries-contract.ts +4 -3
- package/apps/server/src/plugin-support/workspace-sandbox-host.ts +2 -0
- package/apps/web/src/App.tsx +0 -10
- package/apps/web/src/PluginChat.tsx +8 -8
- package/apps/web/src/PluginRegistry.tsx +0 -12
- package/apps/web/src/StartSurface.tsx +2 -2
- package/apps/web/src/ThemeSettings.tsx +1 -1
- package/apps/web/src/column/ColumnApp.tsx +83 -7
- package/apps/web/src/column/clipboard.ts +64 -0
- package/apps/web/src/column/host-contract.ts +10 -0
- package/apps/web/src/column.tsx +2 -0
- package/apps/web/src/panel/LoginForm.tsx +9 -3
- package/apps/web/src/panel/OverviewPage.tsx +187 -62
- package/apps/web/src/panel/PanelDialogs.tsx +115 -0
- package/apps/web/src/panel/PanelPage.tsx +1 -1
- package/apps/web/src/panel/SettingsPage.tsx +75 -79
- package/apps/web/src/panel/contract.ts +17 -9
- package/apps/web/src/panel/target-state.ts +27 -2
- package/apps/web/src/ui/theme.css +4 -0
- package/package.json +2 -2
- package/packages/ragents/src/plugin-host.ts +5 -2
- package/packages/ragents/src/plugin-types.ts +3 -2
- package/packages/workspace-executor/src/context.ts +4 -0
- package/packages/workspace-executor/src/executor.ts +9 -2
- package/packages/workspace-executor/src/index.ts +2 -0
- package/packages/workspace-executor/src/language-server/adapters/fsharp.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/roslyn.ts +1 -0
- package/packages/workspace-executor/src/language-server/adapters/typescript.ts +34 -6
- package/packages/workspace-executor/src/language-server/contract.ts +7 -3
- package/packages/workspace-executor/src/language-server/host.ts +275 -138
- package/plugins/ragents.actor-programs/contract.ts +1 -1
- package/plugins/ragents.actor-programs/prompt.hbs +4 -4
- package/plugins/ragents.actor-programs/server/app-project.ts +0 -2
- package/plugins/ragents.actor-programs/server/runtime.ts +1 -1
- package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -5
- package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +7 -39
- package/plugins/ragents.actor-programs/web/api.ts +1 -10
- package/plugins/ragents.lsp-roslyn/web/language-server-plugin.tsx +73 -44
- package/plugins/ragents.orchestration/canvas.hbs +28 -44
- package/plugins/ragents.orchestration/contract.ts +17 -287
- package/plugins/ragents.orchestration/server/canvas-tool.ts +18 -123
- package/plugins/ragents.orchestration/tiled-layout.ts +12 -4
- package/plugins/ragents.orchestration/web/ActorCanvasControls.tsx +23 -47
- package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +3 -9
- package/plugins/ragents.orchestration/web/ActorShortcut.tsx +3 -5
- package/plugins/ragents.orchestration/web/ActorTile.tsx +3 -2
- package/plugins/ragents.orchestration/web/CanvasTiles.tsx +3 -3
- package/plugins/ragents.orchestration/web/Center.tsx +28 -463
- package/plugins/ragents.orchestration/web/MaterialBody.tsx +6 -87
- package/plugins/ragents.orchestration/web/TiledCanvas.tsx +2 -3
- package/plugins/ragents.orchestration/web/canvas-view-settings.ts +7 -13
- package/plugins/ragents.orchestration/web/column/RunColumn.tsx +44 -19
- package/plugins/ragents.orchestration/web/column/column-state.ts +13 -4
- package/plugins/ragents.orchestration/web/constants.ts +0 -2
- package/plugins/ragents.orchestration/web/index.tsx +0 -6
- package/plugins/ragents.orchestration/web/tile-docking.ts +7 -1
- package/plugins/ragents.orchestration/web/tiled-view-settings.ts +7 -9
- package/plugins/ragents.overseer/server/coordinator-prompt.ts +1 -1
- package/plugins/ragents.reference/examples.ts +1 -2
- package/plugins/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/actors/balcony-app/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -1
- package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +2 -2
- package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -4
- package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +9 -15
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +5 -3
- package/plugins/ragents.reference/run-scripts/learning-afternoon/package.json +1 -1
- package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +14 -4
- package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +1 -1
- package/plugins/ragents.reference/skills/210-moderator-handover/SKILL.md +1 -1
- package/plugins/ragents.reference/walkthroughs.ts +14 -14
- package/plugins/ragents.workspace/client/workspace-client.ts +4 -1
- package/scripts/package/publish-package.ts +4 -5
- package/scripts/workspace-client/run-workspace-client.ts +3 -1
- package/apps/web/src/MaterialSettings.tsx +0 -40
- package/apps/web/src/material-settings.ts +0 -104
- package/plugins/ragents.orchestration/web/ActorNet.tsx +0 -607
- package/plugins/ragents.orchestration/web/CardSizeSettings.tsx +0 -42
- package/plugins/ragents.orchestration/web/ScriptActorCard.tsx +0 -56
- package/plugins/ragents.orchestration/web/ZoomSettings.tsx +0 -38
- package/plugins/ragents.orchestration/web/canvas-layout-storage.ts +0 -39
- package/plugins/ragents.orchestration/web/canvas-layout.ts +0 -326
- package/plugins/ragents.orchestration/web/canvas-scene.ts +0 -311
- package/plugins/ragents.orchestration/web/card-size-settings.ts +0 -36
- package/plugins/ragents.orchestration/web/keyboard-navigation.ts +0 -55
- package/plugins/ragents.orchestration/web/material-layout.ts +0 -14
- package/plugins/ragents.orchestration/web/useCanvasMeasurements.ts +0 -78
- package/plugins/ragents.orchestration/web/zoom-settings.ts +0 -28
- package/plugins/ragents.reference/skills/120-canvas-conversation-circle/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/130-canvas-pipeline-with-approval/SKILL.md +0 -12
- package/plugins/ragents.reference/skills/140-canvas-team-tree-and-topic-grid/SKILL.md +0 -12
|
@@ -1,607 +0,0 @@
|
|
|
1
|
-
import { withMaterialSpacing } from "./material-layout";
|
|
2
|
-
import { MaterialBody, materialCardClass, materialHeadClass, materialIconClass, materialTitleClass } from "./MaterialBody";
|
|
3
|
-
import { MATERIAL_DEPTH_PER_STEP, useMaterialSettings } from "@aicontainer/web/material-settings";
|
|
4
|
-
import { canvasLayoutStorageKey, parseCanvasElementSizes } from "./canvas-layout-storage";
|
|
5
|
-
import { Button, Card, SvgEdge, cn } from "@aicontainer/web/ui";
|
|
6
|
-
import { WorkingScenes } from "@aicontainer/web/chat/WorkingScenes";
|
|
7
|
-
import { useEffect, useLayoutEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from "react";
|
|
8
|
-
import { ORCHESTRATION_PLUGIN_ID, canvasLayoutOf, type CanvasLayout, type CanvasShape } from "../contract";
|
|
9
|
-
import { cardSectionsClass } from "./constants";
|
|
10
|
-
import { layoutScene, lineGeometry, withLabelSpacing, type PlacedBox } from "./canvas-layout";
|
|
11
|
-
import { boundaryOf, buildScene, type SceneApp } from "./canvas-scene";
|
|
12
|
-
import { actorSurface, isPendingRunActorInput, type RunActor, type RunView } from "./run-view";
|
|
13
|
-
import { useCanvasMeasurements } from "./useCanvasMeasurements";
|
|
14
|
-
import { ActorChatPreview } from "./ActorChatPreview";
|
|
15
|
-
import { ScriptActorCard } from "./ScriptActorCard";
|
|
16
|
-
import { ACTOR_CARD_SIZE_LIMITS, useActorCardSize, type ActorCardSize } from "./card-size-settings";
|
|
17
|
-
import type {
|
|
18
|
-
CanvasElementContribution,
|
|
19
|
-
CanvasElementDefinition,
|
|
20
|
-
CardSectionContribution,
|
|
21
|
-
EntityReference,
|
|
22
|
-
SessionContext,
|
|
23
|
-
SessionNavigation,
|
|
24
|
-
} from "@aicontainer/web/PluginRegistry";
|
|
25
|
-
|
|
26
|
-
export interface NetBounds {
|
|
27
|
-
left: number;
|
|
28
|
-
top: number;
|
|
29
|
-
right: number;
|
|
30
|
-
bottom: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface CanvasElementInstance {
|
|
34
|
-
layoutKey: string;
|
|
35
|
-
contribution: CanvasElementContribution;
|
|
36
|
-
definition: CanvasElementDefinition;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface ResizeTarget {
|
|
40
|
-
key: string;
|
|
41
|
-
limits: NonNullable<CanvasElementDefinition["resizable"]>;
|
|
42
|
-
onCommit: (size: { width: number; height: number }) => void;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type ActorState = "running" | "waiting" | "done" | "failed" | "stopped";
|
|
46
|
-
type ActorRole = "primary" | "agent";
|
|
47
|
-
|
|
48
|
-
const resizeHandleClass = "absolute right-0.5 bottom-0.5 z-6 block size-5 min-h-5 min-w-5 cursor-nwse-resize touch-none rounded-md p-0 text-black/70 before:pointer-events-none before:absolute before:inset-1 before:rounded-br-full before:border-r-2 before:border-b-2 before:border-current before:content-[\'\'] focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-2";
|
|
49
|
-
|
|
50
|
-
const NOTICE_GAP = 32;
|
|
51
|
-
const NOTICE_LINE = 22;
|
|
52
|
-
const NOTICE_PADDING = 12;
|
|
53
|
-
const NOTICE_WIDTH = 520;
|
|
54
|
-
|
|
55
|
-
const lifecycleKind = (view: RunView, actor: RunActor): ActorState => {
|
|
56
|
-
const lifecycle = actor.lifecycle?.kind ?? "idle";
|
|
57
|
-
if (lifecycle === "stopped") return "stopped";
|
|
58
|
-
if (lifecycle === "running") return "running";
|
|
59
|
-
const turns = view.turns.filter((turn) => turn.actorId === actor.id);
|
|
60
|
-
const latest = turns.at(-1);
|
|
61
|
-
if (latest?.status === "failed" || latest?.status === "interrupted") return "failed";
|
|
62
|
-
const pendingInputs = view.inputs.filter((input) => input.actorId === actor.id && isPendingRunActorInput(input)).length;
|
|
63
|
-
return latest?.status === "completed" && pendingInputs === 0 ? "done" : "waiting";
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const roleOf = (view: RunView, actor: RunActor): ActorRole =>
|
|
67
|
-
actor.id === view.primaryActorId ? "primary" : "agent";
|
|
68
|
-
|
|
69
|
-
const fallbackSize = (actor: RunActor, standardSize: ActorCardSize) => actor.kind === "agent" ? standardSize : ({
|
|
70
|
-
width: actor.kind === "script" ? 300 : Math.min(320, Math.max(150, 66 + (actor.handle.length + 1) * 8.5)),
|
|
71
|
-
height: actor.kind === "script" ? 98 : 44,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const canvasElementInstances = (
|
|
75
|
-
contributions: readonly CanvasElementContribution[],
|
|
76
|
-
session: SessionContext,
|
|
77
|
-
): CanvasElementInstance[] => {
|
|
78
|
-
const instances = contributions.flatMap((contribution) => contribution.select(session).map((definition) => ({
|
|
79
|
-
layoutKey: `canvas:${JSON.stringify([contribution.id, definition.id])}`,
|
|
80
|
-
contribution,
|
|
81
|
-
definition,
|
|
82
|
-
})));
|
|
83
|
-
if (new Set(instances.map((instance) => instance.layoutKey)).size !== instances.length) {
|
|
84
|
-
throw new Error("Ein Canvas-Element wurde mehrfach ausgewählt");
|
|
85
|
-
}
|
|
86
|
-
for (const instance of instances) {
|
|
87
|
-
if (!instance.definition.id.trim()) throw new Error("Ein Canvas-Element hat keine ID");
|
|
88
|
-
if (!Number.isFinite(instance.definition.width) || instance.definition.width <= 0
|
|
89
|
-
|| !Number.isFinite(instance.definition.height) || instance.definition.height <= 0) {
|
|
90
|
-
throw new Error(`Canvas-Element ${instance.definition.id} hat eine ungültige Größe`);
|
|
91
|
-
}
|
|
92
|
-
const collapsedHeight = instance.definition.collapsedHeight;
|
|
93
|
-
if (collapsedHeight !== undefined && (!Number.isFinite(collapsedHeight) || collapsedHeight <= 0)) {
|
|
94
|
-
throw new Error(`Canvas-Element ${instance.definition.id} hat eine ungültige eingeklappte Höhe`);
|
|
95
|
-
}
|
|
96
|
-
const resize = instance.definition.resizable;
|
|
97
|
-
if (resize && (!Number.isFinite(resize.minWidth)
|
|
98
|
-
|| !Number.isFinite(resize.maxWidth)
|
|
99
|
-
|| !Number.isFinite(resize.minHeight)
|
|
100
|
-
|| !Number.isFinite(resize.maxHeight)
|
|
101
|
-
|| resize.minWidth <= 0
|
|
102
|
-
|| resize.minHeight <= 0
|
|
103
|
-
|| resize.maxWidth < resize.minWidth
|
|
104
|
-
|| resize.maxHeight < resize.minHeight)) {
|
|
105
|
-
throw new Error(`Canvas-Element ${instance.definition.id} hat ungültige Größenlimits`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return instances;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const parsedLayout = (state: unknown): { layout?: CanvasLayout; problem?: string } => {
|
|
112
|
-
if (state === undefined) return {};
|
|
113
|
-
try {
|
|
114
|
-
return { layout: canvasLayoutOf(state) };
|
|
115
|
-
} catch (caught) {
|
|
116
|
-
return { problem: caught instanceof Error ? caught.message : String(caught) };
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const STATE_WORDS: Record<ActorState, string> = {
|
|
121
|
-
running: "arbeitet",
|
|
122
|
-
waiting: "wartet",
|
|
123
|
-
done: "fertig",
|
|
124
|
-
failed: "gescheitert",
|
|
125
|
-
stopped: "gestoppt",
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const cardTitle = (actor: RunActor, state: ActorState, inputCount: number): string => {
|
|
129
|
-
const head = `@${actor.handle} ${STATE_WORDS[state]}, ${inputCount} Eingaben`;
|
|
130
|
-
const reason = actor.lifecycle?.kind === "stopped" ? actor.lifecycle.reason : undefined;
|
|
131
|
-
return reason ? `${head}: ${reason}` : head;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const RoleIcon = ({ role }: { role: ActorRole }) => {
|
|
135
|
-
if (role === "primary") {
|
|
136
|
-
return (
|
|
137
|
-
<svg aria-hidden viewBox="0 0 16 16">
|
|
138
|
-
<path d="M 8 1.6 L 9.6 6.4 L 14.4 8 L 9.6 9.6 L 8 14.4 L 6.4 9.6 L 1.6 8 L 6.4 6.4 Z" />
|
|
139
|
-
</svg>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
return (
|
|
143
|
-
<svg aria-hidden viewBox="0 0 16 16">
|
|
144
|
-
<path d="M 2.5 3.5 h 11 v 7 h -6 l -3 3 v -3 h -2 z" />
|
|
145
|
-
</svg>
|
|
146
|
-
);
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const ShapeOutline = ({ shape, box }: { shape: CanvasShape; box: PlacedBox }) => {
|
|
150
|
-
const inset = 1.5;
|
|
151
|
-
const { width, height } = box;
|
|
152
|
-
return (
|
|
153
|
-
<svg aria-hidden height="100%" viewBox={`0 0 ${width} ${height}`} width="100%">
|
|
154
|
-
{shape.kind === "circle"
|
|
155
|
-
? <circle cx={width / 2} cy={height / 2} r={Math.min(width, height) / 2 - inset} />
|
|
156
|
-
: <polygon points={`${width / 2},${inset} ${width - inset},${height / 2} ${width / 2},${height - inset} ${inset},${height / 2}`} />}
|
|
157
|
-
</svg>
|
|
158
|
-
);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
export function NetLayer({
|
|
162
|
-
cardSections,
|
|
163
|
-
canvasElements,
|
|
164
|
-
hiddenActorIds,
|
|
165
|
-
showConnections,
|
|
166
|
-
navigation,
|
|
167
|
-
onLayout,
|
|
168
|
-
onSelect,
|
|
169
|
-
onOverviewTarget,
|
|
170
|
-
selectedActorId,
|
|
171
|
-
session,
|
|
172
|
-
view,
|
|
173
|
-
}: {
|
|
174
|
-
cardSections: readonly CardSectionContribution[];
|
|
175
|
-
canvasElements: readonly CanvasElementContribution[];
|
|
176
|
-
hiddenActorIds: readonly string[];
|
|
177
|
-
showConnections: boolean;
|
|
178
|
-
navigation: SessionNavigation;
|
|
179
|
-
onLayout: (bounds: NetBounds) => void;
|
|
180
|
-
onSelect: (selection: EntityReference) => void;
|
|
181
|
-
onOverviewTarget?: (bounds: NetBounds) => void;
|
|
182
|
-
selectedActorId: string | undefined;
|
|
183
|
-
session: SessionContext;
|
|
184
|
-
view: RunView;
|
|
185
|
-
}) {
|
|
186
|
-
const { sizes, refFor } = useCanvasMeasurements();
|
|
187
|
-
const standardSize = useActorCardSize();
|
|
188
|
-
const { settings: materialSettings } = useMaterialSettings();
|
|
189
|
-
const storageKey = canvasLayoutStorageKey(session.session.id, "elements");
|
|
190
|
-
const [savedSizes] = useState(() => parseCanvasElementSizes(window.localStorage.getItem(storageKey)));
|
|
191
|
-
const [canvasSizes, setCanvasSizes] = useState<Map<string, { width: number; height: number }>>(() => new Map(savedSizes.canvasSizes));
|
|
192
|
-
const [actorSizes, setActorSizes] = useState<Map<string, { width: number; height: number }>>(() => new Map(savedSizes.actorSizes));
|
|
193
|
-
const [expandedActors, setExpandedActors] = useState<ReadonlySet<string>>(() => new Set(savedSizes.expandedActors));
|
|
194
|
-
const [collapsedElements, setCollapsedElements] = useState<ReadonlySet<string>>(() => new Set(savedSizes.collapsedElements));
|
|
195
|
-
useEffect(() => {
|
|
196
|
-
window.localStorage.setItem(storageKey, JSON.stringify({
|
|
197
|
-
canvasSizes: [...canvasSizes], actorSizes: [...actorSizes],
|
|
198
|
-
expandedActors: [...expandedActors], collapsedElements: [...collapsedElements],
|
|
199
|
-
}));
|
|
200
|
-
}, [storageKey, canvasSizes, actorSizes, expandedActors, collapsedElements]);
|
|
201
|
-
const [hoveredId, setHoveredId] = useState<string>();
|
|
202
|
-
const resizeSession = useRef<{ key: string; cancel: () => void } | null>(null);
|
|
203
|
-
|
|
204
|
-
const allElementInstances = useMemo(
|
|
205
|
-
() => canvasElementInstances(canvasElements, session),
|
|
206
|
-
[canvasElements, session],
|
|
207
|
-
);
|
|
208
|
-
const rawElementInstances = useMemo(
|
|
209
|
-
() => allElementInstances.filter((instance) => instance.definition.visible !== false),
|
|
210
|
-
[allElementInstances],
|
|
211
|
-
);
|
|
212
|
-
const hiddenAppIds = useMemo(
|
|
213
|
-
() => allElementInstances.filter((instance) => instance.definition.visible === false).map((instance) => instance.definition.id),
|
|
214
|
-
[allElementInstances],
|
|
215
|
-
);
|
|
216
|
-
useLayoutEffect(() => {
|
|
217
|
-
const actorIds = new Set(view.actors.filter((actor) => actor.kind === "agent").map((actor) => actor.id));
|
|
218
|
-
const visibleKeys = new Set([...rawElementInstances.map((instance) => instance.layoutKey), ...[...actorIds].filter((id) => !hiddenActorIds.includes(id)).map((id) => `actor:${id}`)]);
|
|
219
|
-
if (resizeSession.current && !visibleKeys.has(resizeSession.current.key)) resizeSession.current.cancel();
|
|
220
|
-
setActorSizes((current) => [...current.keys()].every((id) => actorIds.has(id))
|
|
221
|
-
? current : new Map([...current].filter(([id]) => actorIds.has(id))));
|
|
222
|
-
setExpandedActors((current) => [...current].every((id) => actorIds.has(id))
|
|
223
|
-
? current : new Set([...current].filter((id) => actorIds.has(id))));
|
|
224
|
-
const keys = new Set(allElementInstances.map((instance) => instance.layoutKey));
|
|
225
|
-
setCanvasSizes((current) => [...current.keys()].every((key) => keys.has(key))
|
|
226
|
-
? current : new Map([...current].filter(([key]) => keys.has(key))));
|
|
227
|
-
setCollapsedElements((current) => [...current].every((key) => keys.has(key))
|
|
228
|
-
? current : new Set([...current].filter((key) => keys.has(key))));
|
|
229
|
-
}, [rawElementInstances, allElementInstances, view.actors, hiddenActorIds]);
|
|
230
|
-
useLayoutEffect(() => () => resizeSession.current?.cancel(), []);
|
|
231
|
-
const elementInstances = useMemo(
|
|
232
|
-
() => rawElementInstances.map((instance) => {
|
|
233
|
-
const size = canvasSizes.get(instance.layoutKey);
|
|
234
|
-
const collapsedHeight = collapsedElements.has(instance.layoutKey) ? instance.definition.collapsedHeight : undefined;
|
|
235
|
-
return { ...instance, definition: { ...instance.definition, ...size,
|
|
236
|
-
...(collapsedHeight === undefined ? {} : { height: collapsedHeight }),
|
|
237
|
-
} };
|
|
238
|
-
}),
|
|
239
|
-
[rawElementInstances, canvasSizes, collapsedElements],
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
const layoutState = view.pluginStates.find((entry) =>
|
|
243
|
-
entry.pluginId === ORCHESTRATION_PLUGIN_ID && entry.scope.kind === "run")?.state;
|
|
244
|
-
const parsed = useMemo(() => parsedLayout(layoutState), [layoutState]);
|
|
245
|
-
const apps = useMemo(
|
|
246
|
-
(): SceneApp[] => elementInstances.map((instance) => ({
|
|
247
|
-
key: instance.layoutKey,
|
|
248
|
-
id: instance.definition.id,
|
|
249
|
-
width: instance.definition.width,
|
|
250
|
-
height: instance.definition.height,
|
|
251
|
-
...sizes.get(instance.layoutKey),
|
|
252
|
-
anchorActorId: instance.definition.anchorActorId,
|
|
253
|
-
})),
|
|
254
|
-
[elementInstances, sizes],
|
|
255
|
-
);
|
|
256
|
-
const scene = useMemo(() => buildScene({
|
|
257
|
-
view,
|
|
258
|
-
layout: parsed.layout,
|
|
259
|
-
layoutProblem: parsed.problem,
|
|
260
|
-
actorSize: (actor) => sizes.get(`actor:${actor.id}`) ?? fallbackSize(actor, standardSize),
|
|
261
|
-
apps,
|
|
262
|
-
hiddenAppIds,
|
|
263
|
-
hiddenActorIds,
|
|
264
|
-
showConnections,
|
|
265
|
-
}), [view, parsed, sizes, apps, hiddenAppIds, hiddenActorIds, showConnections, standardSize]);
|
|
266
|
-
|
|
267
|
-
// The layout starts at the world origin; the chat floats outside the camera world.
|
|
268
|
-
const originX = 0;
|
|
269
|
-
const originY = 0;
|
|
270
|
-
const depth = materialSettings.steps * MATERIAL_DEPTH_PER_STEP;
|
|
271
|
-
const placed = useMemo(() => layoutScene(withLabelSpacing(withMaterialSpacing(scene.root, depth), scene.lines
|
|
272
|
-
.filter((line) => line.label?.trim())
|
|
273
|
-
.map((line) => ({ ...line, ...(sizes.get(`label:${line.key}`) ?? { width: 0, height: 0 }) }))), originX, originY),
|
|
274
|
-
[scene, sizes, originX, originY, depth]);
|
|
275
|
-
const boxes = useMemo(() => new Map(placed.boxes.map((box) => [box.key, box])), [placed]);
|
|
276
|
-
const hasMaterial = placed.boxes.some((box) => { const type = scene.leaves.get(box.key)?.type; return type === "actor" || type === "app"; });
|
|
277
|
-
const materialDepth = hasMaterial ? depth : 0;
|
|
278
|
-
const noticeHeight = scene.notices.length > 0 ? NOTICE_PADDING * 2 + scene.notices.length * NOTICE_LINE : 0;
|
|
279
|
-
const noticeTop = originY + placed.height + (placed.height > 0 ? NOTICE_GAP : 0);
|
|
280
|
-
const bounds = useMemo((): NetBounds => ({
|
|
281
|
-
left: originX,
|
|
282
|
-
top: originY - materialDepth * .7,
|
|
283
|
-
right: originX + Math.max(placed.width + materialDepth * .5, noticeHeight > 0 ? NOTICE_WIDTH : 0),
|
|
284
|
-
bottom: Math.max(originY + placed.height, noticeTop + noticeHeight),
|
|
285
|
-
}), [originX, originY, placed.width, placed.height, noticeHeight, noticeTop, materialDepth]);
|
|
286
|
-
|
|
287
|
-
const measured = [...scene.leaves].every(([key, leaf]) =>
|
|
288
|
-
(leaf.type !== "actor" && leaf.type !== "app") || sizes.has(key))
|
|
289
|
-
&& scene.lines.every((line) => !line.label?.trim() || sizes.has(`label:${line.key}`));
|
|
290
|
-
useLayoutEffect(() => {
|
|
291
|
-
if (measured) onLayout(bounds);
|
|
292
|
-
}, [bounds, measured, onLayout]);
|
|
293
|
-
|
|
294
|
-
const elementByKey = new Map(elementInstances.map((element) => [element.layoutKey, element]));
|
|
295
|
-
const focusActorId = hoveredId ?? selectedActorId;
|
|
296
|
-
const focusKey = focusActorId === undefined ? undefined : `actor:${focusActorId}`;
|
|
297
|
-
const lineClass = (touched: boolean) =>
|
|
298
|
-
focusKey === undefined || touched ? undefined : "opacity-40";
|
|
299
|
-
|
|
300
|
-
const startResize = (
|
|
301
|
-
event: ReactPointerEvent<HTMLButtonElement>,
|
|
302
|
-
target: ResizeTarget,
|
|
303
|
-
) => {
|
|
304
|
-
const limits = target.limits;
|
|
305
|
-
if (!limits || event.button !== 0 || !event.isPrimary) return;
|
|
306
|
-
event.preventDefault();
|
|
307
|
-
event.stopPropagation();
|
|
308
|
-
const handle = event.currentTarget;
|
|
309
|
-
const container = handle.parentElement;
|
|
310
|
-
if (!container) return;
|
|
311
|
-
resizeSession.current?.cancel();
|
|
312
|
-
const originalWidth = container.style.width;
|
|
313
|
-
const originalHeight = container.style.height;
|
|
314
|
-
let width = container.offsetWidth;
|
|
315
|
-
let height = container.offsetHeight;
|
|
316
|
-
const pointerId = event.pointerId;
|
|
317
|
-
let lastX = event.clientX;
|
|
318
|
-
let lastY = event.clientY;
|
|
319
|
-
let finished = false;
|
|
320
|
-
let frame: number | null = null;
|
|
321
|
-
let pendingX = lastX;
|
|
322
|
-
let pendingY = lastY;
|
|
323
|
-
const applySize = () => {
|
|
324
|
-
frame = null;
|
|
325
|
-
const zoom = container.getBoundingClientRect().width / container.offsetWidth;
|
|
326
|
-
if (!Number.isFinite(zoom) || zoom <= 0) return;
|
|
327
|
-
width = Math.min(limits.maxWidth, Math.max(limits.minWidth, width + (pendingX - lastX) / zoom));
|
|
328
|
-
height = Math.min(limits.maxHeight, Math.max(limits.minHeight, height + (pendingY - lastY) / zoom));
|
|
329
|
-
lastX = pendingX;
|
|
330
|
-
lastY = pendingY;
|
|
331
|
-
container.style.width = `${width}px`;
|
|
332
|
-
container.style.height = `${height}px`;
|
|
333
|
-
};
|
|
334
|
-
const move = (moveEvent: PointerEvent) => {
|
|
335
|
-
if (moveEvent.pointerId !== pointerId) return;
|
|
336
|
-
pendingX = moveEvent.clientX;
|
|
337
|
-
pendingY = moveEvent.clientY;
|
|
338
|
-
if (frame === null) frame = requestAnimationFrame(applySize);
|
|
339
|
-
};
|
|
340
|
-
const finish = (commit: boolean) => {
|
|
341
|
-
if (finished) return;
|
|
342
|
-
finished = true;
|
|
343
|
-
if (frame !== null) cancelAnimationFrame(frame);
|
|
344
|
-
frame = null;
|
|
345
|
-
handle.removeEventListener("pointermove", move);
|
|
346
|
-
handle.removeEventListener("pointerup", up);
|
|
347
|
-
handle.removeEventListener("pointercancel", cancel);
|
|
348
|
-
handle.removeEventListener("lostpointercapture", cancel);
|
|
349
|
-
if (handle.hasPointerCapture(pointerId)) handle.releasePointerCapture(pointerId);
|
|
350
|
-
resizeSession.current = null;
|
|
351
|
-
if (commit && container.isConnected) {
|
|
352
|
-
target.onCommit({ width, height });
|
|
353
|
-
} else {
|
|
354
|
-
container.style.width = originalWidth;
|
|
355
|
-
container.style.height = originalHeight;
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
const up = (upEvent: PointerEvent) => {
|
|
359
|
-
if (upEvent.pointerId !== pointerId) return;
|
|
360
|
-
pendingX = upEvent.clientX;
|
|
361
|
-
pendingY = upEvent.clientY;
|
|
362
|
-
if (frame !== null) cancelAnimationFrame(frame);
|
|
363
|
-
applySize();
|
|
364
|
-
finish(true);
|
|
365
|
-
};
|
|
366
|
-
const cancel = (cancelEvent: PointerEvent) => { if (cancelEvent.pointerId === pointerId) finish(false); };
|
|
367
|
-
resizeSession.current = { key: target.key, cancel: () => finish(false) };
|
|
368
|
-
handle.setPointerCapture(pointerId);
|
|
369
|
-
handle.addEventListener("pointermove", move);
|
|
370
|
-
handle.addEventListener("pointerup", up);
|
|
371
|
-
handle.addEventListener("pointercancel", cancel);
|
|
372
|
-
handle.addEventListener("lostpointercapture", cancel);
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
const renderActor = (actor: RunActor, box: PlacedBox) => {
|
|
376
|
-
const state = lifecycleKind(view, actor);
|
|
377
|
-
const inputCount = view.inputs.filter((input) => input.actorId === actor.id).length;
|
|
378
|
-
const expanded = expandedActors.has(actor.id);
|
|
379
|
-
const requestedExpandedSize = actorSizes.get(actor.id) ?? {
|
|
380
|
-
width: Math.min(ACTOR_CARD_SIZE_LIMITS.maxWidth, Math.max(400, Math.round(standardSize.width * 1.5))),
|
|
381
|
-
height: Math.min(ACTOR_CARD_SIZE_LIMITS.maxHeight, Math.max(340, Math.round(standardSize.height * 1.5))),
|
|
382
|
-
};
|
|
383
|
-
const expandedSize = { ...requestedExpandedSize, height: Math.max(ACTOR_CARD_SIZE_LIMITS.minHeight, requestedExpandedSize.height) };
|
|
384
|
-
const saveSize = (size: { width: number; height: number }) => {
|
|
385
|
-
setActorSizes((current) => new Map(current).set(actor.id, size));
|
|
386
|
-
setExpandedActors((current) => new Set(current).add(actor.id));
|
|
387
|
-
};
|
|
388
|
-
return (
|
|
389
|
-
<div
|
|
390
|
-
className={materialCardClass(actorSurface(view, actor), cn(
|
|
391
|
-
"absolute flex flex-col text-foreground",
|
|
392
|
-
actor.kind === "agent" && "min-h-[260px] min-w-[240px] max-w-[2880px] pr-[22px] pb-2",
|
|
393
|
-
actor.kind === "script" && "min-h-[98px] w-[300px]",
|
|
394
|
-
actor.kind !== "agent" && actor.kind !== "script" && "max-w-[320px]",
|
|
395
|
-
(state === "running" || actor.id === selectedActorId) && "border-primary",
|
|
396
|
-
))}
|
|
397
|
-
key={box.key}
|
|
398
|
-
data-surface="material"
|
|
399
|
-
data-canvas-card="true"
|
|
400
|
-
data-canvas-navigation-id={box.key}
|
|
401
|
-
onPointerEnter={() => setHoveredId(actor.id)}
|
|
402
|
-
onPointerLeave={() => setHoveredId((current) => (current === actor.id ? undefined : current))}
|
|
403
|
-
ref={refFor(box.key)}
|
|
404
|
-
style={{ left: box.x, top: box.y, ...(actor.kind === "agent" ? expanded ? expandedSize : standardSize : {}) }}
|
|
405
|
-
>
|
|
406
|
-
<MaterialBody steps={materialSettings.steps} />
|
|
407
|
-
{actor.kind === "script" ? <ScriptActorCard actor={actor}
|
|
408
|
-
state={state} stateLabel={STATE_WORDS[state]} inputCount={inputCount}
|
|
409
|
-
onSelect={() => onSelect({ type: "actor", id: actor.id })} /> : <div className={cn(materialHeadClass, "min-h-[40px] flex-none",
|
|
410
|
-
actor.kind === "agent" && "-mr-[22px] pr-[34px]",
|
|
411
|
-
"after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:left-3 after:border-t after:border-[#51465738] after:content-['']")}>
|
|
412
|
-
<button
|
|
413
|
-
className="flex w-full cursor-pointer items-center gap-2 rounded-[16px] p-0 text-left"
|
|
414
|
-
onClick={() => onSelect({ type: "actor", id: actor.id })}
|
|
415
|
-
title={cardTitle(actor, state, inputCount)}
|
|
416
|
-
type="button"
|
|
417
|
-
>
|
|
418
|
-
<span className={`${materialIconClass} [&>svg]:size-5`} data-state={state}>
|
|
419
|
-
{actor.kind === "agent" && state === "running"
|
|
420
|
-
? <WorkingScenes compact label={`@${actor.handle} arbeitet`} />
|
|
421
|
-
: <RoleIcon role={roleOf(view, actor)} />}
|
|
422
|
-
</span>
|
|
423
|
-
<span className={materialTitleClass}>@{actor.handle}</span>
|
|
424
|
-
</button>
|
|
425
|
-
{actor.kind === "agent" && <Button className="shrink-0 self-center" size="icon-sm" variant="ghost"
|
|
426
|
-
aria-label={`Karte von @${actor.handle} ${expanded ? "kompakt anzeigen" : "vergrößern"}`} title={`Karte von @${actor.handle} ${expanded ? "kompakt anzeigen" : "vergrößern"}`}
|
|
427
|
-
aria-pressed={expanded}
|
|
428
|
-
onClick={() => {
|
|
429
|
-
resizeSession.current?.cancel();
|
|
430
|
-
setExpandedActors((current) => {
|
|
431
|
-
const next = new Set(current);
|
|
432
|
-
if (expanded) next.delete(actor.id); else next.add(actor.id);
|
|
433
|
-
return next;
|
|
434
|
-
});
|
|
435
|
-
}}>
|
|
436
|
-
<svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7">
|
|
437
|
-
<path d={expanded ? "M3 9h6V3m12 12h-6v6M9 9 3 3m12 12 6 6" : "M9 3H3v6m12 12h6v-6M3 3l6 6m12 12-6-6"} />
|
|
438
|
-
</svg>
|
|
439
|
-
</Button>}
|
|
440
|
-
</div>}
|
|
441
|
-
{actor.kind === "agent" && <ActorChatPreview actor={actor} view={view} running={state === "running"}
|
|
442
|
-
conversation={session.actorConversations?.[actor.id]} historyError={session.conversationError}
|
|
443
|
-
onNavigate={onSelect}
|
|
444
|
-
primaryMessages={actor.id === view.primaryActorId ? session.messages : undefined} />}
|
|
445
|
-
<div className={cn(cardSectionsClass, actor.kind === "agent" && "max-h-[45%] flex-[0_1_auto] overflow-auto",
|
|
446
|
-
actor.kind === "script" && "mx-3 mb-2 border-t px-0 pt-2 pb-0")} data-slot="card-sections">
|
|
447
|
-
{cardSections.map(({ id, Section }) => (
|
|
448
|
-
<Section actor={actor} key={id} navigation={navigation} session={session} />
|
|
449
|
-
))}
|
|
450
|
-
</div>
|
|
451
|
-
{actor.kind === "agent" && <button className={resizeHandleClass} type="button"
|
|
452
|
-
aria-label={`Größe der Karte von @${actor.handle} ändern`} title="Kartengröße ziehen"
|
|
453
|
-
onPointerDown={(event) => startResize(event, {
|
|
454
|
-
key: `actor:${actor.id}`,
|
|
455
|
-
limits: ACTOR_CARD_SIZE_LIMITS,
|
|
456
|
-
onCommit: saveSize,
|
|
457
|
-
})} />}
|
|
458
|
-
</div>
|
|
459
|
-
);
|
|
460
|
-
};
|
|
461
|
-
|
|
462
|
-
const renderApp = (box: PlacedBox) => {
|
|
463
|
-
const current = elementByKey.get(box.key);
|
|
464
|
-
if (!current) return null;
|
|
465
|
-
const Element = current.contribution.Element;
|
|
466
|
-
return (
|
|
467
|
-
<div
|
|
468
|
-
className={materialCardClass("app", cn("absolute flex min-h-0 min-w-0 overflow-visible [&>*:not(svg)]:min-h-0 [&>*:not(svg)]:min-w-0 [&>*:not(svg)]:w-full [&>*:not(svg)]:flex-1",
|
|
469
|
-
current.definition.resizable && "pb-[22px]"))}
|
|
470
|
-
data-surface="material"
|
|
471
|
-
data-canvas-card="true"
|
|
472
|
-
data-resizable={current.definition.resizable ? "true" : undefined}
|
|
473
|
-
data-canvas-entity-type={current.definition.entity?.type}
|
|
474
|
-
data-canvas-navigation-id={box.key}
|
|
475
|
-
data-canvas-entity-id={current.definition.entity?.id}
|
|
476
|
-
key={box.key}
|
|
477
|
-
ref={refFor(box.key)}
|
|
478
|
-
style={{ left: box.x, top: box.y, width: current.definition.width, height: current.definition.height }}
|
|
479
|
-
>
|
|
480
|
-
<MaterialBody steps={materialSettings.steps} />
|
|
481
|
-
<Element
|
|
482
|
-
collapsed={collapsedElements.has(current.layoutKey)}
|
|
483
|
-
definition={current.definition}
|
|
484
|
-
onCollapsedChange={current.definition.collapsedHeight === undefined ? undefined : (collapsed) => {
|
|
485
|
-
resizeSession.current?.cancel();
|
|
486
|
-
setCollapsedElements((previous) => {
|
|
487
|
-
const next = new Set(previous);
|
|
488
|
-
if (collapsed) next.add(current.layoutKey);
|
|
489
|
-
else next.delete(current.layoutKey);
|
|
490
|
-
return next;
|
|
491
|
-
});
|
|
492
|
-
}}
|
|
493
|
-
navigation={navigation}
|
|
494
|
-
session={session}
|
|
495
|
-
/>
|
|
496
|
-
{current.definition.resizable && !collapsedElements.has(current.layoutKey) && (
|
|
497
|
-
<button
|
|
498
|
-
aria-label="Canvas-App skalieren"
|
|
499
|
-
className={resizeHandleClass}
|
|
500
|
-
onPointerDown={(event) => startResize(event, {
|
|
501
|
-
key: current.layoutKey, limits: current.definition.resizable!,
|
|
502
|
-
onCommit: (size) => setCanvasSizes((previous) => new Map(previous).set(current.layoutKey, size)),
|
|
503
|
-
})}
|
|
504
|
-
title="Größe ändern"
|
|
505
|
-
type="button"
|
|
506
|
-
/>
|
|
507
|
-
)}
|
|
508
|
-
</div>
|
|
509
|
-
);
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
return (
|
|
513
|
-
<>
|
|
514
|
-
<div inert={onOverviewTarget !== undefined}>
|
|
515
|
-
<svg aria-label="Linien" className="pointer-events-none absolute top-0 left-0 overflow-visible" height="4" role="group" width="4">
|
|
516
|
-
{scene.lines.map((line) => {
|
|
517
|
-
const from = boxes.get(line.fromKey);
|
|
518
|
-
const to = boxes.get(line.toKey);
|
|
519
|
-
const fromInfo = scene.leaves.get(line.fromKey);
|
|
520
|
-
const toInfo = scene.leaves.get(line.toKey);
|
|
521
|
-
if (!from || !to || !fromInfo || !toInfo) return null;
|
|
522
|
-
const geometry = lineGeometry({ box: from, boundary: boundaryOf(fromInfo) }, { box: to, boundary: boundaryOf(toInfo) });
|
|
523
|
-
const touched = line.fromKey === focusKey || line.toKey === focusKey;
|
|
524
|
-
return (
|
|
525
|
-
<g className={lineClass(touched)} key={line.key}>
|
|
526
|
-
<SvgEdge
|
|
527
|
-
d={`M ${geometry.x1.toFixed(1)} ${geometry.y1.toFixed(1)} L ${geometry.x2.toFixed(1)} ${geometry.y2.toFixed(1)}`}
|
|
528
|
-
arrow={line.arrow} lineStyle={line.style} tone={focusKey !== undefined && touched ? "accent" : "neutral"}
|
|
529
|
-
/>
|
|
530
|
-
{line.label?.trim() && (
|
|
531
|
-
<text className="fill-muted-foreground stroke-canvas stroke-[4px] font-mono text-[11px] font-bold [dominant-baseline:central] [paint-order:stroke]" ref={refFor(`label:${line.key}`)} textAnchor="middle" x={geometry.middle.x} y={geometry.middle.y}>
|
|
532
|
-
{line.label}
|
|
533
|
-
</text>
|
|
534
|
-
)}
|
|
535
|
-
</g>
|
|
536
|
-
);
|
|
537
|
-
})}
|
|
538
|
-
</svg>
|
|
539
|
-
{placed.frames.map((frame) => (
|
|
540
|
-
<div
|
|
541
|
-
className={cn("pointer-events-none absolute", frame.outline && "rounded-xl border border-glass-edge bg-glass-group")}
|
|
542
|
-
key={frame.key}
|
|
543
|
-
style={{ left: frame.x, top: frame.y - materialDepth * .7, width: frame.width + materialDepth * .5, height: frame.height + materialDepth * .7 }}
|
|
544
|
-
>
|
|
545
|
-
{frame.label && <span className={cn("absolute whitespace-nowrap text-[0.66rem] font-bold tracking-[0.07em] uppercase text-muted-foreground", frame.outline ? "top-1.5 left-3.5" : "top-[3px] left-0")}>{frame.label}</span>}
|
|
546
|
-
</div>
|
|
547
|
-
))}
|
|
548
|
-
{placed.boxes.map((box) => {
|
|
549
|
-
const info = scene.leaves.get(box.key);
|
|
550
|
-
if (!info) return null;
|
|
551
|
-
switch (info.type) {
|
|
552
|
-
case "actor":
|
|
553
|
-
return renderActor(info.actor, box);
|
|
554
|
-
case "app":
|
|
555
|
-
return renderApp(box);
|
|
556
|
-
case "shape":
|
|
557
|
-
return (
|
|
558
|
-
<div
|
|
559
|
-
className="absolute grid place-items-center text-center text-foreground [&>svg]:absolute [&>svg]:inset-0 [&>svg]:fill-card [&>svg]:stroke-border-strong [&>svg]:stroke-1"
|
|
560
|
-
data-canvas-navigation-id={box.key}
|
|
561
|
-
key={box.key}
|
|
562
|
-
style={{ left: box.x, top: box.y, width: box.width, height: box.height }}
|
|
563
|
-
title={info.shape.text}
|
|
564
|
-
>
|
|
565
|
-
<ShapeOutline box={box} shape={info.shape} />
|
|
566
|
-
<span className={cn("relative overflow-hidden text-xs font-semibold leading-[1.25] [overflow-wrap:anywhere]",
|
|
567
|
-
info.shape.kind === "circle" ? "max-h-[68%] max-w-[68%]" : "max-h-[48%] max-w-[48%]")}>{info.shape.text}</span>
|
|
568
|
-
</div>
|
|
569
|
-
);
|
|
570
|
-
case "missing":
|
|
571
|
-
return (
|
|
572
|
-
<div
|
|
573
|
-
className="absolute grid place-items-center rounded-lg border border-dotted border-border-strong font-mono text-xs font-bold text-muted-foreground"
|
|
574
|
-
key={box.key}
|
|
575
|
-
style={{ left: box.x, top: box.y, width: box.width, height: box.height }}
|
|
576
|
-
title="Diese Entität gibt es auf der Fläche nicht"
|
|
577
|
-
>
|
|
578
|
-
{info.reference}
|
|
579
|
-
</div>
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
})}
|
|
583
|
-
{scene.notices.length > 0 && (
|
|
584
|
-
<Card className="absolute gap-1 border-border-strong px-3.5 py-3 text-xs leading-[18px] text-foreground shadow-card [&>div]:before:font-bold [&>div]:before:content-['!_']" role="status" style={{ left: originX, top: noticeTop, width: NOTICE_WIDTH }}>
|
|
585
|
-
{scene.notices.map((notice) => <div key={notice}>{notice}</div>)}
|
|
586
|
-
</Card>
|
|
587
|
-
)}
|
|
588
|
-
</div>
|
|
589
|
-
{onOverviewTarget && placed.boxes.map((box) => {
|
|
590
|
-
const info = scene.leaves.get(box.key);
|
|
591
|
-
if (!info || info.type === "missing") return null;
|
|
592
|
-
const label = info.type === "actor" ? `@${info.actor.handle}`
|
|
593
|
-
: info.type === "shape" ? info.shape.text
|
|
594
|
-
: elementByKey.get(box.key)?.definition.title ?? "Mini-App";
|
|
595
|
-
return <button
|
|
596
|
-
aria-label={`${label} bei 100 Prozent zentrieren`}
|
|
597
|
-
className="absolute z-20 m-0 cursor-zoom-in touch-manipulation rounded-lg bg-transparent p-0 hover:bg-primary/12 hover:outline-[length:calc(3px/var(--cam-zoom,1))] hover:outline-primary hover:outline-offset-[calc(3px/var(--cam-zoom,1))] focus-visible:bg-primary/12 focus-visible:outline-[length:calc(3px/var(--cam-zoom,1))] focus-visible:outline-primary focus-visible:outline-offset-[calc(3px/var(--cam-zoom,1))]"
|
|
598
|
-
key={box.key}
|
|
599
|
-
onClick={() => onOverviewTarget({ left: box.x, top: box.y, right: box.x + box.width, bottom: box.y + box.height })}
|
|
600
|
-
style={{ left: box.x, top: box.y, width: box.width, height: box.height }}
|
|
601
|
-
title={label}
|
|
602
|
-
type="button"
|
|
603
|
-
/>;
|
|
604
|
-
})}
|
|
605
|
-
</>
|
|
606
|
-
);
|
|
607
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { useAccess } from "@aicontainer/web/AccessContext";
|
|
3
|
-
import { Button, Input } from "@aicontainer/web/ui";
|
|
4
|
-
import { ACTOR_CARD_SIZE_LIMITS, DEFAULT_ACTOR_CARD_SIZE, saveActorCardSize, useActorCardSize } from "./card-size-settings";
|
|
5
|
-
|
|
6
|
-
export function CardSizeSettings() {
|
|
7
|
-
const access = useAccess();
|
|
8
|
-
const size = useActorCardSize();
|
|
9
|
-
const [width, setWidth] = useState(String(size.width));
|
|
10
|
-
const [height, setHeight] = useState(String(size.height));
|
|
11
|
-
const [message, setMessage] = useState<string>();
|
|
12
|
-
const [error, setError] = useState<string>();
|
|
13
|
-
useEffect(() => { setWidth(String(size.width)); setHeight(String(size.height)); }, [size]);
|
|
14
|
-
const writable = access.can("settings.write");
|
|
15
|
-
const save = (next: { width: number; height: number }) => {
|
|
16
|
-
if (!writable) return;
|
|
17
|
-
setError(undefined);
|
|
18
|
-
setMessage(undefined);
|
|
19
|
-
try {
|
|
20
|
-
saveActorCardSize(next);
|
|
21
|
-
setWidth(String(next.width));
|
|
22
|
-
setHeight(String(next.height));
|
|
23
|
-
setMessage("Standardgröße gespeichert.");
|
|
24
|
-
} catch (cause) {
|
|
25
|
-
setError(cause instanceof Error ? cause.message : String(cause));
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
return <form className="grid gap-3" onSubmit={(event) => {
|
|
29
|
-
event.preventDefault(); save({ width: Number(width), height: Number(height) });
|
|
30
|
-
}}>
|
|
31
|
-
<p className="text-muted-foreground">Standardgröße der LLM-Karten auf dem Canvas, in Pixeln bei 100 Prozent Zoom. Gilt für alle Runs in diesem Browser. Individuell vergrößerte Karten behalten ihre Größe.</p>
|
|
32
|
-
<div className="flex flex-wrap items-end gap-3">
|
|
33
|
-
<label className="grid gap-1.5"><span>Standardbreite</span><Input className="w-[130px]" type="number" required step="1" min={ACTOR_CARD_SIZE_LIMITS.minWidth} max={ACTOR_CARD_SIZE_LIMITS.maxWidth}
|
|
34
|
-
disabled={!writable} value={width} onChange={(event) => { setWidth(event.target.value); setMessage(undefined); }} /></label>
|
|
35
|
-
<label className="grid gap-1.5"><span>Standardhöhe</span><Input className="w-[130px]" type="number" required step="1" min={ACTOR_CARD_SIZE_LIMITS.minHeight} max={ACTOR_CARD_SIZE_LIMITS.maxHeight}
|
|
36
|
-
disabled={!writable} value={height} onChange={(event) => { setHeight(event.target.value); setMessage(undefined); }} /></label>
|
|
37
|
-
<Button type="submit" disabled={!writable}>Speichern</Button>
|
|
38
|
-
<Button disabled={!writable} onClick={() => save(DEFAULT_ACTOR_CARD_SIZE)} variant="outline">Zurücksetzen</Button>
|
|
39
|
-
</div>
|
|
40
|
-
{error ? <p className="text-muted-foreground" role="alert">{error}</p> : message && <p className="text-muted-foreground" role="status">{message}</p>}
|
|
41
|
-
</form>;
|
|
42
|
-
}
|