@ai-matrx/chat 0.2.9 → 0.2.10
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 +13 -0
- package/dist/agent-copy/page-capture/pageCapture.d.ts +11 -62
- package/dist/agent-copy/page-capture/pageCapture.js +5 -22
- package/dist/agents/components/conversation-actions/conversation-verbs.js +2 -6
- package/dist/agents/components/run-controls/PayloadTab.js +6 -1
- package/dist/agents/components/run-controls/panels/BackendTargetPanel.js +6 -1
- package/dist/agents/menu/agent-actions.js +3 -5
- package/dist/agents/redux/agent-shortcuts/thunks.js +9 -5
- package/dist/agents/redux/execution-system/instance-resources/remarks.d.ts +6 -0
- package/dist/agents/redux/execution-system/instance-resources/remarks.js +16 -0
- package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateCore.js +6 -1
- package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateList.js +6 -1
- package/dist/agents/redux/execution-system/message-crud/commit-inline-edit.thunk.js +2 -1
- package/dist/canvas/workspace/useCanvasWorkspaceConversation.d.ts +12 -0
- package/dist/canvas/workspace/useCanvasWorkspaceConversation.js +29 -1
- package/dist/context-menu/types.d.ts +7 -0
- package/dist/cx-chat/components/messages/AssistantMessage.js +8 -10
- package/dist/cx-chat/components/messages/UserMessage.js +6 -1
- package/dist/cx-conversation/AssistantMessage.js +8 -10
- package/dist/cx-conversation/UserMessage.js +6 -1
- package/dist/host/clipboard.d.ts +7 -0
- package/dist/host/clipboard.js +11 -0
- package/dist/host/ui-slots.d.ts +2 -6
- package/dist/host/ui-slots.js +4 -14
- package/dist/public-chat/components/PublicMessageOptionsMenu.js +2 -9
- package/dist/store/create-chat-store.d.ts +10 -1
- package/dist/store/create-chat-store.js +4 -2
- package/dist/surfaces/runtime/SurfaceRuntimeContext.d.ts +10 -0
- package/dist/surfaces/runtime/SurfaceRuntimeContext.js +23 -1
- package/dist/tool-call-visualization/components/ToolTabBodies.js +6 -10
- package/dist/tool-call-visualization/result-fields/ResultFile.js +2 -8
- package/dist/tool-call-visualization/result-fields/ShortId.js +1 -23
- package/dist/voice-agent/components/LiveVoiceDoor.js +1 -1
- package/dist/voice-agent/hooks/useVoiceAgentInstance.js +1 -1
- package/dist/window-panels/windows/agents/ObservationalMemoryWindow.d.ts +0 -17
- package/dist/window-panels/windows/agents/ObservationalMemoryWindow.js +6 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.2.10 — 2026-10-07
|
|
4
|
+
### Re-sync before matrx-frontend consumes the published package (P27)
|
|
5
|
+
|
|
6
|
+
- Carries every matrx-frontend `packages/chat` edit made after base `f5f30ae893` (32 files): ALC-18 live
|
|
7
|
+
values reach the surface scope and the clicked row's declared item, ALC-19 kit copy/`host/clipboard`,
|
|
8
|
+
board chat keeps its conversation id across a reload, voices read from `@ai-matrx/media/voices`,
|
|
9
|
+
remarks durability-port announcement.
|
|
10
|
+
- Their `@/lib/toast` imports use the package notify port and their `agent-copy/export` download imports
|
|
11
|
+
use `@ai-matrx/kit/download` (0.2.9 dropped the chat copy). Public "Save as file" passes the text,
|
|
12
|
+
not a pre-built Blob, so the saved file holds the markdown.
|
|
13
|
+
- Consumer action: none.
|
|
14
|
+
|
|
3
15
|
## 0.2.9 — 2026-10-07
|
|
4
16
|
|
|
5
17
|
ALC-19: the download door moved to the base package.
|
|
6
18
|
|
|
7
19
|
- `agent-copy/export` no longer exports `downloadFile` / `downloadUrl`; they live in `@ai-matrx/kit/download` (kit 0.26.0).
|
|
8
20
|
- Consumer action: import `downloadFile` / `downloadUrl` from `@ai-matrx/kit/download`.
|
|
21
|
+
- The audio-response download, the public chat "save as file" and tool-result file links save through kit's door too (no hand-built anchors left in the package).
|
|
9
22
|
|
|
10
23
|
## 0.2.8 — 2026-10-07
|
|
11
24
|
|
|
@@ -16,67 +16,15 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { AgentPayloadInput } from "@ai-matrx/alchemy/operate";
|
|
18
18
|
import type { AlchemyGroomerConfig } from "@ai-matrx/alchemy/react/workspace";
|
|
19
|
-
|
|
19
|
+
import { mergeLiveCapture, type LiveCapture, type LiveCaptureKind, type LiveCaptureValue, type LiveContribution, type LiveNamed, type LiveRequest, type LiveSection } from "@ai-matrx/alchemy/surface";
|
|
20
|
+
export type PageCaptureKind = LiveCaptureKind;
|
|
20
21
|
/** A chosen thing, by name and id. Either may be null (not chosen / not loaded). */
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export type
|
|
26
|
-
export
|
|
27
|
-
/** Stable id — the Groomer's section id. Unique within one capture. */
|
|
28
|
-
id: string;
|
|
29
|
-
/** What the person calls this part of the page. */
|
|
30
|
-
title: string;
|
|
31
|
-
description?: string;
|
|
32
|
-
/** `data` survives "Only the data"; `request` does not. */
|
|
33
|
-
role: "data" | "request";
|
|
34
|
-
value: unknown;
|
|
35
|
-
/** The one-line brief (counts, a sentence). Defaults to the value's shape. */
|
|
36
|
-
brief?: unknown;
|
|
37
|
-
/**
|
|
38
|
-
* A section too big to hold on the page (a table's records): `value` is the
|
|
39
|
-
* honest stub saying it is not included, and `load` reads it at copy time for
|
|
40
|
-
* the "with …" variants. Never a second read while the page is only open.
|
|
41
|
-
*/
|
|
42
|
-
load?: () => Promise<unknown>;
|
|
43
|
-
}
|
|
44
|
-
export interface PageCaptureRequest {
|
|
45
|
-
method: string;
|
|
46
|
-
path: string;
|
|
47
|
-
status: string;
|
|
48
|
-
httpStatus?: number;
|
|
49
|
-
durationMs?: number;
|
|
50
|
-
requestId?: string;
|
|
51
|
-
/** Which client door (supabase-rest, supabase-rpc, aidream, next-api …). */
|
|
52
|
-
client?: string;
|
|
53
|
-
/** The JSON request body under 8 KB, credentials redacted. */
|
|
54
|
-
requestBody?: unknown;
|
|
55
|
-
requestBodyNote?: string;
|
|
56
|
-
/** A failed request's own sentence from the server (the refusal, verbatim). */
|
|
57
|
-
errorSentence?: string;
|
|
58
|
-
timestamp: number;
|
|
59
|
-
}
|
|
60
|
-
export interface PageCapture {
|
|
61
|
-
kind: PageCaptureKind;
|
|
62
|
-
title: string;
|
|
63
|
-
route: string;
|
|
64
|
-
url?: string;
|
|
65
|
-
/** What this page is OF (table, record, dialog subject …), names and ids. */
|
|
66
|
-
identity: Record<string, PageCaptureValue>;
|
|
67
|
-
/** The exact choices the person made on the page, in order. */
|
|
68
|
-
selection: Record<string, PageCaptureValue>;
|
|
69
|
-
sections: PageCaptureSection[];
|
|
70
|
-
/** The error sentences on screen, verbatim. */
|
|
71
|
-
errors: string[];
|
|
72
|
-
requests: PageCaptureRequest[];
|
|
73
|
-
}
|
|
74
|
-
export interface PageCaptureContribution {
|
|
75
|
-
owner: string;
|
|
76
|
-
sections: PageCaptureSection[];
|
|
77
|
-
/** Names a descendant knows and the page does not (a table's name read inside its mount). */
|
|
78
|
-
identity?: Record<string, PageCaptureValue>;
|
|
79
|
-
}
|
|
22
|
+
export type PageCaptureNamed = LiveNamed;
|
|
23
|
+
export type PageCaptureValue = LiveCaptureValue;
|
|
24
|
+
export type PageCaptureSection = LiveSection;
|
|
25
|
+
export type PageCaptureRequest = LiveRequest;
|
|
26
|
+
export type PageCapture = LiveCapture;
|
|
27
|
+
export type PageCaptureContribution = LiveContribution;
|
|
80
28
|
/** The marker a cycle leaves where the repeated object would have been. */
|
|
81
29
|
export declare const CIRCULAR_MARKER_PREFIX = "[circular reference to ";
|
|
82
30
|
/** A JSON pointer path in words: `/sections/1/load` → `sections › 1 › load`; root is "the capture itself". */
|
|
@@ -95,9 +43,10 @@ export declare function checkedTransferJson<T>(value: T, what: string): T;
|
|
|
95
43
|
/** `Name (id)`, `Name`, `(id)`, or "not chosen". */
|
|
96
44
|
export declare function describeValue(v: PageCaptureValue): string;
|
|
97
45
|
/** Merge descendant sections into a capture. Two owners claiming one id is a loud error. */
|
|
98
|
-
|
|
46
|
+
/** The page's capture plus its descendants' contributions — alchemy's one merge. */
|
|
47
|
+
export declare const mergePageCapture: typeof mergeLiveCapture;
|
|
99
48
|
/**
|
|
100
|
-
* THE CAPTURE MADE PLAIN AT
|
|
49
|
+
* THE CAPTURE MADE PLAIN AT READ (`getActivePageCapture` calls this): identity, selection,
|
|
101
50
|
* every section's value and brief, and every request body are plain JSON. A section's `load` stays a
|
|
102
51
|
* function — it is how the "with …" copies read — and is never handed to a transfer (see
|
|
103
52
|
* `pageCaptureJson`).
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { formatCount } from "@ai-matrx/kit/format";
|
|
2
|
+
import {
|
|
3
|
+
mergeLiveCapture
|
|
4
|
+
} from "@ai-matrx/alchemy/surface";
|
|
2
5
|
const COMPACT_CHARS = 6e3;
|
|
3
6
|
const CIRCULAR_MARKER_PREFIX = "[circular reference to ";
|
|
4
7
|
function pathInWords(path) {
|
|
@@ -118,28 +121,7 @@ function compactValue(value) {
|
|
|
118
121
|
preview: text.slice(0, COMPACT_CHARS)
|
|
119
122
|
};
|
|
120
123
|
}
|
|
121
|
-
|
|
122
|
-
const owners = new Map(base.sections.map((s) => [s.id, "page"]));
|
|
123
|
-
const sections = [...base.sections];
|
|
124
|
-
const identity = { ...base.identity };
|
|
125
|
-
for (const c of contributions) {
|
|
126
|
-
for (const [k, v] of Object.entries(c.identity ?? {})) {
|
|
127
|
-
const prior = identity[k];
|
|
128
|
-
if (prior === void 0 || prior === null || isNamed(prior) && !prior.name) identity[k] = v;
|
|
129
|
-
}
|
|
130
|
-
for (const s of c.sections) {
|
|
131
|
-
const prior = owners.get(s.id);
|
|
132
|
-
if (prior) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
`[page-capture] section "${s.id}" is claimed by both "${prior}" and "${c.owner}"`
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
owners.set(s.id, c.owner);
|
|
138
|
-
sections.push(s);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return { ...base, identity, sections };
|
|
142
|
-
}
|
|
124
|
+
const mergePageCapture = mergeLiveCapture;
|
|
143
125
|
function plainNamedRecord(rec) {
|
|
144
126
|
return toPlainJson(rec);
|
|
145
127
|
}
|
|
@@ -192,6 +174,7 @@ function envelopeContext(c) {
|
|
|
192
174
|
return {
|
|
193
175
|
page: c.title,
|
|
194
176
|
"surface-kind": c.kind,
|
|
177
|
+
...c.surfaceName ? { surface: c.surfaceName } : {},
|
|
195
178
|
route: c.route,
|
|
196
179
|
...keyed(describeRecord(c.identity)),
|
|
197
180
|
...keyed(describeRecord(c.selection))
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { toast } from "../../../host/notify.js";
|
|
2
|
+
import { copyToHostClipboard } from "../../../host/clipboard.js";
|
|
2
3
|
import { toastDoor } from "../../../host/ui-slots.js";
|
|
3
4
|
import { openOverlay, CHAT_WINDOWS } from "../../../host/windows.js";
|
|
4
5
|
import {
|
|
@@ -23,12 +24,7 @@ function resolveAbsoluteHref(href) {
|
|
|
23
24
|
return `${window.location.origin}${href.startsWith("/") ? href : `/${href}`}`;
|
|
24
25
|
}
|
|
25
26
|
async function copyConversationLink(href) {
|
|
26
|
-
|
|
27
|
-
await navigator.clipboard.writeText(resolveAbsoluteHref(href));
|
|
28
|
-
toast.success("Link copied");
|
|
29
|
-
} catch {
|
|
30
|
-
toast.error("Couldn't copy \u2014 your browser blocked clipboard access");
|
|
31
|
-
}
|
|
27
|
+
await copyToHostClipboard(resolveAbsoluteHref(href), "Link copied");
|
|
32
28
|
}
|
|
33
29
|
function shareConversation(dispatch, conversationId, title) {
|
|
34
30
|
dispatch(
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../../../host/notify.js";
|
|
3
5
|
import { Badge, Button } from "@ai-matrx/design-system/controls";
|
|
4
6
|
import { useCallback, useMemo, useState } from "react";
|
|
5
7
|
import { Copy, Check } from "lucide-react";
|
|
@@ -119,6 +121,9 @@ function roledImagesOf(messages) {
|
|
|
119
121
|
return rows;
|
|
120
122
|
}
|
|
121
123
|
function PayloadTab({ conversationId }) {
|
|
124
|
+
const { copyText } = useClipboard({
|
|
125
|
+
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
126
|
+
});
|
|
122
127
|
const assembledRequestSelector = useMemo(
|
|
123
128
|
() => makeSelectAssembledRequest(conversationId),
|
|
124
129
|
[conversationId]
|
|
@@ -163,7 +168,7 @@ function PayloadTab({ conversationId }) {
|
|
|
163
168
|
if (!request) return;
|
|
164
169
|
try {
|
|
165
170
|
const text = JSON.stringify(request, null, 2);
|
|
166
|
-
void
|
|
171
|
+
void copyText(text).then(() => {
|
|
167
172
|
setCopied(true);
|
|
168
173
|
window.setTimeout(() => setCopied(false), 1500);
|
|
169
174
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../../../../host/notify.js";
|
|
3
5
|
import { formatDurationSeconds } from "@ai-matrx/kit/format";
|
|
4
6
|
import { useState } from "react";
|
|
5
7
|
import { Copy, Check } from "lucide-react";
|
|
@@ -19,9 +21,12 @@ function CopyableValue({
|
|
|
19
21
|
mono = true,
|
|
20
22
|
className
|
|
21
23
|
}) {
|
|
24
|
+
const { copyText } = useClipboard({
|
|
25
|
+
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
26
|
+
});
|
|
22
27
|
const [copied, setCopied] = useState(false);
|
|
23
28
|
const handleCopy = () => {
|
|
24
|
-
void
|
|
29
|
+
void copyText(value).then(() => {
|
|
25
30
|
setCopied(true);
|
|
26
31
|
setTimeout(() => setCopied(false), 1200);
|
|
27
32
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Cpu, Copy, RefreshCw, ExternalLink } from "lucide-react";
|
|
3
|
-
import {
|
|
3
|
+
import { copyToHostClipboard } from "../../host/clipboard.js";
|
|
4
4
|
import {
|
|
5
5
|
withAvailability
|
|
6
6
|
} from "../../context-menu/utils/availability.js";
|
|
@@ -30,8 +30,7 @@ function buildAgentMenuSection({
|
|
|
30
30
|
label: "Copy agent ID",
|
|
31
31
|
icon: Copy,
|
|
32
32
|
onSelect: () => {
|
|
33
|
-
void
|
|
34
|
-
toast.success("Agent ID copied");
|
|
33
|
+
void copyToHostClipboard(agentId, "Agent ID copied");
|
|
35
34
|
}
|
|
36
35
|
},
|
|
37
36
|
{
|
|
@@ -41,8 +40,7 @@ function buildAgentMenuSection({
|
|
|
41
40
|
icon: Copy,
|
|
42
41
|
disabled: !agentName,
|
|
43
42
|
onSelect: () => {
|
|
44
|
-
void
|
|
45
|
-
toast.success("Agent name copied");
|
|
43
|
+
void copyToHostClipboard(agentName ?? "", "Agent name copied");
|
|
46
44
|
}
|
|
47
45
|
},
|
|
48
46
|
{ kind: "separator", id: "agent-sep-1" },
|
|
@@ -333,6 +333,9 @@ const saveShortcutField = createAsyncThunk(
|
|
|
333
333
|
dispatch(markShortcutSaved({ id: shortcutId }));
|
|
334
334
|
}
|
|
335
335
|
);
|
|
336
|
+
function withGlobalShortcutAdminLane(request) {
|
|
337
|
+
return request.setHeader("x-matrx-admin-lane", "1");
|
|
338
|
+
}
|
|
336
339
|
const createShortcut = createAsyncThunk("agentShortcut/create", async (shortcutData, { dispatch, getState }) => {
|
|
337
340
|
const userId = selectUserId(getState());
|
|
338
341
|
const organizationId = await ensureOrgId(shortcutData.organizationId);
|
|
@@ -346,7 +349,9 @@ const createShortcut = createAsyncThunk("agentShortcut/create", async (shortcutD
|
|
|
346
349
|
createdAt: "",
|
|
347
350
|
updatedAt: ""
|
|
348
351
|
};
|
|
349
|
-
const
|
|
352
|
+
const isGlobalWrite = shortcutData.userId === null && shortcutData.organizationId == null && shortcutData.projectId == null && shortcutData.taskId == null;
|
|
353
|
+
const insertRequest = shortcutTable(supabase).insert(agentShortcutToInsert(draft)).select().single();
|
|
354
|
+
const { data, error } = await (isGlobalWrite ? withGlobalShortcutAdminLane(insertRequest) : insertRequest);
|
|
350
355
|
if (error) throw pgErrorToError(error);
|
|
351
356
|
const newShortcut = toGlobalOwnershipRecord(
|
|
352
357
|
dbRowToAgentShortcut(data),
|
|
@@ -392,13 +397,12 @@ const duplicateShortcut = createAsyncThunk("agentShortcut/duplicate", async (arg
|
|
|
392
397
|
const promoteShortcutToGlobal = createAsyncThunk(
|
|
393
398
|
"agentShortcut/promoteToGlobal",
|
|
394
399
|
async ({ shortcutId, targetCategoryId, label }, { dispatch }) => {
|
|
395
|
-
const { data, error } = await
|
|
396
|
-
SHORTCUT_RPCS.promoteToGlobal,
|
|
397
|
-
{
|
|
400
|
+
const { data, error } = await withGlobalShortcutAdminLane(
|
|
401
|
+
supabase.rpc(SHORTCUT_RPCS.promoteToGlobal, {
|
|
398
402
|
p_shortcut_id: shortcutId,
|
|
399
403
|
p_target_category_id: targetCategoryId,
|
|
400
404
|
p_label: label && label.trim().length > 0 ? label.trim() : void 0
|
|
401
|
-
}
|
|
405
|
+
})
|
|
402
406
|
);
|
|
403
407
|
if (error) throw pgErrorToError(error);
|
|
404
408
|
const newShortcutId = data;
|
|
@@ -159,6 +159,12 @@ export interface RemarkDurability {
|
|
|
159
159
|
}
|
|
160
160
|
/** True while some chip of this conversation has not yet been saved server-side. */
|
|
161
161
|
export declare function hasPendingRemarkWrites(conversationId: string): boolean;
|
|
162
|
+
/**
|
|
163
|
+
* True once the app has registered how unsent chips are kept. Without it every
|
|
164
|
+
* chip is browser-only and lost on reload, so a missing port is announced, never
|
|
165
|
+
* silently stood in for (the door a person touches must say when it is not wired).
|
|
166
|
+
*/
|
|
167
|
+
export declare function isRemarkDurabilityRegistered(): boolean;
|
|
162
168
|
/** Save every pending chip write and wait for its row/ref (called before a send). */
|
|
163
169
|
export declare function flushRemarkWrites(conversationId: string): Promise<void>;
|
|
164
170
|
/** Put a conversation's unsent chips back (any device) — the composer calls this once on mount. */
|
|
@@ -72,10 +72,23 @@ function buildSource(item, coalesceKey) {
|
|
|
72
72
|
function hasPendingRemarkWrites(conversationId) {
|
|
73
73
|
return durability?.hasPending(conversationId) ?? false;
|
|
74
74
|
}
|
|
75
|
+
function isRemarkDurabilityRegistered() {
|
|
76
|
+
return durability !== null;
|
|
77
|
+
}
|
|
78
|
+
const announcedMissing = /* @__PURE__ */ new Set();
|
|
79
|
+
function announceMissingDurability(op) {
|
|
80
|
+
if (announcedMissing.has(op)) return;
|
|
81
|
+
announcedMissing.add(op);
|
|
82
|
+
console.error(
|
|
83
|
+
`[remarks] ${op} ran with no durability port registered: unsent chips are NOT kept server-side. The app must call registerRemarkDurability (providers/ChatSurfaceRegistrations).`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
75
86
|
async function flushRemarkWrites(conversationId) {
|
|
87
|
+
if (!durability) announceMissingDurability("flush");
|
|
76
88
|
await durability?.flush(conversationId);
|
|
77
89
|
}
|
|
78
90
|
function restoreComposerRemarks(conversationId) {
|
|
91
|
+
if (!durability) announceMissingDurability("restore");
|
|
79
92
|
durability?.restore(conversationId);
|
|
80
93
|
}
|
|
81
94
|
let durability = null;
|
|
@@ -100,6 +113,7 @@ function stageRemark(conversationId, item, options = {}) {
|
|
|
100
113
|
dispatch(setResourceSource({ conversationId, resourceId: live.resourceId, source }));
|
|
101
114
|
dispatch(setResourcePreview({ conversationId, resourceId: live.resourceId, preview: source.label }));
|
|
102
115
|
if (!options.fromServer && !item.blockStateRef) {
|
|
116
|
+
if (!durability) announceMissingDurability("save");
|
|
103
117
|
durability?.save(conversationId, live.resourceId, coalesceKey, item);
|
|
104
118
|
}
|
|
105
119
|
return live.resourceId;
|
|
@@ -110,6 +124,7 @@ function stageRemark(conversationId, item, options = {}) {
|
|
|
110
124
|
dispatch(addResource({ conversationId, blockType: REMARKS_BLOCK_TYPE, source, resourceId }));
|
|
111
125
|
dispatch(setResourcePreview({ conversationId, resourceId, preview: source.label }));
|
|
112
126
|
if (!options.fromServer && !item.blockStateRef) {
|
|
127
|
+
if (!durability) announceMissingDurability("save");
|
|
113
128
|
durability?.save(conversationId, resourceId, coalesceKey, item);
|
|
114
129
|
}
|
|
115
130
|
return resourceId;
|
|
@@ -205,6 +220,7 @@ export {
|
|
|
205
220
|
dismissRemarkChip,
|
|
206
221
|
flushRemarkWrites,
|
|
207
222
|
hasPendingRemarkWrites,
|
|
223
|
+
isRemarkDurabilityRegistered,
|
|
208
224
|
readStoredRemarks,
|
|
209
225
|
registerRemarkDurability,
|
|
210
226
|
remarkChipTitle,
|
package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateCore.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../../../../../host/notify.js";
|
|
3
5
|
import { Badge, Button, Tile } from "@ai-matrx/design-system/controls";
|
|
4
6
|
import { useState } from "react";
|
|
5
7
|
import { cn } from "@ai-matrx/design-system";
|
|
@@ -18,9 +20,12 @@ import {
|
|
|
18
20
|
selectInstanceTitle
|
|
19
21
|
} from "../instance-ui-state.selectors.js";
|
|
20
22
|
function useCopyText(text) {
|
|
23
|
+
const { copyText } = useClipboard({
|
|
24
|
+
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
25
|
+
});
|
|
21
26
|
const [copied, setCopied] = useState(false);
|
|
22
27
|
const copy = () => {
|
|
23
|
-
|
|
28
|
+
copyText(text).then(() => {
|
|
24
29
|
setCopied(true);
|
|
25
30
|
setTimeout(() => setCopied(false), 1500);
|
|
26
31
|
});
|
package/dist/agents/redux/execution-system/instance-ui-state/components/InstanceUIStateList.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../../../../../host/notify.js";
|
|
3
5
|
import { useState } from "react";
|
|
4
6
|
import { cn } from "@ai-matrx/design-system";
|
|
5
7
|
import {
|
|
@@ -19,9 +21,12 @@ import {
|
|
|
19
21
|
} from "../instance-ui-state.selectors.js";
|
|
20
22
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
21
23
|
function useCopyText(text) {
|
|
24
|
+
const { copyText } = useClipboard({
|
|
25
|
+
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
26
|
+
});
|
|
22
27
|
const [copied, setCopied] = useState(false);
|
|
23
28
|
const copy = () => {
|
|
24
|
-
|
|
29
|
+
copyText(text).then(() => {
|
|
25
30
|
setCopied(true);
|
|
26
31
|
setTimeout(() => setCopied(false), 1500);
|
|
27
32
|
});
|
|
@@ -6,6 +6,7 @@ import { updateMessageRecord } from "../messages/messages.slice.js";
|
|
|
6
6
|
import { saveAnswerEdit } from "./save-answer-edit.thunk.js";
|
|
7
7
|
import { refetchSingleMessage } from "./refetch-single-message.thunk.js";
|
|
8
8
|
import { toast } from "../../../../host/notify.js";
|
|
9
|
+
import { copyToHostClipboard } from "../../../../host/clipboard.js";
|
|
9
10
|
import { buildContentBlocksForSave } from "../../../../cx-chat/utils/buildContentBlocksForSave.js";
|
|
10
11
|
import { stripCitationMarkers } from "../messages/message-citations.js";
|
|
11
12
|
import { extractFlatText } from "../messages/messages.selectors.js";
|
|
@@ -97,7 +98,7 @@ async function persistInlineEdit(dispatch, conversationId, messageId, entry) {
|
|
|
97
98
|
action: {
|
|
98
99
|
label: "Copy my edit",
|
|
99
100
|
onClick: () => {
|
|
100
|
-
void
|
|
101
|
+
void copyToHostClipboard(draft);
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
});
|
|
@@ -62,6 +62,18 @@ export declare function conversationInAddress(param: string, search: string): st
|
|
|
62
62
|
export declare function workspaceChatMemoryKey(surfaceKey: string): string;
|
|
63
63
|
export declare function recallWorkspaceConversation(surfaceKey: string): string | null;
|
|
64
64
|
export declare function rememberWorkspaceConversation(surfaceKey: string, conversationId: string | null): void;
|
|
65
|
+
/**
|
|
66
|
+
* THE RESERVED ID OF AN UNSENT CHAT (2026-10-06). A new chat that has not been
|
|
67
|
+
* sent is unknown to the server, so a reload cannot reopen it and used to mint a
|
|
68
|
+
* different id — which orphaned every chip staged into it (a comment riding
|
|
69
|
+
* "with next message" came back gone). The id is reserved on this device per
|
|
70
|
+
* home and handed to the launch, so the unsent chat comes back under the SAME id
|
|
71
|
+
* and its saved chips are found. Released when the server has the conversation
|
|
72
|
+
* (the memory above takes over) and by New chat (a new chat is a new id).
|
|
73
|
+
*/
|
|
74
|
+
export declare function reservedChatKey(surfaceKey: string): string;
|
|
75
|
+
export declare function reserveWorkspaceConversationId(surfaceKey: string, fresh: boolean): string | undefined;
|
|
76
|
+
export declare function releaseWorkspaceConversationId(surfaceKey: string): void;
|
|
65
77
|
/** `search` with `?<param>=` set to `conversationId`, or removed when null. */
|
|
66
78
|
export declare function addressWithConversation(param: string, search: string, conversationId: string | null): string;
|
|
67
79
|
export interface CanvasWorkspaceConversationController {
|
|
@@ -39,6 +39,27 @@ function rememberWorkspaceConversation(surfaceKey, conversationId) {
|
|
|
39
39
|
} catch {
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
function reservedChatKey(surfaceKey) {
|
|
43
|
+
return `matrx:workspace-chat-reserved:${surfaceKey}`;
|
|
44
|
+
}
|
|
45
|
+
function reserveWorkspaceConversationId(surfaceKey, fresh) {
|
|
46
|
+
try {
|
|
47
|
+
const key = reservedChatKey(surfaceKey);
|
|
48
|
+
const kept = fresh ? null : window.localStorage.getItem(key);
|
|
49
|
+
if (kept && CONVERSATION_ID.test(kept)) return kept;
|
|
50
|
+
const id = window.crypto.randomUUID();
|
|
51
|
+
window.localStorage.setItem(key, id);
|
|
52
|
+
return id;
|
|
53
|
+
} catch {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function releaseWorkspaceConversationId(surfaceKey) {
|
|
58
|
+
try {
|
|
59
|
+
window.localStorage.removeItem(reservedChatKey(surfaceKey));
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
42
63
|
function addressWithConversation(param, search, conversationId) {
|
|
43
64
|
const params = new URLSearchParams(search);
|
|
44
65
|
if (conversationId) params.set(param, conversationId);
|
|
@@ -99,6 +120,7 @@ function useCanvasWorkspaceConversation(surfaceKey, input) {
|
|
|
99
120
|
if (addressParam === null || !addressRead || !conversationId) return;
|
|
100
121
|
const shown = request.kind === "open" || serverHasIt ? conversationId : null;
|
|
101
122
|
rememberWorkspaceConversation(surfaceKey, shown);
|
|
123
|
+
if (shown) releaseWorkspaceConversationId(surfaceKey);
|
|
102
124
|
const search = addressWithConversation(addressParam, window.location.search, shown);
|
|
103
125
|
if (search === window.location.search) return;
|
|
104
126
|
replaceAddressWithoutNavigating(`${window.location.pathname}${search}${window.location.hash}`);
|
|
@@ -111,8 +133,11 @@ function useCanvasWorkspaceConversation(surfaceKey, input) {
|
|
|
111
133
|
const stale = () => handled.current !== key;
|
|
112
134
|
if (request.kind === "new" || request.kind === "agent") {
|
|
113
135
|
const runtime = ownSurface ? { runtime: { surfaceName: null } } : {};
|
|
114
|
-
const
|
|
136
|
+
const reserved = addressParam !== null ? reserveWorkspaceConversationId(surfaceKey, request.nonce > 0) : void 0;
|
|
137
|
+
const reservation = reserved ? { conversationId: reserved } : {};
|
|
138
|
+
const launch = request.kind === "agent" ? launchAgent(request.agentId, { surfaceKey, sourceFeature: "chat", ...reservation, ...runtime }) : launchMandate(request.mandateKey ?? DEFAULT_NEW_CHAT_MANDATE_KEY, {
|
|
115
139
|
surfaceKey,
|
|
140
|
+
...reservation,
|
|
116
141
|
// A REGISTERED feature, never a new string: this surface IS the chat.
|
|
117
142
|
sourceFeature: "chat",
|
|
118
143
|
...runtime
|
|
@@ -193,7 +218,10 @@ export {
|
|
|
193
218
|
addressWithConversation,
|
|
194
219
|
conversationInAddress,
|
|
195
220
|
recallWorkspaceConversation,
|
|
221
|
+
releaseWorkspaceConversationId,
|
|
196
222
|
rememberWorkspaceConversation,
|
|
223
|
+
reserveWorkspaceConversationId,
|
|
224
|
+
reservedChatKey,
|
|
197
225
|
useCanvasWorkspaceConversation,
|
|
198
226
|
workspaceChatMemoryKey
|
|
199
227
|
};
|
|
@@ -4,6 +4,7 @@ import type { SourceFeature } from "../agents/types/instance.types.js";
|
|
|
4
4
|
import type { Scope } from "../agents/redux/shared/scope.js";
|
|
5
5
|
import type { ApplicationScope } from "../agents/types/scope.types.js";
|
|
6
6
|
import type { EntityTypeToken } from "@ai-matrx/associations";
|
|
7
|
+
import type { ResolvedItem } from "@ai-matrx/alchemy/declare";
|
|
7
8
|
/**
|
|
8
9
|
* The few shapes the host owns (the rich document's content source and actions, the sharing
|
|
9
10
|
* system's resource types). The package declares them open; the host fills them in by augmenting
|
|
@@ -399,6 +400,12 @@ export interface MenuContentProps {
|
|
|
399
400
|
variant: MenuVariant;
|
|
400
401
|
/** The clicked target's own name for the header (`CONTEXT_MENU_HEADING_KEY`). */
|
|
401
402
|
heading?: ContextMenuHeading | null;
|
|
403
|
+
/**
|
|
404
|
+
* The declared item the menu opened on (a table row: `table_row`, identity `table_id` + `row_id`),
|
|
405
|
+
* raw-free, as `itemSources` resolved it. It becomes the click target's `item`, so Actions and
|
|
406
|
+
* menu rows read the declared item instead of re-deriving it. Null = not on an item.
|
|
407
|
+
*/
|
|
408
|
+
item?: ResolvedItem | null;
|
|
402
409
|
sourceFeature: SourceFeature;
|
|
403
410
|
surfaceName?: string;
|
|
404
411
|
menuVersion: number;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../../../host/notify.js";
|
|
3
5
|
import { recognizeOurFileUrl } from "@ai-matrx/media/files/engine";
|
|
4
6
|
import { useState, useEffect, useCallback } from "react";
|
|
5
7
|
import {
|
|
@@ -22,6 +24,7 @@ import { chatConversationsActions } from "../../_legacy-stubs.js";
|
|
|
22
24
|
import { RichDocumentActions } from "../../../host/rich-document-slots.js";
|
|
23
25
|
import { MessageTimestamp } from "../../../agents/components/messages-display/MessageTimestamp.js";
|
|
24
26
|
import { ErrorAlchemyMenu } from "../../../host/ui-slots.js";
|
|
27
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
25
28
|
function AssistantMessage({
|
|
26
29
|
message,
|
|
27
30
|
sessionId,
|
|
@@ -31,6 +34,9 @@ function AssistantMessage({
|
|
|
31
34
|
isTtsRequest = false,
|
|
32
35
|
onContentChange
|
|
33
36
|
}) {
|
|
37
|
+
const { copyText } = useClipboard({
|
|
38
|
+
notify: (message2, kind) => kind === "error" ? toast.error(message2) : toast.success(message2)
|
|
39
|
+
});
|
|
34
40
|
const [isAppearing, setIsAppearing] = useState(true);
|
|
35
41
|
const [isAudioLinkCopied, setIsAudioLinkCopied] = useState(false);
|
|
36
42
|
const [isDownloading, setIsDownloading] = useState(false);
|
|
@@ -97,15 +103,8 @@ function AssistantMessage({
|
|
|
97
103
|
try {
|
|
98
104
|
const resp = await fetch(audioUrl);
|
|
99
105
|
const blob = await resp.blob();
|
|
100
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
101
106
|
const ext = audioMimeType?.split("/")[1] ?? "wav";
|
|
102
|
-
|
|
103
|
-
link.href = objectUrl;
|
|
104
|
-
link.download = `audio-response.${ext}`;
|
|
105
|
-
document.body.appendChild(link);
|
|
106
|
-
link.click();
|
|
107
|
-
document.body.removeChild(link);
|
|
108
|
-
URL.revokeObjectURL(objectUrl);
|
|
107
|
+
downloadFile(`audio-response.${ext}`, blob, blob.type);
|
|
109
108
|
} catch {
|
|
110
109
|
} finally {
|
|
111
110
|
setIsDownloading(false);
|
|
@@ -186,8 +185,7 @@ function AssistantMessage({
|
|
|
186
185
|
variant: "quiet",
|
|
187
186
|
onClick: async () => {
|
|
188
187
|
if (!audioUrl) return;
|
|
189
|
-
await
|
|
190
|
-
});
|
|
188
|
+
await copyText(audioUrl);
|
|
191
189
|
setIsAudioLinkCopied(true);
|
|
192
190
|
setTimeout(() => setIsAudioLinkCopied(false), 2e3);
|
|
193
191
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../../../host/notify.js";
|
|
3
5
|
import { useState, useEffect, useRef, useMemo } from "react";
|
|
4
6
|
import {
|
|
5
7
|
Check,
|
|
@@ -84,6 +86,9 @@ function UserMessage({
|
|
|
84
86
|
onContentChange,
|
|
85
87
|
compact = false
|
|
86
88
|
}) {
|
|
89
|
+
const { copyText } = useClipboard({
|
|
90
|
+
notify: (message2, kind) => kind === "error" ? toast.error(message2) : toast.success(message2)
|
|
91
|
+
});
|
|
87
92
|
const [isCollapsed, setIsCollapsed] = useState(true);
|
|
88
93
|
const [isCopied, setIsCopied] = useState(false);
|
|
89
94
|
const [shouldBeCollapsible, setShouldBeCollapsible] = useState(false);
|
|
@@ -130,7 +135,7 @@ function UserMessage({
|
|
|
130
135
|
}, [isEditing]);
|
|
131
136
|
const handleCopy = (e) => {
|
|
132
137
|
e.stopPropagation();
|
|
133
|
-
|
|
138
|
+
copyText(message.content).then(() => {
|
|
134
139
|
setIsCopied(true);
|
|
135
140
|
setTimeout(() => setIsCopied(false), 2e3);
|
|
136
141
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../host/notify.js";
|
|
3
5
|
import { recognizeOurFileUrl } from "@ai-matrx/media/files/engine";
|
|
4
6
|
import { useState, useEffect, useCallback } from "react";
|
|
5
7
|
import {
|
|
@@ -22,6 +24,7 @@ import { chatConversationsActions } from "./_legacy-stubs.js";
|
|
|
22
24
|
import { RichDocumentActions } from "../host/rich-document-slots.js";
|
|
23
25
|
import { MessageTimestamp } from "../agents/components/messages-display/MessageTimestamp.js";
|
|
24
26
|
import { ErrorAlchemyMenu } from "../host/ui-slots.js";
|
|
27
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
25
28
|
function AssistantMessage({
|
|
26
29
|
message,
|
|
27
30
|
sessionId,
|
|
@@ -31,6 +34,9 @@ function AssistantMessage({
|
|
|
31
34
|
isTtsRequest = false,
|
|
32
35
|
onContentChange
|
|
33
36
|
}) {
|
|
37
|
+
const { copyText } = useClipboard({
|
|
38
|
+
notify: (message2, kind) => kind === "error" ? toast.error(message2) : toast.success(message2)
|
|
39
|
+
});
|
|
34
40
|
const [isAppearing, setIsAppearing] = useState(true);
|
|
35
41
|
const [isAudioLinkCopied, setIsAudioLinkCopied] = useState(false);
|
|
36
42
|
const [isDownloading, setIsDownloading] = useState(false);
|
|
@@ -97,15 +103,8 @@ function AssistantMessage({
|
|
|
97
103
|
try {
|
|
98
104
|
const resp = await fetch(audioUrl);
|
|
99
105
|
const blob = await resp.blob();
|
|
100
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
101
106
|
const ext = audioMimeType?.split("/")[1] ?? "wav";
|
|
102
|
-
|
|
103
|
-
link.href = objectUrl;
|
|
104
|
-
link.download = `audio-response.${ext}`;
|
|
105
|
-
document.body.appendChild(link);
|
|
106
|
-
link.click();
|
|
107
|
-
document.body.removeChild(link);
|
|
108
|
-
URL.revokeObjectURL(objectUrl);
|
|
107
|
+
downloadFile(`audio-response.${ext}`, blob, blob.type);
|
|
109
108
|
} catch {
|
|
110
109
|
} finally {
|
|
111
110
|
setIsDownloading(false);
|
|
@@ -186,8 +185,7 @@ function AssistantMessage({
|
|
|
186
185
|
variant: "quiet",
|
|
187
186
|
onClick: async () => {
|
|
188
187
|
if (!audioUrl) return;
|
|
189
|
-
await
|
|
190
|
-
});
|
|
188
|
+
await copyText(audioUrl);
|
|
191
189
|
setIsAudioLinkCopied(true);
|
|
192
190
|
setTimeout(() => setIsAudioLinkCopied(false), 2e3);
|
|
193
191
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../host/notify.js";
|
|
3
5
|
import { useState, useEffect, useRef, useMemo } from "react";
|
|
4
6
|
import {
|
|
5
7
|
Check,
|
|
@@ -84,6 +86,9 @@ function UserMessage({
|
|
|
84
86
|
onContentChange,
|
|
85
87
|
compact = false
|
|
86
88
|
}) {
|
|
89
|
+
const { copyText } = useClipboard({
|
|
90
|
+
notify: (message2, kind) => kind === "error" ? toast.error(message2) : toast.success(message2)
|
|
91
|
+
});
|
|
87
92
|
const [isCollapsed, setIsCollapsed] = useState(true);
|
|
88
93
|
const [isCopied, setIsCopied] = useState(false);
|
|
89
94
|
const [shouldBeCollapsible, setShouldBeCollapsible] = useState(false);
|
|
@@ -130,7 +135,7 @@ function UserMessage({
|
|
|
130
135
|
}, [isEditing]);
|
|
131
136
|
const handleCopy = (e) => {
|
|
132
137
|
e.stopPropagation();
|
|
133
|
-
|
|
138
|
+
copyText(message.content).then(() => {
|
|
134
139
|
setIsCopied(true);
|
|
135
140
|
setTimeout(() => setIsCopied(false), 2e3);
|
|
136
141
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The clipboard seam — plain-text copy for package code outside a React component.
|
|
3
|
+
*
|
|
4
|
+
* The kit `copyText` with its success and failure notices routed through the notify seam, so a
|
|
5
|
+
* refused copy always announces itself. Resolves to whether the copy landed.
|
|
6
|
+
*/
|
|
7
|
+
export declare function copyToHostClipboard(text: string, successMessage?: string): Promise<boolean>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { copyText } from "@ai-matrx/kit/clipboard";
|
|
2
|
+
import { toast } from "./notify.js";
|
|
3
|
+
function copyToHostClipboard(text, successMessage) {
|
|
4
|
+
return copyText(text, {
|
|
5
|
+
successMessage,
|
|
6
|
+
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
copyToHostClipboard
|
|
11
|
+
};
|
package/dist/host/ui-slots.d.ts
CHANGED
|
@@ -317,12 +317,10 @@ export interface ChatUiSlots {
|
|
|
317
317
|
dispatchWarRoomTool: AnyFn;
|
|
318
318
|
dispatchWarRoomMasterTool: AnyFn;
|
|
319
319
|
resolveGmailSendConnection: AnyFn;
|
|
320
|
-
voiceDisplayName: AnyFn;
|
|
321
320
|
canvasGetVersionHistory: (canvasId: string) => Promise<CanvasItemRow[]>;
|
|
322
321
|
canvasGetById: (canvasId: string) => Promise<CanvasItemRow | null>;
|
|
323
322
|
createSandboxFilesystemAdapter: AnyFn;
|
|
324
323
|
notesGetById: AnyFn;
|
|
325
|
-
isLiveConversationVoice: AnyFn;
|
|
326
324
|
createHtmlPage: AnyFn;
|
|
327
325
|
convertMarkdownToHtml: AnyFn;
|
|
328
326
|
sklActions: Record<string, AnyFn>;
|
|
@@ -338,7 +336,6 @@ export interface ChatUiSlots {
|
|
|
338
336
|
WorkspaceGate: AnyComponent;
|
|
339
337
|
OrganizationContextNotice: AnyComponent;
|
|
340
338
|
JsonInspector: AnyComponent;
|
|
341
|
-
InlineCopyButton: AnyComponent;
|
|
342
339
|
ConfirmDialog: SlotComponent<OpenChangeProps>;
|
|
343
340
|
ModelListDropdown: SlotComponent<ModelListDropdownProps>;
|
|
344
341
|
TextWithDoors: AnyComponent;
|
|
@@ -357,6 +354,7 @@ export interface ChatUiSlots {
|
|
|
357
354
|
AccessGate: AnyComponent;
|
|
358
355
|
ReferenceCopyMenuItem: AnyComponent;
|
|
359
356
|
ReferenceCopyButton: AnyComponent;
|
|
357
|
+
InlineCopyButton: AnyComponent;
|
|
360
358
|
MandateNotesPanel: AnyComponent;
|
|
361
359
|
SurfaceBoundAgentsList: SlotComponent<AgentsListProps>;
|
|
362
360
|
ProposedDirectivesZone: AnyComponent;
|
|
@@ -604,7 +602,6 @@ export declare const StaleDataNotice: AnyComponent;
|
|
|
604
602
|
export declare const WorkspaceGate: AnyComponent;
|
|
605
603
|
export declare const OrganizationContextNotice: AnyComponent;
|
|
606
604
|
export declare const JsonInspector: AnyComponent;
|
|
607
|
-
export declare const InlineCopyButton: AnyComponent;
|
|
608
605
|
export declare const ConfirmDialog: SlotComponent<OpenChangeProps>;
|
|
609
606
|
export declare const ModelListDropdown: SlotComponent<ModelListDropdownProps>;
|
|
610
607
|
export declare const TextWithDoors: AnyComponent;
|
|
@@ -623,6 +620,7 @@ export declare const AnswerTextPreview: AnyComponent;
|
|
|
623
620
|
export declare const AccessGate: AnyComponent;
|
|
624
621
|
export declare const ReferenceCopyMenuItem: AnyComponent;
|
|
625
622
|
export declare const ReferenceCopyButton: AnyComponent;
|
|
623
|
+
export declare const InlineCopyButton: AnyComponent;
|
|
626
624
|
export declare const MandateNotesPanel: AnyComponent;
|
|
627
625
|
export declare const SurfaceBoundAgentsList: SlotComponent<AgentsListProps>;
|
|
628
626
|
export declare const ProposedDirectivesZone: AnyComponent;
|
|
@@ -712,12 +710,10 @@ export declare const adoptCloudBrowserRunFromStream: AnyFn;
|
|
|
712
710
|
export declare const dispatchWarRoomTool: AnyFn;
|
|
713
711
|
export declare const dispatchWarRoomMasterTool: AnyFn;
|
|
714
712
|
export declare const resolveGmailSendConnection: AnyFn;
|
|
715
|
-
export declare const voiceDisplayName: AnyFn;
|
|
716
713
|
export declare const canvasGetVersionHistory: (canvasId: string) => Promise<CanvasItemRow[]>;
|
|
717
714
|
export declare const canvasGetById: (canvasId: string) => Promise<CanvasItemRow | null>;
|
|
718
715
|
export declare const createSandboxFilesystemAdapter: AnyFn;
|
|
719
716
|
export declare const notesGetById: AnyFn;
|
|
720
|
-
export declare const isLiveConversationVoice: AnyFn;
|
|
721
717
|
export declare const createHtmlPage: AnyFn;
|
|
722
718
|
export declare const convertMarkdownToHtml: AnyFn;
|
|
723
719
|
/** The host's skill-library action creators (`sklActions.x(...)`); resolved at call time, a bare host throws naming the slot. */
|
package/dist/host/ui-slots.js
CHANGED
|
@@ -4,6 +4,7 @@ import { formatCost, usdToPoints } from "@ai-matrx/kit/format";
|
|
|
4
4
|
import { reportUnregisteredHostSlot as reportUnregistered } from "./diagnostics.js";
|
|
5
5
|
import { DefaultWebpageSnapshotView } from "./defaults/webpage-snapshot-view.js";
|
|
6
6
|
import { DefaultFullScreenOverlay, DefaultWindowPanel } from "./defaults/window-panel.js";
|
|
7
|
+
import { copyText } from "@ai-matrx/kit/clipboard";
|
|
7
8
|
const slots = {};
|
|
8
9
|
function registerChatUi(next) {
|
|
9
10
|
Object.assign(slots, next);
|
|
@@ -68,14 +69,7 @@ const failingThunk = (name) => () => () => {
|
|
|
68
69
|
const STAND_INS = {
|
|
69
70
|
confirm: async () => false,
|
|
70
71
|
copyRichContent: async () => false,
|
|
71
|
-
copyToClipboard:
|
|
72
|
-
try {
|
|
73
|
-
await navigator.clipboard.writeText(String(text));
|
|
74
|
-
return true;
|
|
75
|
-
} catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
},
|
|
72
|
+
copyToClipboard: (text) => copyText(String(text)),
|
|
79
73
|
useTablesEverywhere: () => NONE,
|
|
80
74
|
useCenterControlFit: () => true,
|
|
81
75
|
connectorDefinitionFromMcp: () => null,
|
|
@@ -327,7 +321,6 @@ const StaleDataNotice = slotComponent("StaleDataNotice", unregisteredWidget("Sta
|
|
|
327
321
|
const WorkspaceGate = slotComponent("WorkspaceGate", unregisteredWidget("WorkspaceGate"));
|
|
328
322
|
const OrganizationContextNotice = slotComponent("OrganizationContextNotice", unregisteredWidget("OrganizationContextNotice"));
|
|
329
323
|
const JsonInspector = slotComponent("JsonInspector", unregisteredWidget("JsonInspector"));
|
|
330
|
-
const InlineCopyButton = slotComponent("InlineCopyButton", unregisteredWidget("InlineCopyButton"));
|
|
331
324
|
const ConfirmDialog = slotComponent("ConfirmDialog", unregisteredWidget("ConfirmDialog"));
|
|
332
325
|
const ModelListDropdown = slotComponent("ModelListDropdown", unregisteredWidget("ModelListDropdown"));
|
|
333
326
|
const TextWithDoors = slotComponent("TextWithDoors", unregisteredWidget("TextWithDoors"));
|
|
@@ -346,6 +339,7 @@ const AnswerTextPreview = slotComponent("AnswerTextPreview", unregisteredWidget(
|
|
|
346
339
|
const AccessGate = slotComponent("AccessGate", unregisteredWidget("AccessGate"));
|
|
347
340
|
const ReferenceCopyMenuItem = slotComponent("ReferenceCopyMenuItem", unregisteredWidget("ReferenceCopyMenuItem"));
|
|
348
341
|
const ReferenceCopyButton = slotComponent("ReferenceCopyButton", unregisteredWidget("ReferenceCopyButton"));
|
|
342
|
+
const InlineCopyButton = slotComponent("InlineCopyButton", unregisteredWidget("InlineCopyButton"));
|
|
349
343
|
const MandateNotesPanel = slotComponent("MandateNotesPanel", unregisteredWidget("MandateNotesPanel"));
|
|
350
344
|
const SurfaceBoundAgentsList = slotComponent("SurfaceBoundAgentsList", unregisteredWidget("SurfaceBoundAgentsList"));
|
|
351
345
|
const ProposedDirectivesZone = slotComponent("ProposedDirectivesZone", unregisteredWidget("ProposedDirectivesZone"));
|
|
@@ -417,12 +411,10 @@ const adoptCloudBrowserRunFromStream = slotFn("adoptCloudBrowserRunFromStream");
|
|
|
417
411
|
const dispatchWarRoomTool = slotFn("dispatchWarRoomTool");
|
|
418
412
|
const dispatchWarRoomMasterTool = slotFn("dispatchWarRoomMasterTool");
|
|
419
413
|
const resolveGmailSendConnection = slotFn("resolveGmailSendConnection");
|
|
420
|
-
const voiceDisplayName = slotFn("voiceDisplayName", (_set, id) => id);
|
|
421
414
|
const canvasGetVersionHistory = slotFn("canvasGetVersionHistory");
|
|
422
415
|
const canvasGetById = slotFn("canvasGetById");
|
|
423
416
|
const createSandboxFilesystemAdapter = slotFn("createSandboxFilesystemAdapter");
|
|
424
417
|
const notesGetById = slotFn("notesGetById");
|
|
425
|
-
const isLiveConversationVoice = slotFn("isLiveConversationVoice", () => false);
|
|
426
418
|
const createHtmlPage = slotFn("createHtmlPage");
|
|
427
419
|
const convertMarkdownToHtml = slotFn("convertMarkdownToHtml");
|
|
428
420
|
const sklActions = new Proxy({}, { get: (_t, prop) => (...args) => {
|
|
@@ -668,7 +660,6 @@ export {
|
|
|
668
660
|
humanLines,
|
|
669
661
|
invalidateCanvasItemCache,
|
|
670
662
|
invalidateMandateCatalogueCache,
|
|
671
|
-
isLiveConversationVoice,
|
|
672
663
|
isSkillStreamEvent,
|
|
673
664
|
isUuidValue,
|
|
674
665
|
isWarRoomThreadAgentSurface,
|
|
@@ -771,6 +762,5 @@ export {
|
|
|
771
762
|
useSkills,
|
|
772
763
|
useTablesEverywhere,
|
|
773
764
|
useTextareaFormatting,
|
|
774
|
-
useUrlSync
|
|
775
|
-
voiceDisplayName
|
|
765
|
+
useUrlSync
|
|
776
766
|
};
|
|
@@ -29,6 +29,7 @@ import { openOverlay, CHAT_WINDOWS } from "../../host/windows.js";
|
|
|
29
29
|
import { kindTextToMarkdown } from "../../utils/content-ir/surfaces/kind-text-to-markdown.js";
|
|
30
30
|
import { removeCodeSpans, replaceFences } from "@ai-matrx/content-ir/source";
|
|
31
31
|
import { selectOrganizationId, ensureOrganizationContext, isOrganizationSelectionCancelled } from "../../host/org.js";
|
|
32
|
+
import { downloadFile } from "@ai-matrx/kit/download";
|
|
32
33
|
const PENDING_ACTION_KEY = "matrx_pending_post_auth_action";
|
|
33
34
|
const PublicMessageOptionsMenu = ({
|
|
34
35
|
content: answerContent,
|
|
@@ -248,15 +249,7 @@ const PublicMessageOptionsMenu = ({
|
|
|
248
249
|
};
|
|
249
250
|
const handleSaveAsFile = () => {
|
|
250
251
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
251
|
-
|
|
252
|
-
const url = URL.createObjectURL(blob);
|
|
253
|
-
const link = document.createElement("a");
|
|
254
|
-
link.href = url;
|
|
255
|
-
link.download = `ai-response-${timestamp}.md`;
|
|
256
|
-
document.body.appendChild(link);
|
|
257
|
-
link.click();
|
|
258
|
-
document.body.removeChild(link);
|
|
259
|
-
URL.revokeObjectURL(url);
|
|
252
|
+
downloadFile(`ai-response-${timestamp}.md`, content, "text/markdown;charset=utf-8");
|
|
260
253
|
onClose();
|
|
261
254
|
};
|
|
262
255
|
const handleEmailToMe = async () => {
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
+
import { type FilesHost } from "@ai-matrx/media/files/engine";
|
|
1
2
|
import type { ChatState } from "./state.js";
|
|
2
|
-
export
|
|
3
|
+
export interface CreateChatStoreOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The files engine's host (database, server client, optional scope/org/share/notify) for a
|
|
6
|
+
* host that has not called `configureFilesHost` itself. The engine is then wired to THIS
|
|
7
|
+
* store, so file thunks read and write the slice chat renders from.
|
|
8
|
+
*/
|
|
9
|
+
files?: Omit<FilesHost, "store">;
|
|
10
|
+
}
|
|
11
|
+
export declare function createChatStore(preloadedState?: Partial<ChatState>, options?: CreateChatStoreOptions): import("@reduxjs/toolkit").EnhancedStore<{
|
|
3
12
|
voiceAgent: import("../voice-agent/types.js").VoiceAgentState;
|
|
4
13
|
agentDefinition: import("../agents/types/agent-definition.types.js").AgentDefinitionSliceState;
|
|
5
14
|
conversationList: import("../agents/redux/conversation-list/conversation-list.types.js").ConversationListState;
|
|
@@ -4,12 +4,13 @@ import { fork } from "redux-saga/effects";
|
|
|
4
4
|
import {
|
|
5
5
|
cloudFilesMutationToastMiddleware,
|
|
6
6
|
cloudFilesRealtimeMiddleware,
|
|
7
|
-
cloudFilesReducer
|
|
7
|
+
cloudFilesReducer,
|
|
8
|
+
configureFilesHost
|
|
8
9
|
} from "@ai-matrx/media/files/engine";
|
|
9
10
|
import { chatReducers } from "./slices.js";
|
|
10
11
|
import { chatMiddlewares } from "./middlewares.js";
|
|
11
12
|
import { chatSagas } from "./sagas.js";
|
|
12
|
-
function createChatStore(preloadedState) {
|
|
13
|
+
function createChatStore(preloadedState, options = {}) {
|
|
13
14
|
const sagaMiddleware = createSagaMiddleware();
|
|
14
15
|
const store = configureStore({
|
|
15
16
|
// Files are part of chat (P16f): the files engine's slice and its two middlewares come
|
|
@@ -30,6 +31,7 @@ function createChatStore(preloadedState) {
|
|
|
30
31
|
),
|
|
31
32
|
devTools: process.env.NODE_ENV !== "production"
|
|
32
33
|
});
|
|
34
|
+
if (options.files) configureFilesHost({ ...options.files, store: () => store });
|
|
33
35
|
sagaMiddleware.run(function* chatRootSaga() {
|
|
34
36
|
for (const saga of chatSagas()) yield fork(saga);
|
|
35
37
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LiveValuesStore } from "@ai-matrx/alchemy/surface";
|
|
1
2
|
import { type ReactNode } from "react";
|
|
2
3
|
import type { SurfaceScopePayload } from "../types.js";
|
|
3
4
|
import type { InstanceContextEntry } from "../../agents/types/instance.types.js";
|
|
@@ -198,6 +199,15 @@ export interface SurfaceRegistry {
|
|
|
198
199
|
/** Called after every registration change. */
|
|
199
200
|
subscribe: (listener: () => void) => () => void;
|
|
200
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* THE PAGE'S LIVE VALUES REACH THE AGENT (ALC-18). A value the surface DECLARES that its runtime
|
|
204
|
+
* and contributions left unset is answered from the page's live capture (`@ai-matrx/alchemy/surface`
|
|
205
|
+
* `liveValues`: identity, selection, sections), only while the publishing page IS this surface —
|
|
206
|
+
* the same rule as `createSurfacePort({ live })`'s `getValue`. A supplied value always wins, and an
|
|
207
|
+
* undeclared live name (the request log, a section nobody declared) never rides along. Every reader
|
|
208
|
+
* of `getScope()` — the send path's context entries, the context window, write-back — gets it.
|
|
209
|
+
*/
|
|
210
|
+
export declare function withLiveValues(surfaceName: string, loaded: SurfaceScopePayload, live?: LiveValuesStore): SurfaceScopePayload;
|
|
201
211
|
/**
|
|
202
212
|
* The hosts `surfaceName` was mounted inside earlier in this page session
|
|
203
213
|
* that are STILL mounted, deepest first. Empty when `surfaceName` itself is
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { announceUndeclaredLoadedValues } from "./loaded-value-check.js";
|
|
4
|
+
import { liveValueOf, liveValues } from "@ai-matrx/alchemy/surface";
|
|
5
|
+
import { getManifest } from "./registry.js";
|
|
4
6
|
import {
|
|
5
7
|
createContext,
|
|
6
8
|
useCallback,
|
|
@@ -15,6 +17,25 @@ import {
|
|
|
15
17
|
useAlchemySurfaceHandle
|
|
16
18
|
} from "../../agent-copy/AlchemySurfaceBridge.js";
|
|
17
19
|
let nextId = 0;
|
|
20
|
+
function withLiveValues(surfaceName, loaded, live = liveValues) {
|
|
21
|
+
const declared = getManifest(surfaceName)?.values ?? [];
|
|
22
|
+
const missing = declared.filter((v) => !(v.name in loaded));
|
|
23
|
+
if (missing.length === 0) return loaded;
|
|
24
|
+
let capture;
|
|
25
|
+
try {
|
|
26
|
+
capture = live.readFor(surfaceName);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error(`[surfaces] the live values of "${surfaceName}" could not be read`, err);
|
|
29
|
+
return loaded;
|
|
30
|
+
}
|
|
31
|
+
if (!capture) return loaded;
|
|
32
|
+
const filled = { ...loaded };
|
|
33
|
+
for (const v of missing) {
|
|
34
|
+
const value = liveValueOf(capture, v.name);
|
|
35
|
+
if (value !== void 0) filled[v.name] = value;
|
|
36
|
+
}
|
|
37
|
+
return filled;
|
|
38
|
+
}
|
|
18
39
|
function createRegistry(kind, onRegister) {
|
|
19
40
|
let stack = [];
|
|
20
41
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -70,7 +91,7 @@ function createRegistry(kind, onRegister) {
|
|
|
70
91
|
}
|
|
71
92
|
const loaded = { ...own, ...contributed };
|
|
72
93
|
announceUndeclaredLoadedValues(surfaceName, loaded);
|
|
73
|
-
return loaded;
|
|
94
|
+
return withLiveValues(surfaceName, loaded);
|
|
74
95
|
};
|
|
75
96
|
return () => {
|
|
76
97
|
const own = getScope();
|
|
@@ -531,5 +552,6 @@ export {
|
|
|
531
552
|
waitForCapturedRuntime,
|
|
532
553
|
wasPageOwnConversationOf,
|
|
533
554
|
wasSurfaceMountedThisSession,
|
|
555
|
+
withLiveValues,
|
|
534
556
|
withScopeContributions
|
|
535
557
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../../host/notify.js";
|
|
3
5
|
import { resultToHuman } from "../utils/human-copy.js";
|
|
4
6
|
import React, { useState } from "react";
|
|
5
7
|
import { useToolConversationId } from "./ToolConversationContext.js";
|
|
@@ -33,18 +35,12 @@ const CopyButton = ({
|
|
|
33
35
|
text,
|
|
34
36
|
className
|
|
35
37
|
}) => {
|
|
38
|
+
const { copyText } = useClipboard({
|
|
39
|
+
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
40
|
+
});
|
|
36
41
|
const [copied, setCopied] = useState(false);
|
|
37
42
|
const handleCopy = async () => {
|
|
38
|
-
|
|
39
|
-
await navigator.clipboard.writeText(text);
|
|
40
|
-
} catch {
|
|
41
|
-
const textarea = document.createElement("textarea");
|
|
42
|
-
textarea.value = text;
|
|
43
|
-
document.body.appendChild(textarea);
|
|
44
|
-
textarea.select();
|
|
45
|
-
document.execCommand("copy");
|
|
46
|
-
document.body.removeChild(textarea);
|
|
47
|
-
}
|
|
43
|
+
await copyText(text);
|
|
48
44
|
setCopied(true);
|
|
49
45
|
setTimeout(() => setCopied(false), 2e3);
|
|
50
46
|
};
|
|
@@ -8,6 +8,7 @@ import { formatFileSize } from "@ai-matrx/kit/format";
|
|
|
8
8
|
import { useFileActions } from "../../host/ui-slots.js";
|
|
9
9
|
import { useMediaResolution } from "@ai-matrx/media/core";
|
|
10
10
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
11
|
+
import { downloadUrl } from "@ai-matrx/kit/download";
|
|
11
12
|
function deriveName(file) {
|
|
12
13
|
if (file.file_name && file.file_name.trim().length > 0) return file.file_name.trim();
|
|
13
14
|
const src = file.download_url ?? file.url;
|
|
@@ -33,14 +34,7 @@ function deriveTypeLabel(file, name) {
|
|
|
33
34
|
return null;
|
|
34
35
|
}
|
|
35
36
|
function triggerAnchorDownload(href, name) {
|
|
36
|
-
|
|
37
|
-
a.href = href;
|
|
38
|
-
a.download = name;
|
|
39
|
-
a.target = "_blank";
|
|
40
|
-
a.rel = "noopener noreferrer";
|
|
41
|
-
document.body.appendChild(a);
|
|
42
|
-
a.click();
|
|
43
|
-
a.remove();
|
|
37
|
+
downloadUrl(href, name, { newTab: true });
|
|
44
38
|
}
|
|
45
39
|
const ResultFile = ({ file, density = "inline", className }) => {
|
|
46
40
|
const [busy, setBusy] = useState(false);
|
|
@@ -2,31 +2,9 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import { Check, Copy } from "lucide-react";
|
|
5
|
+
import { copyText } from "@ai-matrx/kit/clipboard";
|
|
5
6
|
import { cn } from "@ai-matrx/design-system";
|
|
6
7
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
7
|
-
async function copyText(text) {
|
|
8
|
-
try {
|
|
9
|
-
if (navigator.clipboard?.writeText) {
|
|
10
|
-
await navigator.clipboard.writeText(text);
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
} catch {
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
const ta = document.createElement("textarea");
|
|
17
|
-
ta.value = text;
|
|
18
|
-
ta.style.position = "fixed";
|
|
19
|
-
ta.style.opacity = "0";
|
|
20
|
-
ta.style.pointerEvents = "none";
|
|
21
|
-
document.body.appendChild(ta);
|
|
22
|
-
ta.select();
|
|
23
|
-
const ok = document.execCommand("copy");
|
|
24
|
-
document.body.removeChild(ta);
|
|
25
|
-
return ok;
|
|
26
|
-
} catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
8
|
const ShortId = ({
|
|
31
9
|
value,
|
|
32
10
|
variant = "compact",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { SettingDoor } from "../../host/prefs-react.js";
|
|
4
4
|
import { useAppSelector } from "../../store/hooks.js";
|
|
5
|
-
import { voiceDisplayName } from "
|
|
5
|
+
import { voiceDisplayName } from "@ai-matrx/media/voices";
|
|
6
6
|
function LiveVoiceDoor({
|
|
7
7
|
voiceId,
|
|
8
8
|
agentId
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
useRealtimeSessionConfig
|
|
13
13
|
} from "../realtimeSession.js";
|
|
14
14
|
import { useSessionKnob } from "../../host/prefs-react.js";
|
|
15
|
-
import { isLiveConversationVoice } from "
|
|
15
|
+
import { isLiveConversationVoice } from "@ai-matrx/media/voices";
|
|
16
16
|
const LIVE_CONVERSATION_VOICE_KNOB = "media.conversation.voice";
|
|
17
17
|
function isVoiceId(v) {
|
|
18
18
|
return isLiveConversationVoice(v);
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObservationalMemoryWindow
|
|
3
|
-
*
|
|
4
|
-
* Admin-only floating inspector for the Observational Memory feature.
|
|
5
|
-
* Wraps `ObservationalMemoryCore` in the standard WindowPanel shell with a
|
|
6
|
-
* sidebar listing every conversation the session has seen memory activity
|
|
7
|
-
* for — that means either:
|
|
8
|
-
* - the conversation has a persisted `observational_memory` metadata block
|
|
9
|
-
* (hydrated on conversation load), OR
|
|
10
|
-
* - we have received at least one `memory_*` stream event for it this
|
|
11
|
-
* session, OR
|
|
12
|
-
* - an authoritative cost summary has been fetched for it.
|
|
13
|
-
*
|
|
14
|
-
* See `/window-panel-authoring` for the registration contract. This window is
|
|
15
|
-
* persisted (NOT ephemeral) — admins get their last-selected conversation
|
|
16
|
-
* back after a reload, matching the per-conversation nature of the feature.
|
|
17
|
-
*/
|
|
18
1
|
import React from "react";
|
|
19
2
|
interface ObservationalMemoryWindowProps {
|
|
20
3
|
isOpen: boolean;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useClipboard } from "@ai-matrx/kit/clipboard";
|
|
4
|
+
import { toast } from "../../../host/notify.js";
|
|
3
5
|
import { useCallback, useMemo, useState } from "react";
|
|
4
6
|
import {
|
|
5
7
|
Brain,
|
|
@@ -36,9 +38,12 @@ import {
|
|
|
36
38
|
import { CHAT_WINDOWS } from "../../../host/windows.js";
|
|
37
39
|
import { Button } from "@ai-matrx/design-system/controls";
|
|
38
40
|
function useCopyText(text) {
|
|
41
|
+
const { copyText } = useClipboard({
|
|
42
|
+
notify: (message, kind) => kind === "error" ? toast.error(message) : toast.success(message)
|
|
43
|
+
});
|
|
39
44
|
const [copied, setCopied] = useState(false);
|
|
40
45
|
const copy = useCallback(() => {
|
|
41
|
-
|
|
46
|
+
copyText(text).then(() => {
|
|
42
47
|
setCopied(true);
|
|
43
48
|
setTimeout(() => setCopied(false), 1500);
|
|
44
49
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/chat",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "THE one AI Matrx person-to-agent chat system: the full chat (running agents, conversations, tool calls, streaming, context, surfaces, voice) as per-module subpaths @ai-matrx/chat/<domain>/<module> over a host contract (/host, /host/react) whose only required value is a db; plus the kernel compatibility entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) until P28.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -208,17 +208,17 @@
|
|
|
208
208
|
"@ai-matrx/canvas": "latest",
|
|
209
209
|
"@ai-matrx/content-ir": "latest",
|
|
210
210
|
"@ai-matrx/data": "latest",
|
|
211
|
-
"@ai-matrx/content-ir-react": "latest",
|
|
212
211
|
"@ai-matrx/design-system": "latest",
|
|
213
212
|
"@ai-matrx/diff": "latest",
|
|
214
213
|
"@ai-matrx/icons": "latest",
|
|
214
|
+
"@ai-matrx/content-ir-react": "latest",
|
|
215
215
|
"@ai-matrx/media": "latest",
|
|
216
|
-
"@ai-matrx/
|
|
216
|
+
"@ai-matrx/print": "latest",
|
|
217
217
|
"@ai-matrx/realtime": "latest",
|
|
218
218
|
"@ai-matrx/records": "latest",
|
|
219
|
-
"@ai-matrx/print": "latest",
|
|
220
219
|
"@ai-matrx/records-ui": "latest",
|
|
221
|
-
"@ai-matrx/tap-target": "latest"
|
|
220
|
+
"@ai-matrx/tap-target": "latest",
|
|
221
|
+
"@ai-matrx/kit": "latest"
|
|
222
222
|
},
|
|
223
223
|
"devDependencies": {
|
|
224
224
|
"@arethetypeswrong/cli": "^0.18.5",
|