@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.4.26
|
|
4
|
+
|
|
5
|
+
- `RichCopySplit` renders the content action set (`ContentActions` from rich-content 0.2.52): Copy, Plain (with the new `viewKey` prop), Export, Print and Transform (the host's Alchemy palette) each one click. New `exportTitle` prop names exported files.
|
|
6
|
+
- A chat answer honors its bar's Plain switch: the exact source in place of the render (the render stays mounted).
|
|
7
|
+
|
|
8
|
+
Consumer action: pass `viewKey` and wrap the host's content in `ContentView` to get Plain.
|
|
9
|
+
|
|
10
|
+
## 0.4.25 — nine more host slots draw the real component in any host
|
|
11
|
+
|
|
12
|
+
- `ReadFailure`, `UntrustedCount`, `OptionCombobox`, `AspectRatioSelect`, `FloatingSheet`,
|
|
13
|
+
`ConfigurationTable`, `ConfigurationTableRow`, `FieldHelp` and `TextInputDialog` default to
|
|
14
|
+
`@ai-matrx/design-system` 0.82.0's components (moved there whole from matrx-frontend); a slot whose
|
|
15
|
+
default is the package component no longer reports itself as unregistered.
|
|
16
|
+
- `./ui/aspect-ratio-options` re-exports design-system's ratio helpers (one copy).
|
|
17
|
+
- Guard: `slots-draw-their-package-default.test.tsx` (the list only grows).
|
|
18
|
+
|
|
19
|
+
Consumer action: none — a host's registration still wins; matrx-frontend may drop its registrations of
|
|
20
|
+
these nine slots (they are the same components).
|
|
21
|
+
|
|
22
|
+
Also in 0.4.25 — `createMemoryNavigation` (from `@ai-matrx/chat/host`): a navigation port that keeps the
|
|
23
|
+
chat's address in memory for hosts whose own address must not move (an extension side panel; Matrx
|
|
24
|
+
Local, whose router already owns `/chat`). Options `initial`, `home`, `persist(address)`,
|
|
25
|
+
`openExternal(href)`; back/forward, link clicks, one search-params object per address. Guard:
|
|
26
|
+
`memory-navigation-keeps-the-page-still.test.tsx` (red 2/5 on a planted break → green 5/5). Consumer
|
|
27
|
+
action: none; matrx-extend may drop its own `memory-navigation.tsx`.
|
|
28
|
+
|
|
29
|
+
Also in 0.4.25 — a host names the mandate that answers its new-chat screen: `app.defaultChatMandateKey`
|
|
30
|
+
(Matrx Local `local.cloud_chat`, Matrx Extend `extend.browser_chat`); absent = `chat.default_new_chat`.
|
|
31
|
+
`ChatNewClient`, `ChatConversationRoom`, `ChatNewHeader` read it through one hook
|
|
32
|
+
(`useDefaultChatMandateKey`). Guard: `a-host-names-its-default-chat-mandate.test.tsx` (red 1/3 on a
|
|
33
|
+
planted break → green 3/3). Consumer action: none.
|
|
34
|
+
|
|
35
|
+
## 0.4.24
|
|
36
|
+
|
|
37
|
+
Republish of 0.4.23 (its publish failed in finish-build on a member call read as an import; fixed in 4008c0cbbc). No consumer action required.
|
|
38
|
+
|
|
39
|
+
## 0.4.23
|
|
40
|
+
|
|
41
|
+
- An answer open in the in-place editor stays open when its conversation is re-read (`hydrateMessages` carries `_editingInPlace`); in the floating chat window the editor closed ~1.5s after a double-click. Guard: `hydrate-keeps-an-open-in-place-editor.test.ts`.
|
|
42
|
+
- A Chat link naming its agent and conversation (`agent:<id>:m-run_a-<agent>_c-<conversation>`) opens them: the window adopts a seed that arrives after it mounted, never over the person's own choice (`adoptLateWindowSeed`). Guard: `a-chat-link-opens-its-conversation.test.ts`.
|
|
43
|
+
|
|
44
|
+
No consumer action required.
|
|
45
|
+
|
|
3
46
|
## 0.4.22
|
|
4
47
|
|
|
5
48
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
@@ -2,9 +2,19 @@ import * as React from "react";
|
|
|
2
2
|
import type { MatrxCopyMenuProps } from "@ai-matrx/alchemy/react/workspace";
|
|
3
3
|
export type RichCopySplitProps = MatrxCopyMenuProps & {
|
|
4
4
|
human: NonNullable<MatrxCopyMenuProps["human"]>;
|
|
5
|
+
/**
|
|
6
|
+
* The item's Plain / Formatted key: the host renders its content through
|
|
7
|
+
* <ContentView viewKey={…} text={…}> (@ai-matrx/rich-content/copy/ContentActions).
|
|
8
|
+
* Guard: matrx-frontend content-actions.census — every display host passes it.
|
|
9
|
+
*/
|
|
10
|
+
viewKey?: string;
|
|
11
|
+
/** File name / print title for Export and Print. */
|
|
12
|
+
exportTitle?: string;
|
|
5
13
|
};
|
|
6
14
|
/**
|
|
7
|
-
* THE
|
|
15
|
+
* THE content action set of a markdown host (ContentActions, Arman 2026-10-08: copy, Plain,
|
|
16
|
+
* Export, Print and Transform each one click away; Transform opens the Alchemy palette below).
|
|
17
|
+
* Formerly the split Copy of a markdown host (Arman, 2026-10-04: "one click to get either the markdown
|
|
8
18
|
* version or the no-markup version"). One click copies the person's flavor (`copy.default_flavor`);
|
|
9
19
|
* the chevron offers exactly Copy markdown and Copy plain text (2026-10-07). The Alchemy palette
|
|
10
20
|
* (formatted, JSON, download, AI, the host's record copies) is "Export…" — ONE row under a divider
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { ContentActions } from "@ai-matrx/rich-content/copy/ContentActions";
|
|
5
5
|
import { copyRichContent } from "@ai-matrx/rich-content/copy/copy-commands";
|
|
6
6
|
import { ExportPaletteAnchor, openExportPalette, useExportPaletteKey } from "@ai-matrx/rich-content/copy/ExportPalette";
|
|
7
7
|
import { cn } from "@ai-matrx/design-system";
|
|
@@ -17,12 +17,18 @@ function RichCopySplit(props) {
|
|
|
17
17
|
const exportKey = useExportPaletteKey();
|
|
18
18
|
return /* @__PURE__ */ jsxs("span", { className: cn("inline-flex shrink-0 items-center", className), children: [
|
|
19
19
|
/* @__PURE__ */ jsx(
|
|
20
|
-
|
|
20
|
+
ContentActions,
|
|
21
21
|
{
|
|
22
|
+
content: () => {
|
|
23
|
+
const value = typeof human === "function" ? human() : human;
|
|
24
|
+
return typeof value === "string" ? value : String(value ?? "");
|
|
25
|
+
},
|
|
22
26
|
copy,
|
|
23
27
|
size: splitSize,
|
|
24
28
|
label: `Copy ${props.label ?? ""}`.trim(),
|
|
25
|
-
|
|
29
|
+
title: props.exportTitle ?? props.label,
|
|
30
|
+
viewKey: props.viewKey,
|
|
31
|
+
onTransform: () => void openExportPalette(exportKey)
|
|
26
32
|
}
|
|
27
33
|
),
|
|
28
34
|
/* @__PURE__ */ jsx(ExportPaletteAnchor, { exportKey, controllerRef: palette }),
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { ChatRoomClient } from "./ChatRoomClient.js";
|
|
4
4
|
import { ChatMandateUnavailable, ChatNewLandingSkeleton } from "./ChatNewClient.js";
|
|
5
|
-
import {
|
|
5
|
+
import { useDefaultChatMandateKey } from "./default-chat-mandate.js";
|
|
6
6
|
import { useMandate } from "../../../mandates/useMandate.js";
|
|
7
7
|
import { WorkspaceGate } from "../../../host/ui-slots.js";
|
|
8
8
|
function ChatConversationRoom({
|
|
@@ -13,6 +13,7 @@ function ChatConversationRoom({
|
|
|
13
13
|
composer,
|
|
14
14
|
organizationId = null
|
|
15
15
|
}) {
|
|
16
|
+
const defaultMandateKey = useDefaultChatMandateKey();
|
|
16
17
|
if (agentId && !ownedByMandate) {
|
|
17
18
|
return /* @__PURE__ */ jsx(
|
|
18
19
|
ChatRoomClient,
|
|
@@ -30,7 +31,7 @@ function ChatConversationRoom({
|
|
|
30
31
|
{
|
|
31
32
|
agentId,
|
|
32
33
|
conversationId,
|
|
33
|
-
mandateKey:
|
|
34
|
+
mandateKey: defaultMandateKey,
|
|
34
35
|
sandboxBinding,
|
|
35
36
|
composer
|
|
36
37
|
}
|
|
@@ -52,8 +53,9 @@ function ChatConversationRoomResolved({
|
|
|
52
53
|
sandboxBinding,
|
|
53
54
|
composer
|
|
54
55
|
}) {
|
|
56
|
+
const defaultMandateKey = useDefaultChatMandateKey();
|
|
55
57
|
const { mandate, loading, error, organizationPending } = useMandate(
|
|
56
|
-
|
|
58
|
+
defaultMandateKey,
|
|
57
59
|
organizationId ? { organizationId } : {}
|
|
58
60
|
);
|
|
59
61
|
if (loading) return /* @__PURE__ */ jsx(ChatNewLandingSkeleton, {});
|
|
@@ -66,7 +68,7 @@ function ChatConversationRoomResolved({
|
|
|
66
68
|
{
|
|
67
69
|
agentId: mandate.agentId,
|
|
68
70
|
conversationId,
|
|
69
|
-
mandateKey:
|
|
71
|
+
mandateKey: defaultMandateKey,
|
|
70
72
|
sandboxBinding,
|
|
71
73
|
composer
|
|
72
74
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
ComposerGreeting,
|
|
12
12
|
ComposerQuickActionsSkeleton
|
|
13
13
|
} from "../inputs/smart-input/composer/ComposerSplash.js";
|
|
14
|
-
import {
|
|
14
|
+
import { useDefaultChatMandateKey } from "./default-chat-mandate.js";
|
|
15
15
|
import { useMandate } from "../../../mandates/useMandate.js";
|
|
16
16
|
import { ErrorAlchemyMenu } from "../../../host/ui-slots.js";
|
|
17
17
|
import { asClause } from "@ai-matrx/kit/text";
|
|
@@ -21,7 +21,8 @@ function ChatNewClient({
|
|
|
21
21
|
pinned = false,
|
|
22
22
|
composer
|
|
23
23
|
}) {
|
|
24
|
-
const
|
|
24
|
+
const defaultMandateKey = useDefaultChatMandateKey();
|
|
25
|
+
const { mandate } = useMandate(defaultMandateKey, { optional: true });
|
|
25
26
|
if (pinned && agentId) {
|
|
26
27
|
return /* @__PURE__ */ jsx(ChatNewBody, { agentId, composer, pinned: true });
|
|
27
28
|
}
|
|
@@ -31,7 +32,8 @@ function ChatNewClient({
|
|
|
31
32
|
function ChatNewClientResolved({
|
|
32
33
|
composer
|
|
33
34
|
}) {
|
|
34
|
-
const
|
|
35
|
+
const defaultMandateKey = useDefaultChatMandateKey();
|
|
36
|
+
const { mandate, loading, error, organizationPending } = useMandate(defaultMandateKey);
|
|
35
37
|
if (loading) return /* @__PURE__ */ jsx(ChatNewLandingSkeleton, {});
|
|
36
38
|
if (organizationPending) {
|
|
37
39
|
return /* @__PURE__ */ jsx(WorkspaceGate, { blocked: true, sentence: "Chat needs a workspace to open.", children: /* @__PURE__ */ jsx(ChatNewLandingSkeleton, {}) });
|
|
@@ -71,12 +73,13 @@ function ChatNewBody({
|
|
|
71
73
|
composer,
|
|
72
74
|
pinned = false
|
|
73
75
|
}) {
|
|
76
|
+
const defaultMandateKey = useDefaultChatMandateKey();
|
|
74
77
|
const surfaceKey = chatRouteSurfaceKey(agentId);
|
|
75
78
|
return /* @__PURE__ */ jsx(
|
|
76
79
|
ChatRoomClient,
|
|
77
80
|
{
|
|
78
81
|
agentId,
|
|
79
|
-
mandateKey: pinned ? void 0 :
|
|
82
|
+
mandateKey: pinned ? void 0 : defaultMandateKey,
|
|
80
83
|
composer,
|
|
81
84
|
landingContent: (conversationId, composerPresentation) => composerPresentation ? /* @__PURE__ */ jsx(
|
|
82
85
|
NewChatGreeting,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { ChatRunHeader } from "./ChatRunHeader.js";
|
|
4
|
-
import {
|
|
4
|
+
import { useDefaultChatMandateKey } from "./default-chat-mandate.js";
|
|
5
5
|
import { useMandate } from "../../../mandates/useMandate.js";
|
|
6
6
|
function ChatNewHeader({
|
|
7
7
|
agentId,
|
|
@@ -20,7 +20,8 @@ function ChatNewHeader({
|
|
|
20
20
|
function ChatNewHeaderResolved({
|
|
21
21
|
composerMode
|
|
22
22
|
}) {
|
|
23
|
-
const
|
|
23
|
+
const defaultMandateKey = useDefaultChatMandateKey();
|
|
24
|
+
const { mandate } = useMandate(defaultMandateKey);
|
|
24
25
|
return /* @__PURE__ */ jsx(ChatRunHeader, { activeAgentId: mandate?.agentId, composerMode });
|
|
25
26
|
}
|
|
26
27
|
export {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mandate that answers the new-chat screen and agentless conversations in THIS host: the host's
|
|
3
|
+
* `app.defaultChatMandateKey` (Matrx Local names `local.cloud_chat`), else the web's
|
|
4
|
+
* `chat.default_new_chat`. One reader, so every screen that resolves the default agrees.
|
|
5
|
+
*/
|
|
6
|
+
import type { AnyMandateKey } from "@ai-matrx/agents/mandates";
|
|
7
|
+
export declare function useDefaultChatMandateKey(): AnyMandateKey;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useMaybeChatHost } from "../../../host/react.js";
|
|
3
|
+
import { DEFAULT_NEW_CHAT_MANDATE_KEY } from "./chat-quick-actions.config.js";
|
|
4
|
+
function useDefaultChatMandateKey() {
|
|
5
|
+
return useMaybeChatHost()?.app.defaultChatMandateKey ?? DEFAULT_NEW_CHAT_MANDATE_KEY;
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
useDefaultChatMandateKey
|
|
9
|
+
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* The default for a context-item body the host did not register: the generic body, which shows the
|
|
3
|
+
* item itself (the missing editor is reported once by the slot; never a "not set up here" line).
|
|
4
|
+
*/
|
|
2
5
|
import type { ContextItemBodyProps } from "../types.js";
|
|
3
|
-
export declare function UnregisteredBody({ name,
|
|
6
|
+
export declare function UnregisteredBody({ name, props }: {
|
|
4
7
|
name: string;
|
|
5
|
-
what
|
|
8
|
+
what?: string;
|
|
6
9
|
props: ContextItemBodyProps;
|
|
7
10
|
}): import("react").JSX.Element;
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { GenericBody } from "./GenericBody.js";
|
|
4
|
-
function UnregisteredBody({ name,
|
|
5
|
-
return /* @__PURE__ */
|
|
6
|
-
/* @__PURE__ */ jsxs("div", { className: "px-4 pt-3 text-[11px] text-muted-foreground", children: [
|
|
7
|
-
what,
|
|
8
|
-
" is not set up here"
|
|
9
|
-
] }),
|
|
10
|
-
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(GenericBody, { ...props }) })
|
|
11
|
-
] });
|
|
4
|
+
function UnregisteredBody({ name, props }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-full min-h-0 flex-col", "data-chat-slot-fallback": name, children: /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(GenericBody, { ...props }) }) });
|
|
12
6
|
}
|
|
13
7
|
export {
|
|
14
8
|
UnregisteredBody
|
|
@@ -54,6 +54,7 @@ function resourceLabel(resource) {
|
|
|
54
54
|
case "table":
|
|
55
55
|
return resource.data.table_name ?? "Table";
|
|
56
56
|
case "webpage":
|
|
57
|
+
if (typeof resource.data === "string") return resource.data.replace(/^https?:\/\/(www\.)?/i, "").replace(/\/$/, "");
|
|
57
58
|
return resource.data.title ?? resource.data.url ?? "Webpage";
|
|
58
59
|
case "youtube":
|
|
59
60
|
return resource.data.title ?? "YouTube";
|
|
@@ -8,6 +8,8 @@ import { InboxQueueStrip } from "./InboxQueueStrip.js";
|
|
|
8
8
|
import { selectViewerCanReply } from "../../../redux/execution-system/conversations/conversations.selectors.js";
|
|
9
9
|
import { ViewOnlyComposerBar } from "./ViewOnlyComposerBar.js";
|
|
10
10
|
import { selectHasUserInput } from "../../../redux/execution-system/instance-user-input/instance-user-input.selectors.js";
|
|
11
|
+
import { useConversationHeld } from "../../../../host/conversation-hooks-react.js";
|
|
12
|
+
import { Hourglass } from "lucide-react";
|
|
11
13
|
function SmartAgentInput({
|
|
12
14
|
conversationId,
|
|
13
15
|
uploadRoot = "userContent",
|
|
@@ -41,7 +43,8 @@ function SmartAgentInput({
|
|
|
41
43
|
const ambientHasNothingToSend = useAppSelector(
|
|
42
44
|
(state) => isLauncher && conversationId ? !selectHasUserInput(conversationId)(state) : false
|
|
43
45
|
);
|
|
44
|
-
const
|
|
46
|
+
const heldSentence = useConversationHeld(conversationId);
|
|
47
|
+
const sendDisabled = disableSend || ambientHasNothingToSend || heldSentence !== null;
|
|
45
48
|
const viewerCanReply = useAppSelector(
|
|
46
49
|
(state) => conversationId ? selectViewerCanReply(conversationId)(state) : true
|
|
47
50
|
);
|
|
@@ -52,6 +55,18 @@ function SmartAgentInput({
|
|
|
52
55
|
}
|
|
53
56
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
54
57
|
queueStrip,
|
|
58
|
+
heldSentence ? /* @__PURE__ */ jsxs(
|
|
59
|
+
"div",
|
|
60
|
+
{
|
|
61
|
+
role: "status",
|
|
62
|
+
"data-testid": "composer-held",
|
|
63
|
+
className: "mx-auto mb-1.5 flex w-full items-center gap-2 px-1 text-xs text-muted-foreground",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ jsx(Hourglass, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true }),
|
|
66
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", title: heldSentence, children: heldSentence })
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
) : null,
|
|
55
70
|
/* @__PURE__ */ jsx(
|
|
56
71
|
SmartAgentInputStacked,
|
|
57
72
|
{
|
|
@@ -12,6 +12,7 @@ import { Check, ChevronRight } from "lucide-react";
|
|
|
12
12
|
import { Popover, PopoverAnchor, PopoverContent } from "@ai-matrx/design-system";
|
|
13
13
|
import { Switch } from "../../../../../ui/switch.js";
|
|
14
14
|
import { cn } from "@ai-matrx/design-system";
|
|
15
|
+
import { SlotBoundary } from "../../../../../host/ui-slots.js";
|
|
15
16
|
const ComposerMenuPresentationContext = createContext("popover");
|
|
16
17
|
const ComposerSheetNavContext = createContext(null);
|
|
17
18
|
const SHEET_ROW = "sheet-row flex w-full min-w-0 shrink-0 items-center gap-3 bg-card px-3 text-left text-base text-foreground transition-colors active:bg-accent first:rounded-t-xl last:rounded-b-xl [.sheet-gap+&]:rounded-t-xl [&:has(+.sheet-gap)]:rounded-b-xl [.sheet-row+&]:border-t [.sheet-row+&]:border-border/60";
|
|
@@ -272,7 +273,7 @@ function ComposerSubmenu({
|
|
|
272
273
|
// A panel with no fixed height on desktop is content-sized on the
|
|
273
274
|
// phone too — one sizing decision, both presentations.
|
|
274
275
|
fit: panelClassName && !/(^|\s)h-\[/.test(panelClassName) ? "content" : "fill",
|
|
275
|
-
render: () => typeof children === "function" ? children(sheetNav.pop) : children
|
|
276
|
+
render: () => /* @__PURE__ */ jsx(SlotBoundary, { name: `menu:${typeof row.label === "string" ? row.label : "panel"}`, children: typeof children === "function" ? children(sheetNav.pop) : children })
|
|
276
277
|
})
|
|
277
278
|
}
|
|
278
279
|
);
|
|
@@ -321,7 +322,7 @@ function ComposerSubmenu({
|
|
|
321
322
|
panelClassName ?? "w-72",
|
|
322
323
|
"max-sm:!w-[calc(100vw-1rem)]"
|
|
323
324
|
),
|
|
324
|
-
children: /* @__PURE__ */ jsx(ComposerMenuLevel, { children: drillInPlace ? /* @__PURE__ */ jsx(InPlaceNav, { children: typeof children === "function" ? children(close) : children }) : typeof children === "function" ? children(close) : children })
|
|
325
|
+
children: /* @__PURE__ */ jsx(ComposerMenuLevel, { children: drillInPlace ? /* @__PURE__ */ jsx(InPlaceNav, { children: /* @__PURE__ */ jsx(SlotBoundary, { name: `menu:${typeof row.label === "string" ? row.label : "panel"}`, children: typeof children === "function" ? children(close) : children }) }) : /* @__PURE__ */ jsx(SlotBoundary, { name: `menu:${typeof row.label === "string" ? row.label : "panel"}`, children: typeof children === "function" ? children(close) : children }) })
|
|
325
326
|
}
|
|
326
327
|
)
|
|
327
328
|
] });
|
|
@@ -62,7 +62,8 @@ import { setUserInputText } from "../../../../redux/execution-system/instance-us
|
|
|
62
62
|
import { prependTemplateToDraft } from "../prepend-template-to-draft.js";
|
|
63
63
|
import { SmartInputMessageTemplatePicker } from "../../../../../host/ui-slots.js";
|
|
64
64
|
import { useOpenContextPreviewPanel } from "../../../../../host/window-openers.js";
|
|
65
|
-
import { useOpenRunControlsWindow } from "../../../../../host/window-openers.js";
|
|
65
|
+
import { useHasChatWindowOpener, useOpenRunControlsWindow } from "../../../../../host/window-openers.js";
|
|
66
|
+
import { isHostSlotRegistered } from "../../../../../host/ui-slots.js";
|
|
66
67
|
import { useOpenCloudBrowserCanvas } from "../../../../../host/ui-slots.js";
|
|
67
68
|
import { ActiveContextTree } from "../../../../../context/sources/scopes.js";
|
|
68
69
|
import { useComputeTargetActions } from "../use-compute-target-actions.js";
|
|
@@ -123,6 +124,9 @@ function ComposerPlusMenu({
|
|
|
123
124
|
const agentId = useAppSelector(selectAgentIdFromInstance(conversationId));
|
|
124
125
|
const openContextPreview = useOpenContextPreviewPanel();
|
|
125
126
|
const openRunControlsWindow = useOpenRunControlsWindow();
|
|
127
|
+
const runControlsWindowHosted = useHasChatWindowOpener("openRunControlsWindow");
|
|
128
|
+
const runControlsInline = presentation === "sheet" || !runControlsWindowHosted;
|
|
129
|
+
const hostPicker = isHostSlotRegistered("ResourcePickerMenu");
|
|
126
130
|
const openCloudBrowser = useOpenCloudBrowserCanvas();
|
|
127
131
|
const sandboxBlocked = useSandboxBindingBlocked(conversationId);
|
|
128
132
|
const workingDocEnabled = useAppSelector(selectWorkingDocEnabled(conversationId));
|
|
@@ -168,40 +172,42 @@ function ComposerPlusMenu({
|
|
|
168
172
|
/* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: FolderOpen, label: "Add files or photos" }, panelClassName: PICKER_PANEL, children: (closeCascade) => picker("files", closeCascade) })
|
|
169
173
|
),
|
|
170
174
|
/* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Link2, label: "Add a link" }, panelClassName: LINK_PANEL, children: (closeCascade) => picker("webpage", closeCascade) }),
|
|
171
|
-
/* @__PURE__ */
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/* @__PURE__ */
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
),
|
|
183
|
-
/* @__PURE__ */ jsx(
|
|
184
|
-
ComposerSubmenu,
|
|
185
|
-
{
|
|
186
|
-
row: { icon: contextValuesItem().icon, label: contextValuesItem().label },
|
|
187
|
-
panelClassName: PICKER_PANEL,
|
|
188
|
-
children: (closeCascade) => picker("context_values", closeCascade)
|
|
189
|
-
}
|
|
190
|
-
),
|
|
191
|
-
/* @__PURE__ */ jsx(
|
|
192
|
-
ComposerMenuRow,
|
|
193
|
-
{
|
|
194
|
-
icon: Search,
|
|
195
|
-
label: "Search your knowledge",
|
|
196
|
-
detail: touchOnly ? void 0 : "\u2318K",
|
|
197
|
-
onClick: () => {
|
|
198
|
-
close();
|
|
199
|
-
openAttachSearch();
|
|
175
|
+
hostPicker ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
176
|
+
/* @__PURE__ */ jsx(
|
|
177
|
+
ComposerSubmenu,
|
|
178
|
+
{
|
|
179
|
+
row: { icon: Boxes, label: "From your workspace" },
|
|
180
|
+
panelClassName: CONTENT_PANEL,
|
|
181
|
+
drillInPlace: true,
|
|
182
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col p-1.5", children: [
|
|
183
|
+
/* @__PURE__ */ jsx(ComposerMenuLabel, { children: "Attach from AI Matrx" }),
|
|
184
|
+
workspaceRows().map((row) => /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: row.icon, label: row.label }, children: (back) => picker(row.view, back) }, row.view))
|
|
185
|
+
] })
|
|
200
186
|
}
|
|
201
|
-
|
|
202
|
-
|
|
187
|
+
),
|
|
188
|
+
/* @__PURE__ */ jsx(
|
|
189
|
+
ComposerSubmenu,
|
|
190
|
+
{
|
|
191
|
+
row: { icon: contextValuesItem().icon, label: contextValuesItem().label },
|
|
192
|
+
panelClassName: PICKER_PANEL,
|
|
193
|
+
children: (closeCascade) => picker("context_values", closeCascade)
|
|
194
|
+
}
|
|
195
|
+
),
|
|
196
|
+
/* @__PURE__ */ jsx(
|
|
197
|
+
ComposerMenuRow,
|
|
198
|
+
{
|
|
199
|
+
icon: Search,
|
|
200
|
+
label: "Search your knowledge",
|
|
201
|
+
detail: touchOnly ? void 0 : "\u2318K",
|
|
202
|
+
onClick: () => {
|
|
203
|
+
close();
|
|
204
|
+
openAttachSearch();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
] }) : null,
|
|
203
209
|
shows("plus.skills") || shows("plus.tools") || shows("plus.connectors") || shows("plus.environment") ? /* @__PURE__ */ jsx(ComposerMenuDivider, {}) : null,
|
|
204
|
-
shows("plus.skills") ?
|
|
210
|
+
shows("plus.skills") ? runControlsInline ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Lightbulb, label: "Skills", badge: counts.skills }, children: /* @__PURE__ */ jsx(RunSkillPicker, { conversationId }) }) : /* @__PURE__ */ jsx(
|
|
205
211
|
ComposerMenuRow,
|
|
206
212
|
{
|
|
207
213
|
icon: Lightbulb,
|
|
@@ -213,7 +219,7 @@ function ComposerPlusMenu({
|
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
221
|
) : null,
|
|
216
|
-
shows("plus.tools") ?
|
|
222
|
+
shows("plus.tools") ? runControlsInline ? (
|
|
217
223
|
// Phone: a full-height page — THE Tools surface, one pane at a time.
|
|
218
224
|
/* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Wrench, label: "Tools", badge: counts.tools }, children: /* @__PURE__ */ jsx(RunToolPicker, { conversationId }) })
|
|
219
225
|
) : (
|
|
@@ -234,7 +240,7 @@ function ComposerPlusMenu({
|
|
|
234
240
|
) : null,
|
|
235
241
|
shows("plus.connectors") ? /* @__PURE__ */ jsxs(ComposerSubmenu, { row: { icon: Plug, label: "Connections" }, panelClassName: "w-[360px] h-[min(70dvh,480px)]", children: [
|
|
236
242
|
/* @__PURE__ */ jsx(ComposerConnectorsPanel, { conversationId, onNavigate: close }),
|
|
237
|
-
/* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Globe, label: "Google Workspace files" }, panelClassName: PICKER_PANEL, children: (closeCascade) => picker("google", closeCascade) })
|
|
243
|
+
hostPicker ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Globe, label: "Google Workspace files" }, panelClassName: PICKER_PANEL, children: (closeCascade) => picker("google", closeCascade) }) : null
|
|
238
244
|
] }) : null,
|
|
239
245
|
shows("plus.environment") ? /* @__PURE__ */ jsx(ComposerSubmenu, { row: { icon: Server, label: "Environment" }, panelClassName: "w-80", children: /* @__PURE__ */ jsx(
|
|
240
246
|
ComposerEnvironmentPanel,
|
|
@@ -74,6 +74,9 @@ import {
|
|
|
74
74
|
import { retryConversationTurn } from "../../../redux/execution-system/message-crud/retry-turn.thunk.js";
|
|
75
75
|
import { commitInlineContentEdit } from "../../../redux/execution-system/message-crud/commit-inline-edit.thunk.js";
|
|
76
76
|
import { InPlaceAnswerEditor } from "./InPlaceAnswerEditor.js";
|
|
77
|
+
import { PlainTextView } from "@ai-matrx/rich-content/copy/ContentActions";
|
|
78
|
+
import { usePlainView } from "@ai-matrx/rich-content/copy/content-view-store";
|
|
79
|
+
import { richDocumentViewKey } from "@ai-matrx/rich-content/rich-document/runtime/useActionSurfaceProvider";
|
|
77
80
|
import { updateMessageRecord } from "../../../redux/execution-system/messages/messages.slice.js";
|
|
78
81
|
import { handInPlaceCaret } from "@ai-matrx/rich-editor/in-place/caret-handoff";
|
|
79
82
|
import { toast } from "../../../../host/notify.js";
|
|
@@ -197,6 +200,9 @@ function AgentAssistantMessage({
|
|
|
197
200
|
() => extractFlatText(record, { withCitationMarkers: hasCitations, keepInlineThinking: true }),
|
|
198
201
|
[record, hasCitations]
|
|
199
202
|
);
|
|
203
|
+
const plainView = usePlainView(
|
|
204
|
+
messageId ? richDocumentViewKey({ type: "chat-message", conversationId, messageId }, "") : null
|
|
205
|
+
);
|
|
200
206
|
const attachmentParts = extractContentBlocks(record).filter(
|
|
201
207
|
(b) => isAttachmentMessagePart(b) && !isInlineAssistantMedia(b)
|
|
202
208
|
);
|
|
@@ -427,26 +433,29 @@ function AgentAssistantMessage({
|
|
|
427
433
|
) : (
|
|
428
434
|
// THE READING SET on a saved chat answer (highlight, comment, suggest, link +
|
|
429
435
|
// the Notes & comments dock) — the same mount every note and saved document uses.
|
|
430
|
-
/* @__PURE__ */ jsx(RecordAnnotations, { record: annotationRecord, children: /* @__PURE__ */
|
|
431
|
-
|
|
432
|
-
{
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
436
|
+
/* @__PURE__ */ jsx(RecordAnnotations, { record: annotationRecord, children: /* @__PURE__ */ jsxs("div", { "data-message-content": true, ...answerReadProps, children: [
|
|
437
|
+
plainView ? /* @__PURE__ */ jsx(PlainTextView, { text: renderedText ?? "" }) : null,
|
|
438
|
+
/* @__PURE__ */ jsx("div", { className: plainView ? "hidden" : "contents", children: /* @__PURE__ */ jsx(
|
|
439
|
+
MarkdownStream,
|
|
440
|
+
{
|
|
441
|
+
imagePolicy: "ai",
|
|
442
|
+
requestId: effectiveRequestId,
|
|
443
|
+
recordMessageIds,
|
|
444
|
+
streamSlotStart: streamSlotStart ?? record?._streamSlotStart,
|
|
445
|
+
streamSlotEnd: streamSlotEnd ?? record?._streamSlotEnd,
|
|
446
|
+
turnId: messageId,
|
|
447
|
+
conversationId,
|
|
448
|
+
messageId: messageId ?? void 0,
|
|
449
|
+
content: renderedText,
|
|
450
|
+
isStreamActive: isStreamActive && !failed,
|
|
451
|
+
hideCopyButton: true,
|
|
452
|
+
allowFullScreenEditor: false,
|
|
453
|
+
serverProcessedBlocks,
|
|
454
|
+
onContentChange: handleInlineContentChange,
|
|
455
|
+
applyLocalEdits: false
|
|
456
|
+
}
|
|
457
|
+
) })
|
|
458
|
+
] }) })
|
|
450
459
|
),
|
|
451
460
|
!isClosedStreamSegment && displaySources.length > 0 && /* @__PURE__ */ jsx(MessageSourcesRow, { sources: displaySources, className: "mt-2" }),
|
|
452
461
|
!isStreamActive && /* @__PURE__ */ jsx(PrefillNote, { metadata: record?.metadata }),
|
|
@@ -41,6 +41,7 @@ import { isAttachmentMessagePart } from "../../context-items/normalize.js";
|
|
|
41
41
|
import MarkdownStream from "../../../../ui/markdown-stream/MarkdownStream.js";
|
|
42
42
|
import "../rich-document/chat-message-source.js";
|
|
43
43
|
import { literalUserText } from "./literal-user-text.js";
|
|
44
|
+
import { getConversationHooks } from "../../../../host/conversation-hooks.js";
|
|
44
45
|
import { buildVariableDisplayLines } from "../../../utils/variable-display-lines.js";
|
|
45
46
|
import {
|
|
46
47
|
recordTranscriptEvent,
|
|
@@ -57,7 +58,8 @@ function AgentUserMessageContent({
|
|
|
57
58
|
bodyBlocks,
|
|
58
59
|
variables
|
|
59
60
|
}) {
|
|
60
|
-
const
|
|
61
|
+
const shown = getConversationHooks(conversationId)?.displayUserText?.(text) ?? text;
|
|
62
|
+
const trimmedText = shown.trim();
|
|
61
63
|
const hasBodyBlocks = !!bodyBlocks && bodyBlocks.length > 0;
|
|
62
64
|
if (!trimmedText && !hasBodyBlocks && attachmentParts.length === 0 && (!variables || buildVariableDisplayLines(variables).length === 0)) {
|
|
63
65
|
return null;
|
|
@@ -120,6 +120,15 @@ interface AgentConversationColumnBaseProps {
|
|
|
120
120
|
* web chat exactly. See ./transcript-layout.tsx.
|
|
121
121
|
*/
|
|
122
122
|
layout?: TranscriptLayout;
|
|
123
|
+
/** A quiet note UNDER the composer (host chrome). Absent ⇒ nothing is drawn. */
|
|
124
|
+
belowInput?: React.ReactNode;
|
|
125
|
+
/**
|
|
126
|
+
* Words to put in an EMPTY composer, once per distinct value (a hand-off
|
|
127
|
+
* from elsewhere on the page). Never overwrites text the person typed.
|
|
128
|
+
*/
|
|
129
|
+
draftSeed?: string | null;
|
|
130
|
+
/** The column's accessible name (a host's keyboard scope may key on it). */
|
|
131
|
+
label?: string;
|
|
123
132
|
}
|
|
124
|
-
export declare function AgentConversationColumn({ conversationId, displayConversationId, surfaceKey, constrainWidth, edgeToEdgeScroll, smartInputProps, landingContent, hideInput, hideCreatorPanel, afterMessages, aboveInput, deferColdMarkdown, audience, layout, }: AgentConversationColumnProps): import("react").JSX.Element;
|
|
133
|
+
export declare function AgentConversationColumn({ conversationId, displayConversationId, surfaceKey, constrainWidth, edgeToEdgeScroll, smartInputProps, landingContent, hideInput, hideCreatorPanel, afterMessages, aboveInput, deferColdMarkdown, audience, layout, belowInput, draftSeed, label, }: AgentConversationColumnProps): import("react").JSX.Element;
|
|
125
134
|
export {};
|
|
@@ -19,6 +19,7 @@ import { PendingAsksZone } from "../../ui-first-tools/ui/PendingAsksZone.js";
|
|
|
19
19
|
import { ServerOperationBanner } from "../../runtime-reconnect/ServerOperationBanner.js";
|
|
20
20
|
import { ProposedDirectivesZone } from "../../../host/ui-slots.js";
|
|
21
21
|
import { useAppDispatch, useAppSelector } from "../../../store/hooks.js";
|
|
22
|
+
import { setUserInputText } from "../../redux/execution-system/instance-user-input/instance-user-input.slice.js";
|
|
22
23
|
import { selectMessageCount } from "../../redux/execution-system/messages/messages.selectors.js";
|
|
23
24
|
import {
|
|
24
25
|
revealOlderGroups,
|
|
@@ -63,12 +64,16 @@ function AgentConversationColumn({
|
|
|
63
64
|
aboveInput,
|
|
64
65
|
deferColdMarkdown = false,
|
|
65
66
|
audience = "builder",
|
|
66
|
-
layout
|
|
67
|
+
layout,
|
|
68
|
+
belowInput,
|
|
69
|
+
draftSeed,
|
|
70
|
+
label
|
|
67
71
|
}) {
|
|
68
72
|
const resolvedLayout = resolveTranscriptLayout(layout);
|
|
69
73
|
const columnClass = transcriptColumnClass(resolvedLayout);
|
|
70
74
|
const dispatch = useAppDispatch();
|
|
71
75
|
const displayId = displayConversationId ?? conversationId;
|
|
76
|
+
useDraftSeed(conversationId, draftSeed ?? null);
|
|
72
77
|
const scrollRef = useRef(null);
|
|
73
78
|
const contentRef = useRef(null);
|
|
74
79
|
const didAutoRevealRef = useRef(false);
|
|
@@ -213,6 +218,8 @@ function AgentConversationColumn({
|
|
|
213
218
|
return /* @__PURE__ */ jsx(ConversationAbilitiesProvider, { conversationId: displayId, children: /* @__PURE__ */ jsx(TranscriptAudienceProvider, { audience, children: /* @__PURE__ */ jsx(TranscriptLayoutProvider, { layout: resolvedLayout, children: /* @__PURE__ */ jsxs(
|
|
214
219
|
"div",
|
|
215
220
|
{
|
|
221
|
+
"data-chat-column": "",
|
|
222
|
+
...label ? { "aria-label": label, role: "region" } : {},
|
|
216
223
|
className: cn(
|
|
217
224
|
"h-full flex flex-col overflow-hidden",
|
|
218
225
|
// Legacy: pad the whole column. Edge mode: padding lives on the
|
|
@@ -349,7 +356,8 @@ function AgentConversationColumn({
|
|
|
349
356
|
surfaceKey,
|
|
350
357
|
...smartInputProps
|
|
351
358
|
}
|
|
352
|
-
)
|
|
359
|
+
),
|
|
360
|
+
belowInput ? /* @__PURE__ */ jsx("div", { className: "px-1 pb-1 text-xs text-muted-foreground", "data-chat-below-input": "", children: belowInput }) : null
|
|
353
361
|
]
|
|
354
362
|
}
|
|
355
363
|
)
|
|
@@ -357,6 +365,22 @@ function AgentConversationColumn({
|
|
|
357
365
|
}
|
|
358
366
|
) }) }) });
|
|
359
367
|
}
|
|
368
|
+
function useDraftSeed(conversationId, seed) {
|
|
369
|
+
const dispatch = useAppDispatch();
|
|
370
|
+
const current = useAppSelector(
|
|
371
|
+
(state) => state.instanceUserInput?.byConversationId[conversationId]?.text ?? null
|
|
372
|
+
);
|
|
373
|
+
const exists = useAppSelector(
|
|
374
|
+
(state) => state.instanceUserInput?.byConversationId[conversationId] !== void 0
|
|
375
|
+
);
|
|
376
|
+
const appliedRef = useRef(null);
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
if (!seed || !exists || appliedRef.current === seed) return;
|
|
379
|
+
appliedRef.current = seed;
|
|
380
|
+
if (current && current.trim()) return;
|
|
381
|
+
dispatch(setUserInputText({ conversationId, text: seed }));
|
|
382
|
+
}, [seed, exists, current, conversationId, dispatch]);
|
|
383
|
+
}
|
|
360
384
|
export {
|
|
361
385
|
AgentConversationColumn
|
|
362
386
|
};
|