@meshagent/meshagent-tailwind 0.39.8 → 0.40.0
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 +8 -0
- package/dist/cjs/{ChatBotView.js → chat/chat-bot-view.js} +37 -22
- package/dist/cjs/{chat-hooks.d.ts → chat/chat-hooks.d.ts} +5 -1
- package/dist/cjs/{chat-hooks.js → chat/chat-hooks.js} +12 -2
- package/dist/cjs/{ChatInput.js → chat/chat-input.js} +9 -9
- package/dist/cjs/chat/chat-thread.d.ts +12 -0
- package/dist/cjs/{ChatThread.js → chat/chat-thread.js} +75 -28
- package/dist/cjs/{ChatTypingIndicator.js → chat/chat-typing-indicator.js} +4 -4
- package/dist/{esm → cjs/chat}/conversation-descriptor.d.ts +7 -1
- package/dist/cjs/{conversation-descriptor.js → chat/conversation-descriptor.js} +41 -7
- package/dist/cjs/chat/dataset-chat-thread.d.ts +13 -0
- package/dist/cjs/chat/dataset-chat-thread.js +1840 -0
- package/dist/cjs/{FileUploader.js → chat/file-uploader.js} +4 -4
- package/dist/cjs/{multi-thread-view.js → chat/multi-thread-view.js} +8 -3
- package/dist/cjs/chat/new-chat-thread.d.ts +17 -0
- package/dist/cjs/{Chat.js → chat/new-chat-thread.js} +43 -168
- package/dist/cjs/{UploadPill.js → chat/upload-pill.js} +5 -5
- package/dist/cjs/file-preview/file-preview.d.ts +34 -0
- package/dist/cjs/file-preview/file-preview.js +329 -0
- package/dist/cjs/forms/email-address.d.ts +10 -0
- package/dist/cjs/forms/email-address.js +105 -0
- package/dist/cjs/forms/form.d.ts +27 -0
- package/dist/cjs/forms/form.js +200 -0
- package/dist/cjs/forms/multi-select-autocomplete.d.ts +35 -0
- package/dist/cjs/forms/multi-select-autocomplete.js +294 -0
- package/dist/cjs/forms/select-users-dialog.d.ts +20 -0
- package/dist/cjs/forms/select-users-dialog.js +145 -0
- package/dist/cjs/forms/select-users.d.ts +16 -0
- package/dist/cjs/forms/select-users.js +117 -0
- package/dist/cjs/index.d.ts +19 -11
- package/dist/cjs/index.js +19 -11
- package/dist/cjs/meetings/audio-visualization.d.ts +7 -0
- package/dist/cjs/meetings/audio-visualization.js +74 -0
- package/dist/cjs/meetings/controls.d.ts +19 -0
- package/dist/cjs/meetings/controls.js +300 -0
- package/dist/cjs/meetings/meeting-scope.d.ts +83 -0
- package/dist/cjs/meetings/meeting-scope.js +309 -0
- package/dist/cjs/meetings/meetings.d.ts +5 -0
- package/dist/cjs/meetings/meetings.js +22 -0
- package/dist/cjs/meetings/participants.d.ts +13 -0
- package/dist/cjs/meetings/participants.js +154 -0
- package/dist/cjs/meetings/wake-lock.d.ts +4 -0
- package/dist/cjs/meetings/wake-lock.js +55 -0
- package/dist/esm/{ChatBotView.js → chat/chat-bot-view.js} +34 -19
- package/dist/esm/{chat-hooks.d.ts → chat/chat-hooks.d.ts} +5 -1
- package/dist/esm/{chat-hooks.js → chat/chat-hooks.js} +12 -2
- package/dist/esm/{ChatInput.js → chat/chat-input.js} +4 -4
- package/dist/esm/chat/chat-thread.d.ts +12 -0
- package/dist/esm/{ChatThread.js → chat/chat-thread.js} +70 -23
- package/dist/esm/{ChatTypingIndicator.js → chat/chat-typing-indicator.js} +1 -1
- package/dist/{cjs → esm/chat}/conversation-descriptor.d.ts +7 -1
- package/dist/esm/{conversation-descriptor.js → chat/conversation-descriptor.js} +41 -7
- package/dist/esm/chat/dataset-chat-thread.d.ts +13 -0
- package/dist/esm/chat/dataset-chat-thread.js +1815 -0
- package/dist/esm/{FileUploader.js → chat/file-uploader.js} +1 -1
- package/dist/esm/{multi-thread-view.js → chat/multi-thread-view.js} +8 -3
- package/dist/esm/chat/new-chat-thread.d.ts +17 -0
- package/dist/esm/{Chat.js → chat/new-chat-thread.js} +40 -165
- package/dist/esm/{UploadPill.js → chat/upload-pill.js} +2 -2
- package/dist/esm/file-preview/file-preview.d.ts +34 -0
- package/dist/esm/file-preview/file-preview.js +316 -0
- package/dist/esm/forms/email-address.d.ts +10 -0
- package/dist/esm/forms/email-address.js +85 -0
- package/dist/esm/forms/form.d.ts +27 -0
- package/dist/esm/forms/form.js +193 -0
- package/dist/esm/forms/multi-select-autocomplete.d.ts +35 -0
- package/dist/esm/forms/multi-select-autocomplete.js +274 -0
- package/dist/esm/forms/select-users-dialog.d.ts +20 -0
- package/dist/esm/forms/select-users-dialog.js +132 -0
- package/dist/esm/forms/select-users.d.ts +16 -0
- package/dist/esm/forms/select-users.js +97 -0
- package/dist/esm/index.d.ts +19 -11
- package/dist/esm/index.js +19 -11
- package/dist/esm/meetings/audio-visualization.d.ts +7 -0
- package/dist/esm/meetings/audio-visualization.js +54 -0
- package/dist/esm/meetings/controls.d.ts +19 -0
- package/dist/esm/meetings/controls.js +294 -0
- package/dist/esm/meetings/meeting-scope.d.ts +83 -0
- package/dist/esm/meetings/meeting-scope.js +294 -0
- package/dist/esm/meetings/meetings.d.ts +5 -0
- package/dist/esm/meetings/meetings.js +5 -0
- package/dist/esm/meetings/participants.d.ts +13 -0
- package/dist/esm/meetings/participants.js +137 -0
- package/dist/esm/meetings/wake-lock.d.ts +4 -0
- package/dist/esm/meetings/wake-lock.js +35 -0
- package/dist/index.css +2 -2
- package/package.json +7 -4
- package/dist/cjs/Chat.d.ts +0 -15
- package/dist/cjs/ChatThread.d.ts +0 -21
- package/dist/esm/Chat.d.ts +0 -15
- package/dist/esm/ChatThread.d.ts +0 -21
- /package/dist/cjs/{ChatBotView.d.ts → chat/chat-bot-view.d.ts} +0 -0
- /package/dist/cjs/{ChatInput.d.ts → chat/chat-input.d.ts} +0 -0
- /package/dist/cjs/{chat-message.d.ts → chat/chat-message.d.ts} +0 -0
- /package/dist/cjs/{chat-message.js → chat/chat-message.js} +0 -0
- /package/dist/cjs/{ChatTypingIndicator.d.ts → chat/chat-typing-indicator.d.ts} +0 -0
- /package/dist/cjs/{file-attachment.d.ts → chat/file-attachment.d.ts} +0 -0
- /package/dist/cjs/{file-attachment.js → chat/file-attachment.js} +0 -0
- /package/dist/cjs/{FileUploader.d.ts → chat/file-uploader.d.ts} +0 -0
- /package/dist/cjs/{multi-thread-view.d.ts → chat/multi-thread-view.d.ts} +0 -0
- /package/dist/cjs/{UploadPill.d.ts → chat/upload-pill.d.ts} +0 -0
- /package/dist/esm/{ChatBotView.d.ts → chat/chat-bot-view.d.ts} +0 -0
- /package/dist/esm/{ChatInput.d.ts → chat/chat-input.d.ts} +0 -0
- /package/dist/esm/{chat-message.d.ts → chat/chat-message.d.ts} +0 -0
- /package/dist/esm/{chat-message.js → chat/chat-message.js} +0 -0
- /package/dist/esm/{ChatTypingIndicator.d.ts → chat/chat-typing-indicator.d.ts} +0 -0
- /package/dist/esm/{file-attachment.d.ts → chat/file-attachment.d.ts} +0 -0
- /package/dist/esm/{file-attachment.js → chat/file-attachment.js} +0 -0
- /package/dist/esm/{FileUploader.d.ts → chat/file-uploader.d.ts} +0 -0
- /package/dist/esm/{multi-thread-view.d.ts → chat/multi-thread-view.d.ts} +0 -0
- /package/dist/esm/{UploadPill.d.ts → chat/upload-pill.d.ts} +0 -0
|
@@ -0,0 +1,1840 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var dataset_chat_thread_exports = {};
|
|
30
|
+
__export(dataset_chat_thread_exports, {
|
|
31
|
+
DatasetChatThread: () => DatasetChatThread
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(dataset_chat_thread_exports);
|
|
34
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
+
var import_react = require("react");
|
|
36
|
+
var import_meshagent = require("@meshagent/meshagent");
|
|
37
|
+
var import_meshagent_react = require("@meshagent/meshagent-react");
|
|
38
|
+
var import_lucide_react = require("lucide-react");
|
|
39
|
+
var import_react_markdown = __toESM(require("react-markdown"));
|
|
40
|
+
var import_rehype_highlight = __toESM(require("rehype-highlight"));
|
|
41
|
+
var import_rehype_sanitize = __toESM(require("rehype-sanitize"));
|
|
42
|
+
var import_remark_gfm = __toESM(require("remark-gfm"));
|
|
43
|
+
var import_chat_input = require("./chat-input");
|
|
44
|
+
var import_chat_typing_indicator = require("./chat-typing-indicator");
|
|
45
|
+
var import_button = require("../components/ui/button");
|
|
46
|
+
var import_spinner = require("../components/ui/spinner");
|
|
47
|
+
var import_file_attachment = require("./file-attachment");
|
|
48
|
+
var import_chat_hooks = require("./chat-hooks");
|
|
49
|
+
var import_utils = require("../lib/utils");
|
|
50
|
+
var import_chat_thread = require("./chat-thread");
|
|
51
|
+
const agentRoomMessageType = "agent-message";
|
|
52
|
+
const agentTurnStartType = "meshagent.agent.turn.start";
|
|
53
|
+
const agentTurnSteerType = "meshagent.agent.turn.steer";
|
|
54
|
+
const agentTurnInterruptType = "meshagent.agent.turn.interrupt";
|
|
55
|
+
const agentThreadOpenType = "meshagent.agent.thread.open";
|
|
56
|
+
const agentThreadCloseType = "meshagent.agent.thread.close";
|
|
57
|
+
const agentTurnStartAcceptedType = "meshagent.agent.turn.start.accepted";
|
|
58
|
+
const agentTurnStartRejectedType = "meshagent.agent.turn.start.rejected";
|
|
59
|
+
const agentTurnSteerAcceptedType = "meshagent.agent.turn.steer.accepted";
|
|
60
|
+
const agentTurnSteerRejectedType = "meshagent.agent.turn.steer.rejected";
|
|
61
|
+
const agentTurnStartedType = "meshagent.agent.turn.started";
|
|
62
|
+
const agentTurnSteeredType = "meshagent.agent.turn.steered";
|
|
63
|
+
const agentTextContentDeltaType = "meshagent.agent.text_content.delta";
|
|
64
|
+
const agentTextContentEndedType = "meshagent.agent.text_content.ended";
|
|
65
|
+
const agentReasoningContentStartedType = "meshagent.agent.reasoning_content.started";
|
|
66
|
+
const agentReasoningContentDeltaType = "meshagent.agent.reasoning_content.delta";
|
|
67
|
+
const agentReasoningContentEndedType = "meshagent.agent.reasoning_content.ended";
|
|
68
|
+
const agentFileContentStartedType = "meshagent.agent.file_content.started";
|
|
69
|
+
const agentFileContentDeltaType = "meshagent.agent.file_content.delta";
|
|
70
|
+
const agentFileContentEndedType = "meshagent.agent.file_content.ended";
|
|
71
|
+
const agentToolCallPendingType = "meshagent.agent.tool_call.pending";
|
|
72
|
+
const agentToolCallInProgressType = "meshagent.agent.tool_call.in_progress";
|
|
73
|
+
const agentToolCallStartedType = "meshagent.agent.tool_call.started";
|
|
74
|
+
const agentToolCallEndedType = "meshagent.agent.tool_call.ended";
|
|
75
|
+
const agentImageGenerationStartedType = "meshagent.agent.image_generation.started";
|
|
76
|
+
const agentImageGenerationPartialType = "meshagent.agent.image_generation.partial";
|
|
77
|
+
const agentImageGenerationCompletedType = "meshagent.agent.image_generation.completed";
|
|
78
|
+
const agentImageGenerationFailedType = "meshagent.agent.image_generation.failed";
|
|
79
|
+
const stickyBottomThresholdPx = 24;
|
|
80
|
+
const maxPreviewEdgePx = 312.5;
|
|
81
|
+
function createDatasetThreadModel() {
|
|
82
|
+
return {
|
|
83
|
+
rowsByItemId: /* @__PURE__ */ new Map(),
|
|
84
|
+
initialRowsByItemId: /* @__PURE__ */ new Map(),
|
|
85
|
+
agentRowsByItemId: /* @__PURE__ */ new Map(),
|
|
86
|
+
nextAgentSequence: 0,
|
|
87
|
+
error: null,
|
|
88
|
+
fatalError: false,
|
|
89
|
+
ready: false
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function isRecord(value) {
|
|
93
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
94
|
+
}
|
|
95
|
+
function stringValue(value) {
|
|
96
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
|
|
97
|
+
}
|
|
98
|
+
function intValue(value) {
|
|
99
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
100
|
+
return Math.trunc(value);
|
|
101
|
+
}
|
|
102
|
+
if (typeof value === "bigint") {
|
|
103
|
+
return Number(value);
|
|
104
|
+
}
|
|
105
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
106
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
107
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
108
|
+
}
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
function doubleValue(value) {
|
|
112
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
if (typeof value === "string") {
|
|
116
|
+
const parsed = Number.parseFloat(value.trim());
|
|
117
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
function stringList(value) {
|
|
122
|
+
if (!Array.isArray(value)) {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
return value.map((item) => String(item).trim()).filter((item) => item !== "");
|
|
126
|
+
}
|
|
127
|
+
function mapValue(value) {
|
|
128
|
+
if (value instanceof import_meshagent.DatasetJson) {
|
|
129
|
+
const json = value.toJson();
|
|
130
|
+
return isRecord(json) ? json : null;
|
|
131
|
+
}
|
|
132
|
+
if (value instanceof import_meshagent.DatasetStruct) {
|
|
133
|
+
return value.toJson();
|
|
134
|
+
}
|
|
135
|
+
if (isRecord(value)) {
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
139
|
+
try {
|
|
140
|
+
const decoded = JSON.parse(value);
|
|
141
|
+
return isRecord(decoded) ? decoded : null;
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function rowData(row) {
|
|
149
|
+
return mapValue(row.data);
|
|
150
|
+
}
|
|
151
|
+
function dateTimeFromEpochValue(value) {
|
|
152
|
+
const absolute = Math.abs(value);
|
|
153
|
+
if (absolute >= 1e17) {
|
|
154
|
+
return new Date(Math.trunc(value / 1e6));
|
|
155
|
+
}
|
|
156
|
+
if (absolute >= 1e14) {
|
|
157
|
+
return new Date(Math.trunc(value / 1e3));
|
|
158
|
+
}
|
|
159
|
+
if (absolute >= 1e11) {
|
|
160
|
+
return new Date(value);
|
|
161
|
+
}
|
|
162
|
+
return new Date(value * 1e3);
|
|
163
|
+
}
|
|
164
|
+
function rowTimestamp(row) {
|
|
165
|
+
const value = row.timestamp;
|
|
166
|
+
if (value instanceof Date) {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
170
|
+
return dateTimeFromEpochValue(Math.round(value));
|
|
171
|
+
}
|
|
172
|
+
if (typeof value === "bigint") {
|
|
173
|
+
return dateTimeFromEpochValue(Number(value));
|
|
174
|
+
}
|
|
175
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
176
|
+
const parsed = new Date(value);
|
|
177
|
+
return Number.isNaN(parsed.getTime()) ? /* @__PURE__ */ new Date() : parsed;
|
|
178
|
+
}
|
|
179
|
+
return /* @__PURE__ */ new Date();
|
|
180
|
+
}
|
|
181
|
+
function compareDatasetThreadRows(left, right) {
|
|
182
|
+
const sequenceOrder = intValue(left.sequence) - intValue(right.sequence);
|
|
183
|
+
if (sequenceOrder !== 0) {
|
|
184
|
+
return sequenceOrder;
|
|
185
|
+
}
|
|
186
|
+
const timestampOrder = rowTimestamp(left).getTime() - rowTimestamp(right).getTime();
|
|
187
|
+
if (timestampOrder !== 0) {
|
|
188
|
+
return timestampOrder;
|
|
189
|
+
}
|
|
190
|
+
return String(left.item_id ?? "").localeCompare(String(right.item_id ?? ""));
|
|
191
|
+
}
|
|
192
|
+
function tableToRows(table) {
|
|
193
|
+
const fieldNames = table.schema.fields.map((field) => field.name);
|
|
194
|
+
const rows = [];
|
|
195
|
+
for (let rowIndex = 0; rowIndex < table.numRows; rowIndex += 1) {
|
|
196
|
+
const row = {};
|
|
197
|
+
for (const fieldName of fieldNames) {
|
|
198
|
+
row[fieldName] = table.getChild(fieldName)?.get(rowIndex);
|
|
199
|
+
}
|
|
200
|
+
rows.push(row);
|
|
201
|
+
}
|
|
202
|
+
return rows;
|
|
203
|
+
}
|
|
204
|
+
function parseDatasetThreadRef(url) {
|
|
205
|
+
let path = url.trim();
|
|
206
|
+
if (!path.startsWith("dataset://")) {
|
|
207
|
+
throw new Error("dataset thread URL must start with dataset://");
|
|
208
|
+
}
|
|
209
|
+
path = path.slice("dataset://".length);
|
|
210
|
+
if (path.startsWith("/")) {
|
|
211
|
+
throw new Error("dataset thread URL must use dataset://path");
|
|
212
|
+
}
|
|
213
|
+
const parts = path.split("/").map((part) => part.trim()).filter((part) => part !== "");
|
|
214
|
+
if (parts.length === 0) {
|
|
215
|
+
throw new Error("dataset thread URL must include a table name");
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
namespace: parts.length === 1 ? void 0 : parts.slice(0, -1),
|
|
219
|
+
table: parts[parts.length - 1]
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function parseDatasetImageUri(uri) {
|
|
223
|
+
let parsed;
|
|
224
|
+
try {
|
|
225
|
+
parsed = new URL(uri.trim());
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
if (parsed.protocol !== "dataset:") {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
const imageId = parsed.searchParams.get("id")?.trim();
|
|
233
|
+
if (!imageId) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
const pathParts = [
|
|
237
|
+
parsed.hostname.trim(),
|
|
238
|
+
...parsed.pathname.split("/").map((part) => part.trim())
|
|
239
|
+
].filter((part) => part !== "");
|
|
240
|
+
if (pathParts.length === 0) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
namespace: pathParts.length === 1 ? void 0 : pathParts.slice(0, -1),
|
|
245
|
+
table: pathParts[pathParts.length - 1],
|
|
246
|
+
imageId
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function isTmpThreadPath(path) {
|
|
250
|
+
return path.trim().startsWith("tmp://");
|
|
251
|
+
}
|
|
252
|
+
function isDatasetTableNotFoundError(error) {
|
|
253
|
+
if (!(error instanceof import_meshagent.RoomServerException)) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (error.statusCode === 404) {
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
const message = error.message.toLowerCase();
|
|
260
|
+
return message.includes("table") && (message.includes("not found") || message.includes("does not exist") || message.includes("no such table"));
|
|
261
|
+
}
|
|
262
|
+
function itemIdsForDeletePredicate(predicate) {
|
|
263
|
+
const normalized = predicate.trim();
|
|
264
|
+
const equality = /^"?item_id"?\s*=\s*['"]([^'"]+)['"]$/iu.exec(normalized);
|
|
265
|
+
if (equality) {
|
|
266
|
+
return [equality[1]];
|
|
267
|
+
}
|
|
268
|
+
const inList = /^"?item_id"?\s+in\s*\((.*)\)$/iu.exec(normalized);
|
|
269
|
+
if (!inList) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
return Array.from(inList[1].matchAll(/['"]([^'"]+)['"]/gu)).map((match) => match[1]);
|
|
273
|
+
}
|
|
274
|
+
function applyDeletePredicate(predicate, target) {
|
|
275
|
+
let changed = false;
|
|
276
|
+
for (const itemId of itemIdsForDeletePredicate(predicate)) {
|
|
277
|
+
changed = target.delete(itemId) || changed;
|
|
278
|
+
}
|
|
279
|
+
return changed;
|
|
280
|
+
}
|
|
281
|
+
function maxDatasetSequence(model) {
|
|
282
|
+
let maxSequence = -1;
|
|
283
|
+
for (const row of [...model.rowsByItemId.values(), ...model.initialRowsByItemId.values()]) {
|
|
284
|
+
maxSequence = Math.max(maxSequence, intValue(row.sequence));
|
|
285
|
+
}
|
|
286
|
+
return maxSequence;
|
|
287
|
+
}
|
|
288
|
+
function advanceNextAgentSequencePastDatasetRows(model) {
|
|
289
|
+
const maxSequence = maxDatasetSequence(model);
|
|
290
|
+
if (maxSequence >= model.nextAgentSequence) {
|
|
291
|
+
model.nextAgentSequence = maxSequence + 1;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function imageGenerationCorrelationKeys(row) {
|
|
295
|
+
const keys = /* @__PURE__ */ new Set();
|
|
296
|
+
const itemId = stringValue(row.item_id);
|
|
297
|
+
if (itemId) {
|
|
298
|
+
keys.add(`item:${itemId}`);
|
|
299
|
+
}
|
|
300
|
+
const data = rowData(row);
|
|
301
|
+
const message = mapValue(data?.message);
|
|
302
|
+
for (const value of [data?.call_id, message?.call_id]) {
|
|
303
|
+
const callId = stringValue(value);
|
|
304
|
+
if (callId) {
|
|
305
|
+
keys.add(`call:${callId}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
for (const value of [message?.item_id, message?.message_id]) {
|
|
309
|
+
const messageItemId = stringValue(value);
|
|
310
|
+
if (messageItemId) {
|
|
311
|
+
keys.add(`item:${messageItemId}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return keys;
|
|
315
|
+
}
|
|
316
|
+
function removeReconciledAgentRowsForDatasetRow(model, datasetRow) {
|
|
317
|
+
const datasetData = rowData(datasetRow);
|
|
318
|
+
if (datasetData?.kind !== "image_generation") {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const datasetKeys = imageGenerationCorrelationKeys(datasetRow);
|
|
322
|
+
if (datasetKeys.size === 0) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
for (const [liveItemId, liveRow] of model.agentRowsByItemId.entries()) {
|
|
326
|
+
const liveData = rowData(liveRow);
|
|
327
|
+
if (liveData?.kind !== "image_generation") {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (Array.from(imageGenerationCorrelationKeys(liveRow)).some((key) => datasetKeys.has(key))) {
|
|
331
|
+
model.agentRowsByItemId.delete(liveItemId);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function isReconciledByDatasetRows(model, liveRow) {
|
|
336
|
+
const liveData = rowData(liveRow);
|
|
337
|
+
if (liveData?.kind !== "image_generation") {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
const liveKeys = imageGenerationCorrelationKeys(liveRow);
|
|
341
|
+
if (liveKeys.size === 0) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
for (const datasetRow of [...model.rowsByItemId.values(), ...model.initialRowsByItemId.values()]) {
|
|
345
|
+
const datasetData = rowData(datasetRow);
|
|
346
|
+
if (datasetData?.kind !== "image_generation") {
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (Array.from(imageGenerationCorrelationKeys(datasetRow)).some((key) => liveKeys.has(key))) {
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
function rebaseAgentRowsAfterDatasetRows(model) {
|
|
356
|
+
const maxSequence = maxDatasetSequence(model);
|
|
357
|
+
if (maxSequence < 0 || model.agentRowsByItemId.size === 0) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
let changed = false;
|
|
361
|
+
let nextSequence = maxSequence + 1;
|
|
362
|
+
const liveRows = Array.from(model.agentRowsByItemId.values()).sort(compareDatasetThreadRows);
|
|
363
|
+
for (const row of liveRows) {
|
|
364
|
+
const itemId = stringValue(row.item_id);
|
|
365
|
+
if (!itemId || model.rowsByItemId.has(itemId) || model.initialRowsByItemId.has(itemId)) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const sequence = intValue(row.sequence);
|
|
369
|
+
if (sequence <= maxSequence) {
|
|
370
|
+
model.agentRowsByItemId.set(itemId, { ...row, sequence: nextSequence });
|
|
371
|
+
changed = true;
|
|
372
|
+
nextSequence += 1;
|
|
373
|
+
} else if (sequence >= nextSequence) {
|
|
374
|
+
nextSequence = sequence + 1;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (nextSequence > model.nextAgentSequence) {
|
|
378
|
+
model.nextAgentSequence = nextSequence;
|
|
379
|
+
}
|
|
380
|
+
return changed;
|
|
381
|
+
}
|
|
382
|
+
function applyRowsToMap(rows, target, model) {
|
|
383
|
+
let changed = false;
|
|
384
|
+
for (const row of rows) {
|
|
385
|
+
const itemId = stringValue(row.item_id);
|
|
386
|
+
if (!itemId) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
target.set(itemId, { ...row });
|
|
390
|
+
model.agentRowsByItemId.delete(itemId);
|
|
391
|
+
removeReconciledAgentRowsForDatasetRow(model, row);
|
|
392
|
+
changed = true;
|
|
393
|
+
}
|
|
394
|
+
if (changed) {
|
|
395
|
+
advanceNextAgentSequencePastDatasetRows(model);
|
|
396
|
+
changed = rebaseAgentRowsAfterDatasetRows(model) || changed;
|
|
397
|
+
}
|
|
398
|
+
return changed;
|
|
399
|
+
}
|
|
400
|
+
function watchEventKind(event) {
|
|
401
|
+
return event.watchEvent ?? event.kind;
|
|
402
|
+
}
|
|
403
|
+
function deletePredicateForWatchEvent(event) {
|
|
404
|
+
const eventRecord = event;
|
|
405
|
+
return stringValue(eventRecord.deletePredicate) ?? stringValue(eventRecord.predicate);
|
|
406
|
+
}
|
|
407
|
+
function handlePreReadyWatchEvent(model, event) {
|
|
408
|
+
let changed = false;
|
|
409
|
+
const kind = watchEventKind(event);
|
|
410
|
+
const deletePredicate = deletePredicateForWatchEvent(event);
|
|
411
|
+
if (kind === "delete" && deletePredicate) {
|
|
412
|
+
changed = applyDeletePredicate(deletePredicate, model.initialRowsByItemId) || changed;
|
|
413
|
+
}
|
|
414
|
+
const rows = event.table ? tableToRows(event.table) : [];
|
|
415
|
+
if (event.table) {
|
|
416
|
+
if (kind === "delete" || event.changeType === "delete") {
|
|
417
|
+
for (const row of rows) {
|
|
418
|
+
const predicate = stringValue(row.predicate);
|
|
419
|
+
if (predicate) {
|
|
420
|
+
changed = applyDeletePredicate(predicate, model.initialRowsByItemId) || changed;
|
|
421
|
+
} else {
|
|
422
|
+
const itemId = stringValue(row.item_id);
|
|
423
|
+
if (itemId) {
|
|
424
|
+
changed = model.initialRowsByItemId.delete(itemId) || changed;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
} else if (kind !== "transactions" && event.changeType !== "transactions") {
|
|
429
|
+
changed = applyRowsToMap(rows, model.initialRowsByItemId, model) || changed;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (kind !== "ready") {
|
|
433
|
+
return changed;
|
|
434
|
+
}
|
|
435
|
+
model.rowsByItemId.clear();
|
|
436
|
+
for (const [itemId, row] of model.initialRowsByItemId.entries()) {
|
|
437
|
+
model.rowsByItemId.set(itemId, row);
|
|
438
|
+
}
|
|
439
|
+
model.initialRowsByItemId.clear();
|
|
440
|
+
model.ready = true;
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
function handleWatchEvent(model, event) {
|
|
444
|
+
if (!model.ready) {
|
|
445
|
+
return handlePreReadyWatchEvent(model, event);
|
|
446
|
+
}
|
|
447
|
+
let changed = false;
|
|
448
|
+
const kind = watchEventKind(event);
|
|
449
|
+
const deletePredicate = deletePredicateForWatchEvent(event);
|
|
450
|
+
if (kind === "delete" && deletePredicate) {
|
|
451
|
+
changed = applyDeletePredicate(deletePredicate, model.rowsByItemId) || changed;
|
|
452
|
+
}
|
|
453
|
+
const rows = event.table ? tableToRows(event.table) : [];
|
|
454
|
+
if (event.table) {
|
|
455
|
+
if (kind === "delete" || event.changeType === "delete") {
|
|
456
|
+
for (const row of rows) {
|
|
457
|
+
const predicate = stringValue(row.predicate);
|
|
458
|
+
if (predicate) {
|
|
459
|
+
changed = applyDeletePredicate(predicate, model.rowsByItemId) || changed;
|
|
460
|
+
} else {
|
|
461
|
+
const itemId = stringValue(row.item_id);
|
|
462
|
+
if (itemId) {
|
|
463
|
+
changed = model.rowsByItemId.delete(itemId) || changed;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return changed;
|
|
468
|
+
}
|
|
469
|
+
if (kind === "transactions" || event.changeType === "transactions") {
|
|
470
|
+
return changed;
|
|
471
|
+
}
|
|
472
|
+
changed = applyRowsToMap(rows, model.rowsByItemId, model) || changed;
|
|
473
|
+
}
|
|
474
|
+
if (kind === "ready") {
|
|
475
|
+
model.ready = true;
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
return changed;
|
|
479
|
+
}
|
|
480
|
+
function payloadItemId(payload) {
|
|
481
|
+
return stringValue(payload.item_id) ?? stringValue(payload.message_id) ?? crypto.randomUUID();
|
|
482
|
+
}
|
|
483
|
+
function payloadTurnId(payload) {
|
|
484
|
+
return stringValue(payload.turn_id);
|
|
485
|
+
}
|
|
486
|
+
function timestampFromPayload(payload) {
|
|
487
|
+
const createdAt = payload.created_at;
|
|
488
|
+
if (createdAt instanceof Date) {
|
|
489
|
+
return createdAt;
|
|
490
|
+
}
|
|
491
|
+
if (typeof createdAt === "string" && createdAt.trim() !== "") {
|
|
492
|
+
const parsed = new Date(createdAt);
|
|
493
|
+
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
function imageGenerationStatusFromType(type) {
|
|
498
|
+
switch (type) {
|
|
499
|
+
case agentImageGenerationCompletedType:
|
|
500
|
+
return "completed";
|
|
501
|
+
case agentImageGenerationFailedType:
|
|
502
|
+
return "failed";
|
|
503
|
+
case agentImageGenerationPartialType:
|
|
504
|
+
return "in_progress";
|
|
505
|
+
default:
|
|
506
|
+
return "pending";
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function upsertAgentRow({
|
|
510
|
+
model,
|
|
511
|
+
itemId,
|
|
512
|
+
turnId,
|
|
513
|
+
data,
|
|
514
|
+
timestamp
|
|
515
|
+
}) {
|
|
516
|
+
if (itemId.trim() === "" || model.rowsByItemId.has(itemId) || model.initialRowsByItemId.has(itemId)) {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
const candidateRow = { turn_id: turnId, item_id: itemId, data };
|
|
520
|
+
if (isReconciledByDatasetRows(model, candidateRow)) {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
const existing = model.agentRowsByItemId.get(itemId);
|
|
524
|
+
const row = {
|
|
525
|
+
turn_id: turnId ?? existing?.turn_id,
|
|
526
|
+
item_id: itemId,
|
|
527
|
+
sequence: existing?.sequence ?? model.nextAgentSequence,
|
|
528
|
+
timestamp: timestamp ?? existing?.timestamp ?? /* @__PURE__ */ new Date(),
|
|
529
|
+
data
|
|
530
|
+
};
|
|
531
|
+
if (!existing) {
|
|
532
|
+
model.nextAgentSequence += 1;
|
|
533
|
+
}
|
|
534
|
+
model.agentRowsByItemId.set(itemId, row);
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
function appendAgentRowText({
|
|
538
|
+
model,
|
|
539
|
+
itemId,
|
|
540
|
+
turnId,
|
|
541
|
+
kind,
|
|
542
|
+
role,
|
|
543
|
+
delta
|
|
544
|
+
}) {
|
|
545
|
+
if (delta === "") {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
const existingData = mapValue(model.agentRowsByItemId.get(itemId)?.data);
|
|
549
|
+
const nextText = `${existingData?.text ?? ""}${delta}`;
|
|
550
|
+
return upsertAgentRow({
|
|
551
|
+
model,
|
|
552
|
+
itemId,
|
|
553
|
+
turnId,
|
|
554
|
+
data: { kind, role, text: nextText }
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
function appendAgentRowUrl({
|
|
558
|
+
model,
|
|
559
|
+
itemId,
|
|
560
|
+
turnId,
|
|
561
|
+
url
|
|
562
|
+
}) {
|
|
563
|
+
const normalizedUrl = stringValue(url);
|
|
564
|
+
if (!normalizedUrl) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
const existingData = mapValue(model.agentRowsByItemId.get(itemId)?.data);
|
|
568
|
+
const urls = stringList(existingData?.urls);
|
|
569
|
+
if (!urls.includes(normalizedUrl)) {
|
|
570
|
+
urls.push(normalizedUrl);
|
|
571
|
+
}
|
|
572
|
+
return upsertAgentRow({
|
|
573
|
+
model,
|
|
574
|
+
itemId,
|
|
575
|
+
turnId,
|
|
576
|
+
data: { kind: "file", role: "assistant", urls }
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function agentRowText(model, itemId) {
|
|
580
|
+
return String(mapValue(model.agentRowsByItemId.get(itemId)?.data)?.text ?? "");
|
|
581
|
+
}
|
|
582
|
+
function normalizeAgentAttachmentUrl(path) {
|
|
583
|
+
const trimmedPath = path.trim();
|
|
584
|
+
if (trimmedPath === "") {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
const parsed = new URL(trimmedPath);
|
|
589
|
+
if (parsed.protocol !== "") {
|
|
590
|
+
return trimmedPath;
|
|
591
|
+
}
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
const roomPath = trimmedPath.startsWith("/") ? trimmedPath.slice(1) : trimmedPath;
|
|
595
|
+
return roomPath === "" ? null : `room:///${roomPath}`;
|
|
596
|
+
}
|
|
597
|
+
function previewPath(path) {
|
|
598
|
+
const prefix = "room:///";
|
|
599
|
+
return path.startsWith(prefix) ? path.slice(prefix.length) : path;
|
|
600
|
+
}
|
|
601
|
+
function comparableThreadAttachmentPath(path) {
|
|
602
|
+
const normalized = previewPath(path.trim());
|
|
603
|
+
return normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
604
|
+
}
|
|
605
|
+
function agentInputContent(text, attachments) {
|
|
606
|
+
const content = [];
|
|
607
|
+
if (text.trim() !== "") {
|
|
608
|
+
content.push({ type: "text", text });
|
|
609
|
+
}
|
|
610
|
+
for (const attachment of attachments) {
|
|
611
|
+
const url = normalizeAgentAttachmentUrl(attachment);
|
|
612
|
+
if (url !== null) {
|
|
613
|
+
content.push({ type: "file", url });
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return content;
|
|
617
|
+
}
|
|
618
|
+
function materializeTurnInputPayload(model, payload) {
|
|
619
|
+
const messageId = stringValue(payload.source_message_id) ?? stringValue(payload.message_id);
|
|
620
|
+
if (!messageId || model.rowsByItemId.has(messageId) || model.initialRowsByItemId.has(messageId) || model.agentRowsByItemId.has(messageId)) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
const content = payload.content;
|
|
624
|
+
if (!Array.isArray(content)) {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
const textParts = [];
|
|
628
|
+
const attachments = [];
|
|
629
|
+
for (const item of content) {
|
|
630
|
+
if (!isRecord(item)) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (item.type === "text") {
|
|
634
|
+
const text2 = typeof item.text === "string" ? item.text : null;
|
|
635
|
+
if (text2) {
|
|
636
|
+
textParts.push(text2);
|
|
637
|
+
}
|
|
638
|
+
} else if (item.type === "file") {
|
|
639
|
+
const url = stringValue(item.url);
|
|
640
|
+
if (url) {
|
|
641
|
+
attachments.push(url);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const text = textParts.join("\n");
|
|
646
|
+
if (text.trim() === "" && attachments.length === 0) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
return upsertAgentRow({
|
|
650
|
+
model,
|
|
651
|
+
itemId: messageId,
|
|
652
|
+
turnId: payloadTurnId(payload),
|
|
653
|
+
timestamp: timestampFromPayload(payload) ?? /* @__PURE__ */ new Date(),
|
|
654
|
+
data: {
|
|
655
|
+
kind: "message",
|
|
656
|
+
role: "user",
|
|
657
|
+
text,
|
|
658
|
+
sender_name: stringValue(payload.sender_name),
|
|
659
|
+
attachments
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
function applyAgentMessagePayload(model, payload, path) {
|
|
664
|
+
if (payload.thread_id !== path) {
|
|
665
|
+
return false;
|
|
666
|
+
}
|
|
667
|
+
const type = typeof payload.type === "string" ? payload.type : null;
|
|
668
|
+
if (!type) {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
if (type === agentTurnStartRejectedType || type === agentTurnSteerRejectedType) {
|
|
672
|
+
const sourceMessageId = stringValue(payload.source_message_id);
|
|
673
|
+
return sourceMessageId ? model.agentRowsByItemId.delete(sourceMessageId) : false;
|
|
674
|
+
}
|
|
675
|
+
const itemId = payloadItemId(payload);
|
|
676
|
+
const turnId = payloadTurnId(payload);
|
|
677
|
+
let changed = false;
|
|
678
|
+
switch (type) {
|
|
679
|
+
case agentTurnStartAcceptedType:
|
|
680
|
+
case agentTurnSteerAcceptedType:
|
|
681
|
+
break;
|
|
682
|
+
case agentTurnStartedType:
|
|
683
|
+
case agentTurnSteeredType:
|
|
684
|
+
changed = materializeTurnInputPayload(model, payload) || changed;
|
|
685
|
+
break;
|
|
686
|
+
case agentTextContentDeltaType:
|
|
687
|
+
changed = appendAgentRowText({
|
|
688
|
+
model,
|
|
689
|
+
itemId,
|
|
690
|
+
turnId,
|
|
691
|
+
kind: "message",
|
|
692
|
+
role: "assistant",
|
|
693
|
+
delta: String(payload.text ?? "")
|
|
694
|
+
}) || changed;
|
|
695
|
+
break;
|
|
696
|
+
case agentTextContentEndedType:
|
|
697
|
+
changed = upsertAgentRow({
|
|
698
|
+
model,
|
|
699
|
+
itemId,
|
|
700
|
+
turnId,
|
|
701
|
+
data: {
|
|
702
|
+
kind: "message",
|
|
703
|
+
role: "assistant",
|
|
704
|
+
text: String(payload.text ?? agentRowText(model, itemId))
|
|
705
|
+
}
|
|
706
|
+
}) || changed;
|
|
707
|
+
break;
|
|
708
|
+
case agentReasoningContentStartedType:
|
|
709
|
+
changed = upsertAgentRow({
|
|
710
|
+
model,
|
|
711
|
+
itemId,
|
|
712
|
+
turnId,
|
|
713
|
+
data: { kind: "reasoning", role: "assistant", text: "" }
|
|
714
|
+
}) || changed;
|
|
715
|
+
break;
|
|
716
|
+
case agentReasoningContentDeltaType:
|
|
717
|
+
changed = appendAgentRowText({
|
|
718
|
+
model,
|
|
719
|
+
itemId,
|
|
720
|
+
turnId,
|
|
721
|
+
kind: "reasoning",
|
|
722
|
+
role: "assistant",
|
|
723
|
+
delta: String(payload.text ?? "")
|
|
724
|
+
}) || changed;
|
|
725
|
+
break;
|
|
726
|
+
case agentReasoningContentEndedType:
|
|
727
|
+
changed = upsertAgentRow({
|
|
728
|
+
model,
|
|
729
|
+
itemId,
|
|
730
|
+
turnId,
|
|
731
|
+
data: {
|
|
732
|
+
kind: "reasoning",
|
|
733
|
+
role: "assistant",
|
|
734
|
+
text: String(payload.text ?? agentRowText(model, itemId))
|
|
735
|
+
}
|
|
736
|
+
}) || changed;
|
|
737
|
+
break;
|
|
738
|
+
case agentFileContentStartedType:
|
|
739
|
+
changed = upsertAgentRow({
|
|
740
|
+
model,
|
|
741
|
+
itemId,
|
|
742
|
+
turnId,
|
|
743
|
+
data: { kind: "file", role: "assistant", urls: [] }
|
|
744
|
+
}) || changed;
|
|
745
|
+
break;
|
|
746
|
+
case agentFileContentDeltaType:
|
|
747
|
+
case agentFileContentEndedType:
|
|
748
|
+
changed = appendAgentRowUrl({ model, itemId, turnId, url: payload.url }) || changed;
|
|
749
|
+
break;
|
|
750
|
+
case agentToolCallPendingType:
|
|
751
|
+
case agentToolCallInProgressType:
|
|
752
|
+
case agentToolCallStartedType:
|
|
753
|
+
case agentToolCallEndedType: {
|
|
754
|
+
const tool = String(payload.tool ?? payload.tool_name ?? payload.name ?? "");
|
|
755
|
+
const isImageGeneration = tool.trim().toLowerCase() === "image_generation";
|
|
756
|
+
if (isImageGeneration && type === agentToolCallEndedType && payload.error == null) {
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
changed = upsertAgentRow({
|
|
760
|
+
model,
|
|
761
|
+
itemId,
|
|
762
|
+
turnId,
|
|
763
|
+
data: isImageGeneration ? {
|
|
764
|
+
kind: "image_generation",
|
|
765
|
+
role: "assistant",
|
|
766
|
+
status: payload.error == null ? "in_progress" : "failed",
|
|
767
|
+
status_detail: payload.error == null ? "Generating image" : String(payload.error),
|
|
768
|
+
call_id: stringValue(payload.call_id),
|
|
769
|
+
arguments: mapValue(payload.arguments)
|
|
770
|
+
} : {
|
|
771
|
+
kind: "tool_call",
|
|
772
|
+
role: "assistant",
|
|
773
|
+
toolkit: String(payload.toolkit ?? payload.toolkit_name ?? ""),
|
|
774
|
+
tool,
|
|
775
|
+
status: type === agentToolCallEndedType ? "completed" : "running"
|
|
776
|
+
}
|
|
777
|
+
}) || changed;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
case agentImageGenerationStartedType:
|
|
781
|
+
case agentImageGenerationPartialType:
|
|
782
|
+
case agentImageGenerationCompletedType:
|
|
783
|
+
case agentImageGenerationFailedType:
|
|
784
|
+
changed = upsertAgentRow({
|
|
785
|
+
model,
|
|
786
|
+
itemId,
|
|
787
|
+
turnId,
|
|
788
|
+
timestamp: timestampFromPayload(payload) ?? /* @__PURE__ */ new Date(),
|
|
789
|
+
data: {
|
|
790
|
+
kind: "image_generation",
|
|
791
|
+
role: "assistant",
|
|
792
|
+
status: imageGenerationStatusFromType(type),
|
|
793
|
+
status_detail: stringValue(payload.status_detail),
|
|
794
|
+
call_id: stringValue(payload.call_id),
|
|
795
|
+
arguments: mapValue(payload.arguments),
|
|
796
|
+
message: payload
|
|
797
|
+
}
|
|
798
|
+
}) || changed;
|
|
799
|
+
break;
|
|
800
|
+
default:
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
return changed;
|
|
804
|
+
}
|
|
805
|
+
function firstGeneratedImage(message) {
|
|
806
|
+
if (!message) {
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
if (Array.isArray(message.images) && message.images.length > 0) {
|
|
810
|
+
return mapValue(message.images[0]);
|
|
811
|
+
}
|
|
812
|
+
return mapValue(message.image);
|
|
813
|
+
}
|
|
814
|
+
function parseImageSize(value) {
|
|
815
|
+
if (typeof value !== "string") {
|
|
816
|
+
return [null, null];
|
|
817
|
+
}
|
|
818
|
+
const match = /^\s*(\d+)\s*[xX]\s*(\d+)\s*$/u.exec(value);
|
|
819
|
+
if (!match) {
|
|
820
|
+
return [null, null];
|
|
821
|
+
}
|
|
822
|
+
return [Number.parseFloat(match[1]), Number.parseFloat(match[2])];
|
|
823
|
+
}
|
|
824
|
+
function imageGenerationDimensions({
|
|
825
|
+
data,
|
|
826
|
+
message,
|
|
827
|
+
image
|
|
828
|
+
}) {
|
|
829
|
+
let width = doubleValue(image?.width);
|
|
830
|
+
let height = doubleValue(image?.height);
|
|
831
|
+
for (const argumentsValue of [mapValue(data.arguments), mapValue(message?.arguments)]) {
|
|
832
|
+
if (!argumentsValue) {
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
width ??= doubleValue(argumentsValue.width);
|
|
836
|
+
height ??= doubleValue(argumentsValue.height);
|
|
837
|
+
if (width == null || height == null) {
|
|
838
|
+
const [parsedWidth, parsedHeight] = parseImageSize(argumentsValue.size);
|
|
839
|
+
width ??= parsedWidth;
|
|
840
|
+
height ??= parsedHeight;
|
|
841
|
+
}
|
|
842
|
+
if (width != null && height != null) {
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return [width ?? void 0, height ?? void 0];
|
|
847
|
+
}
|
|
848
|
+
function imageIdFromDatasetUri(uri) {
|
|
849
|
+
if (!uri?.trim()) {
|
|
850
|
+
return void 0;
|
|
851
|
+
}
|
|
852
|
+
const parsed = parseDatasetImageUri(uri);
|
|
853
|
+
return parsed?.imageId;
|
|
854
|
+
}
|
|
855
|
+
function messageForRow(row) {
|
|
856
|
+
const data = rowData(row);
|
|
857
|
+
if (!data) {
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
const itemId = String(row.item_id ?? crypto.randomUUID());
|
|
861
|
+
const kind = String(data.kind ?? "");
|
|
862
|
+
const role = typeof data.role === "string" ? data.role : void 0;
|
|
863
|
+
switch (kind) {
|
|
864
|
+
case "message": {
|
|
865
|
+
const text = String(data.text ?? "");
|
|
866
|
+
const attachments = stringList(data.attachments);
|
|
867
|
+
if (text.trim() === "" && attachments.length === 0) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
return {
|
|
871
|
+
id: itemId,
|
|
872
|
+
kind: "message",
|
|
873
|
+
role: role === "assistant" ? "agent" : role ?? "agent",
|
|
874
|
+
text,
|
|
875
|
+
authorName: stringValue(data.sender_name) ?? void 0,
|
|
876
|
+
attachments,
|
|
877
|
+
createdAt: rowTimestamp(row)
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
case "file": {
|
|
881
|
+
const urls = stringList(data.urls);
|
|
882
|
+
if (urls.length === 0) {
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
return {
|
|
886
|
+
id: itemId,
|
|
887
|
+
kind: "message",
|
|
888
|
+
role: role === "assistant" ? "agent" : role ?? "agent",
|
|
889
|
+
text: "",
|
|
890
|
+
authorName: stringValue(data.sender_name) ?? void 0,
|
|
891
|
+
attachments: urls,
|
|
892
|
+
createdAt: rowTimestamp(row)
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
case "image_generation": {
|
|
896
|
+
const message = mapValue(data.message);
|
|
897
|
+
const image = firstGeneratedImage(message);
|
|
898
|
+
const [width, height] = imageGenerationDimensions({ data, message, image });
|
|
899
|
+
const imageUri = stringValue(image?.uri) ?? void 0;
|
|
900
|
+
return {
|
|
901
|
+
id: itemId,
|
|
902
|
+
kind: "message",
|
|
903
|
+
role: "agent",
|
|
904
|
+
text: "",
|
|
905
|
+
authorName: stringValue(image?.created_by) ?? void 0,
|
|
906
|
+
attachments: [],
|
|
907
|
+
createdAt: rowTimestamp(row),
|
|
908
|
+
image: {
|
|
909
|
+
uri: imageUri,
|
|
910
|
+
imageId: imageIdFromDatasetUri(imageUri),
|
|
911
|
+
mimeType: stringValue(image?.mime_type) ?? void 0,
|
|
912
|
+
status: stringValue(data.status) ?? stringValue(image?.status) ?? imageGenerationStatusFromType(stringValue(message?.type)),
|
|
913
|
+
statusDetail: stringValue(data.status_detail) ?? stringValue(message?.status_detail) ?? stringValue(image?.status_detail) ?? void 0,
|
|
914
|
+
width,
|
|
915
|
+
height
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
case "reasoning": {
|
|
920
|
+
const text = String(data.text ?? "");
|
|
921
|
+
return text.trim() === "" ? null : {
|
|
922
|
+
id: itemId,
|
|
923
|
+
kind: "reasoning",
|
|
924
|
+
role: "agent",
|
|
925
|
+
text,
|
|
926
|
+
attachments: [],
|
|
927
|
+
createdAt: rowTimestamp(row)
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
case "tool_call": {
|
|
931
|
+
const toolkit = String(data.toolkit ?? "");
|
|
932
|
+
const tool = String(data.tool ?? "");
|
|
933
|
+
const summary = [toolkit, tool].filter((part) => part.trim() !== "").join(".");
|
|
934
|
+
return {
|
|
935
|
+
id: itemId,
|
|
936
|
+
kind: "tool_call",
|
|
937
|
+
role: "agent",
|
|
938
|
+
text: summary || "Tool call",
|
|
939
|
+
attachments: [],
|
|
940
|
+
createdAt: rowTimestamp(row)
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
default:
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
function isImageGenerationPendingStatus(status) {
|
|
948
|
+
const normalized = status?.trim().toLowerCase();
|
|
949
|
+
return normalized === "generating" || normalized === "in_progress" || normalized === "queued" || normalized === "running" || normalized === "pending";
|
|
950
|
+
}
|
|
951
|
+
function isImageGenerationFailedStatus(status) {
|
|
952
|
+
const normalized = status?.trim().toLowerCase();
|
|
953
|
+
return normalized === "failed" || normalized === "cancelled";
|
|
954
|
+
}
|
|
955
|
+
function shouldRenderDatasetThreadMessage(message, showCompletedToolCalls) {
|
|
956
|
+
if (message.kind === "tool_call") {
|
|
957
|
+
return showCompletedToolCalls;
|
|
958
|
+
}
|
|
959
|
+
if (message.image) {
|
|
960
|
+
const hasImageReference = Boolean(message.image.imageId?.trim() || message.image.uri?.trim());
|
|
961
|
+
if (!hasImageReference && !isImageGenerationPendingStatus(message.image.status) && !isImageGenerationFailedStatus(message.image.status)) {
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
function datasetThreadMessageContentMatchesPendingAgentMessage(message, pending) {
|
|
968
|
+
const pendingText = pending.text.trim();
|
|
969
|
+
if (pendingText !== "" && message.text.trim() !== pendingText) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
const pendingAttachments = pending.attachments.map(comparableThreadAttachmentPath).filter((path) => path !== "");
|
|
973
|
+
if (pendingAttachments.length === 0) {
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
const messageAttachments = message.attachments.map(comparableThreadAttachmentPath).filter((path) => path !== "");
|
|
977
|
+
return pendingAttachments.length === messageAttachments.length && pendingAttachments.every((path, index) => path === messageAttachments[index]);
|
|
978
|
+
}
|
|
979
|
+
function datasetThreadMessageMatchesPendingAgentMessage(message, pending) {
|
|
980
|
+
if (message.kind !== "message" || message.role === "agent") {
|
|
981
|
+
return false;
|
|
982
|
+
}
|
|
983
|
+
if (message.id === pending.messageId) {
|
|
984
|
+
return datasetThreadMessageContentMatchesPendingAgentMessage(message, pending);
|
|
985
|
+
}
|
|
986
|
+
if (!pending.matchByContentOnly) {
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
return datasetThreadMessageContentMatchesPendingAgentMessage(message, pending);
|
|
990
|
+
}
|
|
991
|
+
function distanceFromBottom(element) {
|
|
992
|
+
return Math.max(element.scrollHeight - element.clientHeight - element.scrollTop, 0);
|
|
993
|
+
}
|
|
994
|
+
function isNearBottom(element) {
|
|
995
|
+
return distanceFromBottom(element) <= stickyBottomThresholdPx;
|
|
996
|
+
}
|
|
997
|
+
function displayParticipantName(name) {
|
|
998
|
+
return name.split("@")[0]?.trim() || name.trim();
|
|
999
|
+
}
|
|
1000
|
+
function getParticipantName(participant) {
|
|
1001
|
+
const name = participant?.getAttribute("name");
|
|
1002
|
+
return typeof name === "string" ? name.trim() : "";
|
|
1003
|
+
}
|
|
1004
|
+
function findAgentParticipant(room, agentName) {
|
|
1005
|
+
const normalizedAgentName = agentName?.trim();
|
|
1006
|
+
for (const participant of room.messaging.remoteParticipants) {
|
|
1007
|
+
if (normalizedAgentName && getParticipantName(participant) !== normalizedAgentName) {
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (participant.getAttribute("supports_agent_messages") === true) {
|
|
1011
|
+
return participant;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
function describeError(error) {
|
|
1017
|
+
if (error instanceof Error && error.message.trim() !== "") {
|
|
1018
|
+
return error.message;
|
|
1019
|
+
}
|
|
1020
|
+
return String(error);
|
|
1021
|
+
}
|
|
1022
|
+
function MarkdownBlock({ text }) {
|
|
1023
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1024
|
+
import_react_markdown.default,
|
|
1025
|
+
{
|
|
1026
|
+
remarkPlugins: [import_remark_gfm.default],
|
|
1027
|
+
rehypePlugins: [import_rehype_sanitize.default, import_rehype_highlight.default],
|
|
1028
|
+
components: {
|
|
1029
|
+
pre: ({ className, children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1030
|
+
"pre",
|
|
1031
|
+
{
|
|
1032
|
+
...props,
|
|
1033
|
+
className: (0, import_utils.cn)("overflow-x-auto rounded-md border bg-background/80 p-3", className),
|
|
1034
|
+
children
|
|
1035
|
+
}
|
|
1036
|
+
),
|
|
1037
|
+
p: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { ...props, className: "mb-2 last:mb-0", children }),
|
|
1038
|
+
table: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "my-4 w-full overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", { ...props, className: "w-full border-collapse border-spacing-0", children }) }),
|
|
1039
|
+
th: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { ...props, className: "border bg-muted/50 px-3 py-2 text-left text-sm font-semibold", children }),
|
|
1040
|
+
td: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { ...props, className: "border bg-background px-3 py-2 align-top text-sm", children }),
|
|
1041
|
+
ul: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { ...props, className: "mb-2 ml-6 list-disc last:mb-0", children }),
|
|
1042
|
+
ol: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ol", { ...props, className: "mb-2 ml-6 list-decimal last:mb-0", children })
|
|
1043
|
+
},
|
|
1044
|
+
children: text
|
|
1045
|
+
}
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
function ChatBubble({ text, mine }) {
|
|
1049
|
+
if (text.trim() === "") {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1053
|
+
"div",
|
|
1054
|
+
{
|
|
1055
|
+
className: (0, import_utils.cn)(
|
|
1056
|
+
"w-fit max-w-[85%] rounded-md px-4 py-3 text-sm leading-6 shadow-xs sm:max-w-2xl",
|
|
1057
|
+
mine ? "bg-secondary/85 text-foreground" : "bg-muted/70 text-foreground"
|
|
1058
|
+
),
|
|
1059
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownBlock, { text })
|
|
1060
|
+
}
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
function useStorageDownloadUrl(room, path) {
|
|
1064
|
+
const [url, setUrl] = (0, import_react.useState)(null);
|
|
1065
|
+
(0, import_react.useEffect)(() => {
|
|
1066
|
+
let cancelled = false;
|
|
1067
|
+
const normalizedPath = previewPath(path).trim();
|
|
1068
|
+
if (normalizedPath === "") {
|
|
1069
|
+
setUrl(null);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
if (/^https?:\/\//iu.test(normalizedPath)) {
|
|
1073
|
+
setUrl(normalizedPath);
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
void room.storage.downloadUrl(normalizedPath).then((nextUrl) => {
|
|
1077
|
+
if (!cancelled) {
|
|
1078
|
+
setUrl(nextUrl);
|
|
1079
|
+
}
|
|
1080
|
+
}).catch(() => {
|
|
1081
|
+
if (!cancelled) {
|
|
1082
|
+
setUrl(null);
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
return () => {
|
|
1086
|
+
cancelled = true;
|
|
1087
|
+
};
|
|
1088
|
+
}, [path, room]);
|
|
1089
|
+
return url;
|
|
1090
|
+
}
|
|
1091
|
+
function isImagePath(path) {
|
|
1092
|
+
return /\.(avif|bmp|gif|jpe?g|png|svg|webp)$/iu.test(path);
|
|
1093
|
+
}
|
|
1094
|
+
function FileAttachmentView({
|
|
1095
|
+
room,
|
|
1096
|
+
path,
|
|
1097
|
+
openFile
|
|
1098
|
+
}) {
|
|
1099
|
+
const preview = previewPath(path);
|
|
1100
|
+
const url = useStorageDownloadUrl(room, preview);
|
|
1101
|
+
const filename = preview.split("/").pop() ?? preview;
|
|
1102
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1103
|
+
"button",
|
|
1104
|
+
{
|
|
1105
|
+
type: "button",
|
|
1106
|
+
className: "inline-flex max-w-full items-center gap-2 rounded-md bg-muted/60 px-3 py-2 text-left shadow-xs transition-colors hover:bg-muted/80",
|
|
1107
|
+
onClick: () => {
|
|
1108
|
+
if (openFile) {
|
|
1109
|
+
void openFile(preview);
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
if (url) {
|
|
1113
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
children: [
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.FileText, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
1118
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate text-sm font-medium", children: filename }),
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Download, { className: "h-4 w-4 shrink-0 text-muted-foreground" })
|
|
1120
|
+
]
|
|
1121
|
+
}
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
function StorageImageAttachment({
|
|
1125
|
+
room,
|
|
1126
|
+
path
|
|
1127
|
+
}) {
|
|
1128
|
+
const preview = previewPath(path);
|
|
1129
|
+
const url = useStorageDownloadUrl(room, preview);
|
|
1130
|
+
const filename = preview.split("/").pop() ?? "Image";
|
|
1131
|
+
if (!url) {
|
|
1132
|
+
return null;
|
|
1133
|
+
}
|
|
1134
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1135
|
+
"button",
|
|
1136
|
+
{
|
|
1137
|
+
type: "button",
|
|
1138
|
+
className: "block overflow-hidden rounded-md bg-muted/20 shadow-xs transition-opacity hover:opacity-95",
|
|
1139
|
+
onClick: () => {
|
|
1140
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
1141
|
+
},
|
|
1142
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: url, alt: filename, className: "max-h-[312px] w-auto max-w-full object-cover" })
|
|
1143
|
+
}
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
function bytesFromValue(value) {
|
|
1147
|
+
if (value instanceof Uint8Array) {
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1150
|
+
if (value instanceof ArrayBuffer) {
|
|
1151
|
+
return new Uint8Array(value);
|
|
1152
|
+
}
|
|
1153
|
+
if (Array.isArray(value) && value.every((item) => typeof item === "number")) {
|
|
1154
|
+
return Uint8Array.from(value);
|
|
1155
|
+
}
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
async function loadGeneratedImageRecord({
|
|
1159
|
+
room,
|
|
1160
|
+
imageId,
|
|
1161
|
+
imageUri,
|
|
1162
|
+
fallbackMimeType
|
|
1163
|
+
}) {
|
|
1164
|
+
const parsedUri = imageUri ? parseDatasetImageUri(imageUri) : null;
|
|
1165
|
+
const table = parsedUri?.table ?? "images";
|
|
1166
|
+
const namespace = parsedUri?.namespace;
|
|
1167
|
+
const resolvedImageId = parsedUri?.imageId ?? imageId?.trim();
|
|
1168
|
+
if (!resolvedImageId) {
|
|
1169
|
+
return null;
|
|
1170
|
+
}
|
|
1171
|
+
try {
|
|
1172
|
+
const tables = await room.datasets.search({
|
|
1173
|
+
table,
|
|
1174
|
+
namespace,
|
|
1175
|
+
where: { id: resolvedImageId },
|
|
1176
|
+
limit: 1,
|
|
1177
|
+
select: ["data", "mime_type"]
|
|
1178
|
+
});
|
|
1179
|
+
const rows = tables.flatMap((resultTable) => tableToRows(resultTable));
|
|
1180
|
+
const row = rows[0];
|
|
1181
|
+
if (!row) {
|
|
1182
|
+
return null;
|
|
1183
|
+
}
|
|
1184
|
+
const bytes = bytesFromValue(row.data);
|
|
1185
|
+
if (!bytes) {
|
|
1186
|
+
return null;
|
|
1187
|
+
}
|
|
1188
|
+
const storedMimeType = stringValue(row.mime_type);
|
|
1189
|
+
return {
|
|
1190
|
+
bytes,
|
|
1191
|
+
mimeType: storedMimeType ?? fallbackMimeType?.trim() ?? "image/png"
|
|
1192
|
+
};
|
|
1193
|
+
} catch {
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function displayImageSize(width, height) {
|
|
1198
|
+
if (!width || !height || width <= 0 || height <= 0) {
|
|
1199
|
+
return { width: maxPreviewEdgePx, height: maxPreviewEdgePx };
|
|
1200
|
+
}
|
|
1201
|
+
const largestEdge = Math.max(width, height);
|
|
1202
|
+
if (largestEdge <= maxPreviewEdgePx) {
|
|
1203
|
+
return { width, height };
|
|
1204
|
+
}
|
|
1205
|
+
const scale = maxPreviewEdgePx / largestEdge;
|
|
1206
|
+
return { width: width * scale, height: height * scale };
|
|
1207
|
+
}
|
|
1208
|
+
function ImagePlaceholder({
|
|
1209
|
+
image,
|
|
1210
|
+
showSpinner,
|
|
1211
|
+
label
|
|
1212
|
+
}) {
|
|
1213
|
+
const size = displayImageSize(image.width, image.height);
|
|
1214
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1215
|
+
"div",
|
|
1216
|
+
{
|
|
1217
|
+
className: "flex items-center justify-center rounded-md border bg-background text-muted-foreground",
|
|
1218
|
+
style: { width: size.width, height: size.height },
|
|
1219
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex max-w-full flex-col items-center gap-2 px-3 text-center text-xs", children: [
|
|
1220
|
+
showSpinner ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_spinner.Spinner, { className: "h-5 w-5" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ImageOff, { className: "h-5 w-5" }),
|
|
1221
|
+
label?.trim() ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "line-clamp-2", children: label.trim() }) : null
|
|
1222
|
+
] })
|
|
1223
|
+
}
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
function GeneratedImageAttachment({
|
|
1227
|
+
room,
|
|
1228
|
+
image
|
|
1229
|
+
}) {
|
|
1230
|
+
const [record, setRecord] = (0, import_react.useState)(null);
|
|
1231
|
+
const [loading, setLoading] = (0, import_react.useState)(true);
|
|
1232
|
+
const [objectUrl, setObjectUrl] = (0, import_react.useState)(null);
|
|
1233
|
+
const imageUri = image.uri?.trim();
|
|
1234
|
+
const statusDetail = image.statusDetail?.trim();
|
|
1235
|
+
const size = displayImageSize(image.width, image.height);
|
|
1236
|
+
(0, import_react.useEffect)(() => {
|
|
1237
|
+
let cancelled = false;
|
|
1238
|
+
setRecord(null);
|
|
1239
|
+
setLoading(true);
|
|
1240
|
+
if (imageUri && /^https?:\/\//iu.test(imageUri) && !image.imageId) {
|
|
1241
|
+
setLoading(false);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
void loadGeneratedImageRecord({
|
|
1245
|
+
room,
|
|
1246
|
+
imageId: image.imageId,
|
|
1247
|
+
imageUri,
|
|
1248
|
+
fallbackMimeType: image.mimeType
|
|
1249
|
+
}).then((nextRecord) => {
|
|
1250
|
+
if (!cancelled) {
|
|
1251
|
+
setRecord(nextRecord);
|
|
1252
|
+
setLoading(false);
|
|
1253
|
+
}
|
|
1254
|
+
});
|
|
1255
|
+
return () => {
|
|
1256
|
+
cancelled = true;
|
|
1257
|
+
};
|
|
1258
|
+
}, [image.imageId, image.mimeType, imageUri, room]);
|
|
1259
|
+
(0, import_react.useEffect)(() => {
|
|
1260
|
+
if (!record) {
|
|
1261
|
+
setObjectUrl(null);
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const blobBytes = new Uint8Array(record.bytes.byteLength);
|
|
1265
|
+
blobBytes.set(record.bytes);
|
|
1266
|
+
const blob = new Blob([blobBytes], { type: record.mimeType });
|
|
1267
|
+
const nextUrl = URL.createObjectURL(blob);
|
|
1268
|
+
setObjectUrl(nextUrl);
|
|
1269
|
+
return () => {
|
|
1270
|
+
URL.revokeObjectURL(nextUrl);
|
|
1271
|
+
};
|
|
1272
|
+
}, [record]);
|
|
1273
|
+
if (imageUri && /^https?:\/\//iu.test(imageUri) && !image.imageId) {
|
|
1274
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1275
|
+
"button",
|
|
1276
|
+
{
|
|
1277
|
+
type: "button",
|
|
1278
|
+
className: "block overflow-hidden rounded-md bg-muted/20 shadow-xs transition-opacity hover:opacity-95",
|
|
1279
|
+
onClick: () => {
|
|
1280
|
+
window.open(imageUri, "_blank", "noopener,noreferrer");
|
|
1281
|
+
},
|
|
1282
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1283
|
+
"img",
|
|
1284
|
+
{
|
|
1285
|
+
src: imageUri,
|
|
1286
|
+
alt: "Generated image",
|
|
1287
|
+
className: "max-w-full object-contain",
|
|
1288
|
+
style: { width: size.width, height: size.height }
|
|
1289
|
+
}
|
|
1290
|
+
)
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
if (loading) {
|
|
1295
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1296
|
+
ImagePlaceholder,
|
|
1297
|
+
{
|
|
1298
|
+
image,
|
|
1299
|
+
showSpinner: true,
|
|
1300
|
+
label: statusDetail || (isImageGenerationPendingStatus(image.status) ? "Generating image" : "Loading image")
|
|
1301
|
+
}
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
if (!objectUrl) {
|
|
1305
|
+
if (isImageGenerationPendingStatus(image.status)) {
|
|
1306
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ImagePlaceholder, { image, showSpinner: true, label: statusDetail || "Generating image" });
|
|
1307
|
+
}
|
|
1308
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1309
|
+
ImagePlaceholder,
|
|
1310
|
+
{
|
|
1311
|
+
image,
|
|
1312
|
+
showSpinner: false,
|
|
1313
|
+
label: statusDetail || (isImageGenerationFailedStatus(image.status) ? "Image failed" : "Image unavailable")
|
|
1314
|
+
}
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1318
|
+
"button",
|
|
1319
|
+
{
|
|
1320
|
+
type: "button",
|
|
1321
|
+
className: "block overflow-hidden rounded-md bg-muted/20 shadow-xs transition-opacity hover:opacity-95",
|
|
1322
|
+
onClick: () => {
|
|
1323
|
+
window.open(objectUrl, "_blank", "noopener,noreferrer");
|
|
1324
|
+
},
|
|
1325
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1326
|
+
"img",
|
|
1327
|
+
{
|
|
1328
|
+
src: objectUrl,
|
|
1329
|
+
alt: "Generated image",
|
|
1330
|
+
className: "max-w-full object-contain",
|
|
1331
|
+
style: { width: size.width, height: size.height }
|
|
1332
|
+
}
|
|
1333
|
+
)
|
|
1334
|
+
}
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
function AttachmentView({
|
|
1338
|
+
room,
|
|
1339
|
+
path,
|
|
1340
|
+
openFile
|
|
1341
|
+
}) {
|
|
1342
|
+
const preview = previewPath(path);
|
|
1343
|
+
if (isImagePath(preview)) {
|
|
1344
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StorageImageAttachment, { room, path: preview });
|
|
1345
|
+
}
|
|
1346
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileAttachmentView, { room, path: preview, openFile });
|
|
1347
|
+
}
|
|
1348
|
+
function ThreadMessageView({
|
|
1349
|
+
room,
|
|
1350
|
+
message,
|
|
1351
|
+
previous,
|
|
1352
|
+
localParticipantName,
|
|
1353
|
+
agentName,
|
|
1354
|
+
openFile
|
|
1355
|
+
}) {
|
|
1356
|
+
if (message.kind !== "message") {
|
|
1357
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-6 py-1 text-center text-sm text-muted-foreground", children: message.text });
|
|
1358
|
+
}
|
|
1359
|
+
const isAgentMessage = message.role === "agent";
|
|
1360
|
+
const rawAuthorName = message.authorName;
|
|
1361
|
+
const mine = !isAgentMessage && (rawAuthorName === localParticipantName || (!rawAuthorName || rawAuthorName.trim() === "") && message.role === "user");
|
|
1362
|
+
const authorName = rawAuthorName ?? (isAgentMessage ? displayParticipantName(agentName ?? "agent") : localParticipantName);
|
|
1363
|
+
const previousAuthor = previous?.authorName ?? (previous?.role === "agent" ? displayParticipantName(agentName ?? "agent") : localParticipantName);
|
|
1364
|
+
const shouldShowHeader = previous?.kind !== "message" || previousAuthor !== authorName;
|
|
1365
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
1366
|
+
shouldShowHeader && (authorName.trim() !== "" || message.createdAt) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_utils.cn)("flex w-full", mine ? "justify-end" : "justify-start"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_utils.cn)("max-w-[85%] px-1 sm:max-w-2xl", mine ? "text-right" : "text-left"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1367
|
+
"div",
|
|
1368
|
+
{
|
|
1369
|
+
className: (0, import_utils.cn)(
|
|
1370
|
+
"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
|
|
1371
|
+
mine ? "justify-end" : "justify-start"
|
|
1372
|
+
),
|
|
1373
|
+
children: [
|
|
1374
|
+
authorName.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold text-foreground", children: displayParticipantName(authorName) }) : null,
|
|
1375
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: (0, import_chat_thread.timeAgo)(message.createdAt.toISOString()) })
|
|
1376
|
+
]
|
|
1377
|
+
}
|
|
1378
|
+
) }) }) : null,
|
|
1379
|
+
message.text.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_utils.cn)("flex w-full", mine ? "justify-end" : "justify-start"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatBubble, { text: message.text, mine }) }) : null,
|
|
1380
|
+
message.attachments.length > 0 || message.image ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_utils.cn)("flex w-full", mine ? "justify-end" : "justify-start"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1381
|
+
"div",
|
|
1382
|
+
{
|
|
1383
|
+
className: (0, import_utils.cn)(
|
|
1384
|
+
"flex max-w-[85%] flex-wrap gap-3 px-1 sm:max-w-2xl",
|
|
1385
|
+
mine ? "justify-end" : "justify-start"
|
|
1386
|
+
),
|
|
1387
|
+
children: [
|
|
1388
|
+
message.attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1389
|
+
AttachmentView,
|
|
1390
|
+
{
|
|
1391
|
+
room,
|
|
1392
|
+
path: attachment,
|
|
1393
|
+
openFile
|
|
1394
|
+
},
|
|
1395
|
+
`${message.id}:attachment:${attachment}:${index}`
|
|
1396
|
+
)),
|
|
1397
|
+
message.image ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GeneratedImageAttachment, { room, image: message.image }) : null
|
|
1398
|
+
]
|
|
1399
|
+
}
|
|
1400
|
+
) }) : null
|
|
1401
|
+
] });
|
|
1402
|
+
}
|
|
1403
|
+
function PendingMessageView({
|
|
1404
|
+
room,
|
|
1405
|
+
message,
|
|
1406
|
+
localParticipantName
|
|
1407
|
+
}) {
|
|
1408
|
+
const authorName = message.senderName ?? localParticipantName;
|
|
1409
|
+
const createdAt = message.createdAt ?? /* @__PURE__ */ new Date();
|
|
1410
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: (0, import_utils.cn)("flex flex-col gap-2", message.awaitingOnline ? "opacity-70" : null), children: [
|
|
1411
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex w-full justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "max-w-[85%] px-1 text-right sm:max-w-2xl", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-wrap items-center justify-end gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
1412
|
+
authorName.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold text-foreground", children: displayParticipantName(authorName) }) : null,
|
|
1413
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: (0, import_chat_thread.timeAgo)(createdAt.toISOString()) })
|
|
1414
|
+
] }) }) }),
|
|
1415
|
+
message.text.trim() !== "" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex w-full justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChatBubble, { text: message.text, mine: true }) }) : null,
|
|
1416
|
+
message.attachments.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex w-full justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex max-w-[85%] flex-wrap justify-end gap-3 px-1 sm:max-w-2xl", children: message.attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1417
|
+
FileAttachmentView,
|
|
1418
|
+
{
|
|
1419
|
+
room,
|
|
1420
|
+
path: previewPath(attachment)
|
|
1421
|
+
},
|
|
1422
|
+
`${message.messageId}:pending:${attachment}:${index}`
|
|
1423
|
+
)) }) }) : null
|
|
1424
|
+
] });
|
|
1425
|
+
}
|
|
1426
|
+
function EmptyState({
|
|
1427
|
+
title,
|
|
1428
|
+
description
|
|
1429
|
+
}) {
|
|
1430
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mx-auto flex h-full max-w-2xl flex-col items-center justify-center px-6 py-20 text-center", children: [
|
|
1431
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-4xl font-semibold tracking-tight text-foreground sm:text-5xl", children: title }),
|
|
1432
|
+
description?.trim() ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mt-3 max-w-xl text-sm leading-6 text-muted-foreground sm:text-base", children: description }) : null
|
|
1433
|
+
] });
|
|
1434
|
+
}
|
|
1435
|
+
function ErrorBanner({ message }) {
|
|
1436
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mx-auto w-full max-w-[912px] whitespace-pre-wrap rounded-md border border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive", children: message });
|
|
1437
|
+
}
|
|
1438
|
+
function sendThreadSubscriptionMessage({
|
|
1439
|
+
room,
|
|
1440
|
+
agent,
|
|
1441
|
+
messageType,
|
|
1442
|
+
path
|
|
1443
|
+
}) {
|
|
1444
|
+
void room.messaging.sendMessage({
|
|
1445
|
+
to: agent,
|
|
1446
|
+
type: agentRoomMessageType,
|
|
1447
|
+
ignoreOffline: true,
|
|
1448
|
+
message: {
|
|
1449
|
+
payload: { type: messageType, thread_id: path }
|
|
1450
|
+
}
|
|
1451
|
+
}).catch(() => void 0);
|
|
1452
|
+
}
|
|
1453
|
+
function DatasetChatThread({
|
|
1454
|
+
room,
|
|
1455
|
+
path,
|
|
1456
|
+
agentName,
|
|
1457
|
+
emptyStateTitle,
|
|
1458
|
+
emptyStateDescription,
|
|
1459
|
+
inputPlaceholder,
|
|
1460
|
+
initialShowCompletedToolCalls = false,
|
|
1461
|
+
openFile
|
|
1462
|
+
}) {
|
|
1463
|
+
const modelRef = (0, import_react.useRef)(createDatasetThreadModel());
|
|
1464
|
+
const [modelVersion, setModelVersion] = (0, import_react.useState)(0);
|
|
1465
|
+
const [attachments, setAttachments] = (0, import_react.useState)([]);
|
|
1466
|
+
const [sendError, setSendError] = (0, import_react.useState)(null);
|
|
1467
|
+
const [showCompletedToolCalls, setShowCompletedToolCalls] = (0, import_react.useState)(initialShowCompletedToolCalls);
|
|
1468
|
+
const status = (0, import_chat_hooks.useThreadStatus)({ room, path, agentName });
|
|
1469
|
+
const agentParticipant = findAgentParticipant(room, agentName);
|
|
1470
|
+
const localParticipantName = getParticipantName(room.localParticipant);
|
|
1471
|
+
const scrollContainerRef = (0, import_react.useRef)(null);
|
|
1472
|
+
const contentRef = (0, import_react.useRef)(null);
|
|
1473
|
+
const stickToBottomRef = (0, import_react.useRef)(true);
|
|
1474
|
+
const bumpModelVersion = (0, import_react.useCallback)(() => {
|
|
1475
|
+
setModelVersion((current) => current + 1);
|
|
1476
|
+
}, []);
|
|
1477
|
+
(0, import_react.useEffect)(() => {
|
|
1478
|
+
const model2 = createDatasetThreadModel();
|
|
1479
|
+
modelRef.current = model2;
|
|
1480
|
+
bumpModelVersion();
|
|
1481
|
+
if (isTmpThreadPath(path)) {
|
|
1482
|
+
model2.ready = true;
|
|
1483
|
+
bumpModelVersion();
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
let cancelled = false;
|
|
1487
|
+
let retryTimer = null;
|
|
1488
|
+
let iterator = null;
|
|
1489
|
+
const scheduleRetry = () => {
|
|
1490
|
+
if (retryTimer !== null) {
|
|
1491
|
+
clearTimeout(retryTimer);
|
|
1492
|
+
}
|
|
1493
|
+
retryTimer = setTimeout(() => {
|
|
1494
|
+
retryTimer = null;
|
|
1495
|
+
connect();
|
|
1496
|
+
}, 500);
|
|
1497
|
+
};
|
|
1498
|
+
const handleWatchError = (error) => {
|
|
1499
|
+
if (cancelled || modelRef.current !== model2) {
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
if (isDatasetTableNotFoundError(error)) {
|
|
1503
|
+
model2.error = null;
|
|
1504
|
+
model2.fatalError = false;
|
|
1505
|
+
model2.ready = true;
|
|
1506
|
+
scheduleRetry();
|
|
1507
|
+
} else {
|
|
1508
|
+
model2.error = error;
|
|
1509
|
+
model2.fatalError = true;
|
|
1510
|
+
model2.ready = true;
|
|
1511
|
+
}
|
|
1512
|
+
bumpModelVersion();
|
|
1513
|
+
};
|
|
1514
|
+
const connect = () => {
|
|
1515
|
+
if (cancelled || modelRef.current !== model2) {
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
try {
|
|
1519
|
+
const ref = parseDatasetThreadRef(path);
|
|
1520
|
+
iterator = room.datasets.watchTable({ table: ref.table, namespace: ref.namespace })[Symbol.asyncIterator]();
|
|
1521
|
+
} catch (error) {
|
|
1522
|
+
model2.error = error;
|
|
1523
|
+
model2.fatalError = true;
|
|
1524
|
+
model2.ready = true;
|
|
1525
|
+
bumpModelVersion();
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
void (async () => {
|
|
1529
|
+
try {
|
|
1530
|
+
while (!cancelled && modelRef.current === model2 && iterator !== null) {
|
|
1531
|
+
const result = await iterator.next();
|
|
1532
|
+
if (result.done) {
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
if (handleWatchEvent(model2, result.value)) {
|
|
1536
|
+
bumpModelVersion();
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
handleWatchError(error);
|
|
1541
|
+
}
|
|
1542
|
+
})();
|
|
1543
|
+
};
|
|
1544
|
+
connect();
|
|
1545
|
+
return () => {
|
|
1546
|
+
cancelled = true;
|
|
1547
|
+
if (retryTimer !== null) {
|
|
1548
|
+
clearTimeout(retryTimer);
|
|
1549
|
+
}
|
|
1550
|
+
void iterator?.return?.();
|
|
1551
|
+
};
|
|
1552
|
+
}, [bumpModelVersion, path, room]);
|
|
1553
|
+
(0, import_react.useEffect)(() => {
|
|
1554
|
+
const subscription = (0, import_meshagent_react.subscribe)(room.listen(), {
|
|
1555
|
+
next: (event) => {
|
|
1556
|
+
if (!(event instanceof import_meshagent.RoomMessageEvent)) {
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
if (event.message.type !== agentRoomMessageType) {
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
const payload = event.message.message.payload;
|
|
1563
|
+
if (!isRecord(payload)) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
if (applyAgentMessagePayload(modelRef.current, payload, path)) {
|
|
1567
|
+
bumpModelVersion();
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
return () => {
|
|
1572
|
+
subscription.unsubscribe();
|
|
1573
|
+
};
|
|
1574
|
+
}, [bumpModelVersion, path, room]);
|
|
1575
|
+
(0, import_react.useEffect)(() => {
|
|
1576
|
+
if (!agentParticipant) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
sendThreadSubscriptionMessage({
|
|
1580
|
+
room,
|
|
1581
|
+
agent: agentParticipant,
|
|
1582
|
+
messageType: agentThreadOpenType,
|
|
1583
|
+
path
|
|
1584
|
+
});
|
|
1585
|
+
return () => {
|
|
1586
|
+
sendThreadSubscriptionMessage({
|
|
1587
|
+
room,
|
|
1588
|
+
agent: agentParticipant,
|
|
1589
|
+
messageType: agentThreadCloseType,
|
|
1590
|
+
path
|
|
1591
|
+
});
|
|
1592
|
+
};
|
|
1593
|
+
}, [agentParticipant, path, room]);
|
|
1594
|
+
const allMessages = (0, import_react.useMemo)(() => {
|
|
1595
|
+
const model2 = modelRef.current;
|
|
1596
|
+
const mergedRowsByItemId = /* @__PURE__ */ new Map();
|
|
1597
|
+
for (const [itemId, row] of model2.agentRowsByItemId.entries()) {
|
|
1598
|
+
mergedRowsByItemId.set(itemId, row);
|
|
1599
|
+
}
|
|
1600
|
+
for (const [itemId, row] of model2.rowsByItemId.entries()) {
|
|
1601
|
+
mergedRowsByItemId.set(itemId, row);
|
|
1602
|
+
}
|
|
1603
|
+
return Array.from(mergedRowsByItemId.values()).sort(compareDatasetThreadRows).map(messageForRow).filter((message) => message !== null);
|
|
1604
|
+
}, [modelVersion]);
|
|
1605
|
+
const visibleMessages = (0, import_react.useMemo)(
|
|
1606
|
+
() => allMessages.filter((message) => shouldRenderDatasetThreadMessage(message, showCompletedToolCalls)),
|
|
1607
|
+
[allMessages, showCompletedToolCalls]
|
|
1608
|
+
);
|
|
1609
|
+
const hiddenCompletedToolCallCount = (0, import_react.useMemo)(
|
|
1610
|
+
() => allMessages.filter((message) => message.kind === "tool_call").length,
|
|
1611
|
+
[allMessages]
|
|
1612
|
+
);
|
|
1613
|
+
const pendingMessages = (0, import_react.useMemo)(() => {
|
|
1614
|
+
const combined = /* @__PURE__ */ new Map();
|
|
1615
|
+
for (const pending of status.pendingMessages) {
|
|
1616
|
+
combined.set(pending.messageId, pending);
|
|
1617
|
+
}
|
|
1618
|
+
const values = Array.from(combined.values()).filter((pending) => !allMessages.some((message) => datasetThreadMessageMatchesPendingAgentMessage(message, pending)));
|
|
1619
|
+
return [
|
|
1620
|
+
...values.filter((message) => !message.awaitingAcceptance),
|
|
1621
|
+
...values.filter((message) => message.awaitingAcceptance)
|
|
1622
|
+
];
|
|
1623
|
+
}, [allMessages, status.pendingMessages]);
|
|
1624
|
+
const canInterruptActiveTurn = status.supportsAgentMessages && status.turnId != null;
|
|
1625
|
+
const cancelTurn = (0, import_react.useCallback)(async () => {
|
|
1626
|
+
if (!status.turnId?.trim() || !agentParticipant) {
|
|
1627
|
+
return;
|
|
1628
|
+
}
|
|
1629
|
+
await room.messaging.sendMessage({
|
|
1630
|
+
to: agentParticipant,
|
|
1631
|
+
type: agentRoomMessageType,
|
|
1632
|
+
message: {
|
|
1633
|
+
payload: {
|
|
1634
|
+
type: agentTurnInterruptType,
|
|
1635
|
+
thread_id: path,
|
|
1636
|
+
turn_id: status.turnId
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
}, [agentParticipant, path, room, status.turnId]);
|
|
1641
|
+
const selectAttachments = (0, import_react.useCallback)((files) => {
|
|
1642
|
+
const nextAttachments = files.map((file) => new import_file_attachment.MeshagentFileUpload(
|
|
1643
|
+
room,
|
|
1644
|
+
`uploaded-files/${file.name}`,
|
|
1645
|
+
(0, import_file_attachment.fileToAsyncIterable)(file),
|
|
1646
|
+
file.size
|
|
1647
|
+
));
|
|
1648
|
+
setAttachments((current) => [...current, ...nextAttachments]);
|
|
1649
|
+
}, [room]);
|
|
1650
|
+
const handleSend = (0, import_react.useCallback)(async (message) => {
|
|
1651
|
+
if (message.text.trim() === "" && message.attachments.length === 0) {
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (!agentParticipant) {
|
|
1655
|
+
setSendError("This thread requires an online agent that supports agent messages.");
|
|
1656
|
+
return;
|
|
1657
|
+
}
|
|
1658
|
+
const isSteer = status.mode === "steerable" && status.turnId != null;
|
|
1659
|
+
const normalizedAttachments = message.attachments.map(normalizeAgentAttachmentUrl).filter((attachment) => attachment !== null);
|
|
1660
|
+
const senderName = localParticipantName.trim() || void 0;
|
|
1661
|
+
upsertAgentRow({
|
|
1662
|
+
model: modelRef.current,
|
|
1663
|
+
itemId: message.id,
|
|
1664
|
+
turnId: null,
|
|
1665
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1666
|
+
data: {
|
|
1667
|
+
kind: "message",
|
|
1668
|
+
role: "user",
|
|
1669
|
+
text: message.text,
|
|
1670
|
+
sender_name: senderName,
|
|
1671
|
+
attachments: normalizedAttachments
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
bumpModelVersion();
|
|
1675
|
+
try {
|
|
1676
|
+
const payload = {
|
|
1677
|
+
type: isSteer ? agentTurnSteerType : agentTurnStartType,
|
|
1678
|
+
thread_id: path,
|
|
1679
|
+
message_id: message.id,
|
|
1680
|
+
content: agentInputContent(message.text, message.attachments)
|
|
1681
|
+
};
|
|
1682
|
+
if (isSteer && status.turnId) {
|
|
1683
|
+
payload.turn_id = status.turnId;
|
|
1684
|
+
}
|
|
1685
|
+
await room.messaging.sendMessage({
|
|
1686
|
+
to: agentParticipant,
|
|
1687
|
+
type: agentRoomMessageType,
|
|
1688
|
+
message: { payload }
|
|
1689
|
+
});
|
|
1690
|
+
setSendError(null);
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
modelRef.current.agentRowsByItemId.delete(message.id);
|
|
1693
|
+
bumpModelVersion();
|
|
1694
|
+
setSendError(describeError(error));
|
|
1695
|
+
}
|
|
1696
|
+
}, [
|
|
1697
|
+
agentParticipant,
|
|
1698
|
+
bumpModelVersion,
|
|
1699
|
+
localParticipantName,
|
|
1700
|
+
path,
|
|
1701
|
+
room,
|
|
1702
|
+
status.mode,
|
|
1703
|
+
status.turnId
|
|
1704
|
+
]);
|
|
1705
|
+
const hasWireBackedContent = modelRef.current.agentRowsByItemId.size > 0 || status.pendingMessages.length > 0 || pendingMessages.length > 0;
|
|
1706
|
+
const model = modelRef.current;
|
|
1707
|
+
const statusText = status.text?.trim() || null;
|
|
1708
|
+
const hasOverlay = statusText != null;
|
|
1709
|
+
const lastVisibleMessage = visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1] : null;
|
|
1710
|
+
const lastMessageKey = `${lastVisibleMessage?.id ?? ""}:${lastVisibleMessage?.text.length ?? 0}:${pendingMessages.length}:${modelVersion}`;
|
|
1711
|
+
(0, import_react.useEffect)(() => {
|
|
1712
|
+
const container = scrollContainerRef.current;
|
|
1713
|
+
if (!container) {
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
stickToBottomRef.current = true;
|
|
1717
|
+
container.scrollTop = container.scrollHeight;
|
|
1718
|
+
}, [path]);
|
|
1719
|
+
(0, import_react.useEffect)(() => {
|
|
1720
|
+
const container = scrollContainerRef.current;
|
|
1721
|
+
if (!container || !stickToBottomRef.current) {
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
container.scrollTop = container.scrollHeight;
|
|
1725
|
+
}, [hasOverlay, lastMessageKey, statusText]);
|
|
1726
|
+
(0, import_react.useEffect)(() => {
|
|
1727
|
+
const container = scrollContainerRef.current;
|
|
1728
|
+
const content = contentRef.current;
|
|
1729
|
+
if (!container || !content || typeof ResizeObserver === "undefined") {
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
const observer = new ResizeObserver(() => {
|
|
1733
|
+
if (stickToBottomRef.current) {
|
|
1734
|
+
container.scrollTop = container.scrollHeight;
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
observer.observe(content);
|
|
1738
|
+
return () => {
|
|
1739
|
+
observer.disconnect();
|
|
1740
|
+
};
|
|
1741
|
+
}, []);
|
|
1742
|
+
if (model.fatalError && model.error != null && !hasWireBackedContent) {
|
|
1743
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "max-w-xl text-center text-sm text-muted-foreground", children: describeError(model.error) }) });
|
|
1744
|
+
}
|
|
1745
|
+
if (!model.ready && !hasWireBackedContent) {
|
|
1746
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex min-h-0 flex-1 items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_spinner.Spinner, { size: "lg", className: "text-muted-foreground" }) });
|
|
1747
|
+
}
|
|
1748
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
1749
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex min-h-0 flex-1 flex-col", children: [
|
|
1750
|
+
hiddenCompletedToolCallCount > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-none absolute inset-x-0 top-0 z-10 flex justify-center px-4 pt-3", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-auto flex w-full max-w-[912px] justify-end", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1751
|
+
import_button.Button,
|
|
1752
|
+
{
|
|
1753
|
+
type: "button",
|
|
1754
|
+
variant: "ghost",
|
|
1755
|
+
size: "sm",
|
|
1756
|
+
className: "rounded-full border bg-background/90 backdrop-blur",
|
|
1757
|
+
onClick: () => {
|
|
1758
|
+
setShowCompletedToolCalls((current) => !current);
|
|
1759
|
+
},
|
|
1760
|
+
children: showCompletedToolCalls ? "Hide tool calls" : "Show tool calls"
|
|
1761
|
+
}
|
|
1762
|
+
) }) }) : null,
|
|
1763
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1764
|
+
"div",
|
|
1765
|
+
{
|
|
1766
|
+
ref: scrollContainerRef,
|
|
1767
|
+
className: "min-h-0 flex-1 overflow-y-auto overflow-x-hidden [overflow-anchor:none]",
|
|
1768
|
+
onScroll: (event) => {
|
|
1769
|
+
stickToBottomRef.current = isNearBottom(event.currentTarget);
|
|
1770
|
+
},
|
|
1771
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1772
|
+
"div",
|
|
1773
|
+
{
|
|
1774
|
+
ref: contentRef,
|
|
1775
|
+
className: (0, import_utils.cn)(
|
|
1776
|
+
"mx-auto flex min-h-full w-full max-w-[912px] flex-col gap-8 px-4 pt-6",
|
|
1777
|
+
visibleMessages.length > 0 || pendingMessages.length > 0 ? "justify-end" : null,
|
|
1778
|
+
hasOverlay ? "pb-24" : "pb-6"
|
|
1779
|
+
),
|
|
1780
|
+
children: [
|
|
1781
|
+
visibleMessages.length === 0 && pendingMessages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1782
|
+
EmptyState,
|
|
1783
|
+
{
|
|
1784
|
+
title: emptyStateTitle ?? "Chat to get started",
|
|
1785
|
+
description: emptyStateDescription
|
|
1786
|
+
}
|
|
1787
|
+
) : null,
|
|
1788
|
+
visibleMessages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1789
|
+
ThreadMessageView,
|
|
1790
|
+
{
|
|
1791
|
+
room,
|
|
1792
|
+
message,
|
|
1793
|
+
previous: index > 0 ? visibleMessages[index - 1] : null,
|
|
1794
|
+
localParticipantName,
|
|
1795
|
+
agentName,
|
|
1796
|
+
openFile
|
|
1797
|
+
},
|
|
1798
|
+
message.id
|
|
1799
|
+
)),
|
|
1800
|
+
pendingMessages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1801
|
+
PendingMessageView,
|
|
1802
|
+
{
|
|
1803
|
+
room,
|
|
1804
|
+
message,
|
|
1805
|
+
localParticipantName
|
|
1806
|
+
},
|
|
1807
|
+
`pending:${message.messageId}`
|
|
1808
|
+
))
|
|
1809
|
+
]
|
|
1810
|
+
}
|
|
1811
|
+
)
|
|
1812
|
+
}
|
|
1813
|
+
),
|
|
1814
|
+
hasOverlay ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 flex justify-center px-4 pb-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-auto w-full max-w-[912px]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1815
|
+
import_chat_typing_indicator.ChatTypingIndicator,
|
|
1816
|
+
{
|
|
1817
|
+
typing: false,
|
|
1818
|
+
thinking: false,
|
|
1819
|
+
statusText,
|
|
1820
|
+
startedAt: status.startedAt,
|
|
1821
|
+
onCancel: canInterruptActiveTurn ? cancelTurn : void 0,
|
|
1822
|
+
showCancelButton: status.mode != null,
|
|
1823
|
+
cancelEnabled: true
|
|
1824
|
+
}
|
|
1825
|
+
) }) }) : null
|
|
1826
|
+
] }),
|
|
1827
|
+
sendError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-4 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBanner, { message: sendError }) }) : null,
|
|
1828
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1829
|
+
import_chat_input.ChatInput,
|
|
1830
|
+
{
|
|
1831
|
+
onSubmit: handleSend,
|
|
1832
|
+
attachments,
|
|
1833
|
+
onFilesSelected: selectAttachments,
|
|
1834
|
+
setAttachments,
|
|
1835
|
+
disabled: agentParticipant == null,
|
|
1836
|
+
placeholder: inputPlaceholder ?? (agentParticipant ? "Type a message" : `Waiting for ${displayParticipantName(agentName ?? "agent")}`)
|
|
1837
|
+
}
|
|
1838
|
+
)
|
|
1839
|
+
] });
|
|
1840
|
+
}
|