@ai-matrx/chat 0.4.22 → 0.4.26
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/CHANGELOG.md +43 -0
- package/dist/agent-copy/RichCopySplit.d.ts +11 -1
- package/dist/agent-copy/RichCopySplit.js +9 -3
- package/dist/agents/components/chat/ChatConversationRoom.js +6 -4
- package/dist/agents/components/chat/ChatNewClient.js +7 -4
- package/dist/agents/components/chat/ChatNewHeader.js +3 -2
- package/dist/agents/components/chat/default-chat-mandate.d.ts +7 -0
- package/dist/agents/components/chat/default-chat-mandate.js +9 -0
- package/dist/agents/components/context-items/bodies/UnregisteredBody.d.ts +6 -3
- package/dist/agents/components/context-items/bodies/UnregisteredBody.js +3 -9
- package/dist/agents/components/inputs/resources/attach-resource.js +1 -0
- package/dist/agents/components/inputs/smart-input/SmartAgentInput.js +16 -1
- package/dist/agents/components/inputs/smart-input/composer/ComposerMenu.js +3 -2
- package/dist/agents/components/inputs/smart-input/composer/ComposerPlusMenu.js +41 -35
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +29 -20
- package/dist/agents/components/messages-display/user/AgentUserMessage.js +3 -1
- package/dist/agents/components/shared/AgentConversationColumn.d.ts +10 -1
- package/dist/agents/components/shared/AgentConversationColumn.js +26 -2
- package/dist/agents/redux/execution-system/messages/messages.slice.js +6 -1
- package/dist/agents/redux/execution-system/thunks/await-launching-conversation.d.ts +16 -0
- package/dist/agents/redux/execution-system/thunks/await-launching-conversation.js +16 -0
- package/dist/agents/redux/execution-system/thunks/execute-instance.thunk.js +34 -1
- package/dist/agents/redux/execution-system/thunks/process-stream.js +11 -0
- package/dist/agents/redux/execution-system/thunks/smart-execute.thunk.js +10 -0
- package/dist/agents/redux/execution-system/utils/required-organization.js +2 -2
- package/dist/agents/ui-first-tools/redux/build-ambient-context.js +8 -7
- package/dist/host/app-data-slots.d.ts +0 -1
- package/dist/host/app-data-slots.js +25 -5
- package/dist/host/bare-host-chrome.js +4 -0
- package/dist/host/contract.d.ts +8 -2
- package/dist/host/contract.js +2 -1
- package/dist/host/conversation-hooks-react.d.ts +2 -0
- package/dist/host/conversation-hooks-react.js +12 -0
- package/dist/host/conversation-hooks.d.ts +79 -0
- package/dist/host/conversation-hooks.js +91 -0
- package/dist/host/defaults/bare-block-dispatch.d.ts +6 -0
- package/dist/host/defaults/bare-block-dispatch.js +60 -0
- package/dist/host/defaults/files-host.d.ts +26 -0
- package/dist/host/defaults/files-host.js +212 -0
- package/dist/host/defaults/memory-navigation.d.ts +12 -0
- package/dist/host/defaults/memory-navigation.js +102 -0
- package/dist/host/defaults/resource-picker-menu.d.ts +14 -0
- package/dist/host/defaults/resource-picker-menu.js +117 -0
- package/dist/host/defaults/setting-control-input.d.ts +11 -0
- package/dist/host/defaults/setting-control-input.js +91 -0
- package/dist/host/index.d.ts +2 -0
- package/dist/host/index.js +5 -0
- package/dist/host/react.d.ts +1 -0
- package/dist/host/react.js +2 -0
- package/dist/host/ui-slots.d.ts +25 -1
- package/dist/host/ui-slots.js +83 -25
- package/dist/host/window-openers.d.ts +2 -0
- package/dist/host/window-openers.js +4 -0
- package/dist/react.cjs +1 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -1
- package/dist/react.js.map +1 -1
- package/dist/tool-call-visualization/registry/registry.js +4 -4
- package/dist/ui/aspect-ratio-options.d.ts +4 -26
- package/dist/ui/aspect-ratio-options.js +7 -75
- package/dist/ui/chat-panel/ChatPanel.d.ts +50 -0
- package/dist/ui/chat-panel/ChatPanel.js +175 -0
- package/dist/ui/chat-panel/drag.d.ts +87 -0
- package/dist/ui/chat-panel/drag.js +194 -0
- package/dist/window-panels/windows/agents/AgentRunWindow.js +17 -0
- package/dist/window-panels/windows/agents/agentRunWindowAddress.d.ts +19 -0
- package/dist/window-panels/windows/agents/agentRunWindowAddress.js +11 -1
- package/package.json +5 -5
|
@@ -1079,10 +1079,10 @@ function notSetUpRendererFor(toolName) {
|
|
|
1079
1079
|
if (hit) return hit;
|
|
1080
1080
|
const NotSetUp = (props) => {
|
|
1081
1081
|
reportUnregisteredHostSlot(`toolRenderer:${toolName}`, "the generic renderer is drawn");
|
|
1082
|
-
return
|
|
1083
|
-
|
|
1084
|
-
/* @__PURE__ */ jsx(GenericRenderer, { ...props })
|
|
1085
|
-
|
|
1082
|
+
return (
|
|
1083
|
+
// The generic renderer shows the call in full; the app's richer card is reported once, never labelled on screen.
|
|
1084
|
+
/* @__PURE__ */ jsx("div", { "data-chat-slot-fallback": `toolRenderer:${toolName}`, children: /* @__PURE__ */ jsx(GenericRenderer, { ...props }) })
|
|
1085
|
+
);
|
|
1086
1086
|
};
|
|
1087
1087
|
NotSetUp.displayName = `NotSetUpRenderer(${toolName})`;
|
|
1088
1088
|
notSetUpByTool.set(toolName, NotSetUp);
|
|
@@ -1,28 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Aspect-ratio option sets —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* "9:19.5", Runway's pixel pairs "1280:720", plus words like "auto" or
|
|
6
|
-
* "match_input_image"). Any option list that is mostly ratios renders through
|
|
7
|
-
* `AspectRatioSelect` — wherever it came from — so the rule lives on the data,
|
|
8
|
-
* not on one control key.
|
|
2
|
+
* Aspect-ratio option sets — the one copy lives in `@ai-matrx/design-system/controls`
|
|
3
|
+
* (moved there with `AspectRatioSelect`, chat host-slot batch 2, 2026-10-08).
|
|
4
|
+
* This subpath keeps `@ai-matrx/chat/ui/aspect-ratio-options` importers working.
|
|
9
5
|
*/
|
|
10
|
-
export
|
|
11
|
-
w: number;
|
|
12
|
-
h: number;
|
|
13
|
-
}
|
|
14
|
-
export declare function parseRatio(value: string): ParsedRatio | null;
|
|
15
|
-
/** True when at least two options and every option but the words is a ratio. */
|
|
16
|
-
export declare function isAspectRatioOptionSet(options: readonly string[]): boolean;
|
|
17
|
-
/** "Square", "Landscape", … — a plain word for the shape. */
|
|
18
|
-
export declare function ratioShapeName(value: string): string | null;
|
|
19
|
-
/** Words that are not ratios ("auto", "match_input_image") read as words. */
|
|
20
|
-
export declare function humanizeRatioWord(value: string): string;
|
|
21
|
-
export interface RatioGroups {
|
|
22
|
-
/** Non-ratio words first (Auto, Match input image). */
|
|
23
|
-
words: string[];
|
|
24
|
-
common: string[];
|
|
25
|
-
more: string[];
|
|
26
|
-
}
|
|
27
|
-
/** Split options into words, common ratios (fixed order) and the rest (by shape). */
|
|
28
|
-
export declare function groupRatioOptions(options: readonly string[]): RatioGroups;
|
|
6
|
+
export { parseRatio, isAspectRatioOptionSet, ratioShapeName, humanizeRatioWord, groupRatioOptions, type ParsedRatio, type RatioGroups, } from "@ai-matrx/design-system/controls";
|
|
@@ -1,78 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (!(w > 0 && h > 0)) return null;
|
|
9
|
-
return { w, h };
|
|
10
|
-
}
|
|
11
|
-
function isAspectRatioOptionSet(options) {
|
|
12
|
-
const ratios = options.filter((o) => parseRatio(o) !== null).length;
|
|
13
|
-
return ratios >= 2 && ratios >= options.length - 2;
|
|
14
|
-
}
|
|
15
|
-
const COMMON_ORDER = [
|
|
16
|
-
"1:1",
|
|
17
|
-
"16:9",
|
|
18
|
-
"9:16",
|
|
19
|
-
"4:3",
|
|
20
|
-
"3:4",
|
|
21
|
-
"3:2",
|
|
22
|
-
"2:3",
|
|
23
|
-
"4:5",
|
|
24
|
-
"5:4",
|
|
25
|
-
"21:9"
|
|
26
|
-
];
|
|
27
|
-
function reduced(r) {
|
|
28
|
-
const scale = 1e3;
|
|
29
|
-
let a = Math.round(r.w * scale);
|
|
30
|
-
let b = Math.round(r.h * scale);
|
|
31
|
-
const gcd = (x, y) => y === 0 ? x : gcd(y, x % y);
|
|
32
|
-
const g = gcd(a, b);
|
|
33
|
-
a /= g;
|
|
34
|
-
b /= g;
|
|
35
|
-
return `${a}:${b}`;
|
|
36
|
-
}
|
|
37
|
-
function ratioShapeName(value) {
|
|
38
|
-
const r = parseRatio(value);
|
|
39
|
-
if (!r) return null;
|
|
40
|
-
const k = r.w / r.h;
|
|
41
|
-
if (Math.abs(k - 1) < 0.02) return "Square";
|
|
42
|
-
if (k >= 2.2) return "Ultrawide";
|
|
43
|
-
if (k <= 1 / 2.2) return "Tall";
|
|
44
|
-
if (k > 1.6) return "Widescreen";
|
|
45
|
-
if (k < 1 / 1.6) return "Story";
|
|
46
|
-
return k > 1 ? "Landscape" : "Portrait";
|
|
47
|
-
}
|
|
48
|
-
function humanizeRatioWord(value) {
|
|
49
|
-
return humanizeIdentifier(value) || value;
|
|
50
|
-
}
|
|
51
|
-
function groupRatioOptions(options) {
|
|
52
|
-
const words = [];
|
|
53
|
-
const ratioOpts = [];
|
|
54
|
-
for (const o of options) (parseRatio(o) ? ratioOpts : words).push(o);
|
|
55
|
-
const byReduced = /* @__PURE__ */ new Map();
|
|
56
|
-
for (const o of ratioOpts) {
|
|
57
|
-
const key = reduced(parseRatio(o));
|
|
58
|
-
if (!byReduced.has(key)) byReduced.set(key, o);
|
|
59
|
-
}
|
|
60
|
-
const common = [];
|
|
61
|
-
for (const c of COMMON_ORDER) {
|
|
62
|
-
const hit = byReduced.get(reduced(parseRatio(c)));
|
|
63
|
-
if (hit) common.push(hit);
|
|
64
|
-
}
|
|
65
|
-
const commonSet = new Set(common);
|
|
66
|
-
const more = ratioOpts.filter((o) => !commonSet.has(o)).sort((a, b) => {
|
|
67
|
-
const ra = parseRatio(a);
|
|
68
|
-
const rb = parseRatio(b);
|
|
69
|
-
return rb.w / rb.h - ra.w / ra.h;
|
|
70
|
-
});
|
|
71
|
-
if (ratioOpts.length <= 6) {
|
|
72
|
-
return { words, common: [...common, ...more], more: [] };
|
|
73
|
-
}
|
|
74
|
-
return { words, common, more };
|
|
75
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
parseRatio,
|
|
3
|
+
isAspectRatioOptionSet,
|
|
4
|
+
ratioShapeName,
|
|
5
|
+
humanizeRatioWord,
|
|
6
|
+
groupRatioOptions
|
|
7
|
+
} from "@ai-matrx/design-system/controls";
|
|
76
8
|
export {
|
|
77
9
|
groupRatioOptions,
|
|
78
10
|
humanizeRatioWord,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package's chat panel chrome (HOST-OWNED-SOURCE-PLAN §2.6, P28f): a DOCK
|
|
3
|
+
* (a full-height side column with a resize edge) and a floating WINDOW (a
|
|
4
|
+
* draggable card) around whatever chat the host puts inside — normally an
|
|
5
|
+
* `AgentConversationColumn`. Tailwind + the design-system tokens only; no
|
|
6
|
+
* compat sheet.
|
|
7
|
+
*
|
|
8
|
+
* Slots, all optional and absent when not given (never a dead control):
|
|
9
|
+
* header one compact row: `title` (node), `badge`, `working`, `actions`
|
|
10
|
+
* list a conversation list opened by "Conversations" in the header,
|
|
11
|
+
* with `listHeader` above it (what a click costs, etc.)
|
|
12
|
+
* width / onWidthChange / minWidth / maxWidth the host owns the width
|
|
13
|
+
* label the accessible name (a host's keyboard scope may key on it)
|
|
14
|
+
*/
|
|
15
|
+
import { type ReactNode } from "react";
|
|
16
|
+
import { type DraggablePanelPosition } from "./drag.js";
|
|
17
|
+
export declare const CHAT_PANEL_DOCK_MIN_WIDTH = 320;
|
|
18
|
+
export declare const CHAT_PANEL_DOCK_MAX_WIDTH = 720;
|
|
19
|
+
export declare const CHAT_PANEL_WINDOW_WIDTH = 420;
|
|
20
|
+
export interface ChatPanelHeader {
|
|
21
|
+
title?: ReactNode;
|
|
22
|
+
badge?: ReactNode;
|
|
23
|
+
working?: ReactNode;
|
|
24
|
+
actions?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
interface ChatPanelCommon {
|
|
27
|
+
header?: ChatPanelHeader;
|
|
28
|
+
/** A conversation list; present ⇒ the header carries "Conversations". */
|
|
29
|
+
list?: ReactNode;
|
|
30
|
+
listHeader?: ReactNode;
|
|
31
|
+
label?: string;
|
|
32
|
+
className?: string;
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}
|
|
35
|
+
export interface ChatDockPanelProps extends ChatPanelCommon {
|
|
36
|
+
side?: "start" | "end";
|
|
37
|
+
width?: number;
|
|
38
|
+
onWidthChange?: (next: number) => void;
|
|
39
|
+
minWidth?: number;
|
|
40
|
+
maxWidth?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ChatWindowPanelProps extends ChatPanelCommon {
|
|
43
|
+
initialPosition?: DraggablePanelPosition;
|
|
44
|
+
width?: number;
|
|
45
|
+
height?: number | string;
|
|
46
|
+
onPositionCommit?: (position: DraggablePanelPosition) => void;
|
|
47
|
+
}
|
|
48
|
+
export declare function ChatDockPanel({ header, list, listHeader, label, className, children, side, width, onWidthChange, minWidth, maxWidth, }: ChatDockPanelProps): ReactNode;
|
|
49
|
+
export declare function ChatWindowPanel({ header, list, listHeader, label, className, children, initialPosition, width, height, onPositionCommit, }: ChatWindowPanelProps): ReactNode;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
useDraggablePanel,
|
|
6
|
+
useResizableWidth
|
|
7
|
+
} from "./drag.js";
|
|
8
|
+
const CHAT_PANEL_DOCK_MIN_WIDTH = 320;
|
|
9
|
+
const CHAT_PANEL_DOCK_MAX_WIDTH = 720;
|
|
10
|
+
const CHAT_PANEL_WINDOW_WIDTH = 420;
|
|
11
|
+
function HeaderRow({
|
|
12
|
+
header,
|
|
13
|
+
listToggle,
|
|
14
|
+
dragHandleProps
|
|
15
|
+
}) {
|
|
16
|
+
return /* @__PURE__ */ jsxs(
|
|
17
|
+
"div",
|
|
18
|
+
{
|
|
19
|
+
className: "flex h-10 shrink-0 items-center gap-2 border-b border-border px-2",
|
|
20
|
+
"data-chat-panel-header": "",
|
|
21
|
+
...dragHandleProps ?? {},
|
|
22
|
+
children: [
|
|
23
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
24
|
+
header?.title ? /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: header.title }) : null,
|
|
25
|
+
header?.badge ?? null,
|
|
26
|
+
header?.working ?? null
|
|
27
|
+
] }),
|
|
28
|
+
/* @__PURE__ */ jsxs(
|
|
29
|
+
"div",
|
|
30
|
+
{
|
|
31
|
+
className: "flex shrink-0 items-center gap-1",
|
|
32
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
33
|
+
children: [
|
|
34
|
+
listToggle,
|
|
35
|
+
header?.actions ?? null
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
function useListToggle(list) {
|
|
44
|
+
const [open, setOpen] = useState(false);
|
|
45
|
+
const id = useId();
|
|
46
|
+
const toggle = list === void 0 ? null : /* @__PURE__ */ jsx(
|
|
47
|
+
"button",
|
|
48
|
+
{
|
|
49
|
+
type: "button",
|
|
50
|
+
className: "rounded-md px-2 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
51
|
+
"aria-expanded": open,
|
|
52
|
+
"aria-controls": id,
|
|
53
|
+
onClick: () => setOpen((v) => !v),
|
|
54
|
+
children: "Conversations"
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
return { open, id, toggle };
|
|
58
|
+
}
|
|
59
|
+
function ListArea({
|
|
60
|
+
id,
|
|
61
|
+
listHeader,
|
|
62
|
+
list
|
|
63
|
+
}) {
|
|
64
|
+
return /* @__PURE__ */ jsxs("div", { id, className: "max-h-[45%] shrink-0 overflow-y-auto border-b border-border", "data-chat-panel-list": "", children: [
|
|
65
|
+
listHeader ? /* @__PURE__ */ jsx("div", { className: "px-3 pt-2 text-xs text-muted-foreground", children: listHeader }) : null,
|
|
66
|
+
list
|
|
67
|
+
] });
|
|
68
|
+
}
|
|
69
|
+
function ChatDockPanel({
|
|
70
|
+
header,
|
|
71
|
+
list,
|
|
72
|
+
listHeader,
|
|
73
|
+
label,
|
|
74
|
+
className,
|
|
75
|
+
children,
|
|
76
|
+
side = "start",
|
|
77
|
+
width = CHAT_PANEL_DOCK_MIN_WIDTH,
|
|
78
|
+
onWidthChange,
|
|
79
|
+
minWidth = CHAT_PANEL_DOCK_MIN_WIDTH,
|
|
80
|
+
maxWidth = CHAT_PANEL_DOCK_MAX_WIDTH
|
|
81
|
+
}) {
|
|
82
|
+
const listToggle = useListToggle(list);
|
|
83
|
+
const resize = useResizableWidth({
|
|
84
|
+
width,
|
|
85
|
+
min: minWidth,
|
|
86
|
+
max: maxWidth,
|
|
87
|
+
edge: side === "start" ? "end" : "start",
|
|
88
|
+
...onWidthChange === void 0 ? {} : { onWidthChange }
|
|
89
|
+
});
|
|
90
|
+
return /* @__PURE__ */ jsxs(
|
|
91
|
+
"aside",
|
|
92
|
+
{
|
|
93
|
+
ref: resize.panelRef,
|
|
94
|
+
className: `relative flex h-full min-h-0 shrink-0 flex-col bg-background ${side === "start" ? "border-r" : "border-l"} border-border ${className ?? ""}`,
|
|
95
|
+
"data-chat-panel": "dock",
|
|
96
|
+
"data-side": side,
|
|
97
|
+
"data-resizing": resize.resizing,
|
|
98
|
+
style: { width },
|
|
99
|
+
role: "complementary",
|
|
100
|
+
"aria-label": label ?? "Chat",
|
|
101
|
+
children: [
|
|
102
|
+
onWidthChange === void 0 ? null : /* @__PURE__ */ jsx(
|
|
103
|
+
"div",
|
|
104
|
+
{
|
|
105
|
+
className: `absolute inset-y-0 z-10 w-1.5 cursor-col-resize hover:bg-primary/30 focus-visible:bg-primary/40 focus-visible:outline-none ${side === "start" ? "-right-0.5" : "-left-0.5"}`,
|
|
106
|
+
"aria-label": "Resize the chat panel",
|
|
107
|
+
...resize.handleProps
|
|
108
|
+
}
|
|
109
|
+
),
|
|
110
|
+
/* @__PURE__ */ jsx(HeaderRow, { header, listToggle: listToggle.toggle }),
|
|
111
|
+
list !== void 0 && listToggle.open ? /* @__PURE__ */ jsx(ListArea, { id: listToggle.id, listHeader, list }) : null,
|
|
112
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col", children })
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function ChatWindowPanel({
|
|
118
|
+
header,
|
|
119
|
+
list,
|
|
120
|
+
listHeader,
|
|
121
|
+
label,
|
|
122
|
+
className,
|
|
123
|
+
children,
|
|
124
|
+
initialPosition,
|
|
125
|
+
width = CHAT_PANEL_WINDOW_WIDTH,
|
|
126
|
+
height = "min(640px, calc(100vh - 120px))",
|
|
127
|
+
onPositionCommit
|
|
128
|
+
}) {
|
|
129
|
+
const listToggle = useListToggle(list);
|
|
130
|
+
const [fallback] = useState(() => ({
|
|
131
|
+
x: Math.max(8, (globalThis.innerWidth ?? width + 32) - width - 24),
|
|
132
|
+
y: 96
|
|
133
|
+
}));
|
|
134
|
+
const drag = useDraggablePanel({
|
|
135
|
+
initial: initialPosition ?? fallback,
|
|
136
|
+
...onPositionCommit === void 0 ? {} : { onCommit: onPositionCommit }
|
|
137
|
+
});
|
|
138
|
+
return /* @__PURE__ */ jsxs(
|
|
139
|
+
"div",
|
|
140
|
+
{
|
|
141
|
+
ref: drag.panelRef,
|
|
142
|
+
className: `fixed z-40 flex flex-col overflow-hidden rounded-xl border border-border bg-background shadow-xl ${className ?? ""}`,
|
|
143
|
+
"data-chat-panel": "window",
|
|
144
|
+
"data-dragging": drag.dragging,
|
|
145
|
+
role: "dialog",
|
|
146
|
+
"aria-label": label ?? "Chat",
|
|
147
|
+
style: {
|
|
148
|
+
left: drag.position.x,
|
|
149
|
+
top: drag.position.y,
|
|
150
|
+
width,
|
|
151
|
+
height,
|
|
152
|
+
transform: "translate(var(--mx-chat-panel-dx, 0px), var(--mx-chat-panel-dy, 0px))"
|
|
153
|
+
},
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ jsx(
|
|
156
|
+
HeaderRow,
|
|
157
|
+
{
|
|
158
|
+
header,
|
|
159
|
+
listToggle: listToggle.toggle,
|
|
160
|
+
dragHandleProps: drag.dragHandleProps
|
|
161
|
+
}
|
|
162
|
+
),
|
|
163
|
+
list !== void 0 && listToggle.open ? /* @__PURE__ */ jsx(ListArea, { id: listToggle.id, listHeader, list }) : null,
|
|
164
|
+
/* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 flex-col", children })
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
export {
|
|
170
|
+
CHAT_PANEL_DOCK_MAX_WIDTH,
|
|
171
|
+
CHAT_PANEL_DOCK_MIN_WIDTH,
|
|
172
|
+
CHAT_PANEL_WINDOW_WIDTH,
|
|
173
|
+
ChatDockPanel,
|
|
174
|
+
ChatWindowPanel
|
|
175
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useDraggablePanel()` — dragging that does not re-render the chat.
|
|
3
|
+
*
|
|
4
|
+
* THE RULE, lifted from the Workflow Studio's own `FloatingChatPanel` because
|
|
5
|
+
* it was paid for there: a pointer move paints through CSS CUSTOM PROPERTIES
|
|
6
|
+
* inside ONE requestAnimationFrame, and the position is committed to React
|
|
7
|
+
* state EXACTLY ONCE, on release. Writing React state per pointer event
|
|
8
|
+
* re-renders the transcript sixty times a second while an answer is streaming
|
|
9
|
+
* into it, which is precisely the budget design §7 sets out to protect.
|
|
10
|
+
*
|
|
11
|
+
* Two details that look like noise and are not:
|
|
12
|
+
* - the paint offsets are CLEARED in a layout effect on the frame that adopts
|
|
13
|
+
* the committed position, so the panel never jumps by the offset twice;
|
|
14
|
+
* - a pending frame is cancelled on unmount, because a callback holding a
|
|
15
|
+
* detached element is a leak that only shows up under a long session.
|
|
16
|
+
*/
|
|
17
|
+
import { type PointerEvent as ReactPointerEvent, type RefObject } from "react";
|
|
18
|
+
export interface DraggablePanelPosition {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}
|
|
22
|
+
export interface UseResizableWidthOptions {
|
|
23
|
+
/** The host's stored width. This hook never owns it. */
|
|
24
|
+
width: number;
|
|
25
|
+
min: number;
|
|
26
|
+
max: number;
|
|
27
|
+
/** Which edge the handle sits on — the side the panel grows FROM. */
|
|
28
|
+
edge: "start" | "end";
|
|
29
|
+
/** Told once, on release, and on every keyboard step. */
|
|
30
|
+
onWidthChange?: ((next: number) => void) | undefined;
|
|
31
|
+
}
|
|
32
|
+
export interface ResizableWidthHandle {
|
|
33
|
+
panelRef: RefObject<HTMLElement | null>;
|
|
34
|
+
resizing: boolean;
|
|
35
|
+
handleProps: {
|
|
36
|
+
role: "separator";
|
|
37
|
+
tabIndex: 0;
|
|
38
|
+
"aria-orientation": "vertical";
|
|
39
|
+
"aria-valuenow": number;
|
|
40
|
+
"aria-valuemin": number;
|
|
41
|
+
"aria-valuemax": number;
|
|
42
|
+
onPointerDown(event: ReactPointerEvent): void;
|
|
43
|
+
onPointerMove(event: ReactPointerEvent): void;
|
|
44
|
+
onPointerUp(event: ReactPointerEvent): void;
|
|
45
|
+
onPointerCancel(event: ReactPointerEvent): void;
|
|
46
|
+
onKeyDown(event: {
|
|
47
|
+
key: string;
|
|
48
|
+
preventDefault(): void;
|
|
49
|
+
}): void;
|
|
50
|
+
onDoubleClick(): void;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* THE WIDTH IS THE HOST'S, ALWAYS.
|
|
55
|
+
*
|
|
56
|
+
* The package never writes a width to storage: which pref store a surface uses,
|
|
57
|
+
* and whether the width is per-user or per-organization, is the host's to know
|
|
58
|
+
* (Law 6 — opinions become knobs, and the knob is theirs). So this hook paints
|
|
59
|
+
* the drag live and hands the final number back through one callback.
|
|
60
|
+
*
|
|
61
|
+
* The keyboard path is not decoration: a separator that only answers a pointer
|
|
62
|
+
* is unusable with a keyboard or a screen reader, and a panel whose size cannot
|
|
63
|
+
* be changed without a mouse is a dead control for the people who need it most.
|
|
64
|
+
*/
|
|
65
|
+
export declare function useResizableWidth(options: UseResizableWidthOptions): ResizableWidthHandle;
|
|
66
|
+
export interface UseDraggablePanelOptions {
|
|
67
|
+
initial: DraggablePanelPosition;
|
|
68
|
+
/** Keep this much of the panel on screen on every edge. */
|
|
69
|
+
keepVisiblePx?: number | undefined;
|
|
70
|
+
/** Told once per drag, on release — a host that persists the position. */
|
|
71
|
+
onCommit?: ((position: DraggablePanelPosition) => void) | undefined;
|
|
72
|
+
}
|
|
73
|
+
export interface DraggablePanelHandle {
|
|
74
|
+
position: DraggablePanelPosition;
|
|
75
|
+
/** Attach to the element that MOVES. */
|
|
76
|
+
panelRef: RefObject<HTMLElement | null>;
|
|
77
|
+
/** Spread onto the element you drag BY (the header). */
|
|
78
|
+
dragHandleProps: {
|
|
79
|
+
onPointerDown(event: ReactPointerEvent): void;
|
|
80
|
+
onPointerMove(event: ReactPointerEvent): void;
|
|
81
|
+
onPointerUp(event: ReactPointerEvent): void;
|
|
82
|
+
onPointerCancel(event: ReactPointerEvent): void;
|
|
83
|
+
};
|
|
84
|
+
/** True while a pointer is down — a shell suppresses transitions with it. */
|
|
85
|
+
dragging: boolean;
|
|
86
|
+
}
|
|
87
|
+
export declare function useDraggablePanel(options: UseDraggablePanelOptions): DraggablePanelHandle;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useCallback,
|
|
3
|
+
useEffect,
|
|
4
|
+
useLayoutEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState
|
|
7
|
+
} from "react";
|
|
8
|
+
const KEYBOARD_STEP_PX = 24;
|
|
9
|
+
function useResizableWidth(options) {
|
|
10
|
+
const { width, min, max, edge, onWidthChange } = options;
|
|
11
|
+
const panelRef = useRef(null);
|
|
12
|
+
const [resizing, setResizing] = useState(false);
|
|
13
|
+
const frameRef = useRef(null);
|
|
14
|
+
const dragRef = useRef(
|
|
15
|
+
null
|
|
16
|
+
);
|
|
17
|
+
const initialRef = useRef(width);
|
|
18
|
+
const clamp = useCallback(
|
|
19
|
+
(next) => Math.max(min, Math.min(max, Math.round(next))),
|
|
20
|
+
[min, max]
|
|
21
|
+
);
|
|
22
|
+
const commit = useCallback(
|
|
23
|
+
(next) => {
|
|
24
|
+
panelRef.current?.style.removeProperty("width");
|
|
25
|
+
onWidthChange?.(clamp(next));
|
|
26
|
+
},
|
|
27
|
+
[clamp, onWidthChange]
|
|
28
|
+
);
|
|
29
|
+
const end = useCallback(() => {
|
|
30
|
+
const drag = dragRef.current;
|
|
31
|
+
if (frameRef.current !== null) {
|
|
32
|
+
globalThis.cancelAnimationFrame?.(frameRef.current);
|
|
33
|
+
frameRef.current = null;
|
|
34
|
+
}
|
|
35
|
+
dragRef.current = null;
|
|
36
|
+
setResizing(false);
|
|
37
|
+
if (drag !== null) commit(drag.latest);
|
|
38
|
+
}, [commit]);
|
|
39
|
+
useEffect(
|
|
40
|
+
() => () => {
|
|
41
|
+
if (frameRef.current !== null) globalThis.cancelAnimationFrame?.(frameRef.current);
|
|
42
|
+
},
|
|
43
|
+
[]
|
|
44
|
+
);
|
|
45
|
+
return {
|
|
46
|
+
panelRef,
|
|
47
|
+
resizing,
|
|
48
|
+
handleProps: {
|
|
49
|
+
role: "separator",
|
|
50
|
+
tabIndex: 0,
|
|
51
|
+
"aria-orientation": "vertical",
|
|
52
|
+
"aria-valuenow": width,
|
|
53
|
+
"aria-valuemin": min,
|
|
54
|
+
"aria-valuemax": max,
|
|
55
|
+
onPointerDown(event) {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
dragRef.current = { startX: event.clientX, startWidth: width, latest: width };
|
|
58
|
+
setResizing(true);
|
|
59
|
+
event.target.setPointerCapture?.(event.pointerId);
|
|
60
|
+
},
|
|
61
|
+
onPointerMove(event) {
|
|
62
|
+
const drag = dragRef.current;
|
|
63
|
+
if (drag === null) return;
|
|
64
|
+
const delta = edge === "end" ? event.clientX - drag.startX : drag.startX - event.clientX;
|
|
65
|
+
drag.latest = clamp(drag.startWidth + delta);
|
|
66
|
+
if (frameRef.current !== null) return;
|
|
67
|
+
const schedule = globalThis.requestAnimationFrame;
|
|
68
|
+
const paint = () => {
|
|
69
|
+
frameRef.current = null;
|
|
70
|
+
const active = dragRef.current;
|
|
71
|
+
if (active === null || panelRef.current === null) return;
|
|
72
|
+
panelRef.current.style.width = `${active.latest}px`;
|
|
73
|
+
};
|
|
74
|
+
if (typeof schedule === "function") frameRef.current = schedule(paint);
|
|
75
|
+
else paint();
|
|
76
|
+
},
|
|
77
|
+
onPointerUp: end,
|
|
78
|
+
onPointerCancel: end,
|
|
79
|
+
onKeyDown(event) {
|
|
80
|
+
const grow = edge === "end" ? "ArrowRight" : "ArrowLeft";
|
|
81
|
+
const shrink = edge === "end" ? "ArrowLeft" : "ArrowRight";
|
|
82
|
+
if (event.key === grow) {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
commit(width + KEYBOARD_STEP_PX);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (event.key === shrink) {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
commit(width - KEYBOARD_STEP_PX);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
onDoubleClick() {
|
|
93
|
+
commit(initialRef.current);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const DEFAULT_KEEP_VISIBLE = 64;
|
|
99
|
+
function useDraggablePanel(options) {
|
|
100
|
+
const keepVisible = options.keepVisiblePx ?? DEFAULT_KEEP_VISIBLE;
|
|
101
|
+
const [position, setPosition] = useState(options.initial);
|
|
102
|
+
const [dragging, setDragging] = useState(false);
|
|
103
|
+
const panelRef = useRef(null);
|
|
104
|
+
const frameRef = useRef(null);
|
|
105
|
+
const adoptingRef = useRef(false);
|
|
106
|
+
const dragRef = useRef(null);
|
|
107
|
+
const clamp = useCallback(
|
|
108
|
+
(next) => {
|
|
109
|
+
const width = globalThis.innerWidth ?? 0;
|
|
110
|
+
const height = globalThis.innerHeight ?? 0;
|
|
111
|
+
if (width === 0 || height === 0) return next;
|
|
112
|
+
return {
|
|
113
|
+
x: Math.max(0, Math.min(width - keepVisible, next.x)),
|
|
114
|
+
y: Math.max(0, Math.min(height - keepVisible, next.y))
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
[keepVisible]
|
|
118
|
+
);
|
|
119
|
+
const onPointerDown = useCallback(
|
|
120
|
+
(event) => {
|
|
121
|
+
if (event.target.closest("button,a,input,select,textarea")) return;
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
dragRef.current = {
|
|
124
|
+
dx: event.clientX - position.x,
|
|
125
|
+
dy: event.clientY - position.y,
|
|
126
|
+
origin: position,
|
|
127
|
+
latest: position
|
|
128
|
+
};
|
|
129
|
+
setDragging(true);
|
|
130
|
+
event.target.setPointerCapture?.(event.pointerId);
|
|
131
|
+
},
|
|
132
|
+
[position]
|
|
133
|
+
);
|
|
134
|
+
const onPointerMove = useCallback(
|
|
135
|
+
(event) => {
|
|
136
|
+
const drag = dragRef.current;
|
|
137
|
+
if (drag === null) return;
|
|
138
|
+
drag.latest = clamp({ x: event.clientX - drag.dx, y: event.clientY - drag.dy });
|
|
139
|
+
if (frameRef.current !== null) return;
|
|
140
|
+
const schedule = globalThis.requestAnimationFrame;
|
|
141
|
+
const paint = () => {
|
|
142
|
+
frameRef.current = null;
|
|
143
|
+
const active = dragRef.current;
|
|
144
|
+
const element = panelRef.current;
|
|
145
|
+
if (active === null || element === null) return;
|
|
146
|
+
element.style.setProperty("--mx-chat-panel-dx", `${active.latest.x - active.origin.x}px`);
|
|
147
|
+
element.style.setProperty("--mx-chat-panel-dy", `${active.latest.y - active.origin.y}px`);
|
|
148
|
+
};
|
|
149
|
+
if (typeof schedule === "function") frameRef.current = schedule(paint);
|
|
150
|
+
else paint();
|
|
151
|
+
},
|
|
152
|
+
[clamp]
|
|
153
|
+
);
|
|
154
|
+
const end = useCallback(() => {
|
|
155
|
+
const drag = dragRef.current;
|
|
156
|
+
if (frameRef.current !== null) {
|
|
157
|
+
globalThis.cancelAnimationFrame?.(frameRef.current);
|
|
158
|
+
frameRef.current = null;
|
|
159
|
+
}
|
|
160
|
+
dragRef.current = null;
|
|
161
|
+
setDragging(false);
|
|
162
|
+
if (drag === null) return;
|
|
163
|
+
adoptingRef.current = true;
|
|
164
|
+
setPosition(drag.latest);
|
|
165
|
+
options.onCommit?.(drag.latest);
|
|
166
|
+
}, [options]);
|
|
167
|
+
useLayoutEffect(() => {
|
|
168
|
+
if (!adoptingRef.current) return;
|
|
169
|
+
adoptingRef.current = false;
|
|
170
|
+
panelRef.current?.style.removeProperty("--mx-chat-panel-dx");
|
|
171
|
+
panelRef.current?.style.removeProperty("--mx-chat-panel-dy");
|
|
172
|
+
});
|
|
173
|
+
useEffect(
|
|
174
|
+
() => () => {
|
|
175
|
+
if (frameRef.current !== null) globalThis.cancelAnimationFrame?.(frameRef.current);
|
|
176
|
+
},
|
|
177
|
+
[]
|
|
178
|
+
);
|
|
179
|
+
return {
|
|
180
|
+
position,
|
|
181
|
+
panelRef,
|
|
182
|
+
dragging,
|
|
183
|
+
dragHandleProps: {
|
|
184
|
+
onPointerDown,
|
|
185
|
+
onPointerMove,
|
|
186
|
+
onPointerUp: end,
|
|
187
|
+
onPointerCancel: end
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export {
|
|
192
|
+
useDraggablePanel,
|
|
193
|
+
useResizableWidth
|
|
194
|
+
};
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
AGENT_RUN_WINDOW_AGENT_ARG,
|
|
50
50
|
AGENT_RUN_WINDOW_CONVERSATION_ARG,
|
|
51
51
|
addressableConversationId,
|
|
52
|
+
adoptLateWindowSeed,
|
|
52
53
|
AGENT_RUN_WINDOW_URL_MODE
|
|
53
54
|
} from "./agentRunWindowAddress.js";
|
|
54
55
|
import { ErrorAlchemyMenu } from "../../../host/ui-slots.js";
|
|
@@ -534,6 +535,22 @@ function AgentRunWindowInner({
|
|
|
534
535
|
const liveResourceIdentity = seedApplies ? initialResourceIdentity : null;
|
|
535
536
|
const liveAutoRun = seedApplies ? initialAutoRun : false;
|
|
536
537
|
const [selectedConversationId, setSelectedConversationId] = useState(initialSelectedConversationId);
|
|
538
|
+
const [seenSeed, setSeenSeed] = useState({
|
|
539
|
+
agent: initialAgentId,
|
|
540
|
+
conversation: initialSelectedConversationId
|
|
541
|
+
});
|
|
542
|
+
const lateSeed = adoptLateWindowSeed({
|
|
543
|
+
seen: seenSeed,
|
|
544
|
+
incoming: { agent: initialAgentId, conversation: initialSelectedConversationId },
|
|
545
|
+
current: { agent: agentId, conversation: selectedConversationId }
|
|
546
|
+
});
|
|
547
|
+
if (lateSeed) {
|
|
548
|
+
setSeenSeed(lateSeed.seen);
|
|
549
|
+
if (lateSeed.agent !== agentId) setAgentId(lateSeed.agent);
|
|
550
|
+
if (lateSeed.conversation !== selectedConversationId) {
|
|
551
|
+
setSelectedConversationId(lateSeed.conversation);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
537
554
|
const siblingWindowCount = useChatManagedWindowCount("agent-run-window:");
|
|
538
555
|
const initialPosition = AGENT_RUN_WINDOW_POSITIONS[siblingWindowCount % AGENT_RUN_WINDOW_POSITIONS.length];
|
|
539
556
|
const liveAgentName = useAgentName(agentId) ?? null;
|