@agents24/chat-react 0.1.6 → 0.1.8

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.
Files changed (42) hide show
  1. package/README.md +29 -12
  2. package/dist/chunk-E3TJ3I6G.js +358 -0
  3. package/dist/chunk-E3TJ3I6G.js.map +1 -0
  4. package/dist/chunk-EWZO4QJI.js +140 -0
  5. package/dist/chunk-EWZO4QJI.js.map +1 -0
  6. package/dist/chunk-UU7OXHL3.js +11 -0
  7. package/dist/chunk-UU7OXHL3.js.map +1 -0
  8. package/dist/context-window.d.ts +38 -0
  9. package/dist/controller.d.ts +22 -0
  10. package/dist/index.cjs +653 -336
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.ts +10 -466
  13. package/dist/index.js +290 -455
  14. package/dist/index.js.map +1 -1
  15. package/dist/latest-thread-scroller.d.ts +54 -0
  16. package/dist/model.d.ts +29 -0
  17. package/dist/renderers.d.ts +10 -0
  18. package/dist/sse.d.ts +3 -0
  19. package/dist/streaming-text.d.ts +24 -0
  20. package/dist/templates/agent-chat-shell.d.ts +25 -0
  21. package/dist/templates/index.cjs +523 -0
  22. package/dist/templates/index.cjs.map +1 -0
  23. package/dist/templates/index.d.ts +1 -0
  24. package/dist/templates/index.js +140 -0
  25. package/dist/templates/index.js.map +1 -0
  26. package/dist/transport.d.ts +29 -0
  27. package/dist/types.d.ts +312 -0
  28. package/dist/ui/adapters.d.ts +36 -0
  29. package/dist/ui/attachment.d.ts +23 -0
  30. package/dist/ui/bubble.d.ts +17 -0
  31. package/dist/ui/index.cjs +943 -0
  32. package/dist/ui/index.cjs.map +1 -0
  33. package/dist/ui/index.d.ts +7 -0
  34. package/dist/ui/index.js +734 -0
  35. package/dist/ui/index.js.map +1 -0
  36. package/dist/ui/marker.d.ts +11 -0
  37. package/dist/ui/message-response.d.ts +7 -0
  38. package/dist/ui/message.d.ts +10 -0
  39. package/dist/ui/utils.d.ts +2 -0
  40. package/dist/viewport.d.ts +2 -0
  41. package/package.json +22 -4
  42. package/dist/index.d.cts +0 -466
package/dist/index.js CHANGED
@@ -1,9 +1,144 @@
1
+ import {
2
+ LatestThreadScroller,
3
+ LatestThreadScrollerButton,
4
+ LatestThreadScrollerContent,
5
+ LatestThreadScrollerItem,
6
+ LatestThreadScrollerOutline,
7
+ LatestThreadScrollerProvider,
8
+ LatestThreadScrollerRoot,
9
+ LatestThreadScrollerViewport
10
+ } from "./chunk-E3TJ3I6G.js";
11
+ import {
12
+ getActiveStreamingTextPartId,
13
+ isActiveStreamingTextPart,
14
+ useStreamingText
15
+ } from "./chunk-EWZO4QJI.js";
16
+
1
17
  // src/controller.ts
2
18
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
19
 
