@meshagent/meshagent-tailwind 0.38.4 → 0.39.1

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.
@@ -18,6 +18,8 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var chat_hooks_exports = {};
20
20
  __export(chat_hooks_exports, {
21
+ ChatThreadStatusState: () => ChatThreadStatusState,
22
+ PendingAgentMessage: () => PendingAgentMessage,
21
23
  formatThreadStatusText: () => formatThreadStatusText,
22
24
  useChatThread: () => useChatThread,
23
25
  useThreadStatus: () => useThreadStatus
@@ -27,6 +29,103 @@ var import_react = require("react");
27
29
  var import_meshagent = require("@meshagent/meshagent");
28
30
  var import_meshagent_react = require("@meshagent/meshagent-react");
29
31
  var import_file_attachment = require("./file-attachment");
32
+ const agentTurnSteerType = "meshagent.agent.turn.steer";
33
+ class PendingAgentMessage {
34
+ messageId;
35
+ messageType;
36
+ threadPath;
37
+ text;
38
+ attachments;
39
+ senderName;
40
+ awaitingAcceptance;
41
+ awaitingOnline;
42
+ constructor({
43
+ messageId,
44
+ messageType,
45
+ threadPath,
46
+ text,
47
+ attachments,
48
+ senderName,
49
+ awaitingAcceptance = false,
50
+ awaitingOnline = false
51
+ }) {
52
+ this.messageId = messageId;
53
+ this.messageType = messageType;
54
+ this.threadPath = threadPath;
55
+ this.text = text;
56
+ this.attachments = attachments;
57
+ this.senderName = senderName;
58
+ this.awaitingAcceptance = awaitingAcceptance;
59
+ this.awaitingOnline = awaitingOnline;
60
+ }
61
+ static fromQueueJson(json) {
62
+ const content = json["content"];
63
+ const textParts = [];
64
+ const attachments = [];
65
+ if (Array.isArray(content)) {
66
+ for (const item of content) {
67
+ if (item == null || typeof item !== "object") {
68
+ continue;
69
+ }
70
+ const obj = item;
71
+ const type = obj["type"];
72
+ if (type === "text") {
73
+ const text = obj["text"];
74
+ if (typeof text === "string" && text.trim().length > 0) {
75
+ textParts.push(text);
76
+ }
77
+ } else if (type === "file") {
78
+ const url = obj["url"];
79
+ if (typeof url === "string" && url.trim().length > 0) {
80
+ attachments.push(url);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ const senderName = json["sender_name"];
86
+ const messageType = json["message_type"];
87
+ const messageId = json["message_id"];
88
+ const threadPath = json["thread_id"];
89
+ return new PendingAgentMessage({
90
+ messageId: typeof messageId === "string" ? messageId : crypto.randomUUID(),
91
+ messageType: typeof messageType === "string" ? messageType : agentTurnSteerType,
92
+ threadPath: typeof threadPath === "string" ? threadPath : "",
93
+ text: textParts.join("\n\n"),
94
+ attachments,
95
+ senderName: typeof senderName === "string" && senderName.trim().length > 0 ? senderName.trim() : void 0,
96
+ awaitingOnline: false
97
+ });
98
+ }
99
+ }
100
+ class ChatThreadStatusState {
101
+ text;
102
+ startedAt;
103
+ mode;
104
+ turnId;
105
+ pendingMessages;
106
+ pendingItemId;
107
+ supportsAgentMessages;
108
+ constructor({
109
+ text,
110
+ startedAt,
111
+ mode,
112
+ turnId,
113
+ pendingMessages = [],
114
+ pendingItemId,
115
+ supportsAgentMessages: supportsAgentMessages2 = false
116
+ }) {
117
+ this.text = text;
118
+ this.startedAt = startedAt;
119
+ this.mode = mode;
120
+ this.turnId = turnId;
121
+ this.pendingMessages = pendingMessages;
122
+ this.pendingItemId = pendingItemId;
123
+ this.supportsAgentMessages = supportsAgentMessages2;
124
+ }
125
+ get hasStatus() {
126
+ return this.text != null && this.text.trim().length > 0;
127
+ }
128
+ }
30
129
  function getParticipantName(participant) {
31
130
  const name = participant.getAttribute("name");
32
131
  return typeof name === "string" ? name.trim() : "";
@@ -109,73 +208,161 @@ function threadStatusAttributeCandidates(path, prefix) {
109
208
  }
110
209
  return [`${prefix}.${path}`, `${prefix}./${path}`];
111
210
  }
112
- function resolveThreadStatus({ room, path, agentName }) {
113
- const normalizedAgentName = agentName?.trim();
114
- const remoteParticipants = room.messaging.remoteParticipants;
115
- const candidates = normalizedAgentName && normalizedAgentName !== "" ? remoteParticipants.filter((participant) => getParticipantName(participant) === normalizedAgentName) : remoteParticipants.filter((participant) => participant.role === "agent" || supportsAgentMessages(participant));
116
- const textKeys = threadStatusAttributeCandidates(path, "thread.status.text");
117
- const legacyKeys = threadStatusAttributeCandidates(path, "thread.status");
118
- const modeKeys = threadStatusAttributeCandidates(path, "thread.status.mode");
119
- const startedAtKeys = threadStatusAttributeCandidates(path, "thread.status.started_at");
120
- let text = null;
121
- let mode = null;
122
- let startedAt = null;
123
- let hasAgentMessageSupport = false;
211
+ function parsePendingMessagesStatus(participant, path) {
212
+ for (const key of threadStatusAttributeCandidates(
213
+ path,
214
+ "thread.status.pending_messages"
215
+ )) {
216
+ const value = participant.getAttribute(key);
217
+ if (typeof value !== "string" || value.trim().length === 0) {
218
+ continue;
219
+ }
220
+ try {
221
+ const decoded = JSON.parse(value.trim());
222
+ if (decoded != null && typeof decoded === "object" && !Array.isArray(decoded)) {
223
+ return decoded;
224
+ }
225
+ } catch (_) {
226
+ }
227
+ }
228
+ return void 0;
229
+ }
230
+ function resolveThreadStatus({
231
+ room,
232
+ path,
233
+ agentName,
234
+ previous
235
+ }) {
236
+ const keyCandidates = threadStatusAttributeCandidates(path, "thread.status");
237
+ const textKeyCandidates = threadStatusAttributeCandidates(path, "thread.status.text");
238
+ const modeKeyCandidates = threadStatusAttributeCandidates(path, "thread.status.mode");
239
+ const startedAtKeyCandidates = threadStatusAttributeCandidates(path, "thread.status.started_at");
240
+ const pendingItemIdKeyCandidates = threadStatusAttributeCandidates(path, "thread.status.pending_item_id");
241
+ const candidates = agentName != null ? room.messaging.remoteParticipants.filter(
242
+ (participant) => participant.getAttribute("name") === agentName
243
+ ) : room.messaging.remoteParticipants.filter(
244
+ (participant) => participant.role === "agent" || supportsAgentMessages(participant)
245
+ );
246
+ let nextStatus;
247
+ let nextMode;
248
+ let nextStartedAt;
249
+ let nextTurnId;
250
+ let nextPendingMessages = [];
251
+ let nextPendingItemId;
252
+ let nextSupportsAgentMessages = false;
124
253
  for (const participant of candidates) {
125
- hasAgentMessageSupport = hasAgentMessageSupport || supportsAgentMessages(participant);
126
- if (text === null) {
127
- for (const key of [...textKeys, ...legacyKeys]) {
254
+ if (supportsAgentMessages(participant)) {
255
+ nextSupportsAgentMessages = true;
256
+ }
257
+ if (nextStatus == null) {
258
+ for (const key of textKeyCandidates) {
128
259
  const value = participant.getAttribute(key);
129
- if (typeof value === "string" && value.trim() !== "") {
130
- text = value.trim();
260
+ if (typeof value === "string" && value.trim().length > 0) {
261
+ nextStatus = value.trim();
131
262
  break;
132
263
  }
133
264
  }
134
265
  }
135
- if (mode === null) {
136
- for (const key of modeKeys) {
266
+ if (nextStatus == null) {
267
+ for (const key of keyCandidates) {
137
268
  const value = participant.getAttribute(key);
138
- if (typeof value !== "string") {
139
- continue;
140
- }
141
- const normalized = value.trim().toLowerCase();
142
- if (normalized === "busy" || normalized === "steerable") {
143
- mode = normalized;
269
+ if (typeof value === "string" && value.trim().length > 0) {
270
+ nextStatus = value.trim();
144
271
  break;
145
272
  }
146
273
  }
147
274
  }
148
- if (startedAt === null) {
149
- for (const key of startedAtKeys) {
275
+ const pendingStatus = parsePendingMessagesStatus(participant, path);
276
+ if (pendingStatus != null && typeof pendingStatus === "object") {
277
+ if (nextTurnId == null) {
278
+ const turnId = pendingStatus["turn_id"];
279
+ if (typeof turnId === "string" && turnId.trim().length > 0) {
280
+ nextTurnId = turnId.trim();
281
+ }
282
+ }
283
+ if (nextPendingMessages.length === 0) {
284
+ const messages = pendingStatus["messages"];
285
+ if (Array.isArray(messages)) {
286
+ nextPendingMessages = messages.flatMap((item) => {
287
+ if (item != null && typeof item === "object") {
288
+ return [
289
+ PendingAgentMessage.fromQueueJson(
290
+ item
291
+ )
292
+ ];
293
+ }
294
+ return [];
295
+ });
296
+ }
297
+ }
298
+ }
299
+ if (nextMode == null) {
300
+ for (const key of modeKeyCandidates) {
301
+ const value = participant.getAttribute(key);
302
+ if (typeof value === "string") {
303
+ const normalized = value.trim().toLowerCase();
304
+ if (normalized === "busy" || normalized === "steerable") {
305
+ nextMode = normalized;
306
+ break;
307
+ }
308
+ }
309
+ }
310
+ }
311
+ if (nextStartedAt == null) {
312
+ for (const key of startedAtKeyCandidates) {
150
313
  const value = participant.getAttribute(key);
151
- if (typeof value !== "string" || value.trim() === "") {
314
+ if (typeof value !== "string") {
152
315
  continue;
153
316
  }
154
- const parsed = new Date(value);
317
+ const normalized = value.trim();
318
+ if (normalized.length === 0) {
319
+ continue;
320
+ }
321
+ const parsed = new Date(normalized);
155
322
  if (!Number.isNaN(parsed.getTime())) {
156
- startedAt = parsed;
323
+ nextStartedAt = parsed;
324
+ break;
325
+ }
326
+ }
327
+ }
328
+ if (nextPendingItemId == null) {
329
+ for (const key of pendingItemIdKeyCandidates) {
330
+ const value = participant.getAttribute(key);
331
+ if (typeof value === "string" && value.trim().length > 0) {
332
+ nextPendingItemId = value.trim();
157
333
  break;
158
334
  }
159
335
  }
160
336
  }
337
+ if (nextStatus != null && nextMode != null && nextStartedAt != null && nextTurnId != null && nextPendingItemId != null) {
338
+ break;
339
+ }
161
340
  }
162
- if (text === null) {
163
- return {
164
- text: null,
165
- mode: null,
166
- startedAt: null,
167
- supportsAgentMessages: hasAgentMessageSupport
168
- };
341
+ if (nextStatus == null) {
342
+ return new ChatThreadStatusState({
343
+ supportsAgentMessages: nextSupportsAgentMessages
344
+ });
169
345
  }
170
- return {
171
- text,
172
- mode: mode ?? "busy",
173
- startedAt,
174
- supportsAgentMessages: hasAgentMessageSupport
175
- };
346
+ nextMode ??= "busy";
347
+ nextStartedAt ??= previous?.hasStatus === true ? previous.startedAt : /* @__PURE__ */ new Date();
348
+ return new ChatThreadStatusState({
349
+ text: nextStatus,
350
+ startedAt: nextStartedAt,
351
+ mode: nextMode,
352
+ turnId: nextTurnId,
353
+ pendingMessages: nextPendingMessages,
354
+ pendingItemId: nextPendingItemId,
355
+ supportsAgentMessages: nextSupportsAgentMessages
356
+ });
357
+ }
358
+ function pendingMessagesEqual(left, right) {
359
+ return left.length === right.length && left.every((message, index) => {
360
+ const other = right[index];
361
+ return message.messageId === other.messageId && message.messageType === other.messageType && message.text === other.text && stringArraysEqual(message.attachments, other.attachments) && message.senderName === other.senderName;
362
+ });
176
363
  }
177
364
  function threadStatusEquals(left, right) {
178
- return left.text === right.text && left.mode === right.mode && left.startedAt?.getTime() === right.startedAt?.getTime() && left.supportsAgentMessages === right.supportsAgentMessages;
365
+ return left.text === right.text && left.mode === right.mode && left.startedAt?.getTime() === right.startedAt?.getTime() && left.turnId === right.turnId && left.pendingItemId === right.pendingItemId && pendingMessagesEqual(left.pendingMessages, right.pendingMessages) && left.supportsAgentMessages === right.supportsAgentMessages;
179
366
  }
180
367
  function formatThreadStatusText(text, startedAt) {
181
368
  if (!(startedAt instanceof Date) || Number.isNaN(startedAt.getTime())) {
@@ -360,8 +547,15 @@ function useThreadStatus({ room, path, agentName }) {
360
547
  const [status, setStatus] = (0, import_react.useState)(() => resolveThreadStatus({ room, path, agentName }));
361
548
  (0, import_react.useEffect)(() => {
362
549
  const updateStatus = () => {
363
- const nextStatus = resolveThreadStatus({ room, path, agentName });
364
- setStatus((currentStatus) => threadStatusEquals(currentStatus, nextStatus) ? currentStatus : nextStatus);
550
+ setStatus((currentStatus) => {
551
+ const nextStatus = resolveThreadStatus({
552
+ room,
553
+ path,
554
+ agentName,
555
+ previous: currentStatus
556
+ });
557
+ return threadStatusEquals(currentStatus, nextStatus) ? currentStatus : nextStatus;
558
+ });
365
559
  };
366
560
  const roomSubscription = (0, import_meshagent_react.subscribe)(room.listen(), {
367
561
  next: (event) => {
@@ -372,18 +566,15 @@ function useThreadStatus({ room, path, agentName }) {
372
566
  updateStatus();
373
567
  }
374
568
  });
375
- const handleParticipantsChanged = () => {
376
- updateStatus();
377
- };
378
- room.messaging.on("participant_added", handleParticipantsChanged);
379
- room.messaging.on("participant_removed", handleParticipantsChanged);
380
- room.messaging.on("messaging_enabled", handleParticipantsChanged);
569
+ room.messaging.on("participant_added", updateStatus);
570
+ room.messaging.on("participant_removed", updateStatus);
571
+ room.messaging.on("messaging_enabled", updateStatus);
381
572
  updateStatus();
382
573
  return () => {
383
574
  roomSubscription.unsubscribe();
384
- room.messaging.off("participant_added", handleParticipantsChanged);
385
- room.messaging.off("participant_removed", handleParticipantsChanged);
386
- room.messaging.off("messaging_enabled", handleParticipantsChanged);
575
+ room.messaging.off("participant_added", updateStatus);
576
+ room.messaging.off("participant_removed", updateStatus);
577
+ room.messaging.off("messaging_enabled", updateStatus);
387
578
  };
388
579
  }, [agentName, path, room]);
389
580
  return status;
package/dist/esm/Chat.js CHANGED
@@ -111,7 +111,7 @@ function EmptyState({
111
111
  title,
112
112
  description
113
113
  }) {
114
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-2xl flex-col items-center justify-center px-6 py-20 text-center", children: [
114
+ return /* @__PURE__ */ jsxs("div", { className: "h-full max-w-2xl flex flex-col items-center justify-center px-6 py-20 text-center", children: [
115
115
  /* @__PURE__ */ jsx("h2", { className: "text-4xl font-semibold tracking-tight text-foreground sm:text-5xl", children: title }),
116
116
  description?.trim() ? /* @__PURE__ */ jsx("p", { className: "mt-3 max-w-xl text-sm leading-6 text-muted-foreground sm:text-base", children: description }) : null
117
117
  ] });
@@ -381,7 +381,7 @@ function Chat({
381
381
  placeholder: agentName?.trim() ? `Type a message or @${displayParticipantName(agentName)}` : "Type a message"
382
382
  }
383
383
  );
384
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [
384
+ return /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-1 flex-col", children: [
385
385
  activePath ? /* @__PURE__ */ jsx(
386
386
  ResolvedChatView,
387
387
  {
@@ -402,7 +402,7 @@ function Chat({
402
402
  ] }),
403
403
  composer,
404
404
  newThreadError ? /* @__PURE__ */ jsx(ErrorBanner, { message: newThreadError }) : null
405
- ] }) }) : /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [
405
+ ] }) }) : /* @__PURE__ */ jsxs("div", { className: "h-full flex flex-1 flex-col", children: [
406
406
  /* @__PURE__ */ jsx("div", { className: "flex-1", children: emptyStateTitle ? /* @__PURE__ */ jsx(EmptyState, { title: emptyStateTitle, description: emptyStateDescription }) : null }),
407
407
  pendingStatusText ? /* @__PURE__ */ jsx("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-[912px] text-sm text-muted-foreground", children: pendingStatusText }) }) : null,
408
408
  newThreadError ? /* @__PURE__ */ jsx("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsx(ErrorBanner, { message: newThreadError }) }) : null,
@@ -1,5 +1,5 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
3
3
  import { useDocumentChanged } from "@meshagent/meshagent-react";
4
4
  import {
5
5
  AlertTriangle,
@@ -11,6 +11,17 @@ import {
11
11
  import { useThreadStatus } from "./chat-hooks";
12
12
  import { Chat } from "./Chat";
13
13
  import { Button } from "./components/ui/button";
14
+ import {
15
+ Dialog,
16
+ DialogClose,
17
+ DialogContent,
18
+ DialogDescription,
19
+ DialogFooter,
20
+ DialogHeader,
21
+ DialogTitle
22
+ } from "./components/ui/dialog";
23
+ import { Input } from "./components/ui/input";
24
+ import { Label } from "./components/ui/label";
14
25
  import { Spinner } from "./components/ui/spinner";
15
26
  import {
16
27
  ChatThreadDisplayMode,
@@ -110,10 +121,7 @@ function useIsWideLayout(minWidth) {
110
121
  }, [minWidth]);
111
122
  return matches;
112
123
  }
113
- function useThreadListDocument({
114
- room,
115
- path
116
- }) {
124
+ function useThreadListDocument({ room, path }) {
117
125
  const [document, setDocument] = useState(null);
118
126
  const [entries, setEntries] = useState([]);
119
127
  const [loading, setLoading] = useState(path !== null);
@@ -180,7 +188,7 @@ function ThreadListRow({
180
188
  icon,
181
189
  trailing
182
190
  }) {
183
- return /* @__PURE__ */ jsx("div", { className: "px-2 py-1", children: /* @__PURE__ */ jsxs(
191
+ return /* @__PURE__ */ jsx("div", { className: "px-2 py-1 cursor-pointer", children: /* @__PURE__ */ jsxs(
184
192
  "div",
185
193
  {
186
194
  className: cn(
@@ -224,14 +232,13 @@ function ThreadListEntryRow({
224
232
  }) {
225
233
  const status = useThreadStatus({ room, path: entry.path, agentName });
226
234
  const iconClassName = selected ? "text-accent-foreground" : "text-muted-foreground";
227
- const hasStatus = status.text?.trim() !== "";
228
235
  return /* @__PURE__ */ jsx(
229
236
  ThreadListRow,
230
237
  {
231
238
  title: entry.name,
232
239
  selected,
233
240
  onClick: () => onSelect(entry),
234
- icon: hasStatus ? /* @__PURE__ */ jsx(Spinner, { size: "sm", className: iconClassName }) : selected ? /* @__PURE__ */ jsx(Check, { className: cn("h-4 w-4", iconClassName) }) : /* @__PURE__ */ jsx(MessageSquare, { className: cn("h-4 w-4", iconClassName) }),
241
+ icon: status.hasStatus ? /* @__PURE__ */ jsx(Spinner, { size: "sm", className: iconClassName }) : selected ? /* @__PURE__ */ jsx(Check, { className: cn("h-4 w-4", iconClassName) }) : /* @__PURE__ */ jsx(MessageSquare, { className: cn("h-4 w-4", iconClassName) }),
235
242
  trailing: /* @__PURE__ */ jsx(
236
243
  Button,
237
244
  {
@@ -259,7 +266,7 @@ function ThreadListPanel({
259
266
  const { entries, error, loading } = threadList;
260
267
  const hasSelectedEntry = selectedThreadPath !== null && entries.some((entry) => entry.path === selectedThreadPath);
261
268
  const showPendingNewThreadSelection = selectedThreadPath === null || !hasSelectedEntry;
262
- return /* @__PURE__ */ jsx("div", { className: "flex h-full min-h-0 flex-col overflow-hidden rounded-2xl border bg-background", children: loading ? /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsx(Spinner, { size: "lg", className: "text-muted-foreground" }) }) : error ? /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6 text-center text-sm text-muted-foreground", children: `Unable to load threads: ${describeError(error)}` }) : /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-y-auto py-1", children: [
269
+ return /* @__PURE__ */ jsx("div", { className: "h-full flex flex-col rounded-md border", children: loading ? /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsx(Spinner, { size: "lg", className: "text-muted-foreground" }) }) : error ? /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center p-6 text-center text-sm text-muted-foreground", children: `Unable to load threads: ${describeError(error)}` }) : /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-1 flex-col overflow-y-auto py-1", children: [
263
270
  /* @__PURE__ */ jsx(
264
271
  ThreadListRow,
265
272
  {
@@ -284,6 +291,52 @@ function ThreadListPanel({
284
291
  ))
285
292
  ] }) });
286
293
  }
294
+ function RenameThreadDialog({
295
+ dialogState,
296
+ onNameChange,
297
+ onOpenChange,
298
+ onSubmit
299
+ }) {
300
+ const inputId = useId();
301
+ const inputRef = useRef(null);
302
+ const trimmedName = dialogState?.value.trim() ?? "";
303
+ const saveDisabled = dialogState === null || trimmedName === "" || trimmedName === dialogState.entry.name;
304
+ return /* @__PURE__ */ jsx(Dialog, { open: dialogState !== null, onOpenChange, children: /* @__PURE__ */ jsx(
305
+ DialogContent,
306
+ {
307
+ showCloseButton: false,
308
+ className: "sm:max-w-[425px]",
309
+ onOpenAutoFocus: (event) => {
310
+ event.preventDefault();
311
+ inputRef.current?.focus();
312
+ inputRef.current?.select();
313
+ },
314
+ children: /* @__PURE__ */ jsxs("form", { className: "space-y-4", onSubmit, children: [
315
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
316
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Rename thread" }),
317
+ /* @__PURE__ */ jsx(DialogDescription, { children: "Use a short and descriptive name" })
318
+ ] }),
319
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
320
+ /* @__PURE__ */ jsx(Label, { htmlFor: inputId, children: "Name" }),
321
+ /* @__PURE__ */ jsx(
322
+ Input,
323
+ {
324
+ ref: inputRef,
325
+ id: inputId,
326
+ value: dialogState?.value ?? "",
327
+ autoComplete: "off",
328
+ onChange: onNameChange
329
+ }
330
+ )
331
+ ] }),
332
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
333
+ /* @__PURE__ */ jsx(DialogClose, { asChild: true, children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", children: "Cancel" }) }),
334
+ /* @__PURE__ */ jsx(Button, { type: "submit", disabled: saveDisabled, children: "Save" })
335
+ ] })
336
+ ] })
337
+ }
338
+ ) });
339
+ }
287
340
  function MultiThreadUnavailable() {
288
341
  return /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center px-4 py-8", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-[912px] rounded-3xl border border-destructive/30 bg-destructive/5 p-6 text-destructive", children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
289
342
  /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-5 w-5 shrink-0" }),
@@ -330,6 +383,7 @@ function ChatBotView({
330
383
  const [internalSelectedThreadPath, setInternalSelectedThreadPath] = useState(() => explicitSelectedThreadPath ?? legacySelectedThreadPath ?? null);
331
384
  const previousLegacySelectedThreadPathRef = useRef(legacySelectedThreadPath);
332
385
  const previousNewThreadResetVersionRef = useRef(newThreadResetVersion);
386
+ const [renameThreadDialog, setRenameThreadDialog] = useState(null);
333
387
  const activeSelectedThreadPath = explicitSelectedThreadPath ?? internalSelectedThreadPath;
334
388
  const resolvedThreadListDocumentPath = useMemo(
335
389
  () => resolvedThreadListPath(threadListPath, { threadDir, agentName }),
@@ -371,6 +425,9 @@ function ChatBotView({
371
425
  handleSelectedThreadPathChanged(normalizedNextPath);
372
426
  emitResolvedThread(normalizedNextPath, displayName);
373
427
  }, [emitResolvedThread, handleSelectedThreadPathChanged]);
428
+ const closeRenameThreadDialog = useCallback(() => {
429
+ setRenameThreadDialog(null);
430
+ }, []);
374
431
  useEffect(() => {
375
432
  if (threadDisplayMode !== ChatThreadDisplayMode.MultiThreadComposer) {
376
433
  previousNewThreadResetVersionRef.current = newThreadResetVersion;
@@ -382,22 +439,43 @@ function ChatBotView({
382
439
  previousNewThreadResetVersionRef.current = newThreadResetVersion;
383
440
  }, [activeSelectedThreadPath, newThreadResetVersion, setSelectedThread, threadDisplayMode]);
384
441
  const handleRenameThread = useCallback((entry) => {
385
- if (typeof window === "undefined") {
386
- return;
442
+ setRenameThreadDialog({
443
+ entry,
444
+ value: entry.name
445
+ });
446
+ }, []);
447
+ const handleRenameThreadNameChanged = useCallback((event) => {
448
+ const nextValue = event.target.value;
449
+ setRenameThreadDialog((currentDialogState) => {
450
+ if (currentDialogState === null) {
451
+ return currentDialogState;
452
+ }
453
+ return {
454
+ ...currentDialogState,
455
+ value: nextValue
456
+ };
457
+ });
458
+ }, []);
459
+ const handleRenameThreadDialogOpenChange = useCallback((open) => {
460
+ if (!open) {
461
+ closeRenameThreadDialog();
387
462
  }
388
- const nextName = window.prompt("Rename thread", entry.name);
389
- if (nextName === null) {
463
+ }, [closeRenameThreadDialog]);
464
+ const handleRenameThreadSubmit = useCallback((event) => {
465
+ event.preventDefault();
466
+ if (renameThreadDialog === null) {
390
467
  return;
391
468
  }
392
- const trimmedName = nextName.trim();
393
- if (trimmedName === "" || trimmedName === entry.name) {
469
+ const trimmedName = renameThreadDialog.value.trim();
470
+ if (trimmedName === "" || trimmedName === renameThreadDialog.entry.name) {
394
471
  return;
395
472
  }
396
- entry.element.setAttribute("name", trimmedName);
397
- if (entry.path === activeSelectedThreadPath) {
398
- emitResolvedThread(entry.path, trimmedName);
473
+ renameThreadDialog.entry.element.setAttribute("name", trimmedName);
474
+ if (renameThreadDialog.entry.path === activeSelectedThreadPath) {
475
+ emitResolvedThread(renameThreadDialog.entry.path, trimmedName);
399
476
  }
400
- }, [activeSelectedThreadPath, emitResolvedThread]);
477
+ closeRenameThreadDialog();
478
+ }, [activeSelectedThreadPath, closeRenameThreadDialog, emitResolvedThread, renameThreadDialog]);
401
479
  if (threadDisplayMode !== ChatThreadDisplayMode.MultiThreadComposer) {
402
480
  return /* @__PURE__ */ jsx(
403
481
  Chat,
@@ -449,31 +527,53 @@ function ChatBotView({
449
527
  }
450
528
  );
451
529
  if (!showThreadList || resolvedThreadListDocumentPath === null) {
452
- return content;
530
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
531
+ content,
532
+ /* @__PURE__ */ jsx(
533
+ RenameThreadDialog,
534
+ {
535
+ dialogState: renameThreadDialog,
536
+ onNameChange: handleRenameThreadNameChanged,
537
+ onOpenChange: handleRenameThreadDialogOpenChange,
538
+ onSubmit: handleRenameThreadSubmit
539
+ }
540
+ )
541
+ ] });
453
542
  }
454
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-1 h-full", isWideLayout ? "flex-row items-stretch" : "flex-col"), children: [
455
- /* @__PURE__ */ jsx("div", { className: "flex flex-col h-full min-h-0 min-w-0 flex-1", children: content }),
543
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
544
+ /* @__PURE__ */ jsxs("div", { className: cn("flex flex-1 h-full", isWideLayout ? "flex-row items-stretch" : "flex-col"), children: [
545
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col h-full min-h-0 min-w-0 flex-1", children: content }),
546
+ /* @__PURE__ */ jsx(
547
+ "div",
548
+ {
549
+ className: cn("shrink-0 mr-4", isWideLayout ? "ml-3" : "mt-3"),
550
+ style: isWideLayout ? { width: threadListWidth } : { height: threadListCollapsedHeight },
551
+ children: /* @__PURE__ */ jsx(
552
+ ThreadListPanel,
553
+ {
554
+ room,
555
+ threadList,
556
+ selectedThreadPath: activeSelectedThreadPath,
557
+ agentName,
558
+ onSelectThread: (entry) => {
559
+ setSelectedThread(entry.path, entry.name);
560
+ },
561
+ onClearSelection: () => {
562
+ setSelectedThread(null, null);
563
+ },
564
+ onRenameThread: handleRenameThread
565
+ }
566
+ )
567
+ }
568
+ )
569
+ ] }),
456
570
  /* @__PURE__ */ jsx(
457
- "div",
571
+ RenameThreadDialog,
458
572
  {
459
- className: cn("shrink-0 mr-4", isWideLayout ? "ml-3" : "mt-3"),
460
- style: isWideLayout ? { width: threadListWidth } : { height: threadListCollapsedHeight },
461
- children: /* @__PURE__ */ jsx(
462
- ThreadListPanel,
463
- {
464
- room,
465
- threadList,
466
- selectedThreadPath: activeSelectedThreadPath,
467
- agentName,
468
- onSelectThread: (entry) => {
469
- setSelectedThread(entry.path, entry.name);
470
- },
471
- onClearSelection: () => {
472
- setSelectedThread(null, null);
473
- },
474
- onRenameThread: handleRenameThread
475
- }
476
- )
573
+ dialogState: renameThreadDialog,
574
+ onNameChange: handleRenameThreadNameChanged,
575
+ onOpenChange: handleRenameThreadDialogOpenChange,
576
+ onSubmit: handleRenameThreadSubmit
477
577
  }
478
578
  )
479
579
  ] });
@@ -505,7 +505,7 @@ function EmptyState({
505
505
  title,
506
506
  description
507
507
  }) {
508
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-2xl flex-col items-center justify-center px-6 py-20 text-center", children: [
508
+ return /* @__PURE__ */ jsxs("div", { className: "h-full mx-auto flex max-w-2xl flex-col items-center justify-center px-6 py-20 text-center", children: [
509
509
  /* @__PURE__ */ jsx("h2", { className: "text-4xl font-semibold tracking-tight text-foreground sm:text-5xl", children: title }),
510
510
  description?.trim() ? /* @__PURE__ */ jsx("p", { className: "mt-3 max-w-xl text-sm leading-6 text-muted-foreground sm:text-base", children: description }) : null
511
511
  ] });