@schlenkr/ragents 0.1.19 → 0.1.20
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/host-api.json +6 -2
- package/apps/server/src/host-api.ts +3 -2
- package/apps/server/src/host-web.ts +3 -0
- package/apps/server/src/plugin-support/actor-programs/contract.ts +0 -3
- package/apps/server/src/plugin-support/removed-layout.ts +94 -0
- package/apps/server/src/ragents/engine.ts +2 -1
- package/apps/server/src/title-compactor.ts +5 -7
- package/apps/web/dist/assets/highlighted-body-KPVGNVTW-k67VKdbw.js +1 -0
- package/apps/web/dist/assets/main-CVjIz7BS.js +674 -0
- package/apps/web/dist/help/guide-clients.html +22 -42
- package/apps/web/dist/help/guide-clients.md +24 -45
- package/apps/web/dist/help/guide-functions.html +1 -1
- package/apps/web/dist/help/guide-functions.md +1 -1
- package/apps/web/dist/help/guide-getting-started.html +14 -22
- package/apps/web/dist/help/guide-getting-started.md +17 -24
- package/apps/web/dist/help/guide-ideas.html +1 -1
- package/apps/web/dist/help/guide-ideas.md +1 -1
- package/apps/web/dist/help/guide-plugins.html +1 -1
- package/apps/web/dist/help/guide-plugins.md +1 -1
- package/apps/web/dist/help/guide-programs.html +1 -1
- package/apps/web/dist/help/guide-programs.md +1 -1
- package/apps/web/dist/help/index.html +12 -79
- package/apps/web/dist/help/mini-app.css +8 -502
- package/apps/web/dist/host-web.json +28 -23
- package/apps/web/dist/index.html +2 -3
- package/apps/web/dist/run-panel.html +3 -4
- package/apps/web/src/Overview.tsx +4 -143
- package/apps/web/src/PluginChat.tsx +22 -116
- package/apps/web/src/PluginRegistry.tsx +4 -9
- package/apps/web/src/RunPreparationChat.tsx +4 -1
- package/apps/web/src/host-modules.ts +2 -0
- package/apps/web/src/main.tsx +23 -3
- package/apps/web/src/panel/RunLine.tsx +5 -2
- package/apps/web/src/panel/RunsPage.tsx +6 -6
- package/apps/web/src/panel/StartPage.tsx +6 -5
- package/apps/web/src/panel/contract.ts +3 -0
- package/apps/web/src/panel/page-props.ts +2 -0
- package/apps/web/src/run-apps.ts +34 -0
- package/apps/web/src/run-overview.ts +0 -27
- package/apps/web/src/run-panel/PanelContributions.tsx +41 -0
- package/apps/web/src/run-panel/RunPanelApp.tsx +172 -91
- package/apps/web/src/run-panel/RunPanelMenu.tsx +4 -3
- package/apps/web/src/run-panel/RunPanelWorkspace.tsx +8 -2
- package/apps/web/src/run-panel/clipboard.ts +17 -14
- package/apps/web/src/run-panel/host-contract.ts +8 -9
- package/apps/web/src/run-panel/host.ts +5 -31
- package/apps/web/src/run-panel/input-bridge.ts +15 -7
- package/bundles/ragents.activity/ragents-bundle.json +2 -2
- package/bundles/ragents.actor-programs/prompt.hbs +4 -5
- package/bundles/ragents.actor-programs/ragents-bundle.json +4 -14
- package/bundles/ragents.actor-programs/server/index.js +2 -14
- package/bundles/ragents.actor-programs/server/index.js.map +2 -2
- package/bundles/ragents.actor-programs/web/classes.json +1 -1
- package/bundles/ragents.actor-programs/web/index.js +1 -3
- package/bundles/ragents.actor-programs/web/index.js.map +4 -4
- package/bundles/ragents.ask/ragents-bundle.json +2 -2
- package/bundles/ragents.browser/ragents-bundle.json +2 -2
- package/bundles/ragents.documents/ragents-bundle.json +2 -2
- package/bundles/ragents.lsp-fsharp/ragents-bundle.json +2 -2
- package/bundles/ragents.lsp-roslyn/ragents-bundle.json +2 -2
- package/bundles/ragents.lsp-typescript/ragents-bundle.json +2 -2
- package/bundles/ragents.model-relay/ragents-bundle.json +2 -2
- package/bundles/ragents.orchestration/ragents-bundle.json +15 -41
- package/bundles/ragents.orchestration/server/index.js +10 -249
- package/bundles/ragents.orchestration/server/index.js.map +4 -4
- package/bundles/ragents.orchestration/web/chunks/chunk-KU7O6MEX.js +2 -0
- package/bundles/ragents.orchestration/web/chunks/chunk-KU7O6MEX.js.map +7 -0
- package/bundles/ragents.orchestration/web/classes.json +1 -1
- package/bundles/ragents.orchestration/web/exports/web/program-slot.js +1 -1
- package/bundles/ragents.orchestration/web/index.js +10 -18
- package/bundles/ragents.orchestration/web/index.js.map +4 -4
- package/bundles/ragents.overseer/ragents-bundle.json +3 -3
- package/bundles/ragents.overseer/server/index.js +0 -1
- package/bundles/ragents.overseer/server/index.js.map +2 -2
- package/bundles/ragents.processes/ragents-bundle.json +2 -2
- package/bundles/ragents.product/ragents-bundle.json +2 -2
- package/bundles/ragents.profile-distribution/ragents-bundle.json +2 -2
- package/bundles/ragents.reference/ragents-bundle.json +3 -3
- package/bundles/ragents.reference/run-scripts/balcony-wizard/RUN.md +1 -1
- package/bundles/ragents.reference/run-scripts/balcony-wizard/src/server.ts +1 -2
- package/bundles/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +6 -7
- package/bundles/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -3
- package/bundles/ragents.reference/run-scripts/conversation-circle/src/server.ts +3 -13
- package/bundles/ragents.reference/run-scripts/conversation-circle/tests/helpers.ts +1 -1
- package/bundles/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +1 -6
- package/bundles/ragents.reference/run-scripts/learning-afternoon/src/server.ts +1 -15
- package/bundles/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
- package/bundles/ragents.reference/run-scripts/moderated-round/tests/helpers.ts +1 -1
- package/bundles/ragents.reference/run-scripts/quick-note/RUN.md +1 -1
- package/bundles/ragents.reference/run-scripts/quick-note/src/server.ts +1 -2
- package/bundles/ragents.reference/run-scripts/quick-note/tests/program.test.ts +0 -2
- package/bundles/ragents.reference/run-scripts/run-roster/RUN.md +2 -2
- package/bundles/ragents.reference/run-scripts/run-roster/src/server.ts +1 -2
- package/bundles/ragents.reference/run-scripts/run-roster/tests/program.test.ts +0 -2
- package/bundles/ragents.reference/run-scripts/shared-actor-list/src/server.ts +1 -1
- package/bundles/ragents.reference/run-scripts/shared-actor-list/tests/helpers.ts +1 -1
- package/bundles/ragents.reference/run-scripts/word-game/src/contract.ts +1 -1
- package/bundles/ragents.reference/run-scripts/word-game/src/server.ts +0 -1
- package/bundles/ragents.reference/run-scripts/word-game/tests/program.test.ts +1 -2
- package/bundles/ragents.reference/skills/80-actor-text-analysis/SKILL.md +1 -1
- package/bundles/ragents.reference/skills/90-actor-notes/SKILL.md +1 -1
- package/bundles/ragents.todo/ragents-bundle.json +2 -2
- package/bundles/ragents.transcript/ragents-bundle.json +2 -2
- package/bundles/ragents.watch/ragents-bundle.json +2 -2
- package/bundles/ragents.workspace/ragents-bundle.json +2 -2
- package/package.json +8 -8
- package/packages/agent/dist/core/agent-settings.d.ts +1 -1
- package/packages/ai/dist/types.d.ts +2 -1
- package/packages/ragents/src/runtime/journal.ts +4 -0
- package/plugins/ragents.actor-programs/prompt.hbs +4 -5
- package/plugins/ragents.actor-programs/server/access-projections.ts +1 -1
- package/plugins/ragents.actor-programs/server/index.ts +0 -2
- package/plugins/ragents.actor-programs/server/runtime.ts +2 -10
- package/plugins/ragents.actor-programs/web/ActorViewFrame.tsx +5 -20
- package/plugins/ragents.actor-programs/web/AppsPanel.tsx +3 -79
- package/plugins/ragents.actor-programs/web/EmbeddedApps.tsx +0 -9
- package/plugins/ragents.actor-programs/web/api.ts +0 -20
- package/plugins/ragents.actor-programs/web/context.ts +0 -3
- package/plugins/ragents.actor-programs/web/index.tsx +0 -9
- package/plugins/ragents.orchestration/ragents-plugin.json +1 -2
- package/plugins/ragents.orchestration/server/index.ts +1 -11
- package/plugins/ragents.orchestration/web/ActorChat.tsx +1 -1
- package/plugins/ragents.orchestration/web/ActorChatControls.tsx +1 -1
- package/plugins/ragents.orchestration/web/FlowInspector.tsx +2 -4
- package/plugins/ragents.orchestration/web/InspectionPanel.tsx +21 -0
- package/plugins/ragents.orchestration/web/index.tsx +13 -9
- package/plugins/ragents.orchestration/web/program-slot.ts +0 -3
- package/plugins/ragents.orchestration/web/run-panel/AddresseeControl.tsx +15 -36
- package/plugins/ragents.orchestration/web/run-panel/RunPanel.tsx +76 -451
- package/plugins/ragents.orchestration/web/run-panel/run-panel-actors.ts +0 -27
- package/plugins/ragents.orchestration/web/run-panel/run-panel-state.ts +7 -40
- package/plugins/ragents.overseer/server/coordinator-prompt.ts +0 -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/src/server.ts +1 -2
- package/plugins/ragents.reference/run-scripts/balcony-wizard/tests/program.test.ts +6 -7
- package/plugins/ragents.reference/run-scripts/conversation-circle/RUN.md +3 -3
- package/plugins/ragents.reference/run-scripts/conversation-circle/src/server.ts +3 -13
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/helpers.ts +1 -1
- package/plugins/ragents.reference/run-scripts/conversation-circle/tests/program.test.ts +1 -6
- package/plugins/ragents.reference/run-scripts/learning-afternoon/src/server.ts +1 -15
- package/plugins/ragents.reference/run-scripts/learning-afternoon/tests/program.test.ts +1 -1
- package/plugins/ragents.reference/run-scripts/moderated-round/tests/helpers.ts +1 -1
- package/plugins/ragents.reference/run-scripts/quick-note/RUN.md +1 -1
- package/plugins/ragents.reference/run-scripts/quick-note/src/server.ts +1 -2
- package/plugins/ragents.reference/run-scripts/quick-note/tests/program.test.ts +0 -2
- package/plugins/ragents.reference/run-scripts/run-roster/RUN.md +2 -2
- package/plugins/ragents.reference/run-scripts/run-roster/src/server.ts +1 -2
- package/plugins/ragents.reference/run-scripts/run-roster/tests/program.test.ts +0 -2
- package/plugins/ragents.reference/run-scripts/shared-actor-list/src/server.ts +1 -1
- package/plugins/ragents.reference/run-scripts/shared-actor-list/tests/helpers.ts +1 -1
- package/plugins/ragents.reference/run-scripts/word-game/src/contract.ts +1 -1
- package/plugins/ragents.reference/run-scripts/word-game/src/server.ts +0 -1
- package/plugins/ragents.reference/run-scripts/word-game/tests/program.test.ts +1 -2
- package/plugins/ragents.reference/skills/80-actor-text-analysis/SKILL.md +1 -1
- package/plugins/ragents.reference/skills/90-actor-notes/SKILL.md +1 -1
- package/plugins/ragents.reference/walkthroughs.ts +13 -14
- package/apps/web/dist/assets/highlighted-body-KPVGNVTW-BwD4uYEe.js +0 -1
- package/apps/web/dist/assets/main-KBrc5Q77.js +0 -11
- package/apps/web/dist/assets/mermaid-HWGCJPDP-CSCkaArr.js +0 -608
- package/apps/web/dist/assets/runPanel-BjG5NBKm.js +0 -26
- package/apps/web/src/App.tsx +0 -318
- package/apps/web/src/SessionList.tsx +0 -157
- package/apps/web/src/SurfaceShortcuts.tsx +0 -60
- package/apps/web/src/WorkspacePanel.tsx +0 -309
- package/apps/web/src/run-panel.tsx +0 -52
- package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js +0 -11
- package/bundles/ragents.orchestration/server/chunks/chunk-QS3WJ3D7.js.map +0 -7
- package/bundles/ragents.orchestration/server/exports/server/contract.js +0 -9
- package/bundles/ragents.orchestration/server/exports/server/contract.js.map +0 -7
- package/bundles/ragents.orchestration/surface.hbs +0 -39
- package/bundles/ragents.orchestration/web/chunks/chunk-CFMJD5ZI.js +0 -2
- package/bundles/ragents.orchestration/web/chunks/chunk-CFMJD5ZI.js.map +0 -7
- package/bundles/ragents.orchestration/web/chunks/chunk-HOBLI364.js +0 -2
- package/bundles/ragents.orchestration/web/chunks/chunk-HOBLI364.js.map +0 -7
- package/bundles/ragents.orchestration/web/chunks/chunk-UCIMT2R4.js +0 -2
- package/bundles/ragents.orchestration/web/chunks/chunk-UCIMT2R4.js.map +0 -7
- package/bundles/ragents.orchestration/web/chunks/chunk-XGPPQCZR.js +0 -2
- package/bundles/ragents.orchestration/web/chunks/chunk-XGPPQCZR.js.map +0 -7
- package/bundles/ragents.orchestration/web/exports/web/surface-entities.js +0 -2
- package/bundles/ragents.orchestration/web/exports/web/surface-entities.js.map +0 -7
- package/bundles/ragents.orchestration/web/exports/web/tile-docking.js +0 -2
- package/bundles/ragents.orchestration/web/exports/web/tile-docking.js.map +0 -7
- package/plugins/ragents.actor-programs/web/ExpandIcon.tsx +0 -5
- package/plugins/ragents.actor-programs/web/FullscreenHost.tsx +0 -31
- package/plugins/ragents.orchestration/contract.ts +0 -51
- package/plugins/ragents.orchestration/server/contract.ts +0 -14
- package/plugins/ragents.orchestration/server/surface-tool.ts +0 -198
- package/plugins/ragents.orchestration/surface.hbs +0 -39
- package/plugins/ragents.orchestration/tiled-layout.ts +0 -55
- package/plugins/ragents.orchestration/web/ActorChatPreview.tsx +0 -29
- package/plugins/ragents.orchestration/web/ActorShortcut.tsx +0 -74
- package/plugins/ragents.orchestration/web/ActorSurfaceControls.tsx +0 -130
- package/plugins/ragents.orchestration/web/ActorTile.tsx +0 -23
- package/plugins/ragents.orchestration/web/Center.tsx +0 -133
- package/plugins/ragents.orchestration/web/SurfaceTiles.tsx +0 -47
- package/plugins/ragents.orchestration/web/TiledSurface.tsx +0 -280
- package/plugins/ragents.orchestration/web/actor-header-settings.ts +0 -41
- package/plugins/ragents.orchestration/web/run-panel/RunPanelSettings.tsx +0 -50
- package/plugins/ragents.orchestration/web/run-panel/run-panel-settings.ts +0 -47
- package/plugins/ragents.orchestration/web/run-panel/sheet-status.ts +0 -30
- package/plugins/ragents.orchestration/web/surface-entities.ts +0 -21
- package/plugins/ragents.orchestration/web/surface-view-settings.ts +0 -42
- package/plugins/ragents.orchestration/web/tile-docking.ts +0 -105
- package/plugins/ragents.orchestration/web/tiled-view-settings.ts +0 -74
- /package/apps/web/dist/assets/{mermaid-HWGCJPDP-BPn4aV2r.css → main-BPn4aV2r.css} +0 -0
|
@@ -1,82 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ArrowUpRightIcon, LayoutGridIcon,
|
|
1
|
+
import { runApps, selectedRunApp, RunAppView } from "@ragents/web/run-apps";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react";
|
|
3
|
+
import { ArrowUpRightIcon, LayoutGridIcon, SearchIcon } from "lucide-react";
|
|
4
4
|
import { useAccess } from "@ragents/web/AccessContext";
|
|
5
5
|
import { ChatPanel } from "quassel";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import { Button,
|
|
6
|
+
import { useRunPanelHost } from "@ragents/web/run-panel/host";
|
|
7
|
+
import { useSurfaceController, type SurfaceCenterContext, type CardSectionContribution, type SessionContext, type SessionNavigation } from "@ragents/web/PluginRegistry";
|
|
8
|
+
import { INSPECTION_TAB_ID } from "../InspectionPanel";
|
|
9
|
+
import { Button, StartupNotice } from "@ragents/web/ui";
|
|
10
10
|
import { ActorChat } from "../ActorChat";
|
|
11
11
|
import { ActorChatControls } from "../ActorChatControls";
|
|
12
|
-
import { useActorHeaderMode } from "../actor-header-settings";
|
|
13
12
|
import { surfaceStartupState, type SurfaceStartupState } from "../surface-startup";
|
|
14
|
-
import { actorVisibleOnSurface, saveSurfaceViewPreferences, useSurfaceViewPreferences } from "../surface-view-settings";
|
|
15
13
|
import { cardSectionsClass } from "../constants";
|
|
16
14
|
import type { FlowSelection } from "../FlowInspector";
|
|
17
|
-
import { useProgramSlot } from "../program-slot";
|
|
18
15
|
import { chatPrimaryId, runViewFrom, type RunActor, type RunView } from "@ragents/web/run-view";
|
|
19
16
|
import { AddresseeControl } from "./AddresseeControl";
|
|
20
|
-
import { runPanelActors
|
|
21
|
-
import { useRunPanelSettings } from "./run-panel-settings";
|
|
17
|
+
import { runPanelActors } from "./run-panel-actors";
|
|
22
18
|
import { chatShowsContent, useRunPanelStartup } from "./run-panel-startup";
|
|
23
|
-
import {
|
|
24
|
-
import { sheetStatus } from "./sheet-status";
|
|
19
|
+
import { saveRunPanelState, useRunPanelState, type RunPanelState } from "./run-panel-state";
|
|
25
20
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
const chipRowClass = "flex flex-none flex-wrap items-center gap-1.5 border-b border-border bg-shell px-2 py-1.5";
|
|
29
|
-
const chipClass = "flex h-7 max-w-[200px] flex-none cursor-pointer items-center gap-1.5 rounded-full border border-border bg-card pr-2 pl-1 text-[0.72rem] font-semibold text-foreground hover:bg-accent focus-visible:outline-2 focus-visible:outline-ring/60 focus-visible:outline-offset-1 aria-pressed:border-primary aria-pressed:bg-primary/10";
|
|
30
|
-
const chatLayoutClass: Readonly<Record<ChatLayout, string>> = {
|
|
31
|
-
full: "relative flex min-h-0 min-w-0 flex-1 flex-col",
|
|
32
|
-
side: "relative flex min-h-0 min-w-0 flex-col",
|
|
33
|
-
floating: "absolute inset-x-2.5 bottom-0 z-20 flex flex-col overflow-hidden rounded-t-2xl border border-b-0 border-border bg-card shadow-[0_-16px_60px_#00000080] transition-[height] duration-300 ease-[cubic-bezier(.32,.72,0,1)] motion-reduce:transition-none",
|
|
34
|
-
};
|
|
21
|
+
const chipClass = "flex h-7 max-w-[200px] flex-none cursor-pointer items-center gap-1.5 rounded-full border border-border bg-card px-2 text-[0.72rem] font-semibold text-foreground hover:bg-accent focus-visible:outline-2 focus-visible:outline-ring/60 aria-selected:border-primary aria-selected:bg-primary/10";
|
|
22
|
+
const viewClass = "relative flex min-h-0 min-w-0 flex-1 flex-col";
|
|
35
23
|
const chatSurfaceClass = "rounded-none border-0 bg-transparent";
|
|
36
|
-
const chatModeButtonClass = "aria-pressed:bg-accent aria-pressed:text-primary";
|
|
37
|
-
const chatModes: readonly { Icon: typeof MessageSquareIcon; label: string; mode: RunPanelChatMode; title: string }[] = [
|
|
38
|
-
{ Icon: MessageSquareIcon, label: "Chat only", mode: "chat", title: "Chat only: the chat fills the run panel, the mini-app steps back" },
|
|
39
|
-
{ Icon: PanelBottomIcon, label: "Chat at bottom", mode: "bottom", title: "Chat at bottom: the sheet over the mini-app" },
|
|
40
|
-
{ Icon: PanelRightIcon, label: "Chat on right", mode: "side", title: "Chat on right: next to the mini-app" },
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
interface RunPanelElement {
|
|
44
|
-
Element: SurfaceElementContribution["Element"];
|
|
45
|
-
definition: SurfaceElementDefinition;
|
|
46
|
-
}
|
|
47
24
|
|
|
48
25
|
export function OrchestrationRunPanel(props: SurfaceCenterContext) {
|
|
49
26
|
return <RunPanel key={props.session.session.id} {...props} />;
|
|
50
27
|
}
|
|
51
28
|
|
|
52
|
-
function RunPanel({ surfaceElements, cardSections, navigation, renderChat, session
|
|
29
|
+
function RunPanel({ surfaceElements, cardSections, navigation, renderChat, session }: SurfaceCenterContext) {
|
|
53
30
|
const runId = session.session.id;
|
|
31
|
+
const surface = useSurfaceController();
|
|
54
32
|
const inspect = useAccess().can("runs.inspect");
|
|
55
33
|
const host = useRunPanelHost();
|
|
56
34
|
const view = runViewFrom(session.runView);
|
|
57
35
|
const stored = useRunPanelState(runId);
|
|
58
|
-
const centered = useCenterElements(runId);
|
|
59
|
-
const rootRef = useRef<HTMLDivElement>(null);
|
|
60
|
-
const sheetRef = useRef<HTMLElement>(null);
|
|
61
|
-
const grabRef = useRef<HTMLButtonElement>(null);
|
|
62
|
-
const statusRef = useRef<HTMLButtonElement>(null);
|
|
63
|
-
const [chatWidth, setChatWidth] = useState(stored.chatWidth);
|
|
64
|
-
const [width, setWidth] = useState(0);
|
|
65
|
-
const [addresseeOpen, setAddresseeOpen] = useState(false);
|
|
66
|
-
const settings = useRunPanelSettings();
|
|
67
|
-
useEffect(() => setChatWidth(stored.chatWidth), [stored.chatWidth]);
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
const root = rootRef.current;
|
|
70
|
-
if (!root) return;
|
|
71
|
-
const observer = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));
|
|
72
|
-
observer.observe(root);
|
|
73
|
-
return () => observer.disconnect();
|
|
74
|
-
}, []);
|
|
75
36
|
const save = useCallback((patch: Partial<RunPanelState>) => saveRunPanelState(runId, { ...stored, ...patch }), [runId, stored]);
|
|
76
|
-
const elements = useMemo
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
37
|
+
const elements = useMemo(() => runApps(session, surfaceElements), [surfaceElements, session]);
|
|
38
|
+
const selectedElement = host.kind === "browser" ? selectedRunApp(elements, stored.element) : undefined;
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (host.kind === "browser" && session.connected && stored.element !== null && !selectedElement) save({ element: null });
|
|
41
|
+
}, [host.kind, save, selectedElement, session.connected, stored.element]);
|
|
42
|
+
const [visited, setVisited] = useState<ReadonlySet<string>>(() => new Set(stored.element ? [stored.element] : []));
|
|
43
|
+
const selectedId = selectedElement?.definition.id;
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (selectedId) setVisited((current) => current.has(selectedId) ? current : new Set([...current, selectedId]));
|
|
46
|
+
}, [selectedId]);
|
|
80
47
|
const startup = useRunPanelStartup(
|
|
81
48
|
surfaceStartupState({ view, startup: session.startup, connected: session.connected, running: session.running, error: session.conversationError }),
|
|
82
49
|
session.connected,
|
|
@@ -84,424 +51,82 @@ function RunPanel({ surfaceElements, cardSections, navigation, renderChat, sessi
|
|
|
84
51
|
);
|
|
85
52
|
const notice = startup ? <RunPanelStartup state={startup} /> : undefined;
|
|
86
53
|
const actors = useMemo(() => view ? runPanelActors(view, inspect) : [], [inspect, view]);
|
|
87
|
-
const mode = useActorHeaderMode(runId);
|
|
88
|
-
const preferences = useSurfaceViewPreferences(runId);
|
|
89
|
-
const appActorIds = useMemo(() => new Set(elements.flatMap(({ definition }) => definition.anchorActorId ? [definition.anchorActorId] : [])), [elements]);
|
|
90
54
|
const primaryId = view ? chatPrimaryId(view) : undefined;
|
|
91
55
|
const selectedActor = actors.find((actor) => actor.id === stored.actor) ?? actors.find((actor) => actor.id === primaryId) ?? actors[0];
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
selectedId: selectedActor?.id,
|
|
96
|
-
onStage: (actor) => actor.id === primaryId || actorVisibleOnSurface(actor, appActorIds, preferences, primaryId),
|
|
97
|
-
}), [actors, appActorIds, mode, preferences, primaryId, selectedActor?.id]);
|
|
98
|
-
const availableWidth = () => rootRef.current?.clientWidth ?? Number.POSITIVE_INFINITY;
|
|
99
|
-
const selectActor = useCallback((actor: RunActor) => save({ actor: actor.id }), [save]);
|
|
100
|
-
const revealActor = useCallback((actor: RunActor) => {
|
|
101
|
-
saveSurfaceViewPreferences(runId, { ...preferences, actorVisibility: { ...preferences.actorVisibility, [actor.id]: true } });
|
|
56
|
+
const [visitedActors, setVisitedActors] = useState<ReadonlySet<string>>(() => new Set(stored.actor ? [stored.actor] : []));
|
|
57
|
+
const selectActor = useCallback((actor: RunActor) => {
|
|
58
|
+
setVisitedActors((current) => new Set([...current, actor.id]));
|
|
102
59
|
save({ actor: actor.id });
|
|
103
|
-
}, [
|
|
60
|
+
}, [save]);
|
|
104
61
|
const navigate = useCallback((selection: FlowSelection) => {
|
|
105
62
|
const actor = selection.type === "actor" ? actors.find((entry) => entry.id === selection.id) : undefined;
|
|
106
63
|
if (actor) selectActor(actor);
|
|
107
|
-
|
|
64
|
+
else if (inspect) navigation.openTab(INSPECTION_TAB_ID, selection);
|
|
65
|
+
}, [actors, inspect, navigation, selectActor]);
|
|
108
66
|
const primarySelected = selectedActor === undefined || selectedActor.id === primaryId;
|
|
109
|
-
const narrow = width < settings.sideWidth;
|
|
110
|
-
const layout = chatLayoutFor({ chat: stored.chat, hasElement: selectedElement !== undefined, narrow });
|
|
111
|
-
const sheet = useSheet(layout === "floating", sheetRef, grabRef, addresseeOpen, settings.openDelay);
|
|
112
|
-
const geometry = useSheetPeek(layout === "floating", sheetRef, grabRef, statusRef, selectedActor?.id);
|
|
113
|
-
const expandedHeight = Math.max(geometry.minimum, Math.min(stored.sheetExpandedHeight ?? geometry.maximum, geometry.maximum));
|
|
114
|
-
const resize = useSheetResize(layout === "floating", sheetRef, grabRef, sheet, geometry, expandedHeight,
|
|
115
|
-
(height) => save({ sheetExpandedHeight: Math.round(height) }));
|
|
116
|
-
const scrollerRef = useRef<HTMLDivElement | null>(null);
|
|
117
67
|
useEffect(() => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return () => window.clearTimeout(timer);
|
|
125
|
-
}, [layout, sheet.expanded]);
|
|
126
|
-
const status = useMemo(() => {
|
|
127
|
-
if (layout !== "floating" || !selectedActor) return undefined;
|
|
128
|
-
const messages = primarySelected ? session.messages : session.actorConversations?.[selectedActor.id] ?? [];
|
|
129
|
-
const working = primarySelected ? session.connected && (session.running || runIsWorking(session.runView, runId)) : selectedActor.lifecycle?.kind === "running";
|
|
130
|
-
return sheetStatus(messages, working, selectedActor.handle);
|
|
131
|
-
}, [layout, primarySelected, runId, selectedActor, session.actorConversations, session.connected, session.messages, session.runView, session.running]);
|
|
132
|
-
|
|
133
|
-
const addressee = view && selectedActor && <AddresseeControl
|
|
134
|
-
hidden={hidden}
|
|
135
|
-
onOpenChange={setAddresseeOpen}
|
|
136
|
-
onReveal={revealActor}
|
|
68
|
+
const selection = selectedElement ? { type: "run-app", id: selectedElement.definition.id }
|
|
69
|
+
: selectedActor ? { type: "actor", id: selectedActor.id } : undefined;
|
|
70
|
+
if (surface?.selection?.type !== selection?.type || surface?.selection?.id !== selection?.id) surface?.acceptSelection(selection);
|
|
71
|
+
}, [selectedActor, selectedElement, surface]);
|
|
72
|
+
const addressee = view && selectedActor && <><AddresseeControl
|
|
73
|
+
actors={actors}
|
|
137
74
|
onSelect={selectActor}
|
|
138
|
-
runId={runId}
|
|
139
75
|
selected={selectedActor}
|
|
140
|
-
shown={shown}
|
|
141
76
|
technical={inspect}
|
|
142
77
|
view={view}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
? renderChat({ chatElementClassName: chatSurfaceClass, chatScrollerRef: (element) => { scrollerRef.current = element; }, notice, toolbarLeft: addressee })
|
|
152
|
-
: <ActorRunPanelChat actor={selectedActor} cardSections={cardSections} navigation={navigation} notice={notice} onNavigate={navigate} session={session} toolbarLeft={addressee} view={view} />;
|
|
153
|
-
|
|
154
|
-
return <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-app" data-run-panel="run" ref={rootRef}>
|
|
155
|
-
{chatModeSwitch}
|
|
156
|
-
{layout !== "full" && elements.length > 1 && <nav aria-label="Mini-apps of the run" className={chipRowClass}>
|
|
157
|
-
{elements.map(({ definition }) => <ElementChip
|
|
158
|
-
centered={centered.has(definition.id)}
|
|
159
|
-
definition={definition}
|
|
78
|
+
/>{inspect && <Button aria-label={`Inspect @${selectedActor.handle}`} onClick={() => navigation.openTab(INSPECTION_TAB_ID, { type: "actor", id: selectedActor.id })} size="icon-sm" title="Inspect actor" variant="ghost"><SearchIcon /></Button>}</>;
|
|
79
|
+
|
|
80
|
+
return <div className="flex min-h-0 min-w-0 flex-1 flex-col bg-app" data-run-panel="run">
|
|
81
|
+
{elements.length > 0 && <nav aria-label="Mini-apps of the run" className="flex flex-none flex-wrap items-center gap-1.5 border-b border-border bg-shell px-2 py-1.5" role={host.kind === "browser" ? "tablist" : undefined}>
|
|
82
|
+
{host.kind === "browser" && <button aria-selected={!selectedElement} className={chipClass} onClick={() => save({ element: null })} role="tab" type="button">Chat</button>}
|
|
83
|
+
{elements.map(({ definition }) => <button
|
|
84
|
+
aria-selected={host.kind === "browser" ? definition.id === selectedElement?.definition.id : undefined}
|
|
85
|
+
className={chipClass}
|
|
160
86
|
key={definition.id}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
{layout !== "full" && selectedElement && <RunPanelStage centered={centered.has(selectedElement.definition.id)} element={selectedElement} navigation={navigation} peek={layout === "floating" ? geometry.minimum : 0} runId={runId} session={session} />}
|
|
171
|
-
{layout === "side" && <Sash
|
|
172
|
-
onChange={(next) => setChatWidth(clampChatWidth(next, availableWidth()))}
|
|
173
|
-
onCommit={(next) => save({ chatWidth: clampChatWidth(next, availableWidth()) })}
|
|
174
|
-
value={chatWidth}
|
|
175
|
-
/>}
|
|
176
|
-
{layout === "floating" && <div
|
|
177
|
-
aria-hidden
|
|
178
|
-
className="absolute inset-0 z-[15] bg-backdrop opacity-0 transition-opacity duration-300 pointer-events-none data-expanded:pointer-events-auto data-expanded:opacity-100 motion-reduce:transition-none"
|
|
179
|
-
data-expanded={sheet.expanded || undefined}
|
|
180
|
-
onClick={sheet.close}
|
|
181
|
-
/>}
|
|
182
|
-
<section
|
|
183
|
-
aria-label="Chat"
|
|
184
|
-
className={cn(chatLayoutClass[layout], layout === "floating" && "[&_[data-chat=composer]]:[--qsl-panel-height:var(--sheet-available-height)] [&:not([data-expanded])_[data-slot=card-sections]]:hidden [&[data-sheet-resizing]_[data-slot=card-sections]]:hidden")}
|
|
185
|
-
data-expanded={layout === "floating" && sheet.expanded ? true : undefined}
|
|
186
|
-
data-sheet-resizing={resize.height !== undefined || undefined}
|
|
187
|
-
data-view={primarySelected ? "chat" : "actor-chat"}
|
|
188
|
-
onBlurCapture={layout === "floating" ? () => sheet.closeLater(SHEET_BLUR_DELAY) : undefined}
|
|
189
|
-
onFocusCapture={layout === "floating" ? (event) => { if (event.target !== grabRef.current) sheet.open(); } : undefined}
|
|
190
|
-
onPointerEnter={layout === "floating" ? (event) => { if (event.pointerType === "mouse") sheet.openLater(); } : undefined}
|
|
191
|
-
onPointerLeave={layout === "floating" ? () => sheet.closeLater(settings.closeDelay) : undefined}
|
|
192
|
-
ref={sheetRef}
|
|
193
|
-
style={layout === "floating" ? { height: resize.height ?? (sheet.expanded ? expandedHeight : geometry.minimum), transition: resize.height !== undefined ? "none" : undefined, "--sheet-available-height": `${geometry.available}px` } as CSSProperties : undefined}
|
|
87
|
+
onClick={() => {
|
|
88
|
+
if (host.kind === "vscode") host.openApp(runId, definition.id, definition.title ?? definition.id);
|
|
89
|
+
else {
|
|
90
|
+
setVisited((current) => new Set([...current, definition.id]));
|
|
91
|
+
save({ element: definition.id });
|
|
92
|
+
}
|
|
93
|
+
}}
|
|
94
|
+
role={host.kind === "browser" ? "tab" : undefined}
|
|
95
|
+
type="button"
|
|
194
96
|
>
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{status && <button
|
|
217
|
-
className={cn("flex h-6 w-full flex-none cursor-pointer items-center gap-2 px-[var(--qsl-chat-horizontal-padding,24px)] text-left text-[0.7rem] transition-opacity duration-200 in-data-expanded:pointer-events-none in-data-expanded:opacity-0 motion-reduce:transition-none", status.kind === "waiting" ? "text-warning" : "text-muted-foreground")}
|
|
218
|
-
onClick={sheet.toggle}
|
|
219
|
-
ref={statusRef}
|
|
220
|
-
title={status.text}
|
|
221
|
-
type="button"
|
|
222
|
-
>
|
|
223
|
-
{status.kind === "working" && <Spinner aria-hidden className="size-3 flex-none" />}
|
|
224
|
-
<span className="truncate">{status.text}</span>
|
|
225
|
-
</button>}
|
|
226
|
-
{chat}
|
|
227
|
-
</section>
|
|
228
|
-
</div>
|
|
229
|
-
</div>;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/** The three views of the run panel in the header; "Chat on right" stays disabled while the run panel is too narrow. */
|
|
233
|
-
function ChatModeSwitch({ narrow, onChange, selected, sideWidth }: {
|
|
234
|
-
narrow: boolean;
|
|
235
|
-
onChange: (mode: RunPanelChatMode) => void;
|
|
236
|
-
selected: RunPanelChatMode;
|
|
237
|
-
sideWidth: number;
|
|
238
|
-
}) {
|
|
239
|
-
return <div aria-label="Run panel view" className="flex flex-none items-center gap-0.5 self-center rounded-lg border border-border bg-card p-0.5" role="group">
|
|
240
|
-
{chatModes.map(({ Icon, label, mode, title }) => {
|
|
241
|
-
const blocked = mode === "side" && narrow;
|
|
242
|
-
return <Button
|
|
243
|
-
aria-label={label}
|
|
244
|
-
aria-pressed={selected === mode}
|
|
245
|
-
className={chatModeButtonClass}
|
|
246
|
-
disabled={blocked}
|
|
247
|
-
key={mode}
|
|
248
|
-
onClick={() => onChange(mode)}
|
|
249
|
-
size="icon-sm"
|
|
250
|
-
title={blocked ? `Chat stays at the bottom while the panel is narrower than ${sideWidth} pixels` : title}
|
|
251
|
-
variant="ghost"
|
|
252
|
-
><Icon /></Button>;
|
|
97
|
+
<LayoutGridIcon aria-hidden className="size-3" />
|
|
98
|
+
<span className="truncate">{definition.title ?? definition.id}</span>
|
|
99
|
+
{host.kind === "vscode" && <ArrowUpRightIcon aria-hidden className="size-3" />}
|
|
100
|
+
</button>)}
|
|
101
|
+
</nav>}
|
|
102
|
+
<section aria-label="Chat" className={viewClass} data-view={primarySelected ? "chat" : "actor-chat"} hidden={selectedElement !== undefined} inert={selectedElement !== undefined} style={selectedElement ? { display: "none" } : undefined}>
|
|
103
|
+
<div className={viewClass} hidden={!primarySelected} inert={!primarySelected} style={primarySelected ? undefined : { display: "none" }}>
|
|
104
|
+
{renderChat({ chatElementClassName: chatSurfaceClass, notice, toolbarLeft: addressee })}
|
|
105
|
+
</div>
|
|
106
|
+
{view && actors.filter((actor) => actor.id !== primaryId && (visitedActors.has(actor.id) || actor.id === selectedActor?.id)).map((actor) => {
|
|
107
|
+
const selected = actor.id === selectedActor?.id;
|
|
108
|
+
return <div className={viewClass} hidden={!selected} inert={!selected} key={actor.id} style={selected ? undefined : { display: "none" }}>
|
|
109
|
+
<ActorRunPanelChat actor={actor} cardSections={cardSections} navigation={navigation} notice={notice} onNavigate={navigate} session={session} toolbarLeft={addressee} view={view} />
|
|
110
|
+
</div>;
|
|
111
|
+
})}
|
|
112
|
+
</section>
|
|
113
|
+
{host.kind === "browser" && elements.filter(({ definition }) => visited.has(definition.id) || definition.id === selectedElement?.definition.id).map((element) => {
|
|
114
|
+
const selected = element === selectedElement;
|
|
115
|
+
return <section aria-label={`Mini-app ${element.definition.title ?? element.definition.id}`} className={viewClass} hidden={!selected} inert={!selected} key={element.definition.id} role="tabpanel" style={selected ? undefined : { display: "none" }}>
|
|
116
|
+
<RunAppView app={element} navigation={navigation} session={session} />
|
|
117
|
+
</section>;
|
|
253
118
|
})}
|
|
254
119
|
</div>;
|
|
255
120
|
}
|
|
256
121
|
|
|
257
|
-
|
|
258
|
-
function useSheet(active: boolean, sheetRef: RefObject<HTMLElement | null>, grabRef: RefObject<HTMLButtonElement | null>, blocked: boolean, openDelay: number) {
|
|
259
|
-
const [expanded, setExpanded] = useState(false);
|
|
260
|
-
const [held, setHeld] = useState(false);
|
|
261
|
-
const guard = useRef({ held, blocked });
|
|
262
|
-
const resizing = useRef(false);
|
|
263
|
-
const timer = useRef<number>(undefined);
|
|
264
|
-
useEffect(() => { guard.current = { held, blocked }; }, [blocked, held]);
|
|
265
|
-
const cancel = useCallback(() => window.clearTimeout(timer.current), []);
|
|
266
|
-
const open = useCallback(() => { if (!resizing.current) { cancel(); setExpanded(true); } }, [cancel]);
|
|
267
|
-
const openLater = useCallback(() => {
|
|
268
|
-
if (resizing.current) return;
|
|
269
|
-
cancel();
|
|
270
|
-
timer.current = window.setTimeout(() => { if (!resizing.current) setExpanded(true); }, openDelay);
|
|
271
|
-
}, [cancel, openDelay]);
|
|
272
|
-
const closeLater = useCallback((delay: number) => {
|
|
273
|
-
if (resizing.current) return;
|
|
274
|
-
cancel();
|
|
275
|
-
timer.current = window.setTimeout(() => {
|
|
276
|
-
if (resizing.current || guard.current.held || guard.current.blocked
|
|
277
|
-
|| (document.activeElement !== grabRef.current && sheetRef.current?.contains(document.activeElement))) return;
|
|
278
|
-
setExpanded(false);
|
|
279
|
-
}, delay);
|
|
280
|
-
}, [cancel, grabRef, sheetRef]);
|
|
281
|
-
const close = useCallback(() => {
|
|
282
|
-
cancel();
|
|
283
|
-
setHeld(false);
|
|
284
|
-
setExpanded(false);
|
|
285
|
-
if (document.activeElement instanceof HTMLElement && sheetRef.current?.contains(document.activeElement)) document.activeElement.blur();
|
|
286
|
-
}, [cancel, sheetRef]);
|
|
287
|
-
const toggle = useCallback(() => {
|
|
288
|
-
if (expanded) { close(); return; }
|
|
289
|
-
setHeld(true);
|
|
290
|
-
open();
|
|
291
|
-
}, [close, expanded, open]);
|
|
292
|
-
const pauseResize = useCallback(() => { cancel(); resizing.current = true; }, [cancel]);
|
|
293
|
-
const finishResize = useCallback((previous?: { expanded: boolean; held: boolean }) => {
|
|
294
|
-
cancel();
|
|
295
|
-
resizing.current = false;
|
|
296
|
-
setExpanded(previous?.expanded ?? false);
|
|
297
|
-
setHeld(previous?.held ?? false);
|
|
298
|
-
}, [cancel]);
|
|
299
|
-
useEffect(() => { if (!active) close(); }, [active, close]);
|
|
300
|
-
useEffect(() => cancel, [cancel]);
|
|
301
|
-
useEffect(() => {
|
|
302
|
-
if (!active || !expanded) return;
|
|
303
|
-
const onKeyDown = (event: globalThis.KeyboardEvent) => { if (event.key === "Escape" && !resizing.current && !guard.current.blocked) close(); };
|
|
304
|
-
window.addEventListener("keydown", onKeyDown);
|
|
305
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
306
|
-
}, [active, close, expanded]);
|
|
307
|
-
return { expanded, held, open, openLater, closeLater, close, toggle, pauseResize, finishResize };
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/** The height of the collapsed sheet: grip, status line and input including open actions above it, measured on the input of the chat frame. */
|
|
311
|
-
function useSheetPeek(active: boolean, sheetRef: RefObject<HTMLElement | null>, grabRef: RefObject<HTMLButtonElement | null>, statusRef: RefObject<HTMLButtonElement | null>, actorId: string | undefined) {
|
|
312
|
-
const [geometry, setGeometry] = useState({ minimum: 0, maximum: 0, available: 0, scale: 1 });
|
|
313
|
-
useLayoutEffect(() => {
|
|
314
|
-
if (!active) return;
|
|
315
|
-
const sheet = sheetRef.current;
|
|
316
|
-
const grab = grabRef.current;
|
|
317
|
-
const status = statusRef.current;
|
|
318
|
-
const composer = sheet?.querySelector<HTMLElement>('[data-chat="composer"]');
|
|
319
|
-
const container = sheet?.parentElement;
|
|
320
|
-
if (!sheet || !grab || !composer || !container) throw new Error("The panel sheet cannot find the grip, input or frame.");
|
|
321
|
-
const measure = () => {
|
|
322
|
-
const style = getComputedStyle(sheet);
|
|
323
|
-
const width = sheet.getBoundingClientRect().width;
|
|
324
|
-
if (width === 0) return;
|
|
325
|
-
const scale = width / Number.parseFloat(style.width);
|
|
326
|
-
const minimum = Math.ceil((grab.getBoundingClientRect().height + (status?.getBoundingClientRect().height ?? 0)
|
|
327
|
-
+ composer.getBoundingClientRect().height) / scale + Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth));
|
|
328
|
-
const available = Math.floor(container.getBoundingClientRect().height / scale * 0.9);
|
|
329
|
-
const maximum = Math.max(minimum, available);
|
|
330
|
-
setGeometry((previous) => previous.minimum === minimum && previous.maximum === maximum && previous.available === available && previous.scale === scale ? previous : { minimum, maximum, available, scale });
|
|
331
|
-
};
|
|
332
|
-
const observer = new ResizeObserver(measure);
|
|
333
|
-
observer.observe(composer);
|
|
334
|
-
observer.observe(grab);
|
|
335
|
-
observer.observe(container);
|
|
336
|
-
if (status) observer.observe(status);
|
|
337
|
-
measure();
|
|
338
|
-
return () => observer.disconnect();
|
|
339
|
-
}, [active, actorId, grabRef, sheetRef, statusRef]);
|
|
340
|
-
return geometry;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function useSheetResize(active: boolean, sheetRef: RefObject<HTMLElement | null>, grabRef: RefObject<HTMLButtonElement | null>, sheet: ReturnType<typeof useSheet>,
|
|
344
|
-
geometry: { minimum: number; maximum: number; scale: number }, expandedHeight: number, onCommit: (height: number) => void) {
|
|
345
|
-
const [height, setHeight] = useState<number>();
|
|
346
|
-
const drag = useRef<{ pointerId: number; startY: number; startHeight: number; height: number; moved: boolean; expanded: boolean; held: boolean }>(undefined);
|
|
347
|
-
const suppressClick = useRef(false);
|
|
348
|
-
const finish = (commit: boolean) => {
|
|
349
|
-
const previous = drag.current;
|
|
350
|
-
if (!previous) return;
|
|
351
|
-
drag.current = undefined;
|
|
352
|
-
suppressClick.current = previous.moved || !commit;
|
|
353
|
-
const grip = grabRef.current;
|
|
354
|
-
if (grip?.hasPointerCapture(previous.pointerId)) grip.releasePointerCapture(previous.pointerId);
|
|
355
|
-
if (commit && previous.moved) onCommit(previous.height);
|
|
356
|
-
sheet.finishResize(commit && previous.moved ? { expanded: true, held: previous.held } : previous);
|
|
357
|
-
setHeight(undefined);
|
|
358
|
-
};
|
|
359
|
-
const finishResize = sheet.finishResize;
|
|
360
|
-
useEffect(() => {
|
|
361
|
-
if (active) return;
|
|
362
|
-
drag.current = undefined;
|
|
363
|
-
setHeight(undefined);
|
|
364
|
-
finishResize();
|
|
365
|
-
}, [active, finishResize]);
|
|
366
|
-
const clamp = (value: number) => Math.max(geometry.minimum, Math.min(value, geometry.maximum));
|
|
367
|
-
return {
|
|
368
|
-
height,
|
|
369
|
-
onClick: (event: MouseEvent<HTMLButtonElement>) => {
|
|
370
|
-
const suppressed = suppressClick.current && event.detail > 0;
|
|
371
|
-
suppressClick.current = false;
|
|
372
|
-
if (suppressed) return;
|
|
373
|
-
sheet.toggle();
|
|
374
|
-
},
|
|
375
|
-
onPointerDown: (event: PointerEvent<HTMLButtonElement>) => {
|
|
376
|
-
if (event.button !== 0 || drag.current || !sheetRef.current) return;
|
|
377
|
-
event.preventDefault();
|
|
378
|
-
const startHeight = sheetRef.current.getBoundingClientRect().height / geometry.scale;
|
|
379
|
-
drag.current = { pointerId: event.pointerId, startY: event.clientY, startHeight, height: startHeight, moved: false, expanded: sheet.expanded, held: sheet.held };
|
|
380
|
-
suppressClick.current = false;
|
|
381
|
-
sheet.pauseResize();
|
|
382
|
-
setHeight(startHeight);
|
|
383
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
384
|
-
event.currentTarget.focus({ preventScroll: true });
|
|
385
|
-
},
|
|
386
|
-
onPointerMove: (event: PointerEvent<HTMLButtonElement>) => {
|
|
387
|
-
const current = drag.current;
|
|
388
|
-
if (!current || current.pointerId !== event.pointerId) return;
|
|
389
|
-
const distance = (current.startY - event.clientY) / geometry.scale;
|
|
390
|
-
if (!current.moved && Math.abs(distance) < 3) return;
|
|
391
|
-
current.moved = true;
|
|
392
|
-
current.height = clamp(current.startHeight + distance);
|
|
393
|
-
setHeight(current.height);
|
|
394
|
-
},
|
|
395
|
-
onPointerUp: (event: PointerEvent<HTMLButtonElement>) => { if (drag.current?.pointerId === event.pointerId) finish(true); },
|
|
396
|
-
onPointerCancel: (event: PointerEvent<HTMLButtonElement>) => { if (drag.current?.pointerId === event.pointerId) finish(false); },
|
|
397
|
-
onKeyDown: (event: KeyboardEvent<HTMLButtonElement>) => {
|
|
398
|
-
if (event.key === "Escape" && drag.current) {
|
|
399
|
-
event.preventDefault();
|
|
400
|
-
event.stopPropagation();
|
|
401
|
-
finish(false);
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
const next = event.key === "ArrowUp" ? expandedHeight + 24 : event.key === "ArrowDown" ? expandedHeight - 24
|
|
405
|
-
: event.key === "Home" ? geometry.minimum : event.key === "End" ? geometry.maximum : undefined;
|
|
406
|
-
if (next === undefined || drag.current) return;
|
|
407
|
-
event.preventDefault();
|
|
408
|
-
sheet.finishResize({ expanded: true, held: sheet.held });
|
|
409
|
-
onCommit(clamp(next));
|
|
410
|
-
},
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function ElementChip({ centered, definition, onSelect, selected, session, view }: {
|
|
415
|
-
centered: boolean;
|
|
416
|
-
definition: SurfaceElementDefinition;
|
|
417
|
-
onSelect: () => void;
|
|
418
|
-
selected: boolean;
|
|
419
|
-
session: SessionContext;
|
|
420
|
-
view: RunView | undefined;
|
|
421
|
-
}) {
|
|
422
|
-
const programs = useProgramSlot();
|
|
423
|
-
const title = definition.title ?? definition.id;
|
|
424
|
-
const attention = elementNeedsAttention(view, definition, programs?.needsAnswer(session, definition.id) ?? false);
|
|
425
|
-
return <button aria-pressed={selected && !centered} className={chipClass} data-tone="app" onClick={onSelect} title={centered ? `${title} is in the center. Click brings the window to the front.` : `Show mini-app ${title}`} type="button">
|
|
426
|
-
<span className="grid size-5 flex-none place-items-center rounded-full bg-glass-app text-foreground [&>svg]:size-3"><LayoutGridIcon /></span>
|
|
427
|
-
<span className="truncate">{title}</span>
|
|
428
|
-
{attention && <span aria-label="Answer expected" className="grid size-4 flex-none place-items-center rounded-full bg-warning text-[0.62rem] font-bold text-background" role="img">!</span>}
|
|
429
|
-
{centered && <ArrowUpRightIcon aria-hidden className="size-3 flex-none text-muted-foreground" />}
|
|
430
|
-
</button>;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function RunPanelStage({ centered, element, navigation, peek, runId, session }: {
|
|
434
|
-
centered: boolean;
|
|
435
|
-
element: RunPanelElement;
|
|
436
|
-
navigation: SessionNavigation;
|
|
437
|
-
peek: number;
|
|
438
|
-
runId: string;
|
|
439
|
-
session: SessionContext;
|
|
440
|
-
}) {
|
|
441
|
-
const host = useRunPanelHost();
|
|
442
|
-
const programs = useProgramSlot();
|
|
443
|
-
const { Element, definition } = element;
|
|
444
|
-
const title = definition.title ?? definition.id;
|
|
445
|
-
return <section aria-label={`Mini-app ${title}`} className="group/stage relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-app" style={peek > 0 ? { paddingBottom: peek } : undefined}>
|
|
446
|
-
<div className="absolute top-1.5 right-2 z-[5] flex gap-0.5 rounded-lg border border-border bg-card p-0.5 opacity-0 transition-opacity group-hover/stage:opacity-100 group-focus-within/stage:opacity-100 motion-reduce:transition-none">
|
|
447
|
-
{host.kind === "vscode"
|
|
448
|
-
? <Button aria-label={`Move ${title} to the center`} onClick={() => host.openInCenter(runId, definition.id, title)} size="icon-sm" title="Move to the center" variant="ghost"><ArrowUpRightIcon /></Button>
|
|
449
|
-
: programs && <Button aria-label={`Open ${title} in full view`} onClick={() => programs.openFullscreen(definition.id)} size="icon-sm" title="Full view" variant="ghost"><Maximize2Icon /></Button>}
|
|
450
|
-
</div>
|
|
451
|
-
<div className="relative flex min-h-0 flex-1 flex-col" data-run-panel="stage">
|
|
452
|
-
{centered
|
|
453
|
-
? <div className="m-auto grid gap-3 p-4 text-center text-[0.76rem] text-muted-foreground" role="status">
|
|
454
|
-
<span>{title} is in the center.</span>
|
|
455
|
-
<Button onClick={() => host.returnToRunPanel(runId, definition.id)} size="sm" variant="outline">Back to panel</Button>
|
|
456
|
-
</div>
|
|
457
|
-
: <Element definition={definition} navigation={navigation} session={session} />}
|
|
458
|
-
</div>
|
|
459
|
-
</section>;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/** The vertical grip between stage and the chat next to it; dragging left makes the chat wider. */
|
|
463
|
-
function Sash({ onChange, onCommit, value }: { onChange: (next: number) => void; onCommit: (next: number) => void; value: number }) {
|
|
464
|
-
const drag = useRef<{ startX: number; startWidth: number }>(undefined);
|
|
465
|
-
const finish = (event: PointerEvent<HTMLDivElement>) => {
|
|
466
|
-
if (!drag.current) return;
|
|
467
|
-
drag.current = undefined;
|
|
468
|
-
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
469
|
-
onCommit(value);
|
|
470
|
-
};
|
|
471
|
-
return <div
|
|
472
|
-
aria-label="Chat width"
|
|
473
|
-
aria-orientation="vertical"
|
|
474
|
-
aria-valuenow={value}
|
|
475
|
-
className="flex w-2.5 flex-none cursor-col-resize touch-none items-center justify-center border-r border-l border-border bg-shell select-none hover:bg-accent focus-visible:outline-2 focus-visible:outline-ring/60 focus-visible:-outline-offset-2"
|
|
476
|
-
onKeyDown={(event) => {
|
|
477
|
-
const step = event.key === "ArrowRight" ? -24 : event.key === "ArrowLeft" ? 24 : 0;
|
|
478
|
-
if (step === 0) return;
|
|
479
|
-
event.preventDefault();
|
|
480
|
-
onChange(value + step);
|
|
481
|
-
onCommit(value + step);
|
|
482
|
-
}}
|
|
483
|
-
onPointerCancel={finish}
|
|
484
|
-
onPointerDown={(event) => {
|
|
485
|
-
drag.current = { startX: event.clientX, startWidth: value };
|
|
486
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
487
|
-
}}
|
|
488
|
-
onPointerMove={(event) => { if (drag.current) onChange(drag.current.startWidth + drag.current.startX - event.clientX); }}
|
|
489
|
-
onPointerUp={finish}
|
|
490
|
-
role="separator"
|
|
491
|
-
tabIndex={0}
|
|
492
|
-
>
|
|
493
|
-
<span aria-hidden className="h-10 w-1 rounded-full bg-border-strong" />
|
|
494
|
-
</div>;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function ActorRunPanelChat({ actor, cardSections, navigation, notice, onNavigate, session, toolbarLeft, view }: {
|
|
122
|
+
export function ActorRunPanelChat({ actor, cardSections, navigation, notice, onNavigate, session, toolbarLeft, view }: {
|
|
498
123
|
actor: RunActor;
|
|
499
124
|
cardSections: readonly CardSectionContribution[];
|
|
500
125
|
navigation: SessionNavigation;
|
|
501
|
-
notice
|
|
126
|
+
notice?: ReactNode;
|
|
502
127
|
onNavigate: (selection: FlowSelection) => void;
|
|
503
128
|
session: SessionContext;
|
|
504
|
-
toolbarLeft
|
|
129
|
+
toolbarLeft?: ReactNode;
|
|
505
130
|
view: RunView;
|
|
506
131
|
}) {
|
|
507
132
|
const running = actor.lifecycle?.kind === "running";
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { SurfaceElementDefinition } from "@ragents/web/PluginRegistry";
|
|
2
|
-
import { actorVisibleInHeader, type ActorHeaderMode } from "../actor-header-settings";
|
|
3
1
|
import { chatPrimaryId, type RunActor, type RunView } from "@ragents/web/run-view";
|
|
4
2
|
|
|
5
3
|
/** The actors of the chip row: coordinator first, then in creation order; without the inspect right only LLM agents. */
|
|
@@ -10,30 +8,5 @@ export const runPanelActors = (view: RunView, inspect: boolean): RunActor[] => {
|
|
|
10
8
|
return [...primary, ...candidates.filter((actor) => actor.id !== primaryId)];
|
|
11
9
|
};
|
|
12
10
|
|
|
13
|
-
export interface RunPanelActorFilter {
|
|
14
|
-
mode: ActorHeaderMode;
|
|
15
|
-
onStage: (actor: RunActor) => boolean;
|
|
16
|
-
primaryId: string | null | undefined;
|
|
17
|
-
selectedId: string | undefined;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Visible according to the surface's actor display; the coordinator and the selected actor always stay as a chip. */
|
|
21
|
-
export const partitionRunPanelActors = (actors: readonly RunActor[], filter: RunPanelActorFilter): { shown: RunActor[]; hidden: RunActor[] } => {
|
|
22
|
-
const shown: RunActor[] = [];
|
|
23
|
-
const hidden: RunActor[] = [];
|
|
24
|
-
for (const actor of actors) {
|
|
25
|
-
const pinned = actor.id === filter.primaryId || actor.id === filter.selectedId;
|
|
26
|
-
(pinned || actorVisibleInHeader(actor, filter.mode, filter.onStage(actor)) ? shown : hidden).push(actor);
|
|
27
|
-
}
|
|
28
|
-
return { shown, hidden };
|
|
29
|
-
};
|
|
30
|
-
|
|
31
11
|
export const pendingInputCount = (view: RunView, actorId: string): number =>
|
|
32
12
|
view.inputs.filter((input) => input.actorId === actorId && input.lifecycle.kind === "pending").length;
|
|
33
|
-
|
|
34
|
-
export const pendingActionCount = (view: RunView, askedBy: string): number =>
|
|
35
|
-
view.actions.filter((action) => action.status === "pending" && action.askedBy === askedBy).length;
|
|
36
|
-
|
|
37
|
-
/** A chip carries an exclamation mark when a host confirmation or an action of the actor waits for input. */
|
|
38
|
-
export const elementNeedsAttention = (view: RunView | undefined, definition: SurfaceElementDefinition, confirmationPending: boolean): boolean =>
|
|
39
|
-
confirmationPending || (view !== undefined && definition.anchorActorId !== undefined && pendingActionCount(view, definition.anchorActorId) > 0);
|