@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.
- package/README.md +29 -12
- package/dist/chunk-E3TJ3I6G.js +358 -0
- package/dist/chunk-E3TJ3I6G.js.map +1 -0
- package/dist/chunk-EWZO4QJI.js +140 -0
- package/dist/chunk-EWZO4QJI.js.map +1 -0
- package/dist/chunk-UU7OXHL3.js +11 -0
- package/dist/chunk-UU7OXHL3.js.map +1 -0
- package/dist/context-window.d.ts +38 -0
- package/dist/controller.d.ts +22 -0
- package/dist/index.cjs +653 -336
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -466
- package/dist/index.js +290 -455
- package/dist/index.js.map +1 -1
- package/dist/latest-thread-scroller.d.ts +54 -0
- package/dist/model.d.ts +29 -0
- package/dist/renderers.d.ts +10 -0
- package/dist/sse.d.ts +3 -0
- package/dist/streaming-text.d.ts +24 -0
- package/dist/templates/agent-chat-shell.d.ts +25 -0
- package/dist/templates/index.cjs +523 -0
- package/dist/templates/index.cjs.map +1 -0
- package/dist/templates/index.d.ts +1 -0
- package/dist/templates/index.js +140 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/transport.d.ts +29 -0
- package/dist/types.d.ts +312 -0
- package/dist/ui/adapters.d.ts +36 -0
- package/dist/ui/attachment.d.ts +23 -0
- package/dist/ui/bubble.d.ts +17 -0
- package/dist/ui/index.cjs +943 -0
- package/dist/ui/index.cjs.map +1 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.js +734 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/marker.d.ts +11 -0
- package/dist/ui/message-response.d.ts +7 -0
- package/dist/ui/message.d.ts +10 -0
- package/dist/ui/utils.d.ts +2 -0
- package/dist/viewport.d.ts +2 -0
- package/package.json +22 -4
- 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
|
-
|
|
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
|
|
266
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
399
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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)
|
|
593
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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(() =>
|
|
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
|
|
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.
|
|
1348
|
+
// src/viewport.ts
|
|
1253
1349
|
import {
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1398
|
+
useMessageScroller,
|
|
1399
|
+
useMessageScrollerScrollable,
|
|
1400
|
+
useMessageScrollerVisibility,
|
|
1566
1401
|
useStreamingText
|
|
1567
1402
|
};
|
|
1568
1403
|
//# sourceMappingURL=index.js.map
|