20
+ // src/context-window.ts
21
+ var SOURCE_PRIORITY = {
22
+ unknown: 0,
23
+ heuristic_estimate: 1,
24
+ tokenizer_estimate: 1,
25
+ text_estimate: 1,
26
+ estimated: 1,
27
+ multimodal_estimate: 2,
28
+ hf_tokenizer: 3,
29
+ runtime_tokenizer: 3,
30
+ provider_count_api: 4,
31
+ provider_usage: 5,
32
+ exact: 5
33
+ };
34
+ var STAGE_PRIORITY = {
35
+ preflight: 0,
36
+ sent_prompt: 1,
37
+ final_usage: 2
38
+ };
39
+ function numberOrNull(value) {
40
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
41
+ }
42
+ function stagePriority(window) {
43
+ return STAGE_PRIORITY[window?.stage || "sent_prompt"] ?? STAGE_PRIORITY.sent_prompt;
44
+ }
45
+ function hasRenderableContextWindow(window) {
46
+ const maxTokens = window?.max_tokens;
47
+ return typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0;
48
+ }
49
+ function windowWeight(window) {
50
+ if (!window) return [0, 0];
51
+ return [stagePriority(window), SOURCE_PRIORITY[window.source] || 0];
52
+ }
53
+ function normalizeContextCompression(value) {
54
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
55
+ const payload = value;
56
+ return {
57
+ active: Boolean(payload.active),
58
+ reason: typeof payload.reason === "string" ? payload.reason : null,
59
+ input_tokens: numberOrNull(payload.input_tokens),
60
+ max_tokens: numberOrNull(payload.max_tokens),
61
+ usage_ratio: numberOrNull(payload.usage_ratio),
62
+ full_frame_count: numberOrNull(payload.full_frame_count),
63
+ compact_frame_count: numberOrNull(payload.compact_frame_count),
64
+ dropped_frame_count: numberOrNull(payload.dropped_frame_count),
65
+ artifact_ref_count: numberOrNull(payload.artifact_ref_count),
66
+ compression_trigger_budget: numberOrNull(payload.compression_trigger_budget),
67
+ compression_target_budget: numberOrNull(payload.compression_target_budget),
68
+ compression_target_ratio: numberOrNull(payload.compression_target_ratio),
69
+ threshold_used: numberOrNull(payload.threshold_used)
70
+ };
71
+ }
72
+ function normalizeContextWindow(value) {
73
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
74
+ const payload = value;
75
+ const rawSource = String(payload.source || "").trim();
76
+ const source = rawSource === "exact" || rawSource === "estimated" || rawSource === "unknown" || rawSource === "provider_count_api" || rawSource === "provider_usage" || rawSource === "runtime_tokenizer" || rawSource === "hf_tokenizer" || rawSource === "multimodal_estimate" || rawSource === "text_estimate" || rawSource === "tokenizer_estimate" || rawSource === "heuristic_estimate" ? rawSource : rawSource ? "estimated" : "unknown";
77
+ const rawStage = String(payload.stage || "").trim();
78
+ const stage = rawStage === "preflight" || rawStage === "sent_prompt" || rawStage === "final_usage" ? rawStage : "sent_prompt";
79
+ const rawConfidence = String(payload.confidence || "").trim();
80
+ const confidence = rawConfidence === "exact" || rawConfidence === "high" || rawConfidence === "medium" || rawConfidence === "low" || rawConfidence === "unknown" ? rawConfidence : null;
81
+ return {
82
+ source,
83
+ run_id: typeof payload.run_id === "string" ? payload.run_id : null,
84
+ stage,
85
+ confidence,
86
+ counter: typeof payload.counter === "string" ? payload.counter : null,
87
+ model_id: typeof payload.model_id === "string" ? payload.model_id : null,
88
+ max_tokens: numberOrNull(payload.max_tokens),
89
+ max_tokens_source: typeof payload.max_tokens_source === "string" ? payload.max_tokens_source : null,
90
+ input_tokens: numberOrNull(payload.input_tokens),
91
+ remaining_tokens: numberOrNull(payload.remaining_tokens),
92
+ usage_ratio: numberOrNull(payload.usage_ratio),
93
+ assembly: payload.assembly && typeof payload.assembly === "object" && !Array.isArray(payload.assembly) ? payload.assembly : null,
94
+ context_compression: normalizeContextCompression(payload.context_compression)
95
+ };
96
+ }
97
+ function mergeContextWindow(current, incoming) {
98
+ if (!incoming) return current ?? null;
99
+ if (!current) return incoming;
100
+ if (hasRenderableContextWindow(current) && !hasRenderableContextWindow(incoming)) return current;
101
+ const currentRunId = current.run_id || null;
102
+ const incomingRunId = incoming.run_id || null;
103
+ if (incomingRunId && currentRunId && incomingRunId !== currentRunId) {
104
+ return stagePriority(incoming) >= STAGE_PRIORITY.sent_prompt ? incoming : current;
105
+ }
106
+ const currentWeight = windowWeight(current);
107
+ const incomingWeight = windowWeight(incoming);
108
+ return incomingWeight[0] > currentWeight[0] || incomingWeight[0] === currentWeight[0] && incomingWeight[1] >= currentWeight[1] ? incoming : current;
109
+ }
110
+ function mergeContextWindowUpdate(current, incoming) {
111
+ return mergeContextWindow(current, normalizeContextWindow(incoming));
112
+ }
113
+ function compressionFromContextWindow(contextWindow) {
114
+ if (!contextWindow) return null;
115
+ if (contextWindow.context_compression) return contextWindow.context_compression;
116
+ const assembly = contextWindow.assembly;
117
+ if (!assembly || typeof assembly !== "object" || Array.isArray(assembly)) return null;
118
+ const artifactRefs = Array.isArray(assembly.artifact_refs) ? assembly.artifact_refs : [];
119
+ const compactFrameCount = numberOrNull(assembly.compact_frame_count) ?? 0;
120
+ const droppedFrameCount = numberOrNull(assembly.dropped_frame_count) ?? 0;
121
+ const reason = typeof assembly.compaction_reason === "string" ? assembly.compaction_reason : "none";
122
+ return {
123
+ active: reason !== "none" || compactFrameCount > 0 || droppedFrameCount > 0,
124
+ reason,
125
+ input_tokens: contextWindow.input_tokens ?? null,
126
+ max_tokens: contextWindow.max_tokens ?? null,
127
+ usage_ratio: contextWindow.usage_ratio ?? null,
128
+ full_frame_count: numberOrNull(assembly.full_frame_count),
129
+ compact_frame_count: compactFrameCount,
130
+ dropped_frame_count: droppedFrameCount,
131
+ artifact_ref_count: artifactRefs.length,
132
+ compression_trigger_budget: numberOrNull(assembly.compression_trigger_budget),
133
+ compression_target_budget: numberOrNull(assembly.compression_target_budget),
134
+ compression_target_ratio: numberOrNull(assembly.compression_target_ratio),
135
+ threshold_used: numberOrNull(assembly.threshold_used)
136
+ };
137
+ }
138
+
4
139
  // src/model.ts
5
140
  var DEFAULT_THREAD_PAGE_SIZE = 5;
