@ai-matrx/chat 0.2.0 → 0.2.2
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 +22 -0
- package/dist/agent-copy/AlchemySurfaceBridge.d.ts +1 -1
- package/dist/agent-copy/AlchemySurfaceBridge.js +1 -1
- package/dist/agent-copy/buildAgentPayload.d.ts +1 -1
- package/dist/agent-copy/buildAgentPayload.js +1 -1
- package/dist/agent-copy/page-capture/pageCapture.d.ts +1 -1
- package/dist/agents/components/inputs/smart-input/AgentTextarea.d.ts +1 -2
- package/dist/agents/components/inputs/smart-input/AgentTextarea.js +0 -12
- package/dist/agents/components/inputs/smart-input/InputActionButtons.js +3 -3
- package/dist/agents/components/inputs/smart-input/SmartAgentInputStacked.js +13 -7
- package/dist/agents/components/inputs/smart-input/SmartInputFileDropTarget.d.ts +8 -1
- package/dist/agents/components/inputs/smart-input/SmartInputFileDropTarget.js +3 -1
- package/dist/agents/components/inputs/smart-input/composer/ComposerMetaRow.js +1 -1
- package/dist/agents/components/inputs/smart-input/composer/ComposerModeSwitch.js +10 -15
- package/dist/agents/components/inputs/smart-input/composer/useComposerFold.d.ts +3 -2
- package/dist/agents/components/inputs/smart-input/composer/useComposerFold.js +30 -5
- package/dist/agents/components/messages-display/MessageTimestamp.d.ts +8 -1
- package/dist/agents/components/messages-display/MessageTimestamp.js +3 -4
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +5 -3
- package/dist/agents/components/messages-display/assistant/AssistantMessageFooter.d.ts +5 -0
- package/dist/agents/components/messages-display/assistant/AssistantMessageFooter.js +36 -13
- package/dist/agents/components/messages-display/assistant/AssistantTurnGroup.js +3 -1
- package/dist/surfaces/manifests/_baseline.manifest.d.ts +59 -0
- package/dist/surfaces/manifests/_baseline.manifest.js +56 -1
- package/dist/surfaces/runtime/SurfaceRuntimeContext.d.ts +1 -1
- package/dist/surfaces/runtime/custom-field-targets.d.ts +6 -6
- package/dist/surfaces/runtime/custom-field-targets.js +7 -21
- package/dist/surfaces/runtime/surface-feedback.d.ts +7 -9
- package/dist/surfaces/runtime/surface-feedback.js +8 -21
- package/dist/surfaces/runtime/surface-writeback.d.ts +5 -2
- package/dist/surfaces/runtime/surface-writeback.js +171 -135
- package/dist/surfaces/runtime/window-forms.d.ts +2 -2
- package/dist/surfaces/runtime/window-forms.js +3 -9
- package/dist/utils/connectors/marks.d.ts +2 -2
- package/dist/utils/connectors/marks.js +2 -9
- package/package.json +14 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
### Last re-sync before matrx-frontend consumes the published package (P27)
|
|
5
|
+
|
|
6
|
+
- Carries the frontend's latest `packages/chat` changes: the composer's run-controls row folds when
|
|
7
|
+
it overflows (`useComposerFold`, `ComposerMetaRow`), message timestamp and assistant footer/turn
|
|
8
|
+
group updates, input action buttons and stacked input.
|
|
9
|
+
- The module tree's suites run here under Jest (`pnpm test:modules`); the host-tie ratchet moved
|
|
10
|
+
in from matrx-frontend (`pnpm check:host-boundary`, budget 0).
|
|
11
|
+
- Consumer action: none.
|
|
12
|
+
|
|
13
|
+
## 0.2.1
|
|
14
|
+
### Re-sync with matrx-frontend's packages/chat (P27)
|
|
15
|
+
|
|
16
|
+
- Carries every frontend-side change made to `packages/chat` after the 0.2.0 copy: a pasted image
|
|
17
|
+
attaches once (react-dropzone owns paste; the hand-rolled paste handler is gone; the host's
|
|
18
|
+
no-paste setting reaches the drop target), the Chat/Work/Advanced switch is the design system's
|
|
19
|
+
`SegmentedControl`, the send tooltip uses `isMacLike` and the Connect chip the one GitHub glyph,
|
|
20
|
+
surfaces import `@ai-matrx/alchemy/operate` (ALC-16/17: platform write targets, Save to
|
|
21
|
+
document/workbook through the one door, `receipt.result` carries what the page's handler made).
|
|
22
|
+
- Fixes the copy's stale host suites (they import `testing/fake-db`, as the frontend moved them).
|
|
23
|
+
- Consumer action: none.
|
|
24
|
+
|
|
3
25
|
## 0.2.0
|
|
4
26
|
### The full chat system moves in (P27, PACKAGE-INDEPENDENCE)
|
|
5
27
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import { type SurfaceHandle } from "@ai-matrx/
|
|
2
|
+
import { type SurfaceHandle } from "@ai-matrx/alchemy/operate";
|
|
3
3
|
import type { SurfaceScopePayload } from "../surfaces/types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Creates a handle for this exact React mount. Hook-only runtime registrations
|
|
@@ -3,7 +3,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useId, useRef } from "react";
|
|
4
4
|
import {
|
|
5
5
|
createSurfaceTransferHandle
|
|
6
|
-
} from "@ai-matrx/
|
|
6
|
+
} from "@ai-matrx/alchemy/operate";
|
|
7
7
|
import { ContentTransferSurfaceProvider } from "@ai-matrx/design-system/content-transfer";
|
|
8
8
|
import { getSurfaceManifest } from "../host/ui-slots.js";
|
|
9
9
|
function useAlchemySurfaceHandle(surfaceName, getScope) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Compatibility import path; agent envelope serialization is package-owned. */
|
|
2
|
-
export { buildAgentPayload, fenceJsonBlock, type AgentPayloadInput, } from "@ai-matrx/
|
|
2
|
+
export { buildAgentPayload, fenceJsonBlock, type AgentPayloadInput, } from "@ai-matrx/alchemy/operate";
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* sections, and the recent requests with their timings. Pure: no React, no
|
|
15
15
|
* network — the suite drives it directly.
|
|
16
16
|
*/
|
|
17
|
-
import type { AgentPayloadInput } from "@ai-matrx/
|
|
17
|
+
import type { AgentPayloadInput } from "@ai-matrx/alchemy/operate";
|
|
18
18
|
import type { AlchemyGroomerConfig } from "@ai-matrx/design-system/content-transfer";
|
|
19
19
|
export type PageCaptureKind = "table-page" | "record" | "dialog" | "admin-page";
|
|
20
20
|
/** A chosen thing, by name and id. Either may be null (not chosen / not loaded). */
|
|
@@ -21,7 +21,6 @@ interface AgentTextareaProps {
|
|
|
21
21
|
singleRow?: boolean;
|
|
22
22
|
uploadRoot?: string;
|
|
23
23
|
uploadPath?: string;
|
|
24
|
-
enablePasteImages?: boolean;
|
|
25
24
|
surfaceKey?: string;
|
|
26
25
|
/**
|
|
27
26
|
* The key an unsent draft is also kept under, so it survives a reload of a
|
|
@@ -62,5 +61,5 @@ interface AgentTextareaProps {
|
|
|
62
61
|
/** With `composerType`: each text line is one 32px composer row (splash · page). */
|
|
63
62
|
composerRows?: boolean;
|
|
64
63
|
}
|
|
65
|
-
export declare function AgentTextarea({ conversationId, compact, singleRow, uploadRoot, uploadPath,
|
|
64
|
+
export declare function AgentTextarea({ conversationId, compact, singleRow, uploadRoot, uploadPath, surfaceKey, draftAlias, disableSend, initiallyExpanded, autoFocus, showExpandToggle, placeholder, minHeightPx, flush, composerType, composerRows, maxHeightPx, textMenu, }: AgentTextareaProps): React.JSX.Element;
|
|
66
65
|
export {};
|
|
@@ -23,14 +23,12 @@ import {
|
|
|
23
23
|
selectComposerHasSomethingToSend,
|
|
24
24
|
selectIsExecuting
|
|
25
25
|
} from "../../../redux/execution-system/selectors/aggregate.selectors.js";
|
|
26
|
-
import { useClipboardPaste } from "../../../../host/ui-slots.js";
|
|
27
26
|
import { focusUnlessTypingElsewhere } from "../../../../utils/dom/focus-guard.js";
|
|
28
27
|
import {
|
|
29
28
|
composerKeyIntent,
|
|
30
29
|
intentTakesTheKey
|
|
31
30
|
} from "../../../../ui/composer/composerSubmit.js";
|
|
32
31
|
import { readVerticalChrome, snapToLineGrid } from "./textarea-line-grid.js";
|
|
33
|
-
import { usePasteImageResource } from "../resources/usePasteImageResource.js";
|
|
34
32
|
import { useInstanceInputUndoRedo } from "../../../hooks/useInstanceInputUndoRedo.js";
|
|
35
33
|
import { ComposerDraftNotice } from "./ComposerDraftNotice.js";
|
|
36
34
|
import { ComposerToolsNotice } from "./ComposerToolsNotice.js";
|
|
@@ -48,7 +46,6 @@ function AgentTextarea({
|
|
|
48
46
|
singleRow = false,
|
|
49
47
|
uploadRoot = "userContent",
|
|
50
48
|
uploadPath = "agent-attachments",
|
|
51
|
-
enablePasteImages = true,
|
|
52
49
|
surfaceKey,
|
|
53
50
|
draftAlias,
|
|
54
51
|
disableSend = false,
|
|
@@ -128,15 +125,6 @@ function AgentTextarea({
|
|
|
128
125
|
if (disableSend) return;
|
|
129
126
|
dispatch(interruptAndSend({ conversationId, surfaceKey }));
|
|
130
127
|
}, [disableSend, conversationId, surfaceKey, dispatch]);
|
|
131
|
-
const handlePasteImage = usePasteImageResource(conversationId, {
|
|
132
|
-
uploadRoot,
|
|
133
|
-
uploadPath
|
|
134
|
-
});
|
|
135
|
-
useClipboardPaste({
|
|
136
|
-
textareaRef,
|
|
137
|
-
onPasteImage: handlePasteImage,
|
|
138
|
-
disabled: !enablePasteImages
|
|
139
|
-
});
|
|
140
128
|
const handleTextChange = useCallback(
|
|
141
129
|
(value) => {
|
|
142
130
|
dispatch(
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
smartExecute,
|
|
37
37
|
cancelExecution
|
|
38
38
|
} from "../../../redux/execution-system/thunks/smart-execute.thunk.js";
|
|
39
|
+
import { isMacLike } from "../../../hooks/useAgentUndoRedo.js";
|
|
39
40
|
import { MicDeviceMenu } from "../../../../host/ui-slots.js";
|
|
40
41
|
import { Button, SplitButton } from "@ai-matrx/design-system/controls";
|
|
41
42
|
import { cn } from "@ai-matrx/design-system";
|
|
@@ -171,7 +172,7 @@ function InputActionButtons({
|
|
|
171
172
|
] });
|
|
172
173
|
}
|
|
173
174
|
if (composer.part === "controls") {
|
|
174
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center justify-between gap-1.5 shrink-0", children: [
|
|
175
|
+
return /* @__PURE__ */ jsxs("div", { "data-composer-row": "", className: "flex min-w-0 items-center justify-between gap-1.5 shrink-0", children: [
|
|
175
176
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 shrink-0 items-center gap-1.5", children: [
|
|
176
177
|
plusMenu,
|
|
177
178
|
/* @__PURE__ */ jsx(DesktopPresenceIndicator, { conversationId }),
|
|
@@ -212,7 +213,6 @@ function ComposerSendSlot({
|
|
|
212
213
|
);
|
|
213
214
|
return /* @__PURE__ */ jsx(Fragment, { children: children(hasSomethingToSend) });
|
|
214
215
|
}
|
|
215
|
-
const IS_MAC = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);
|
|
216
216
|
function Keys({ keys }) {
|
|
217
217
|
return /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center gap-0.5", children: keys.map((key) => /* @__PURE__ */ jsx(
|
|
218
218
|
"kbd",
|
|
@@ -230,7 +230,7 @@ function ComposerSendButton({
|
|
|
230
230
|
disabled,
|
|
231
231
|
onSend
|
|
232
232
|
}) {
|
|
233
|
-
const mod =
|
|
233
|
+
const mod = isMacLike() ? "\u2318" : "Ctrl";
|
|
234
234
|
const rows = isExecuting ? [
|
|
235
235
|
{ label: "Queue for when it finishes", keys: submitOnEnter ? ["\u21B5"] : [mod, "\u21B5"] },
|
|
236
236
|
...submitOnEnter ? [{ label: "Steer in now", keys: [mod, "\u21B5"] }] : [],
|
|
@@ -22,6 +22,7 @@ import { composerShows } from "./composer/composer-mode-visibility.js";
|
|
|
22
22
|
import { ComposerChipsRow } from "./composer/ComposerChipsRow.js";
|
|
23
23
|
import { ComposerMetaRow, ComposerPills, ComposerScopeCluster, ComposerValueGroupChip } from "./composer/ComposerMetaRow.js";
|
|
24
24
|
import { useComposerFold } from "./composer/useComposerFold.js";
|
|
25
|
+
import { useTouchOnlyDevice } from "../../../../ui/composer/useTouchOnlyDevice.js";
|
|
25
26
|
function SmartAgentInputStacked({
|
|
26
27
|
conversationId,
|
|
27
28
|
uploadRoot = "userContent",
|
|
@@ -44,6 +45,7 @@ function SmartAgentInputStacked({
|
|
|
44
45
|
const [voiceBusy, setVoiceBusy] = useState(false);
|
|
45
46
|
const [expandRequestKey, setExpandRequestKey] = useState(0);
|
|
46
47
|
const { ref: composerRootRef, folded } = useComposerFold();
|
|
48
|
+
const touchOnly = useTouchOnlyDevice();
|
|
47
49
|
const showFreeformInput = useAppSelector(
|
|
48
50
|
selectShowFreeformInput(conversationId ?? "")
|
|
49
51
|
);
|
|
@@ -65,7 +67,8 @@ function SmartAgentInputStacked({
|
|
|
65
67
|
"div",
|
|
66
68
|
{
|
|
67
69
|
className: cn(
|
|
68
|
-
"matrx-touch-targets
|
|
70
|
+
touchOnly && "matrx-touch-targets",
|
|
71
|
+
"mx-auto flex w-full min-w-0 shrink-0 flex-col",
|
|
69
72
|
formCompact ? void 0 : "max-w-[768px]"
|
|
70
73
|
),
|
|
71
74
|
"data-composer-size": composer.size,
|
|
@@ -77,6 +80,7 @@ function SmartAgentInputStacked({
|
|
|
77
80
|
conversationId,
|
|
78
81
|
uploadRoot,
|
|
79
82
|
uploadPath,
|
|
83
|
+
pasteFiles: enablePasteImages,
|
|
80
84
|
className: cn(
|
|
81
85
|
"relative flex w-full min-h-0 flex-col gap-1.5 border border-border bg-card transition-colors focus-within:border-foreground/25",
|
|
82
86
|
"[&_[data-variable-row]]:px-1.5 [&_[data-variable-heading]]:px-1.5 [&>input[type=file]]:!hidden",
|
|
@@ -152,7 +156,6 @@ function SmartAgentInputStacked({
|
|
|
152
156
|
compact: isCompact,
|
|
153
157
|
uploadRoot,
|
|
154
158
|
uploadPath,
|
|
155
|
-
enablePasteImages,
|
|
156
159
|
surfaceKey,
|
|
157
160
|
disableSend: sendBlocked,
|
|
158
161
|
initiallyExpanded: expandRequestKey > 0,
|
|
@@ -182,7 +185,7 @@ function SmartAgentInputStacked({
|
|
|
182
185
|
"div",
|
|
183
186
|
{
|
|
184
187
|
ref: composerRootRef,
|
|
185
|
-
className: "matrx-touch-targets mx-auto flex w-full min-w-0 max-w-[420px] shrink-0 flex-col",
|
|
188
|
+
className: cn(touchOnly && "matrx-touch-targets", "mx-auto flex w-full min-w-0 max-w-[420px] shrink-0 flex-col"),
|
|
186
189
|
"data-composer-size": composer.size,
|
|
187
190
|
"data-composer-mode": composer.mode,
|
|
188
191
|
"data-assist-dock-avoid": "",
|
|
@@ -192,6 +195,7 @@ function SmartAgentInputStacked({
|
|
|
192
195
|
conversationId,
|
|
193
196
|
uploadRoot,
|
|
194
197
|
uploadPath,
|
|
198
|
+
pasteFiles: enablePasteImages,
|
|
195
199
|
className: cn(
|
|
196
200
|
"relative flex w-full min-h-0 flex-col gap-1 rounded-[18px] border p-1.5",
|
|
197
201
|
"border-glass-edge bg-glass shadow-glass backdrop-blur-glass backdrop-saturate-glass transition-[border-color,background-color,box-shadow]",
|
|
@@ -230,7 +234,6 @@ function SmartAgentInputStacked({
|
|
|
230
234
|
compact: true,
|
|
231
235
|
uploadRoot,
|
|
232
236
|
uploadPath,
|
|
233
|
-
enablePasteImages,
|
|
234
237
|
surfaceKey,
|
|
235
238
|
disableSend: sendBlocked,
|
|
236
239
|
autoFocus: false,
|
|
@@ -263,9 +266,11 @@ function SmartAgentInputStacked({
|
|
|
263
266
|
{
|
|
264
267
|
ref: composerRootRef,
|
|
265
268
|
className: cn(
|
|
266
|
-
// THE 44px FLOOR
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
+
// THE 44px FLOOR on a TOUCH device only. The shared class also fires
|
|
270
|
+
// on any viewport under 1024px, which stretched every button and pill
|
|
271
|
+
// of a narrowed desktop window to 44px (2026-10-06).
|
|
272
|
+
touchOnly && "matrx-touch-targets",
|
|
273
|
+
"mx-auto flex w-full min-w-0 shrink-0 flex-col gap-1",
|
|
269
274
|
isCompact ? void 0 : "max-w-[768px]"
|
|
270
275
|
),
|
|
271
276
|
"data-composer-size": composer.size,
|
|
@@ -280,6 +285,7 @@ function SmartAgentInputStacked({
|
|
|
280
285
|
conversationId,
|
|
281
286
|
uploadRoot,
|
|
282
287
|
uploadPath,
|
|
288
|
+
pasteFiles: enablePasteImages,
|
|
283
289
|
className: cardClassName,
|
|
284
290
|
children: [
|
|
285
291
|
/* @__PURE__ */ jsx(
|
|
@@ -2,7 +2,14 @@ interface SmartInputFileDropTargetProps extends Omit<React.ComponentPropsWithout
|
|
|
2
2
|
conversationId: string;
|
|
3
3
|
uploadRoot?: string;
|
|
4
4
|
uploadPath?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Pasted files (a screenshot, Cmd/Ctrl+V of a copied file) attach through
|
|
7
|
+
* the same upload as a drop. react-dropzone OWNS paste-to-upload (v20+,
|
|
8
|
+
* on by default); nothing else in the input may also catch the paste, or
|
|
9
|
+
* every pasted image attaches twice (2026-10-06). False = paste stays text.
|
|
10
|
+
*/
|
|
11
|
+
pasteFiles?: boolean;
|
|
5
12
|
children: React.ReactNode;
|
|
6
13
|
}
|
|
7
|
-
export declare function SmartInputFileDropTarget({ conversationId, uploadRoot, uploadPath, className, children, ...rootProps }: SmartInputFileDropTargetProps): import("react").JSX.Element;
|
|
14
|
+
export declare function SmartInputFileDropTarget({ conversationId, uploadRoot, uploadPath, pasteFiles, className, children, ...rootProps }: SmartInputFileDropTargetProps): import("react").JSX.Element;
|
|
8
15
|
export {};
|
|
@@ -8,6 +8,7 @@ function SmartInputFileDropTarget({
|
|
|
8
8
|
conversationId,
|
|
9
9
|
uploadRoot,
|
|
10
10
|
uploadPath,
|
|
11
|
+
pasteFiles = true,
|
|
11
12
|
className,
|
|
12
13
|
children,
|
|
13
14
|
...rootProps
|
|
@@ -19,7 +20,8 @@ function SmartInputFileDropTarget({
|
|
|
19
20
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
20
21
|
onDrop: (files) => void uploadResources(files),
|
|
21
22
|
noClick: true,
|
|
22
|
-
noKeyboard: true
|
|
23
|
+
noKeyboard: true,
|
|
24
|
+
noPaste: !pasteFiles
|
|
23
25
|
});
|
|
24
26
|
return /* @__PURE__ */ jsxs(
|
|
25
27
|
"div",
|
|
@@ -89,7 +89,7 @@ function ComposerMetaRow({
|
|
|
89
89
|
return (
|
|
90
90
|
// One line, always: a narrow column scrolls the row sideways, never wraps it.
|
|
91
91
|
// Every gap is the tap-target 6px; the clusters sit at the two ends.
|
|
92
|
-
/* @__PURE__ */ jsxs("div", { className: cn(COMPOSER_ROW_CLASS, "justify-between gap-1.5 px-1"), children: [
|
|
92
|
+
/* @__PURE__ */ jsxs("div", { "data-composer-row": "", className: cn(COMPOSER_ROW_CLASS, "justify-between gap-1.5 px-1"), children: [
|
|
93
93
|
/* @__PURE__ */ jsx(ComposerScopeCluster, { conversationId, composer, folded }),
|
|
94
94
|
/* @__PURE__ */ jsx(ComposerPills, { conversationId, composer, menuSide, folded })
|
|
95
95
|
] })
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
COMPOSER_MODE_LABELS
|
|
17
17
|
} from "./composer-types.js";
|
|
18
18
|
import { useComposerMode } from "./useComposerMode.js";
|
|
19
|
-
import {
|
|
19
|
+
import { SegmentedControl } from "@ai-matrx/design-system/controls";
|
|
20
20
|
function ComposerModeSwitch({
|
|
21
21
|
initialMode,
|
|
22
22
|
size = "bar",
|
|
@@ -30,20 +30,15 @@ function ComposerModeSwitch({
|
|
|
30
30
|
const fit = useCenterControlFit(cellRef, [fullRef, triggerRef], mode);
|
|
31
31
|
const measured = useSyncExternalStore(noopSubscribe, () => true, () => false);
|
|
32
32
|
const renderSegments = (interactive) => /* @__PURE__ */ jsx(
|
|
33
|
-
|
|
33
|
+
SegmentedControl,
|
|
34
34
|
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
),
|
|
43
|
-
children: COMPOSER_MODES.map((value) => {
|
|
44
|
-
const on = value === mode;
|
|
45
|
-
return /* @__PURE__ */ jsx(Button, { variant: "quiet", pressed: !!on, role: interactive ? "tab" : void 0, "aria-selected": interactive ? on : void 0, "aria-pressed": interactive ? void 0 : on, tabIndex: interactive ? void 0 : -1, onClick: interactive ? () => setMode(value) : void 0, children: COMPOSER_MODE_LABELS[value] }, value);
|
|
46
|
-
})
|
|
35
|
+
"aria-label": "Composer mode",
|
|
36
|
+
value: mode,
|
|
37
|
+
onValueChange: (value) => {
|
|
38
|
+
if (interactive) setMode(value);
|
|
39
|
+
},
|
|
40
|
+
data: COMPOSER_MODES.map((value) => ({ value, label: COMPOSER_MODE_LABELS[value] })),
|
|
41
|
+
className: cn("shrink-0", !interactive && "w-max max-w-none", className)
|
|
47
42
|
}
|
|
48
43
|
);
|
|
49
44
|
if (panel) return renderSegments(true);
|
|
@@ -61,7 +56,7 @@ function ComposerModeSwitch({
|
|
|
61
56
|
className: "relative flex w-full min-w-0 justify-center",
|
|
62
57
|
"data-route-nav-inflow": fit.inflow ? "" : void 0,
|
|
63
58
|
children: [
|
|
64
|
-
/* @__PURE__ */ jsxs("div", { "aria-hidden": true, className: "pointer-events-none invisible absolute left-0 top-0", children: [
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { "aria-hidden": true, inert: true, className: "pointer-events-none invisible absolute left-0 top-0", children: [
|
|
65
60
|
/* @__PURE__ */ jsx("div", { ref: fullRef, className: "w-max max-w-none", children: renderSegments(false) }),
|
|
66
61
|
/* @__PURE__ */ jsx("span", { ref: triggerRef, "data-route-nav-min": true, className: cn(triggerClass, "w-max max-w-none"), children: triggerFace })
|
|
67
62
|
] }),
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const COMPOSER_FOLD_WIDTH_PX = 480;
|
|
7
7
|
/**
|
|
8
|
-
* Watches the composer's own width and
|
|
9
|
-
* fold
|
|
8
|
+
* Watches the composer's own width — and whether a row under the card
|
|
9
|
+
* overflows — and re-renders only when the fold flips, never per pixel while
|
|
10
|
+
* a panel is dragged.
|
|
10
11
|
*/
|
|
11
12
|
export declare function useComposerFold(): {
|
|
12
13
|
ref: import("react").Dispatch<import("react").SetStateAction<HTMLDivElement | null>>;
|
|
@@ -6,15 +6,40 @@ function useComposerFold() {
|
|
|
6
6
|
const [folded, setFolded] = useState(false);
|
|
7
7
|
useEffect(() => {
|
|
8
8
|
if (!el || typeof ResizeObserver === "undefined") return void 0;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
let foldedAt = 0;
|
|
10
|
+
const evaluate = () => {
|
|
11
|
+
const width = el.getBoundingClientRect().width;
|
|
12
|
+
if (width === 0) return;
|
|
13
|
+
const rows = Array.from(el.querySelectorAll("[data-composer-row]"));
|
|
14
|
+
const overflowing = rows.some((row) => row.scrollWidth > row.clientWidth + 1);
|
|
15
|
+
setFolded((was) => {
|
|
16
|
+
if (width < COMPOSER_FOLD_WIDTH_PX) {
|
|
17
|
+
foldedAt = Math.max(foldedAt, width);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (!was && overflowing) {
|
|
21
|
+
foldedAt = width;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
if (was && width > foldedAt + UNFOLD_MARGIN_PX && !overflowing) {
|
|
25
|
+
foldedAt = 0;
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return was;
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const observer = new ResizeObserver(evaluate);
|
|
13
32
|
observer.observe(el);
|
|
14
|
-
|
|
33
|
+
const mutations = new MutationObserver(() => requestAnimationFrame(evaluate));
|
|
34
|
+
mutations.observe(el, { subtree: true, childList: true, characterData: true });
|
|
35
|
+
return () => {
|
|
36
|
+
observer.disconnect();
|
|
37
|
+
mutations.disconnect();
|
|
38
|
+
};
|
|
15
39
|
}, [el]);
|
|
16
40
|
return { ref, folded };
|
|
17
41
|
}
|
|
42
|
+
const UNFOLD_MARGIN_PX = 80;
|
|
18
43
|
export {
|
|
19
44
|
COMPOSER_FOLD_WIDTH_PX,
|
|
20
45
|
useComposerFold
|
|
@@ -9,6 +9,13 @@ interface TimestampDisplay {
|
|
|
9
9
|
compact: string;
|
|
10
10
|
}
|
|
11
11
|
export declare function formatTimestampDisplay(timestamp: TimestampInput): TimestampDisplay | null;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* The time at the END of the action row. It always occupies its space — it is
|
|
14
|
+
* revealed by opacity on hover/focus, never inserted — so showing it shifts
|
|
15
|
+
* nothing. It never wraps: a narrow row gets the time alone ("2:23 PM"), a
|
|
16
|
+
* wider one the date too ("Oct 6, 2:23 PM"). The exact date, seconds and zone
|
|
17
|
+
* stay in the tooltip and accessible name. A touch screen has no hover to
|
|
18
|
+
* reveal it, so there it takes no space at all and the actions get the row.
|
|
19
|
+
*/
|
|
13
20
|
export declare function MessageTimestamp({ timestamp }: MessageTimestampProps): import("react").JSX.Element | null;
|
|
14
21
|
export {};
|
|
@@ -50,11 +50,10 @@ function MessageTimestamp({ timestamp }) {
|
|
|
50
50
|
tabIndex: 0,
|
|
51
51
|
"aria-label": `${display.absolute} (${display.relative})`,
|
|
52
52
|
title: `${display.absolute} (${display.relative})`,
|
|
53
|
-
|
|
54
|
-
className: "sr-only text-[10px] font-normal text-muted-foreground/65 focus:not-sr-only [@media(hover:hover)]:group-hover/assistant-msg:not-sr-only [@media(hover:hover)]:group-focus-within/assistant-msg:not-sr-only",
|
|
53
|
+
className: "shrink-0 whitespace-nowrap [@media(hover:none)]:hidden text-[10px] font-normal text-muted-foreground/65 opacity-0 transition-opacity focus:opacity-100 [@media(hover:hover)]:group-hover/assistant-msg:opacity-100 [@media(hover:hover)]:group-focus-within/assistant-msg:opacity-100",
|
|
55
54
|
children: [
|
|
56
|
-
/* @__PURE__ */ jsx("span", { className: "@min-[
|
|
57
|
-
/* @__PURE__ */ jsx("span", { className: "hidden @min-[
|
|
55
|
+
/* @__PURE__ */ jsx("span", { className: "@min-[420px]/message-footer:hidden", children: display.compact }),
|
|
56
|
+
/* @__PURE__ */ jsx("span", { className: "hidden @min-[420px]/message-footer:inline", children: display.short })
|
|
58
57
|
]
|
|
59
58
|
}
|
|
60
59
|
);
|
|
@@ -52,7 +52,8 @@ import { AssistantWarning } from "../../run/AssistantWarning.js";
|
|
|
52
52
|
import { BreathingOrb } from "./BreathingOrb.js";
|
|
53
53
|
import {
|
|
54
54
|
AssistantMessageContextMenu,
|
|
55
|
-
AssistantMessageFooter
|
|
55
|
+
AssistantMessageFooter,
|
|
56
|
+
AssistantMessageFooterPlaceholder
|
|
56
57
|
} from "./AssistantMessageFooter.js";
|
|
57
58
|
import { AssistantNoAnswer } from "./AssistantNoAnswer.js";
|
|
58
59
|
import { selectInstanceStatus } from "../../../redux/execution-system/conversations/conversations.selectors.js";
|
|
@@ -442,7 +443,7 @@ function AgentAssistantMessage({
|
|
|
442
443
|
!isClosedStreamSegment && displaySources.length > 0 && /* @__PURE__ */ jsx(MessageSourcesRow, { sources: displaySources, className: "mt-2" }),
|
|
443
444
|
!isStreamActive && /* @__PURE__ */ jsx(PrefillNote, { metadata: record?.metadata }),
|
|
444
445
|
!isStreamActive && /* @__PURE__ */ jsx(StoppedNote, { metadata: record?.metadata, requestStatus }),
|
|
445
|
-
isStreamActive && !failed && (phase === "text_streaming" || phase === "interstitial") && /* @__PURE__ */ jsx(BreathingOrb, { className: "
|
|
446
|
+
isStreamActive && !failed && (phase === "text_streaming" || phase === "interstitial") && /* @__PURE__ */ jsx("div", { className: "relative h-0 overflow-visible", children: /* @__PURE__ */ jsx(BreathingOrb, { className: "absolute left-0 top-[3px]", size: 40 }) })
|
|
446
447
|
] }),
|
|
447
448
|
!isClosedStreamSegment && visibleWarnings?.map((warning, index) => /* @__PURE__ */ jsx(
|
|
448
449
|
AssistantWarning,
|
|
@@ -475,7 +476,8 @@ function AgentAssistantMessage({
|
|
|
475
476
|
isCapturing,
|
|
476
477
|
surfaceKey
|
|
477
478
|
}
|
|
478
|
-
)
|
|
479
|
+
),
|
|
480
|
+
!hideActionBar && (isStreamActive || !messageId) && !failed && !editingInPlace && /* @__PURE__ */ jsx(AssistantMessageFooterPlaceholder, {})
|
|
479
481
|
]
|
|
480
482
|
}
|
|
481
483
|
);
|
|
@@ -51,6 +51,11 @@ export declare function useAssistantMessageActions({ messageId, conversationId,
|
|
|
51
51
|
showOptions: boolean;
|
|
52
52
|
isLatestAssistant: boolean;
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Holds the action row's space while a reply streams (the real footer needs
|
|
56
|
+
* the saved message id), so nothing below jumps when the actions arrive.
|
|
57
|
+
*/
|
|
58
|
+
export declare function AssistantMessageFooterPlaceholder(): React.JSX.Element;
|
|
54
59
|
export declare function AssistantMessageFooter(props: AssistantMessageFooterProps): React.JSX.Element;
|
|
55
60
|
export default AssistantMessageFooter;
|
|
56
61
|
/** Agent-menu placements over a rendered answer (never "Content Blocks"). */
|
|
@@ -146,6 +146,10 @@ function useAssistantMessageActions({
|
|
|
146
146
|
isLatestAssistant
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
|
+
let lastFooterRowHeight = 46;
|
|
150
|
+
function AssistantMessageFooterPlaceholder() {
|
|
151
|
+
return /* @__PURE__ */ jsx("div", { "aria-hidden": true, style: { height: lastFooterRowHeight } });
|
|
152
|
+
}
|
|
149
153
|
function AssistantMessageFooter(props) {
|
|
150
154
|
const { messageId, conversationId, surfaceKey } = props;
|
|
151
155
|
const {
|
|
@@ -162,6 +166,17 @@ function AssistantMessageFooter(props) {
|
|
|
162
166
|
isLatestAssistant
|
|
163
167
|
} = useAssistantMessageActions(props);
|
|
164
168
|
const isHoverOnly = density === "compact" && !isLatestAssistant;
|
|
169
|
+
const rowRef = useRef(null);
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
const row = rowRef.current;
|
|
172
|
+
if (!row) return;
|
|
173
|
+
const observer = new ResizeObserver(() => {
|
|
174
|
+
const height = row.getBoundingClientRect().height;
|
|
175
|
+
if (height > 0) lastFooterRowHeight = height;
|
|
176
|
+
});
|
|
177
|
+
observer.observe(row);
|
|
178
|
+
return () => observer.disconnect();
|
|
179
|
+
}, []);
|
|
165
180
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
166
181
|
/* @__PURE__ */ jsxs(
|
|
167
182
|
"div",
|
|
@@ -171,19 +186,26 @@ function AssistantMessageFooter(props) {
|
|
|
171
186
|
isHoverOnly && "opacity-0 group-hover/assistant-msg:opacity-100 focus-within:opacity-100"
|
|
172
187
|
),
|
|
173
188
|
children: [
|
|
174
|
-
/* @__PURE__ */ jsxs(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
/* @__PURE__ */ jsxs(
|
|
190
|
+
"div",
|
|
191
|
+
{
|
|
192
|
+
ref: rowRef,
|
|
193
|
+
className: "@container/message-footer flex w-full min-w-0 items-center gap-2",
|
|
194
|
+
children: [
|
|
195
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
|
|
196
|
+
RichDocumentActions,
|
|
197
|
+
{
|
|
198
|
+
content: config.content,
|
|
199
|
+
source: config.source,
|
|
200
|
+
actions: config.actions,
|
|
201
|
+
hideOverflow: !showOptions,
|
|
202
|
+
className: "px-0"
|
|
203
|
+
}
|
|
204
|
+
) }),
|
|
205
|
+
/* @__PURE__ */ jsx(MessageTimestamp, { timestamp: record?.createdAt })
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
),
|
|
187
209
|
/* @__PURE__ */ jsx(
|
|
188
210
|
NegativeVerdictFollowUp,
|
|
189
211
|
{
|
|
@@ -243,6 +265,7 @@ export {
|
|
|
243
265
|
ASSISTANT_ANSWER_PLACEMENT_MODE,
|
|
244
266
|
AssistantMessageContextMenu,
|
|
245
267
|
AssistantMessageFooter,
|
|
268
|
+
AssistantMessageFooterPlaceholder,
|
|
246
269
|
AssistantMessageFooter_default as default,
|
|
247
270
|
useAssistantMessageActions
|
|
248
271
|
};
|
|
@@ -5,7 +5,8 @@ import { useDomCapturePrint } from "../../../../conversation/hooks/useDomCapture
|
|
|
5
5
|
import { AgentAssistantMessage } from "./AgentAssistantMessage.js";
|
|
6
6
|
import {
|
|
7
7
|
AssistantMessageContextMenu,
|
|
8
|
-
AssistantMessageFooter
|
|
8
|
+
AssistantMessageFooter,
|
|
9
|
+
AssistantMessageFooterPlaceholder
|
|
9
10
|
} from "./AssistantMessageFooter.js";
|
|
10
11
|
import {
|
|
11
12
|
membersForRender,
|
|
@@ -111,6 +112,7 @@ function AssistantTurnGroup({
|
|
|
111
112
|
},
|
|
112
113
|
`files-${m.key}`
|
|
113
114
|
)),
|
|
115
|
+
lastMember && !showBar && /* @__PURE__ */ jsx(AssistantMessageFooterPlaceholder, {}),
|
|
114
116
|
showBar && anchorMessageId && // The group's bar sits BESIDE its members, so it carries the turn's
|
|
115
117
|
// one menu itself — its ⋯ opens the same menu the answer's
|
|
116
118
|
// right-click does (RC-B6), never a second drawn menu.
|
|
@@ -150,6 +150,65 @@ export declare const PLATFORM_CONTEXT_VALUES: {
|
|
|
150
150
|
readonly sortOrder: 9997;
|
|
151
151
|
};
|
|
152
152
|
};
|
|
153
|
+
/**
|
|
154
|
+
* THE BASELINE SURFACE — where the platform's own write targets are declared
|
|
155
|
+
* (Matrx Alchemy ALC-17). Every surface inherits it: the one write door reads
|
|
156
|
+
* it as the root ancestor of every surface (`surfaceWriteDeclarations` in
|
|
157
|
+
* `runtime/surface-writeback.ts`), so a write to a platform target from ANY
|
|
158
|
+
* surface resolves here, and the platform's headless handlers are registered
|
|
159
|
+
* on this name. It is not a page and is never mounted.
|
|
160
|
+
*/
|
|
161
|
+
export declare const BASELINE_SURFACE_NAME = "matrx-platform/baseline";
|
|
162
|
+
/** `surface_feedback` value bounds — the description and the check read the same numbers. */
|
|
163
|
+
export declare const SURFACE_FEEDBACK_KINDS: readonly ["missing_capability", "wrong_or_unclear_description", "bug", "missing_data", "suggestion"];
|
|
164
|
+
export declare const SURFACE_FEEDBACK_MESSAGE_MIN = 10;
|
|
165
|
+
export declare const SURFACE_FEEDBACK_MESSAGE_MAX = 4000;
|
|
166
|
+
/** At most this many custom fields per `custom_fields_add` write. */
|
|
167
|
+
export declare const CUSTOM_FIELDS_MAX_PER_WRITE = 10;
|
|
168
|
+
/**
|
|
169
|
+
* THE PLATFORM WRITE TARGETS, declared once on the baseline surface. Each has
|
|
170
|
+
* the apply policy it always had: window fields and custom fields are `ask`
|
|
171
|
+
* (an agent's write waits for the person's card; a person's own write is the
|
|
172
|
+
* approval); feedback is `auto` (it changes nothing on the page). The runtime
|
|
173
|
+
* files word the live offer from these (`customFieldsTarget()` adds the
|
|
174
|
+
* mounted sections), and register the handlers that apply them.
|
|
175
|
+
*/
|
|
176
|
+
export declare const PLATFORM_WRITE_TARGETS: {
|
|
177
|
+
readonly window_form_fields: {
|
|
178
|
+
readonly name: "window_form_fields";
|
|
179
|
+
readonly label: "Fields in an open window";
|
|
180
|
+
readonly description: 'Change fields in an open window that has no registered surface (listed in your context as window::<title>). Value: { "window": "<title exactly as listed>", "changes": [{ "field": "<field key>", "value": <new value> }] }. A text or number field takes a string or number, a checkbox or switch takes true/false, a list takes the text of one of its choices (a wrong choice is refused with the real ones). Every change is checked against the field\'s rules before anything lands; the person approves first, and the window\'s own Save still decides.';
|
|
181
|
+
readonly valueType: "object";
|
|
182
|
+
readonly mode: "draft";
|
|
183
|
+
readonly applyPolicy: "ask";
|
|
184
|
+
};
|
|
185
|
+
readonly surface_feedback: {
|
|
186
|
+
readonly name: "surface_feedback";
|
|
187
|
+
readonly label: "Feedback about this page";
|
|
188
|
+
readonly description: string;
|
|
189
|
+
readonly valueType: "object";
|
|
190
|
+
readonly mode: "entity";
|
|
191
|
+
readonly applyPolicy: "auto";
|
|
192
|
+
};
|
|
193
|
+
readonly custom_fields_add: {
|
|
194
|
+
readonly name: "custom_fields_add";
|
|
195
|
+
readonly label: "Add custom fields";
|
|
196
|
+
readonly description: string;
|
|
197
|
+
readonly valueType: "object";
|
|
198
|
+
readonly mode: "entity";
|
|
199
|
+
readonly applyPolicy: "ask";
|
|
200
|
+
};
|
|
201
|
+
readonly custom_fields_set: {
|
|
202
|
+
readonly name: "custom_fields_set";
|
|
203
|
+
readonly label: "Fill in custom fields";
|
|
204
|
+
readonly description: string;
|
|
205
|
+
readonly valueType: "object";
|
|
206
|
+
readonly mode: "entity";
|
|
207
|
+
readonly applyPolicy: "ask";
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
export type PlatformWriteTargetName = keyof typeof PLATFORM_WRITE_TARGETS;
|
|
211
|
+
export declare function isPlatformWriteTarget(name: string): name is PlatformWriteTargetName;
|
|
153
212
|
/** Names only the platform writes — a manifest declaring one is refused. */
|
|
154
213
|
export declare const PLATFORM_RESERVED_NAMES: {
|
|
155
214
|
values: ReadonlyArray<string>;
|