@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
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configureFilesHost,
|
|
3
|
+
isFilesHostConfigured
|
|
4
|
+
} from "@ai-matrx/media/files/engine/host/configure";
|
|
5
|
+
import { getStoreSingleton } from "../../store/store-singleton.js";
|
|
6
|
+
const DEFAULT_FILES_SERVICE_URL = "https://files.matrxserver.com";
|
|
7
|
+
const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
8
|
+
const FILES_SERVICE_ROUTES = [
|
|
9
|
+
[/^\/files\/upload\/?$/i, /* @__PURE__ */ new Set(["POST"])],
|
|
10
|
+
[/^\/files\/bulk\/?$/i, /* @__PURE__ */ new Set(["POST"])],
|
|
11
|
+
[/^\/files\/sync\/(?:changes|folders)\/?$/i, /* @__PURE__ */ new Set(["GET"])],
|
|
12
|
+
[new RegExp(`^/files/${UUID}/?$`, "i"), /* @__PURE__ */ new Set(["GET", "PATCH", "DELETE"])],
|
|
13
|
+
[new RegExp(`^/files/${UUID}/download/?$`, "i"), /* @__PURE__ */ new Set(["GET"])],
|
|
14
|
+
[new RegExp(`^/files/${UUID}/pdf-pages/?$`, "i"), /* @__PURE__ */ new Set(["POST"])],
|
|
15
|
+
[new RegExp(`^/files/${UUID}/restore/?$`, "i"), /* @__PURE__ */ new Set(["POST"])],
|
|
16
|
+
[new RegExp(`^/files/${UUID}/asset/?$`, "i"), /* @__PURE__ */ new Set(["GET"])],
|
|
17
|
+
[new RegExp(`^/files/${UUID}/share-links/?$`, "i"), /* @__PURE__ */ new Set(["GET", "POST"])],
|
|
18
|
+
[/^\/assets\/?$/i, /* @__PURE__ */ new Set(["POST"])],
|
|
19
|
+
[/^\/assets\/presets\/?$/i, /* @__PURE__ */ new Set(["GET"])],
|
|
20
|
+
[/^\/assets\/(?:preview|preview\/multipart|pdf-compress|pdf-compress\/multipart)\/?$/i, /* @__PURE__ */ new Set(["POST"])],
|
|
21
|
+
[new RegExp(`^/assets/${UUID}/?$`, "i"), /* @__PURE__ */ new Set(["GET"])],
|
|
22
|
+
[/^\/share\/[^/]+(?:\/download)?\/?$/i, /* @__PURE__ */ new Set(["GET"])]
|
|
23
|
+
];
|
|
24
|
+
function isFilesServiceRoute(path, method = "GET") {
|
|
25
|
+
const pathname = path.split(/[?#]/, 1)[0];
|
|
26
|
+
return FILES_SERVICE_ROUTES.some(([pattern, methods]) => pattern.test(pathname) && methods.has(method.toUpperCase()));
|
|
27
|
+
}
|
|
28
|
+
class FilesRequestError extends Error {
|
|
29
|
+
status;
|
|
30
|
+
code;
|
|
31
|
+
userMessage;
|
|
32
|
+
constructor(status, message, code = "internal") {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "FilesRequestError";
|
|
35
|
+
this.status = status;
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.userMessage = message;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const trim = (url) => url.replace(/\/+$/, "");
|
|
41
|
+
function createDefaultFilesServer(host) {
|
|
42
|
+
const filesBase = (override) => trim(override ?? DEFAULT_FILES_SERVICE_URL);
|
|
43
|
+
const serverBase = (override) => trim(override ?? host().server.baseUrl());
|
|
44
|
+
const baseFor = (path, override, method) => override ? trim(override) : isFilesServiceRoute(path, method) ? filesBase() : serverBase();
|
|
45
|
+
async function buildHeaders(opts, includeContentType) {
|
|
46
|
+
const requestId = opts.requestId ?? (globalThis.crypto?.randomUUID?.() ?? `${Date.now()}`);
|
|
47
|
+
const headers = { ...await host().server.headers?.() ?? {}, "X-Request-Id": requestId };
|
|
48
|
+
if (opts.organizationId) headers["X-Organization-Id"] = opts.organizationId;
|
|
49
|
+
if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
50
|
+
if (includeContentType) headers["Content-Type"] = "application/json";
|
|
51
|
+
return { headers, requestId };
|
|
52
|
+
}
|
|
53
|
+
const meta = (status, requestId, serverRequestId) => ({ requestId, status, serverRequestId });
|
|
54
|
+
async function failure(response) {
|
|
55
|
+
let message = `HTTP ${response.status}`;
|
|
56
|
+
let code = response.status >= 500 ? "internal" : "invalid_request";
|
|
57
|
+
try {
|
|
58
|
+
const body = await response.json();
|
|
59
|
+
const said = body.user_message ?? body.message ?? body.detail;
|
|
60
|
+
if (typeof said === "string" && said) message = said;
|
|
61
|
+
if (typeof body.error === "string") code = body.error;
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
return new FilesRequestError(response.status, message, code);
|
|
65
|
+
}
|
|
66
|
+
async function send(method, path, opts = {}, body, form) {
|
|
67
|
+
const { headers, requestId } = await buildHeaders(opts, body !== void 0 && !form);
|
|
68
|
+
const response = await fetch(`${baseFor(path, opts.baseUrlOverride, method)}${path}`, {
|
|
69
|
+
method,
|
|
70
|
+
headers,
|
|
71
|
+
body: form ?? (body === void 0 ? void 0 : JSON.stringify(body)),
|
|
72
|
+
signal: opts.signal
|
|
73
|
+
});
|
|
74
|
+
if (!response.ok) throw await failure(response);
|
|
75
|
+
const text = await response.text();
|
|
76
|
+
const data = text ? JSON.parse(text) : null;
|
|
77
|
+
return { data, meta: meta(response.status, requestId, response.headers.get("x-request-id")) };
|
|
78
|
+
}
|
|
79
|
+
async function download(path, opts = {}) {
|
|
80
|
+
const { headers, requestId } = await buildHeaders(opts, false);
|
|
81
|
+
const response = await fetch(`${baseFor(path, opts.baseUrlOverride, "GET")}${path}`, { headers, signal: opts.signal });
|
|
82
|
+
if (!response.ok) throw await failure(response);
|
|
83
|
+
const disposition = response.headers.get("content-disposition");
|
|
84
|
+
const filename = disposition?.match(/filename\*?=(?:UTF-8'')?"?([^";]+)"?/i)?.[1] ?? null;
|
|
85
|
+
return { blob: await response.blob(), meta: meta(response.status, requestId, response.headers.get("x-request-id")), filename: filename ? decodeURIComponent(filename) : null };
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
resolveFilesBaseUrl: filesBase,
|
|
89
|
+
resolveBaseUrlForPath: baseFor,
|
|
90
|
+
buildHeaders: (opts, includeContentType) => buildHeaders(opts, includeContentType),
|
|
91
|
+
getJson: (path, opts) => send("GET", path, opts),
|
|
92
|
+
postJson: (path, body, opts) => send("POST", path, opts, body),
|
|
93
|
+
patchJson: (path, body, opts) => send("PATCH", path, opts, body),
|
|
94
|
+
putJson: (path, body, opts) => send("PUT", path, opts, body),
|
|
95
|
+
del: (path, opts) => send("DELETE", path, opts),
|
|
96
|
+
delJson: (path, body, opts) => send("DELETE", path, opts, body),
|
|
97
|
+
postMultipart: (path, form, opts) => send("POST", path, opts, void 0, form),
|
|
98
|
+
uploadWithProgress: (path, form, onProgress, opts = {}) => new Promise((resolve, reject) => {
|
|
99
|
+
void buildHeaders(opts, false).then(({ headers, requestId }) => {
|
|
100
|
+
const xhr = new XMLHttpRequest();
|
|
101
|
+
xhr.open("POST", `${baseFor(path, opts.baseUrlOverride, "POST")}${path}`);
|
|
102
|
+
for (const [key, value] of Object.entries(headers)) xhr.setRequestHeader(key, value);
|
|
103
|
+
xhr.upload.addEventListener("progress", (event) => onProgress({ loaded: event.loaded, total: event.total }));
|
|
104
|
+
xhr.addEventListener("load", () => {
|
|
105
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
106
|
+
try {
|
|
107
|
+
resolve({ data: xhr.responseText ? JSON.parse(xhr.responseText) : null, meta: meta(xhr.status, requestId, xhr.getResponseHeader("x-request-id")) });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
reject(error);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
let message = `Upload failed (${xhr.status}).`;
|
|
113
|
+
try {
|
|
114
|
+
const body = JSON.parse(xhr.responseText);
|
|
115
|
+
message = body.user_message ?? body.message ?? body.detail ?? message;
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
reject(new FilesRequestError(xhr.status, message));
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
xhr.addEventListener("error", () => reject(new FilesRequestError(null, "Upload failed \u2014 check your connection.")));
|
|
122
|
+
xhr.addEventListener("abort", () => reject(new FilesRequestError(null, "Upload cancelled.", "cancelled")));
|
|
123
|
+
if (opts.signal) {
|
|
124
|
+
if (opts.signal.aborted) xhr.abort();
|
|
125
|
+
else opts.signal.addEventListener("abort", () => xhr.abort(), { once: true });
|
|
126
|
+
}
|
|
127
|
+
xhr.send(form);
|
|
128
|
+
}, reject);
|
|
129
|
+
}),
|
|
130
|
+
downloadBlob: download,
|
|
131
|
+
downloadBlobWithProgress: async (path, onProgress, opts) => {
|
|
132
|
+
const result = await download(path, opts);
|
|
133
|
+
onProgress({ loaded: result.blob.size, total: result.blob.size });
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const DEFAULT_SHARE_LINK_ORIGIN = "https://aimatrx.com";
|
|
139
|
+
function createDefaultShareLinks(host) {
|
|
140
|
+
const rpc = (fn, args) => host().db.rpc(fn, args);
|
|
141
|
+
const shareLinkUrl = (token) => `${DEFAULT_SHARE_LINK_ORIGIN}/s/${token}`;
|
|
142
|
+
return {
|
|
143
|
+
shareLinkUrl,
|
|
144
|
+
async createShareLink(options) {
|
|
145
|
+
const { data, error } = await rpc("create_share_link", {
|
|
146
|
+
p_resource_type: options.resourceType,
|
|
147
|
+
p_resource_id: options.resourceId,
|
|
148
|
+
p_permission_level: options.permissionLevel ?? "viewer",
|
|
149
|
+
p_expires_at: options.expiresAt ?? void 0,
|
|
150
|
+
p_max_uses: options.maxUses ?? void 0,
|
|
151
|
+
p_label: options.label ?? void 0
|
|
152
|
+
});
|
|
153
|
+
if (error) return { success: false, error: error.message ?? "Failed to create link" };
|
|
154
|
+
const res = data;
|
|
155
|
+
if (!res?.success || !res.token) return { success: false, error: res?.error ?? "Failed to create link" };
|
|
156
|
+
return { success: true, token: res.token, shortToken: res.short_token ?? null, url: shareLinkUrl(res.token) };
|
|
157
|
+
},
|
|
158
|
+
async listShareLinks(resourceType, resourceId) {
|
|
159
|
+
const { data, error } = await rpc("list_share_links", { p_resource_type: resourceType, p_resource_id: resourceId });
|
|
160
|
+
if (error || !Array.isArray(data)) throw new Error(`Could not load this item's share links${error?.message ? `: ${error.message}` : "."}`);
|
|
161
|
+
return data.map((r) => ({
|
|
162
|
+
id: r.id,
|
|
163
|
+
token: r.token,
|
|
164
|
+
shortToken: r.short_token ?? null,
|
|
165
|
+
permissionLevel: r.permission_level,
|
|
166
|
+
label: r.label ?? null,
|
|
167
|
+
expiresAt: r.expires_at ?? null,
|
|
168
|
+
maxUses: r.max_uses ?? null,
|
|
169
|
+
useCount: r.use_count ?? 0,
|
|
170
|
+
isActive: r.is_active ?? false,
|
|
171
|
+
createdAt: r.created_at ?? null,
|
|
172
|
+
lastUsedAt: r.last_used_at ?? null
|
|
173
|
+
}));
|
|
174
|
+
},
|
|
175
|
+
async revokeShareLink(linkId) {
|
|
176
|
+
const { data, error } = await rpc("revoke_share_link", { p_link_id: linkId });
|
|
177
|
+
if (error) return { success: false, error: error.message };
|
|
178
|
+
const res = data;
|
|
179
|
+
return { success: !!res?.success, error: res?.error };
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const UPLOAD_REASON = "upload this file";
|
|
184
|
+
function configureBareFilesHost(host) {
|
|
185
|
+
if (isFilesHostConfigured()) return;
|
|
186
|
+
configureFilesHost({
|
|
187
|
+
db: host.db,
|
|
188
|
+
server: createDefaultFilesServer(() => host),
|
|
189
|
+
store: () => getStoreSingleton(),
|
|
190
|
+
scope: () => ({
|
|
191
|
+
userId: host.identity.current().userId ?? null,
|
|
192
|
+
organizationId: host.org.active()?.id ?? null,
|
|
193
|
+
projectId: null,
|
|
194
|
+
taskId: null
|
|
195
|
+
}),
|
|
196
|
+
shareLinks: createDefaultShareLinks(() => host),
|
|
197
|
+
ensureOrganizationContext: async ({ organizationId }) => organizationId ?? await host.org.require(UPLOAD_REASON),
|
|
198
|
+
notify: {
|
|
199
|
+
success: (message) => host.notify.success(message),
|
|
200
|
+
error: (message) => host.notify.error(message)
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
export {
|
|
205
|
+
DEFAULT_FILES_SERVICE_URL,
|
|
206
|
+
DEFAULT_SHARE_LINK_ORIGIN,
|
|
207
|
+
FilesRequestError,
|
|
208
|
+
configureBareFilesHost,
|
|
209
|
+
createDefaultFilesServer,
|
|
210
|
+
createDefaultShareLinks,
|
|
211
|
+
isFilesServiceRoute
|
|
212
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatNavigationPort } from "../contract.js";
|
|
2
|
+
export interface MemoryNavigationOptions {
|
|
3
|
+
initial?: string | null;
|
|
4
|
+
home?: string;
|
|
5
|
+
persist?: (address: string) => void;
|
|
6
|
+
openExternal?: (href: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/** A memory port plus the address it holds, for hosts that read or restore it. */
|
|
9
|
+
export interface MemoryNavigationPort extends ChatNavigationPort {
|
|
10
|
+
address(): string;
|
|
11
|
+
}
|
|
12
|
+
export declare function createMemoryNavigation(options?: MemoryNavigationOptions): MemoryNavigationPort;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
4
|
+
const LOCAL_ORIGIN = "https://chat.memory.local";
|
|
5
|
+
function defaultOpenExternal(href) {
|
|
6
|
+
if (typeof window !== "undefined") window.open(href, "_blank", "noopener,noreferrer");
|
|
7
|
+
}
|
|
8
|
+
function createMemoryNavigation(options = {}) {
|
|
9
|
+
const home = options.home ?? "/chat";
|
|
10
|
+
const openExternal = options.openExternal ?? defaultOpenExternal;
|
|
11
|
+
let address = options.initial && options.initial.startsWith("/") ? options.initial : home;
|
|
12
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
13
|
+
const history = [];
|
|
14
|
+
const ahead = [];
|
|
15
|
+
const set = (next) => {
|
|
16
|
+
if (next === address) return;
|
|
17
|
+
address = next;
|
|
18
|
+
for (const listener of listeners) listener();
|
|
19
|
+
options.persist?.(next);
|
|
20
|
+
};
|
|
21
|
+
const toAddress = (href) => {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(href, LOCAL_ORIGIN + address);
|
|
24
|
+
return url.origin === LOCAL_ORIGIN ? url.pathname + url.search : null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const go = (href, mode) => {
|
|
30
|
+
const next = toAddress(href);
|
|
31
|
+
if (next === null) {
|
|
32
|
+
openExternal(href);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (mode === "push" && next !== address) {
|
|
36
|
+
history.push(address);
|
|
37
|
+
ahead.length = 0;
|
|
38
|
+
}
|
|
39
|
+
set(next);
|
|
40
|
+
};
|
|
41
|
+
const router = {
|
|
42
|
+
push: (href) => go(href, "push"),
|
|
43
|
+
replace: (href) => go(href, "replace"),
|
|
44
|
+
back: () => {
|
|
45
|
+
ahead.push(address);
|
|
46
|
+
set(history.pop() ?? home);
|
|
47
|
+
},
|
|
48
|
+
forward: () => {
|
|
49
|
+
const next = ahead.pop();
|
|
50
|
+
if (next === void 0) return;
|
|
51
|
+
history.push(address);
|
|
52
|
+
set(next);
|
|
53
|
+
},
|
|
54
|
+
refresh: () => void 0,
|
|
55
|
+
prefetch: () => void 0
|
|
56
|
+
};
|
|
57
|
+
const subscribe = (listener) => {
|
|
58
|
+
listeners.add(listener);
|
|
59
|
+
return () => {
|
|
60
|
+
listeners.delete(listener);
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
const useAddress = () => useSyncExternalStore(subscribe, () => address, () => address);
|
|
64
|
+
let cached = null;
|
|
65
|
+
const paramsFor = (current) => {
|
|
66
|
+
if (cached?.address !== current) {
|
|
67
|
+
cached = { address: current, params: new URLSearchParams(current.split("?")[1] ?? "") };
|
|
68
|
+
}
|
|
69
|
+
return cached.params;
|
|
70
|
+
};
|
|
71
|
+
function MemoryLink({ href, children, onClick, prefetch, replace, scroll, ...anchor }) {
|
|
72
|
+
void prefetch;
|
|
73
|
+
void scroll;
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
"a",
|
|
76
|
+
{
|
|
77
|
+
...anchor,
|
|
78
|
+
href,
|
|
79
|
+
onClick: (event) => {
|
|
80
|
+
onClick?.(event);
|
|
81
|
+
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey) return;
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
go(href, replace ? "replace" : "push");
|
|
84
|
+
},
|
|
85
|
+
children
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
address: () => address,
|
|
91
|
+
push: router.push,
|
|
92
|
+
replace: router.replace,
|
|
93
|
+
back: router.back,
|
|
94
|
+
useRouter: () => router,
|
|
95
|
+
usePathname: () => useAddress().split("?")[0] || home,
|
|
96
|
+
useSearchParams: () => paramsFor(useAddress()),
|
|
97
|
+
Link: MemoryLink
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
createMemoryNavigation
|
|
102
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Resource } from "../../agents/resources/types.js";
|
|
2
|
+
import type { ResourcePickerViewId } from "../../agents/resources/picker-view-id.js";
|
|
3
|
+
export interface DefaultResourcePickerMenuProps {
|
|
4
|
+
conversationId: string;
|
|
5
|
+
initialView?: ResourcePickerViewId;
|
|
6
|
+
initialUploadFiles?: readonly File[];
|
|
7
|
+
onResourceSelected?: (resource: Resource) => unknown;
|
|
8
|
+
onExitInitialView?: () => void;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
[extra: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/** The views this default draws; every other view is the host picker's. */
|
|
13
|
+
export declare const DEFAULT_PICKER_VIEWS: ReadonlySet<ResourcePickerViewId>;
|
|
14
|
+
export declare function DefaultResourcePickerMenu({ conversationId, initialView, initialUploadFiles, onResourceSelected, onExitInitialView, onClose, }: DefaultResourcePickerMenuProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { Button, Input } from "@ai-matrx/design-system";
|
|
5
|
+
import { useUploadAgentResources } from "../../agents/components/inputs/resources/usePasteImageResource.js";
|
|
6
|
+
const DEFAULT_PICKER_VIEWS = /* @__PURE__ */ new Set(["files", "webpage"]);
|
|
7
|
+
function FilesView({ conversationId, initialUploadFiles, done }) {
|
|
8
|
+
const upload = useUploadAgentResources(conversationId);
|
|
9
|
+
const input = useRef(null);
|
|
10
|
+
const [dragging, setDragging] = useState(false);
|
|
11
|
+
const started = useRef(false);
|
|
12
|
+
const send = (files) => {
|
|
13
|
+
if (files.length === 0) return;
|
|
14
|
+
void upload(files);
|
|
15
|
+
done();
|
|
16
|
+
};
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (started.current || !initialUploadFiles?.length) return;
|
|
19
|
+
started.current = true;
|
|
20
|
+
send([...initialUploadFiles]);
|
|
21
|
+
});
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
"div",
|
|
24
|
+
{
|
|
25
|
+
className: `m-2 flex flex-col items-center gap-2 rounded-md border border-dashed p-4 text-sm ${dragging ? "border-primary bg-primary/5" : "border-border"}`,
|
|
26
|
+
"data-chat-default-picker": "files",
|
|
27
|
+
onDragOver: (event) => {
|
|
28
|
+
event.preventDefault();
|
|
29
|
+
setDragging(true);
|
|
30
|
+
},
|
|
31
|
+
onDragLeave: () => setDragging(false),
|
|
32
|
+
onDrop: (event) => {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
setDragging(false);
|
|
35
|
+
send(Array.from(event.dataTransfer.files));
|
|
36
|
+
},
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Drop files here" }),
|
|
39
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: () => input.current?.click(), children: "Choose files" }),
|
|
40
|
+
/* @__PURE__ */ jsx(
|
|
41
|
+
"input",
|
|
42
|
+
{
|
|
43
|
+
ref: input,
|
|
44
|
+
type: "file",
|
|
45
|
+
multiple: true,
|
|
46
|
+
hidden: true,
|
|
47
|
+
onChange: (event) => {
|
|
48
|
+
send(Array.from(event.target.files ?? []));
|
|
49
|
+
event.target.value = "";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function normalizeUrl(raw) {
|
|
58
|
+
const text = raw.trim();
|
|
59
|
+
if (!text) return null;
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(text) ? text : `https://${text}`);
|
|
62
|
+
return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function LinkView({ select }) {
|
|
68
|
+
const [text, setText] = useState("");
|
|
69
|
+
const url = normalizeUrl(text);
|
|
70
|
+
return /* @__PURE__ */ jsxs(
|
|
71
|
+
"form",
|
|
72
|
+
{
|
|
73
|
+
className: "flex items-center gap-2 p-2",
|
|
74
|
+
"data-chat-default-picker": "webpage",
|
|
75
|
+
onSubmit: (event) => {
|
|
76
|
+
event.preventDefault();
|
|
77
|
+
if (url) select(url);
|
|
78
|
+
},
|
|
79
|
+
children: [
|
|
80
|
+
/* @__PURE__ */ jsx(Input, { autoFocus: true, className: "h-8 text-sm", placeholder: "https://", value: text, onChange: (event) => setText(event.target.value) }),
|
|
81
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", disabled: !url, children: "Attach" })
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
function DefaultResourcePickerMenu({
|
|
87
|
+
conversationId,
|
|
88
|
+
initialView,
|
|
89
|
+
initialUploadFiles,
|
|
90
|
+
onResourceSelected,
|
|
91
|
+
onExitInitialView,
|
|
92
|
+
onClose
|
|
93
|
+
}) {
|
|
94
|
+
const done = () => {
|
|
95
|
+
onExitInitialView?.();
|
|
96
|
+
onClose?.();
|
|
97
|
+
};
|
|
98
|
+
if (initialView === "files") {
|
|
99
|
+
return /* @__PURE__ */ jsx(FilesView, { conversationId, initialUploadFiles, done });
|
|
100
|
+
}
|
|
101
|
+
if (initialView === "webpage") {
|
|
102
|
+
return /* @__PURE__ */ jsx(
|
|
103
|
+
LinkView,
|
|
104
|
+
{
|
|
105
|
+
select: (url) => {
|
|
106
|
+
void onResourceSelected?.({ type: "webpage", data: url });
|
|
107
|
+
done();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
DEFAULT_PICKER_VIEWS,
|
|
116
|
+
DefaultResourcePickerMenu
|
|
117
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ControlDefinition } from "../../agents/redux/agent-settings/types.js";
|
|
2
|
+
export interface DefaultSettingControlInputProps {
|
|
3
|
+
settingKey: string;
|
|
4
|
+
control: ControlDefinition;
|
|
5
|
+
value: unknown;
|
|
6
|
+
onChange: (value: unknown) => void;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
id?: string;
|
|
9
|
+
[extra: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export declare function DefaultSettingControlInput({ settingKey, control, value, onChange, disabled, id }: DefaultSettingControlInputProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Input, Switch, Textarea } from "@ai-matrx/design-system";
|
|
5
|
+
const FIELD = "h-8 w-full text-sm";
|
|
6
|
+
function JsonField({ id, value, disabled, onChange }) {
|
|
7
|
+
const text = value === void 0 || value === null ? "" : JSON.stringify(value, null, 2);
|
|
8
|
+
const [draft, setDraft] = useState(text);
|
|
9
|
+
const [invalid, setInvalid] = useState(false);
|
|
10
|
+
useEffect(() => setDraft(text), [text]);
|
|
11
|
+
return /* @__PURE__ */ jsx(
|
|
12
|
+
Textarea,
|
|
13
|
+
{
|
|
14
|
+
id,
|
|
15
|
+
value: draft,
|
|
16
|
+
disabled,
|
|
17
|
+
rows: 3,
|
|
18
|
+
"aria-invalid": invalid || void 0,
|
|
19
|
+
className: "w-full font-mono text-xs",
|
|
20
|
+
onChange: (event) => setDraft(event.target.value),
|
|
21
|
+
onBlur: () => {
|
|
22
|
+
if (draft.trim() === "") return onChange(void 0);
|
|
23
|
+
try {
|
|
24
|
+
onChange(JSON.parse(draft));
|
|
25
|
+
setInvalid(false);
|
|
26
|
+
} catch {
|
|
27
|
+
setInvalid(true);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
function DefaultSettingControlInput({ settingKey, control, value, onChange, disabled, id }) {
|
|
34
|
+
const fieldId = id ?? `setting-${settingKey}`;
|
|
35
|
+
switch (control.type) {
|
|
36
|
+
case "boolean":
|
|
37
|
+
return /* @__PURE__ */ jsx(Switch, { id: fieldId, checked: value === true, disabled, onCheckedChange: (checked) => onChange(checked) });
|
|
38
|
+
case "enum":
|
|
39
|
+
return /* @__PURE__ */ jsxs(
|
|
40
|
+
"select",
|
|
41
|
+
{
|
|
42
|
+
id: fieldId,
|
|
43
|
+
className: `${FIELD} rounded-md border border-input bg-background px-2`,
|
|
44
|
+
value: typeof value === "string" ? value : "",
|
|
45
|
+
disabled,
|
|
46
|
+
onChange: (event) => onChange(event.target.value === "" ? void 0 : event.target.value),
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Default" }),
|
|
49
|
+
(control.enum ?? []).map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option))
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
case "number":
|
|
54
|
+
case "integer":
|
|
55
|
+
return /* @__PURE__ */ jsx(
|
|
56
|
+
Input,
|
|
57
|
+
{
|
|
58
|
+
id: fieldId,
|
|
59
|
+
type: "number",
|
|
60
|
+
className: FIELD,
|
|
61
|
+
value: typeof value === "number" ? String(value) : "",
|
|
62
|
+
min: control.min,
|
|
63
|
+
max: control.max,
|
|
64
|
+
step: control.type === "integer" ? 1 : "any",
|
|
65
|
+
disabled,
|
|
66
|
+
onChange: (event) => {
|
|
67
|
+
const raw = event.target.value;
|
|
68
|
+
if (raw === "") return onChange(void 0);
|
|
69
|
+
const parsed = control.type === "integer" ? Number.parseInt(raw, 10) : Number(raw);
|
|
70
|
+
if (Number.isFinite(parsed)) onChange(parsed);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
case "string":
|
|
75
|
+
return /* @__PURE__ */ jsx(
|
|
76
|
+
Input,
|
|
77
|
+
{
|
|
78
|
+
id: fieldId,
|
|
79
|
+
className: FIELD,
|
|
80
|
+
value: typeof value === "string" ? value : value === void 0 || value === null ? "" : String(value),
|
|
81
|
+
disabled,
|
|
82
|
+
onChange: (event) => onChange(event.target.value === "" ? void 0 : event.target.value)
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
default:
|
|
86
|
+
return /* @__PURE__ */ jsx(JsonField, { id: fieldId, value, disabled, onChange });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
DefaultSettingControlInput
|
|
91
|
+
};
|
package/dist/host/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { createDefaultServer, DEFAULT_CHAT_SERVER_URL, } from "./defaults/server
|
|
|
12
12
|
export { createDomNotifier } from "./defaults/notify.js";
|
|
13
13
|
export { createWebPrefs, createHostKeptPrefs, CHAT_PREFS_PREFIX, type HostKeptPrefs } from "./defaults/prefs.js";
|
|
14
14
|
export { createWindowNavigation } from "./defaults/navigation.js";
|
|
15
|
+
export { createMemoryNavigation, type MemoryNavigationOptions, type MemoryNavigationPort, } from "./defaults/memory-navigation.js";
|
|
15
16
|
export { createUnhostedWindows } from "./defaults/windows.js";
|
|
16
17
|
export { CHAT_WINDOWS, CHAT_WINDOW_IDS, DEFAULT_WINDOW_INSTANCE_ID, isChatWindowId, type ChatWindowId, } from "./windows.js";
|
|
17
18
|
export type { ChatWindowOpeners } from "./window-openers.js";
|
|
@@ -24,4 +25,5 @@ export { createClient, getClaimsUser, schedulerDb, supabase, type ChatClaimsUser
|
|
|
24
25
|
export type { ChatDatabase } from "./db-types.js";
|
|
25
26
|
export { ChatNoDatabaseError, NO_DATABASE_CODE_PREFIX, createNoDatabaseStandIn, isChatNoDatabaseError, isNoDatabase, } from "./no-database.js";
|
|
26
27
|
export * from "./source.js";
|
|
28
|
+
export * from "./conversation-hooks.js";
|
|
27
29
|
export * from "../agents/routing/confidential-routing.js";
|
package/dist/host/index.js
CHANGED
|
@@ -29,6 +29,9 @@ import {
|
|
|
29
29
|
import { createDomNotifier } from "./defaults/notify.js";
|
|
30
30
|
import { createWebPrefs, createHostKeptPrefs, CHAT_PREFS_PREFIX } from "./defaults/prefs.js";
|
|
31
31
|
import { createWindowNavigation } from "./defaults/navigation.js";
|
|
32
|
+
import {
|
|
33
|
+
createMemoryNavigation
|
|
34
|
+
} from "./defaults/memory-navigation.js";
|
|
32
35
|
import { createUnhostedWindows } from "./defaults/windows.js";
|
|
33
36
|
import {
|
|
34
37
|
CHAT_WINDOWS,
|
|
@@ -55,6 +58,7 @@ import {
|
|
|
55
58
|
isNoDatabase
|
|
56
59
|
} from "./no-database.js";
|
|
57
60
|
export * from "./source.js";
|
|
61
|
+
export * from "./conversation-hooks.js";
|
|
58
62
|
export * from "../agents/routing/confidential-routing.js";
|
|
59
63
|
export {
|
|
60
64
|
CHAT_ACTIVE_ORG_STORAGE_KEY,
|
|
@@ -88,6 +92,7 @@ export {
|
|
|
88
92
|
createDomNotifier,
|
|
89
93
|
createHostKeptPrefs,
|
|
90
94
|
createLogClientErrorDiagnostics,
|
|
95
|
+
createMemoryNavigation,
|
|
91
96
|
createNoDatabaseStandIn,
|
|
92
97
|
createNoPickerOrg,
|
|
93
98
|
createStoredOrg,
|
package/dist/host/react.d.ts
CHANGED
|
@@ -31,3 +31,4 @@ export declare function useMaybeChatHost(): ResolvedChatHost | null;
|
|
|
31
31
|
/** The host from the nearest <ChatProvider>. Throws the named, remedied error outside one. */
|
|
32
32
|
export declare function useChatHost(): ResolvedChatHost;
|
|
33
33
|
export { Link, usePathname, useRouter, useSearchParams } from "./navigation.js";
|
|
34
|
+
export { useConversationHeld } from "./conversation-hooks-react.js";
|
package/dist/host/react.js
CHANGED
|
@@ -65,10 +65,12 @@ function useChatHost() {
|
|
|
65
65
|
return host;
|
|
66
66
|
}
|
|
67
67
|
import { Link, usePathname, useRouter, useSearchParams } from "./navigation.js";
|
|
68
|
+
import { useConversationHeld } from "./conversation-hooks-react.js";
|
|
68
69
|
export {
|
|
69
70
|
ChatProvider,
|
|
70
71
|
Link,
|
|
71
72
|
useChatHost,
|
|
73
|
+
useConversationHeld,
|
|
72
74
|
useMaybeChatHost,
|
|
73
75
|
usePathname,
|
|
74
76
|
useRouter,
|
package/dist/host/ui-slots.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ComponentType, type ChangeEvent, type ReactNode } from "react";
|
|
1
|
+
import { Component, type ComponentType, type ChangeEvent, type ReactNode } from "react";
|
|
2
2
|
import type { EditableContextMenuProps, NonEditableContextMenuProps } from "../context-menu/types.js";
|
|
3
3
|
/** The host context menu's props (its registration is typed against them). */
|
|
4
4
|
export type { EditableContextMenuProps, NonEditableContextMenuProps };
|
|
@@ -419,6 +419,28 @@ export interface ChatUiSlots {
|
|
|
419
419
|
export declare function registerChatUi(next: Partial<ChatUiSlots>): void;
|
|
420
420
|
/** Test seam: forget every registration. */
|
|
421
421
|
export declare function resetChatUiForTests(): void;
|
|
422
|
+
/** True when the host registered `name` (a door that only a host implementation can open shows only then). */
|
|
423
|
+
export declare function isHostSlotRegistered(name: keyof ChatUiSlots): boolean;
|
|
424
|
+
/**
|
|
425
|
+
* EVERY SLOT DEGRADES, NEVER CRASHES (P29a finding 1). A host panel that throws while rendering
|
|
426
|
+
* (a hook slot it needs is missing, its own bug) draws nothing in its place; the failure is
|
|
427
|
+
* reported once through the diagnostics port. The page around it keeps working.
|
|
428
|
+
*/
|
|
429
|
+
export declare class SlotBoundary extends Component<{
|
|
430
|
+
name: string;
|
|
431
|
+
children?: ReactNode;
|
|
432
|
+
}, {
|
|
433
|
+
failed: boolean;
|
|
434
|
+
}> {
|
|
435
|
+
state: {
|
|
436
|
+
failed: boolean;
|
|
437
|
+
};
|
|
438
|
+
static getDerivedStateFromError(): {
|
|
439
|
+
failed: boolean;
|
|
440
|
+
};
|
|
441
|
+
componentDidCatch(error: unknown): void;
|
|
442
|
+
render(): ReactNode;
|
|
443
|
+
}
|
|
422
444
|
/**
|
|
423
445
|
* A named slot with the caller's own stand-in (for a package default richer than one line,
|
|
424
446
|
* e.g. a context-item body falling back to `GenericBody`). Same contract as the wrappers above:
|
|
@@ -498,6 +520,7 @@ export declare const WindowPanel: AnyComponent;
|
|
|
498
520
|
/** A bare host draws a plain full-screen tabbed sheet. */
|
|
499
521
|
export declare const FullScreenOverlay: AnyComponent;
|
|
500
522
|
export declare const ResourcePickerWindow: AnyComponent;
|
|
523
|
+
/** A bare host gets the package's own Files + Link picker (`defaults/resource-picker-menu`); the app registers its full one. */
|
|
501
524
|
export declare const ResourcePickerMenu: SlotComponent<ResourcePickerMenuProps>;
|
|
502
525
|
export declare const FilesResourcePicker: SlotComponent<FilesPickerProps>;
|
|
503
526
|
export declare const NotePickerPopover: SlotComponent<NotePickerProps>;
|
|
@@ -736,6 +759,7 @@ export declare const CreatorRunPanel: SlotComponent<{
|
|
|
736
759
|
tabs?: string[];
|
|
737
760
|
}>;
|
|
738
761
|
export declare const AgentSettingsCore: AnyComponent;
|
|
762
|
+
/** A bare host gets the package's own per-type setting control (`defaults/setting-control-input`). */
|
|
739
763
|
export declare const SettingControlInput: AnyComponent;
|
|
740
764
|
export declare const InputCapabilitiesEditor: AnyComponent;
|
|
741
765
|
export declare const CustomDataBindingSummary: AnyComponent;
|