6
- var isRunningThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running"])).has(String(status || "").toLowerCase());
141
+ var isRunningThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling"])).has(String(status || "").toLowerCase());
7
142
  var createChatId = () => {
8
143
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
9
144
  return `chat-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
@@ -42,6 +177,16 @@ var attachmentsFromTurn = (turn) => (turn.attachments || []).map((attachment, in
42
177
  mediaType: String(record.mime_type || record.mediaType || record.type || "application/octet-stream")
43
178
  };
44
179
  });
180
+ var tokenUsageFromTurn = (turn) => {
181
+ const usage = turn.run_usage;
182
+ if (!usage || typeof usage !== "object") return void 0;
183
+ return {
184
+ inputTokens: usage.input_tokens ?? null,
185
+ outputTokens: usage.output_tokens ?? null,
186
+ totalTokens: usage.total_tokens ?? null,
187
+ usageSource: usage.source ?? null
188
+ };
189
+ };
45
190
  var latestEventPayloadValue = (events, key) => {
46
191
  for (let index = (events || []).length - 1; index >= 0; index -= 1) {
47
192
  const payload = events?.[index]?.payload;
@@ -243,7 +388,8 @@ var turnToMessages = (turn, activeRunId) => {
243
388
  createdAt: turn.completed_at ? new Date(turn.completed_at) : createdAt,
244
389
  parts,
245
390
  reasoningSteps: mergeReasoningSteps(reasoningStepsFromParts(parts), { finalize: !isRunning }),
246
- isFinal: !isRunning
391
+ isFinal: !isRunning,
392
+ tokenUsage: tokenUsageFromTurn(turn)
247
393
  });
248
394
  }
249
395
  return messages;
@@ -260,10 +406,11 @@ var threadPaging = (thread) => {
260
406
  };
261
407
  };
262
408
  var latestContextWindowFromThread = (thread) => {
263
- if (thread.context_window && typeof thread.context_window === "object") return thread.context_window;
409
+ const threadContextWindow = normalizeContextWindow(thread.context_window);
410
+ if (threadContextWindow) return threadContextWindow;
264
411
  for (let index = (thread.turns || []).length - 1; index >= 0; index -= 1) {
265
- const value = thread.turns?.[index]?.context_window;
266
- if (value && typeof value === "object") return value;
412
+ const contextWindow = normalizeContextWindow(thread.turns?.[index]?.context_window);
413
+ if (contextWindow) return contextWindow;
267
414
  }
268
415
  return null;
269
416
  };
@@ -294,10 +441,6 @@ var activeRunIdFromThreadDetail = (thread) => {
294
441
  };
295
442
 
296
443
  // src/controller.ts
297
- var mergeContextWindow = (current, incoming) => {
298
- if (!incoming || typeof incoming !== "object") return current;
299
- return { ...current || {}, ...incoming };
300
- };
301
444
  var threadSummaryToStored = (thread) => ({
302
445
  ...thread,
303
446
  id: String(thread.id),
@@ -315,7 +458,9 @@ function useAgents24ChatController({
315
458
  createId = createChatId,
316
459
  onActiveThreadIdChange,
317
460
  onSourceClick,
318
- onStreamErrorMessage
461
+ onStreamErrorMessage,
462
+ onRuntimeEvent,
463
+ onThreadDetailLoaded
319
464
  }) {
320
465
  const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
321
466
  const initialCached = activeThreadId ? storage.getThread(activeThreadId) : void 0;
@@ -332,6 +477,9 @@ function useAgents24ChatController({
332
477
  const [disliked, setDisliked] = useState({});
333
478
  const [copiedMessageId, setCopiedMessageId] = useState(null);
334
479
  const [lastThinkingDurationMs, setLastThinkingDurationMs] = useState(null);
480
+ const [threads, setThreads] = useState(() => storage.listThreads());
481
+ const [isRefreshingThreads, setIsRefreshingThreads] = useState(false);
482
+ const [activeRunId, setActiveRunId] = useState(null);
335
483
  const textareaRef = useRef(null);
336
484
  const activeThreadIdRef = useRef(activeThreadId);
337
485
  const messagesRef = useRef(messages);
@@ -348,6 +496,20 @@ function useAgents24ChatController({
348
496
  const streamingMessageIdRef = useRef(null);
349
497
  const reasoningRef = useRef([]);
350
498
  const liveVoiceIdsRef = useRef({});
499
+ const setActiveRunIdValue = useCallback((runId) => {
500
+ activeRunIdRef.current = runId;
501
+ setActiveRunId(runId);
502
+ }, []);
503
+ const syncThreadsFromStorage = useCallback(() => {
504
+ setThreads(storage.listThreads());
505
+ }, [storage]);
506
+ const upsertStoredThread = useCallback(
507
+ (thread) => {
508
+ storage.upsertThread(thread);
509
+ syncThreadsFromStorage();
510
+ },
511
+ [storage, syncThreadsFromStorage]
512
+ );
351
513
  useEffect(() => {
352
514
  messagesRef.current = messages;
353
515
  }, [messages]);
@@ -355,7 +517,7 @@ function useAgents24ChatController({
355
517
  (threadId, nextMessages, paging, options) => {
356
518
  const existing = storage.getThread(threadId);
357
519
  const firstUser = nextMessages.find((message) => message.role === "user");
358
- storage.upsertThread({
520
+ upsertStoredThread({
359
521
  ...existing || {},
360
522
  id: threadId,
361
523
  title: existing?.title || (firstUser ? titleFromMessage(firstUser.content, firstUser.attachments || []) : "New chat"),
@@ -366,13 +528,13 @@ function useAgents24ChatController({
366
528
  nextBeforeTurnIndex: paging?.nextBeforeTurnIndex ?? nextBeforeTurnIndexRef.current
367
529
  });
368
530
  },
369
- [storage]
531
+ [storage, upsertStoredThread]
370
532
  );
371
533
  const markThreadRunStatus = useCallback(
372
534
  (threadId, runId, status, lastEventSeq) => {
373
535
  const existing = storage.getThread(threadId);
374
536
  if (!existing || !runId) return;
375
- storage.upsertThread({
537
+ upsertStoredThread({
376
538
  ...existing,
377
539
  last_run_id: runId,
378
540
  last_run_status: status,
@@ -389,14 +551,25 @@ function useAgents24ChatController({
389
551
  created_at: existing.activeRun?.created_at ?? existing.active_run?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
390
552
  },
391
553
  lastEventSeq: typeof lastEventSeq === "number" ? lastEventSeq : existing.lastEventSeq ?? null,
392
- isRunning: status === "queued" || status === "running"
554
+ isRunning: activeRunIdFromThread({
555
+ lastRunId: runId,
556
+ lastRunStatus: status,
557
+ activeRun: { run_id: runId, status }
558
+ }) !== null
393
559
  });
394
560
  },
395
- [storage]
561
+ [storage, upsertStoredThread]
396
562
  );
397
563
  const refresh = useCallback(async () => {
398
- const data = await transport.listThreads();
399
- storage.setThreads((data.items || []).map((item) => threadSummaryToStored(item)));
564
+ setIsRefreshingThreads(true);
565
+ try {
566
+ const data = await transport.listThreads();
567
+ const nextThreads = (data.items || []).map((item) => threadSummaryToStored(item));
568
+ storage.setThreads(nextThreads);
569
+ setThreads(storage.listThreads());
570
+ } finally {
571
+ setIsRefreshingThreads(false);
572
+ }
400
573
  }, [storage, transport]);
401
574
  const applyThreadId = useCallback(
402
575
  (threadId, baseMessages) => {
@@ -424,7 +597,7 @@ function useAgents24ChatController({
424
597
  const controller = abortControllerRef.current;
425
598
  abortControllerRef.current = null;
426
599
  controller?.abort();
427
- activeRunIdRef.current = null;
600
+ setActiveRunIdValue(null);
428
601
  reattachedRunIdRef.current = null;
429
602
  streamingMessageIdRef.current = null;
430
603
  streamingContentRef.current = "";
@@ -433,7 +606,7 @@ function useAgents24ChatController({
433
606
  setIsLoading(false);
434
607
  setStreamingContent("");
435
608
  setCurrentReasoning([]);
436
- }, []);
609
+ }, [setActiveRunIdValue]);
437
610
  const setLiveAssistantMessage = useCallback(
438
611
  (input) => {
439
612
  setMessages((prev) => {
@@ -503,7 +676,7 @@ function useAgents24ChatController({
503
676
  persistThread(input.threadId, completed);
504
677
  const existingThread = storage.getThread(input.threadId);
505
678
  if (existingThread && input.runId) {
506
- storage.upsertThread({
679
+ upsertStoredThread({
507
680
  ...existingThread,
508
681
  messages: completed,
509
682
  last_run_id: input.runId,
@@ -518,7 +691,7 @@ function useAgents24ChatController({
518
691
  }
519
692
  return completed;
520
693
  },
521
- [createId, persistThread, storage]
694
+ [createId, persistThread, storage, upsertStoredThread]
522
695
  );
523
696
  const loadThread = useCallback(
524
697
  async (threadId) => {
@@ -533,6 +706,7 @@ function useAgents24ChatController({
533
706
  includeRunEvents: false
534
707
  });
535
708
  if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
709
+ void onThreadDetailLoaded?.(detail);
536
710
  const nextMessages = threadDetailToMessages(detail);
537
711
  const paging = threadPaging(detail);
538
712
  setContextStatus(latestContextWindowFromThread(detail));
@@ -542,7 +716,7 @@ function useAgents24ChatController({
542
716
  hasOlderTurnsRef.current = paging.hasOlderTurns;
543
717
  nextBeforeTurnIndexRef.current = paging.nextBeforeTurnIndex;
544
718
  loadedThreadIdRef.current = threadId;
545
- storage.upsertThread({
719
+ upsertStoredThread({
546
720
  ...threadSummaryToStored(detail),
547
721
  messages: nextMessages,
548
722
  isHydrated: true,
@@ -554,7 +728,7 @@ function useAgents24ChatController({
554
728
  if (activeThreadIdRef.current === threadId) setLoadingHistory(false);
555
729
  }
556
730
  },
557
- [pageSize, setLoadingHistory, storage, transport]
731
+ [onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
558
732
  );
559
733
  const loadOlderTurns = useCallback(async () => {
560
734
  const threadId = activeThreadIdRef.current;
@@ -586,11 +760,18 @@ function useAgents24ChatController({
586
760
  }, [pageSize, persistThread, transport]);
587
761
  const handleStreamEvent = useCallback(
588
762
  (input) => {
589
- const { event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
763
+ const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
590
764
  const payload = event.payload || {};
591
765
  const responseBlocks = Array.isArray(payload.response_blocks) ? payload.response_blocks : null;
592
- if (event.run_id) activeRunIdRef.current = event.run_id;
593
- setContextStatus((current) => mergeContextWindow(current, payload.context_window));
766
+ if (event.run_id) setActiveRunIdValue(event.run_id);
767
+ void onRuntimeEvent?.(event, {
768
+ mode,
769
+ assistantMessageId,
770
+ threadId: streamThreadIdRef.current,
771
+ runId: event.run_id || activeRunIdRef.current,
772
+ startedAt
773
+ });
774
+ setContextStatus((current) => mergeContextWindowUpdate(current, payload.context_window));
594
775
  if (responseBlocks) {
595
776
  const blockText = String(payload.assistant_output_text || "") || assistantTextFromResponseBlocks(responseBlocks) || streamingContentRef.current;
596
777
  if (blockText) setStreamingText(blockText);
@@ -636,7 +817,7 @@ function useAgents24ChatController({
636
817
  error: isFailed ? String(payload.message || payload.error || event.diagnostics?.[0]?.message || onStreamErrorMessage?.(event) || "The chat run failed.") : void 0
637
818
  });
638
819
  },
639
- [applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onStreamErrorMessage, setLiveAssistantMessage, setReasoningSteps]
820
+ [applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
640
821
  );
641
822
  const runStream = useCallback(
642
823
  async (input) => {
@@ -698,16 +879,16 @@ function useAgents24ChatController({
698
879
  }
699
880
  try {
700
881
  if (input.mode === "attach") {
701
- activeRunIdRef.current = input.runId;
882
+ setActiveRunIdValue(input.runId);
702
883
  reattachedRunIdRef.current = input.runId;
703
884
  await transport.attachRun(
704
885
  { runId: input.runId, signal: controller.signal },
705
- (event) => handleStreamEvent({ event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
886
+ (event) => handleStreamEvent({ mode: "attach", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
706
887
  );
707
888
  } else {
708
889
  await transport.streamMessage(
709
- { text: input.message.text, files: input.message.files || [], threadId: activeThreadIdRef.current, signal: controller.signal },
710
- (event) => handleStreamEvent({ event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
890
+ { ...input.message, files: input.message.files || [], threadId: activeThreadIdRef.current, signal: controller.signal },
891
+ (event) => handleStreamEvent({ mode: "submit", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
711
892
  );
712
893
  }
713
894
  if (streamingContentRef.current.trim() && streamingMessageIdRef.current) {
@@ -739,7 +920,7 @@ function useAgents24ChatController({
739
920
  const isCurrentStream = abortControllerRef.current === controller || streamingMessageIdRef.current === assistantMessageId;
740
921
  if (abortControllerRef.current === controller) abortControllerRef.current = null;
741
922
  if (isCurrentStream) {
742
- activeRunIdRef.current = null;
923
+ setActiveRunIdValue(null);
743
924
  streamingMessageIdRef.current = null;
744
925
  reattachedRunIdRef.current = null;
745
926
  setStreamingMessageId(null);
@@ -750,7 +931,7 @@ function useAgents24ChatController({
750
931
  }
751
932
  }
752
933
  },
753
- [createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setReasoningSteps, transport]
934
+ [createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
754
935
  );
755
936
  const handleSubmit = useCallback(
756
937
  async (message) => {
@@ -765,7 +946,7 @@ function useAgents24ChatController({
765
946
  const liveMessageId = streamingMessageIdRef.current;
766
947
  abortControllerRef.current?.abort();
767
948
  abortControllerRef.current = null;
768
- activeRunIdRef.current = null;
949
+ setActiveRunIdValue(null);
769
950
  streamingMessageIdRef.current = null;
770
951
  setStreamingMessageId(null);
771
952
  setIsLoading(false);
@@ -782,10 +963,16 @@ function useAgents24ChatController({
782
963
  messageId: liveMessageId
783
964
  });
784
965
  }
785
- }, [finalizeAssistantMessage, lastThinkingDurationMs, setReasoningSteps, transport]);
966
+ }, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
786
967
  useEffect(() => {
787
- refresh().catch(() => storage.setThreads([]));
968
+ refresh().catch(() => {
969
+ storage.setThreads([]);
970
+ setThreads([]);
971
+ });
788
972
  }, [refresh, storage]);
973
+ useEffect(() => {
974
+ syncThreadsFromStorage();
975
+ }, [storageKey, syncThreadsFromStorage]);
789
976
  useEffect(() => {
790
977
  const previous = activeThreadIdRef.current;
791
978
  activeThreadIdRef.current = activeThreadId;
@@ -893,7 +1080,40 @@ function useAgents24ChatController({
893
1080
  return next;
894
1081
  });
895
1082
  }, [createId, persistThread]);
1083
+ const startNewThread = useCallback(() => {
1084
+ detachActiveStream();
1085
+ requestSeqRef.current += 1;
1086
+ activeThreadIdRef.current = null;
1087
+ loadedThreadIdRef.current = null;
1088
+ nextBeforeTurnIndexRef.current = null;
1089
+ hasOlderTurnsRef.current = false;
1090
+ storage.setActiveThreadId?.(null);
1091
+ onActiveThreadIdChange?.(null);
1092
+ setMessages([]);
1093
+ messagesRef.current = [];
1094
+ setHasOlderTurns(false);
1095
+ setIsLoadingOlder(false);
1096
+ setContextStatus(null);
1097
+ setLoadingHistory(false);
1098
+ }, [detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage]);
1099
+ const loadThreadById = useCallback(
1100
+ async (threadId) => {
1101
+ if (!threadId) return;
1102
+ if (activeThreadIdRef.current !== threadId) {
1103
+ if (abortControllerRef.current) detachActiveStream();
1104
+ activeThreadIdRef.current = threadId;
1105
+ storage.setActiveThreadId?.(threadId);
1106
+ onActiveThreadIdChange?.(threadId);
1107
+ }
1108
+ await loadThread(threadId);
1109
+ },
1110
+ [detachActiveStream, loadThread, onActiveThreadIdChange, storage]
1111
+ );
1112
+ const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
896
1113
  return useMemo(() => ({
1114
+ threads,
1115
+ activeThreadId,
1116
+ activeThread,
897
1117
  messages,
898
1118
  streamingContent,
899
1119
  streamingMessageId,
@@ -902,12 +1122,13 @@ function useAgents24ChatController({
902
1122
  isLoading,
903
1123
  isLoadingHistory,
904
1124
  isLoadingOlder,
1125
+ isRefreshingThreads,
905
1126
  hasOlderTurns,
906
1127
  liked,
907
1128
  disliked,
908
1129
  copiedMessageId,
909
1130
  lastThinkingDurationMs,
910
- activeRunId: activeRunIdRef.current,
1131
+ activeRunId,
911
1132
  handleSubmit,
912
1133
  handleStop,
913
1134
  handleCopy,
@@ -916,11 +1137,16 @@ function useAgents24ChatController({
916
1137
  handleRetry,
917
1138
  handleSourceClick: (citations) => onSourceClick?.(citations),
918
1139
  upsertLiveVoiceMessage,
1140
+ startNewThread,
1141
+ loadThreadById,
919
1142
  loadOlderTurns,
920
1143
  refresh,
921
1144
  textareaRef
922
1145
  }), [
923
1146
  copiedMessageId,
1147
+ activeRunId,
1148
+ activeThread,
1149
+ activeThreadId,
924
1150
  contextStatus,
925
1151
  currentReasoning,
926
1152
  disliked,
@@ -934,14 +1160,18 @@ function useAgents24ChatController({
934
1160
  isLoading,
935
1161
  isLoadingHistory,
936
1162
  isLoadingOlder,
1163
+ isRefreshingThreads,
937
1164
  lastThinkingDurationMs,
938
1165
  liked,
1166
+ loadThreadById,
939
1167
  loadOlderTurns,
940
1168
  messages,
941
1169
  onSourceClick,
942
1170
  refresh,
943
1171
  streamingContent,
944
1172
  streamingMessageId,
1173
+ startNewThread,
1174
+ threads,
945
1175
  upsertLiveVoiceMessage
946
1176
  ]);
947
1177
  }
@@ -1035,140 +1265,6 @@ var consumeSseResponse = async (response, onEvent) => {
1035
1265
  return { threadId, runId };
1036
1266
  };
1037
1267
 
1038
- // src/streaming-text.ts
1039
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
1040
- var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
1041
- var defaultCompletedTextCache = /* @__PURE__ */ new Map();
1042
- var defaultStreamingTextCache = {
1043
- get: (id) => defaultCompletedTextCache.get(id),
1044
- set: (id, text) => {
1045
- defaultCompletedTextCache.set(id, text);
1046
- if (defaultCompletedTextCache.size > DEFAULT_COMPLETED_TEXT_CACHE_SIZE) {
1047
- const firstKey = defaultCompletedTextCache.keys().next().value;
1048
- if (firstKey) defaultCompletedTextCache.delete(firstKey);
1049
- }
1050
- }
1051
- };
1052
- var getActiveStreamingTextPartId = (message, streamingMessageId) => {
1053
- if (message.role !== "assistant" || streamingMessageId !== message.id) return null;
1054
- return message.parts.slice().reverse().find((part) => part.kind === "text")?.id || null;
1055
- };
1056
- var isActiveStreamingTextPart = (message, part, streamingMessageId) => part.kind === "text" && part.id === getActiveStreamingTextPartId(message, streamingMessageId);
1057
- function useStreamingText({
1058
- id,
1059
- isStreaming,
1060
- text,
1061
- cache = defaultStreamingTextCache,
1062
- charsPerSecond = 72,
1063
- catchupThreshold = 40,
1064
- maxCatchupChars = 20
1065
- }) {
1066
- const cacheAdapter = cache === false ? null : cache;
1067
- const [displayedText, setDisplayedText] = useState2(() => {
1068
- const cachedText = cacheAdapter?.get(id);
1069
- return isStreaming && cachedText !== text ? "" : text;
1070
- });
1071
- const targetRef = useRef2(text);
1072
- const displayedRef = useRef2(displayedText);
1073
- const rafRef = useRef2(null);
1074
- const lastFrameAtRef = useRef2(null);
1075
- const idRef = useRef2(id);
1076
- const shouldAnimateRef = useRef2(isStreaming);
1077
- useEffect2(() => {
1078
- if (isStreaming || !text) return;
1079
- cacheAdapter?.set(id, text);
1080
- }, [cacheAdapter, id, isStreaming, text]);
1081
- useEffect2(() => {
1082
- targetRef.current = text;
1083
- }, [text]);
1084
- useEffect2(() => {
1085
- if (idRef.current === id) return;
1086
- idRef.current = id;
1087
- const cachedText = cacheAdapter?.get(id);
1088
- const initial = isStreaming && cachedText !== text ? "" : text;
1089
- shouldAnimateRef.current = isStreaming && initial !== text;
1090
- displayedRef.current = initial;
1091
- setDisplayedText(initial);
1092
- if (rafRef.current !== null) {
1093
- cancelAnimationFrame(rafRef.current);
1094
- rafRef.current = null;
1095
- }
1096
- lastFrameAtRef.current = null;
1097
- }, [cacheAdapter, id, isStreaming, text]);
1098
- useEffect2(() => {
1099
- if (typeof window === "undefined") {
1100
- displayedRef.current = text;
1101
- setDisplayedText(text);
1102
- return;
1103
- }
1104
- const reduceMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1105
- if (reduceMotion) {
1106
- displayedRef.current = text;
1107
- setDisplayedText(text);
1108
- shouldAnimateRef.current = false;
1109
- return;
1110
- }
1111
- const cachedText = cacheAdapter?.get(id);
1112
- if (cachedText === text) {
1113
- displayedRef.current = text;
1114
- setDisplayedText(text);
1115
- shouldAnimateRef.current = false;
1116
- return;
1117
- }
1118
- if (isStreaming) {
1119
- shouldAnimateRef.current = true;
1120
- }
1121
- if (!shouldAnimateRef.current) {
1122
- displayedRef.current = text;
1123
- setDisplayedText(text);
1124
- return;
1125
- }
1126
- if (!text.startsWith(displayedRef.current)) {
1127
- displayedRef.current = "";
1128
- setDisplayedText("");
1129
- }
1130
- const tick = (timestamp) => {
1131
- const previousTimestamp = lastFrameAtRef.current ?? timestamp;
1132
- lastFrameAtRef.current = timestamp;
1133
- const target = targetRef.current;
1134
- const current = displayedRef.current;
1135
- if (current.length >= target.length) {
1136
- shouldAnimateRef.current = false;
1137
- rafRef.current = null;
1138
- return;
1139
- }
1140
- const elapsedMs = Math.max(8, timestamp - previousTimestamp);
1141
- const charsFromTime = Math.max(1, Math.floor(elapsedMs / 1e3 * charsPerSecond));
1142
- const gap = target.length - current.length;
1143
- const catchupStep = gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;
1144
- const nextLength = Math.min(
1145
- target.length,
1146
- current.length + Math.max(charsFromTime, catchupStep)
1147
- );
1148
- const next = target.slice(0, nextLength);
1149
- displayedRef.current = next;
1150
- setDisplayedText(next);
1151
- rafRef.current = window.requestAnimationFrame(tick);
1152
- };
1153
- if (rafRef.current === null && displayedRef.current.length < text.length) {
1154
- rafRef.current = window.requestAnimationFrame(tick);
1155
- }
1156
- return () => {
1157
- if (rafRef.current !== null) {
1158
- cancelAnimationFrame(rafRef.current);
1159
- rafRef.current = null;
1160
- }
1161
- lastFrameAtRef.current = null;
1162
- };
1163
- }, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);
1164
- return {
1165
- displayedText,
1166
- isAnimating: isStreaming,
1167
- mode: isStreaming ? "streaming" : "static",
1168
- parseIncompleteMarkdown: isStreaming
1169
- };
1170
- }
1171
-
1172
1268
  // src/transport.ts
1173
1269
  var jsonHeaders = (headers) => ({
1174
1270
  ...headers || {},
@@ -1249,312 +1345,49 @@ var createFetchChatTransport = ({
1249
1345
  };
1250
1346
  };
1251
1347
 
1252
- // src/viewport.tsx
1348
+ // src/viewport.ts
1253
1349
  import {
1254
- useCallback as useCallback2,
1255
- useEffect as useEffect3,
1256
- useLayoutEffect,
1257
- useMemo as useMemo2,
1258
- useRef as useRef3,
1259
- useState as useState3
1260
- } from "react";
1261
- import { jsxs as jsxs2 } from "react/jsx-runtime";
1262
- var LATEST_EDGE_THRESHOLD_PX = 2;
1263
- var MIN_OLDER_PREFETCH_PX = 320;
1264
- var MAX_OLDER_PREFETCH_PX = 900;
1265
- var isScrollable = (element) => element.scrollHeight - element.clientHeight > LATEST_EDGE_THRESHOLD_PX;
1266
- var isAtTimelineLatestEdge = (element, isTopOrigin) => {
1267
- if (!isScrollable(element)) return true;
1268
- return isTopOrigin ? element.scrollHeight - element.clientHeight - element.scrollTop <= LATEST_EDGE_THRESHOLD_PX : Math.abs(element.scrollTop) <= LATEST_EDGE_THRESHOLD_PX;
1269
- };
1270
- var shouldPrefetchOlder = (element) => Math.abs(element.scrollTop) + element.clientHeight >= element.scrollHeight - Math.min(MAX_OLDER_PREFETCH_PX, Math.max(MIN_OLDER_PREFETCH_PX, element.clientHeight * 0.75));
1271
- var getLatestScrollTop = (element, isTopOrigin) => isTopOrigin ? Math.max(0, element.scrollHeight - element.clientHeight) : 0;
1272
- var shouldUseTopOriginTimeline = ({
1273
- hasOlder,
1274
- itemCount,
1275
- topOriginMaxItems = 4
1276
- }) => !hasOlder && itemCount > 0 && itemCount <= topOriginMaxItems;
1277
- var nextLatestFollowStateOnScroll = ({
1278
- atLatest,
1279
- current,
1280
- isProgrammatic
1281
- }) => {
1282
- if (isProgrammatic) return current;
1283
- return atLatest ? "following" : "detached";
1284
- };
1285
- function useLatestThreadViewport({
1286
- itemCount,
1287
- hasOlder,
1288
- isLoadingOlder,
1289
- onLoadOlder,
1290
- activeStreamKey,
1291
- shouldAutoFollow = true,
1292
- topOriginMaxItems = 4
1293
- }) {
1294
- const scrollContainerRef = useRef3(null);
1295
- const olderPagePreserveRef = useRef3(null);
1296
- const olderPageRequestInFlightRef = useRef3(false);
1297
- const intrinsicResizePreserveRef = useRef3(null);
1298
- const followStateRef = useRef3("following");
1299
- const programmaticScrollRef = useRef3(false);
1300
- const programmaticScrollTimeoutRef = useRef3(null);
1301
- const activeStreamKeyRef = useRef3(null);
1302
- const observedScrollHeightRef = useRef3(null);
1303
- const [isAtLatest, setIsAtLatest] = useState3(true);
1304
- const [followState, setFollowState] = useState3("following");
1305
- const isTopOrigin = shouldUseTopOriginTimeline({ hasOlder, itemCount, topOriginMaxItems });
1306
- const setLatestFollowState = useCallback2((next) => {
1307
- if (followStateRef.current === next) return;
1308
- followStateRef.current = next;
1309
- setFollowState(next);
1310
- }, []);
1311
- const markProgrammaticScroll = useCallback2(() => {
1312
- programmaticScrollRef.current = true;
1313
- if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
1314
- programmaticScrollTimeoutRef.current = setTimeout(() => {
1315
- programmaticScrollRef.current = false;
1316
- programmaticScrollTimeoutRef.current = null;
1317
- }, 80);
1318
- }, []);
1319
- const scrollToLatest = useCallback2((options) => {
1320
- const element = scrollContainerRef.current;
1321
- if (!element) return;
1322
- if (options?.reattach !== false) setLatestFollowState("following");
1323
- markProgrammaticScroll();
1324
- element.scrollTop = getLatestScrollTop(element, isTopOrigin);
1325
- setIsAtLatest(true);
1326
- observedScrollHeightRef.current = element.scrollHeight;
1327
- }, [isTopOrigin, markProgrammaticScroll, setLatestFollowState]);
1328
- const detachFromLatest = useCallback2(() => {
1329
- setLatestFollowState("detached");
1330
- }, [setLatestFollowState]);
1331
- const reattachToLatest = useCallback2(() => {
1332
- scrollToLatest({ reattach: true });
1333
- }, [scrollToLatest]);
1334
- const handleUserScrollIntent = useCallback2(() => {
1335
- const element = scrollContainerRef.current;
1336
- if (!activeStreamKey || !element) return;
1337
- if (!isAtTimelineLatestEdge(element, isTopOrigin)) detachFromLatest();
1338
- }, [activeStreamKey, detachFromLatest, isTopOrigin]);
1339
- useEffect3(() => {
1340
- return () => {
1341
- if (programmaticScrollTimeoutRef.current) clearTimeout(programmaticScrollTimeoutRef.current);
1342
- const preserve = intrinsicResizePreserveRef.current;
1343
- if (preserve?.frame !== null && preserve?.frame !== void 0) cancelAnimationFrame(preserve.frame);
1344
- };
1345
- }, []);
1346
- useLayoutEffect(() => {
1347
- const element = scrollContainerRef.current;
1348
- const preserve = olderPagePreserveRef.current;
1349
- if (!element) return;
1350
- observedScrollHeightRef.current = element.scrollHeight;
1351
- if (!preserve) {
1352
- setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1353
- return;
1354
- }
1355
- const addedHeight = element.scrollHeight - preserve.previousHeight;
1356
- markProgrammaticScroll();
1357
- element.scrollTop = preserve.previousTop - addedHeight;
1358
- olderPagePreserveRef.current = null;
1359
- olderPageRequestInFlightRef.current = false;
1360
- setIsAtLatest(isAtTimelineLatestEdge(element, isTopOrigin));
1361
- }, [isTopOrigin, itemCount, markProgrammaticScroll]);
1362
- const handleScroll = useCallback2(
1363
- (event) => {
1364
- const element = event.currentTarget;
1365
- const atLatest = isAtTimelineLatestEdge(element, isTopOrigin);
1366
- setIsAtLatest(atLatest);
1367
- setLatestFollowState(
1368
- nextLatestFollowStateOnScroll({
1369
- atLatest,
1370
- current: followStateRef.current,
1371
- isProgrammatic: programmaticScrollRef.current
1372
- })
1373
- );
1374
- if (isTopOrigin || !hasOlder || isLoadingOlder || olderPageRequestInFlightRef.current || !shouldPrefetchOlder(element)) {
1375
- return;
1376
- }
1377
- olderPageRequestInFlightRef.current = true;
1378
- olderPagePreserveRef.current = {
1379
- previousHeight: element.scrollHeight,
1380
- previousTop: element.scrollTop
1381
- };
1382
- void Promise.resolve(onLoadOlder()).finally(() => {
1383
- requestAnimationFrame(() => {
1384
- if (!olderPagePreserveRef.current) return;
1385
- olderPagePreserveRef.current = null;
1386
- olderPageRequestInFlightRef.current = false;
1387
- });
1388
- });
1389
- },
1390
- [hasOlder, isLoadingOlder, isTopOrigin, onLoadOlder, setLatestFollowState]
1391
- );
1392
- const preserveIntrinsicResize = useCallback2(() => {
1393
- const element = scrollContainerRef.current;
1394
- if (!element || isTopOrigin) return;
1395
- const active = intrinsicResizePreserveRef.current;
1396
- if (active?.frame !== null && active?.frame !== void 0) cancelAnimationFrame(active.frame);
1397
- intrinsicResizePreserveRef.current = {
1398
- lastHeight: element.scrollHeight,
1399
- endAt: Date.now() + 260,
1400
- frame: null
1401
- };
1402
- const preserveFrame = () => {
1403
- const nextElement = scrollContainerRef.current;
1404
- const preserve = intrinsicResizePreserveRef.current;
1405
- if (!nextElement || !preserve) return;
1406
- const heightDelta = nextElement.scrollHeight - preserve.lastHeight;
1407
- if (Math.abs(heightDelta) >= 1) {
1408
- markProgrammaticScroll();
1409
- nextElement.scrollTop -= heightDelta;
1410
- preserve.lastHeight = nextElement.scrollHeight;
1411
- setIsAtLatest(isAtTimelineLatestEdge(nextElement, isTopOrigin));
1412
- }
1413
- if (Date.now() < preserve.endAt) {
1414
- preserve.frame = requestAnimationFrame(preserveFrame);
1415
- } else {
1416
- intrinsicResizePreserveRef.current = null;
1417
- }
1418
- };
1419
- intrinsicResizePreserveRef.current.frame = requestAnimationFrame(preserveFrame);
1420
- }, [isTopOrigin, markProgrammaticScroll]);
1421
- useLayoutEffect(() => {
1422
- const key = activeStreamKey || null;
1423
- const previousKey = activeStreamKeyRef.current;
1424
- if (!key) {
1425
- activeStreamKeyRef.current = null;
1426
- if (previousKey && shouldAutoFollow && followStateRef.current === "following" && !isLoadingOlder && !olderPageRequestInFlightRef.current) {
1427
- const frame2 = requestAnimationFrame(() => {
1428
- if (followStateRef.current === "following" && !isLoadingOlder && !olderPageRequestInFlightRef.current) {
1429
- scrollToLatest({ reattach: false });
1430
- }
1431
- });
1432
- return () => cancelAnimationFrame(frame2);
1433
- }
1434
- return;
1435
- }
1436
- if (previousKey === key) {
1437
- activeStreamKeyRef.current = key;
1438
- return;
1439
- }
1440
- setLatestFollowState("following");
1441
- const frame = requestAnimationFrame(() => scrollToLatest());
1442
- activeStreamKeyRef.current = key;
1443
- return () => cancelAnimationFrame(frame);
1444
- }, [activeStreamKey, isLoadingOlder, scrollToLatest, setLatestFollowState, shouldAutoFollow]);
1445
- useLayoutEffect(() => {
1446
- if (!activeStreamKey || !shouldAutoFollow || followStateRef.current !== "following") return;
1447
- if (isLoadingOlder || olderPageRequestInFlightRef.current) return;
1448
- const frame = requestAnimationFrame(() => {
1449
- if (followStateRef.current === "following" && !isLoadingOlder && !olderPageRequestInFlightRef.current) {
1450
- scrollToLatest({ reattach: false });
1451
- }
1452
- });
1453
- return () => cancelAnimationFrame(frame);
1454
- }, [activeStreamKey, isLoadingOlder, itemCount, scrollToLatest, shouldAutoFollow]);
1455
- useEffect3(() => {
1456
- if (!activeStreamKey) {
1457
- observedScrollHeightRef.current = scrollContainerRef.current?.scrollHeight ?? null;
1458
- return;
1459
- }
1460
- let frame = null;
1461
- const watchStreamResize = () => {
1462
- const element = scrollContainerRef.current;
1463
- if (!element) return;
1464
- const previousHeight = observedScrollHeightRef.current;
1465
- const nextHeight = element.scrollHeight;
1466
- observedScrollHeightRef.current = nextHeight;
1467
- if (previousHeight !== null && Math.abs(nextHeight - previousHeight) >= 1 && shouldAutoFollow && followStateRef.current === "following" && !isLoadingOlder && !olderPageRequestInFlightRef.current) {
1468
- scrollToLatest({ reattach: false });
1469
- }
1470
- frame = requestAnimationFrame(watchStreamResize);
1471
- };
1472
- frame = requestAnimationFrame(watchStreamResize);
1473
- return () => {
1474
- if (frame !== null) cancelAnimationFrame(frame);
1475
- };
1476
- }, [activeStreamKey, isLoadingOlder, scrollToLatest, shouldAutoFollow]);
1477
- return {
1478
- scrollContainerRef,
1479
- isAtLatest,
1480
- isFollowingLatest: followState === "following",
1481
- isTopOrigin,
1482
- shouldShowLatestButton: !isAtLatest,
1483
- handleScroll,
1484
- handleUserScrollIntent,
1485
- scrollToLatest,
1486
- detachFromLatest,
1487
- reattachToLatest,
1488
- preserveIntrinsicResize
1489
- };
1490
- }
1491
- function LatestThreadViewport({
1492
- items,
1493
- hasOlder,
1494
- isLoadingOlder,
1495
- onLoadOlder,
1496
- activeStreamKey,
1497
- className,
1498
- latestSpacer,
1499
- trailingSpacer,
1500
- renderItem
1501
- }) {
1502
- const viewport = useLatestThreadViewport({
1503
- itemCount: items.length,
1504
- hasOlder,
1505
- isLoadingOlder,
1506
- onLoadOlder,
1507
- activeStreamKey
1508
- });
1509
- const timelineItems = useMemo2(() => viewport.isTopOrigin ? items : [...items].reverse(), [items, viewport.isTopOrigin]);
1510
- return /* @__PURE__ */ jsxs2(
1511
- "div",
1512
- {
1513
- ref: viewport.scrollContainerRef,
1514
- className,
1515
- onKeyDown: viewport.handleUserScrollIntent,
1516
- onScroll: viewport.handleScroll,
1517
- onTouchMove: viewport.handleUserScrollIntent,
1518
- onWheel: viewport.handleUserScrollIntent,
1519
- role: "log",
1520
- style: { overflowAnchor: "none" },
1521
- children: [
1522
- !viewport.isTopOrigin && (latestSpacer ?? null),
1523
- timelineItems.map((item) => renderItem(item, viewport)),
1524
- viewport.isTopOrigin && (trailingSpacer ?? latestSpacer ?? null)
1525
- ]
1526
- }
1527
- );
1528
- }
1350
+ MessageScroller,
1351
+ useMessageScroller,
1352
+ useMessageScrollerScrollable,
1353
+ useMessageScrollerVisibility
1354
+ } from "@shadcn/react/message-scroller";
1529
1355
  export {
1530
1356
  DEFAULT_THREAD_PAGE_SIZE,
1531
1357
  DefaultChatPart,
1532
1358
  DefaultToolPart,
1533
- LatestThreadViewport,
1359
+ LatestThreadScroller,
1360
+ LatestThreadScrollerButton,
1361
+ LatestThreadScrollerContent,
1362
+ LatestThreadScrollerItem,
1363
+ LatestThreadScrollerOutline,
1364
+ LatestThreadScrollerProvider,
1365
+ LatestThreadScrollerRoot,
1366
+ LatestThreadScrollerViewport,
1367
+ MessageScroller,
1534
1368
  activeRunIdFromThread,
1535
1369
  activeRunIdFromThreadDetail,
1536
1370
  assistantTextFromParts,
1537
1371
  assistantTextFromResponseBlocks,
1372
+ compressionFromContextWindow,
1538
1373
  consumeSseResponse,
1539
1374
  createChatId,
1540
1375
  createFetchChatTransport,
1541
1376
  getActiveStreamingTextPartId,
1542
- getLatestScrollTop,
1543
1377
  hasStaleUnfinishedAssistantCache,
1544
1378
  hasUnfinishedAssistantMessage,
1545
1379
  isActiveStreamingTextPart,
1546
- isAtTimelineLatestEdge,
1547
1380
  isRunningThreadStatus,
1548
- isScrollable,
1549
1381
  latestContextWindowFromThread,
1382
+ mergeContextWindow,
1383
+ mergeContextWindowUpdate,
1550
1384
  mergeReasoningSteps,
1551
- nextLatestFollowStateOnScroll,
1385
+ normalizeContextCompression,
1386
+ normalizeContextWindow,
1552
1387
  parseSseBlock,
1553
1388
  partsFromResponseBlocks,
1554
1389
  reasoningStepsFromParts,
1555
1390
  renderChatPart,
1556
- shouldPrefetchOlder,
1557
- shouldUseTopOriginTimeline,
1558
1391
  textFromFinalOutput,
1559
1392
  threadActivityDate,
1560
1393
  threadDetailToMessages,
@@ -1562,7 +1395,9 @@ export {
1562
1395
  titleFromMessage,
1563
1396
  toolStateFromStatus,
1564
1397
  useAgents24ChatController,
1565
- useLatestThreadViewport,
1398
+ useMessageScroller,
1399
+ useMessageScrollerScrollable,
1400
+ useMessageScrollerVisibility,
1566
1401
  useStreamingText
1567
1402
  };
1568
1403
  //# sourceMappingURL=index.js.map