@agents24/chat-react 0.1.5 → 0.1.7
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 +4 -0
- package/dist/index.cjs +297 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -26
- package/dist/index.d.ts +90 -26
- package/dist/index.js +291 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,128 @@
|
|
|
1
1
|
// src/controller.ts
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
|
|
4
|
+
// src/context-window.ts
|
|
5
|
+
var SOURCE_PRIORITY = {
|
|
6
|
+
unknown: 0,
|
|
7
|
+
heuristic_estimate: 1,
|
|
8
|
+
tokenizer_estimate: 1,
|
|
9
|
+
text_estimate: 1,
|
|
10
|
+
estimated: 1,
|
|
11
|
+
multimodal_estimate: 2,
|
|
12
|
+
hf_tokenizer: 3,
|
|
13
|
+
runtime_tokenizer: 3,
|
|
14
|
+
provider_count_api: 4,
|
|
15
|
+
provider_usage: 5,
|
|
16
|
+
exact: 5
|
|
17
|
+
};
|
|
18
|
+
var STAGE_PRIORITY = {
|
|
19
|
+
preflight: 0,
|
|
20
|
+
sent_prompt: 1,
|
|
21
|
+
final_usage: 2
|
|
22
|
+
};
|
|
23
|
+
function numberOrNull(value) {
|
|
24
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
25
|
+
}
|
|
26
|
+
function stagePriority(window2) {
|
|
27
|
+
return STAGE_PRIORITY[window2?.stage || "sent_prompt"] ?? STAGE_PRIORITY.sent_prompt;
|
|
28
|
+
}
|
|
29
|
+
function hasRenderableContextWindow(window2) {
|
|
30
|
+
const maxTokens = window2?.max_tokens;
|
|
31
|
+
return typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0;
|
|
32
|
+
}
|
|
33
|
+
function windowWeight(window2) {
|
|
34
|
+
if (!window2) return [0, 0];
|
|
35
|
+
return [stagePriority(window2), SOURCE_PRIORITY[window2.source] || 0];
|
|
36
|
+
}
|
|
37
|
+
function normalizeContextCompression(value) {
|
|
38
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
39
|
+
const payload = value;
|
|
40
|
+
return {
|
|
41
|
+
active: Boolean(payload.active),
|
|
42
|
+
reason: typeof payload.reason === "string" ? payload.reason : null,
|
|
43
|
+
input_tokens: numberOrNull(payload.input_tokens),
|
|
44
|
+
max_tokens: numberOrNull(payload.max_tokens),
|
|
45
|
+
usage_ratio: numberOrNull(payload.usage_ratio),
|
|
46
|
+
full_frame_count: numberOrNull(payload.full_frame_count),
|
|
47
|
+
compact_frame_count: numberOrNull(payload.compact_frame_count),
|
|
48
|
+
dropped_frame_count: numberOrNull(payload.dropped_frame_count),
|
|
49
|
+
artifact_ref_count: numberOrNull(payload.artifact_ref_count),
|
|
50
|
+
compression_trigger_budget: numberOrNull(payload.compression_trigger_budget),
|
|
51
|
+
compression_target_budget: numberOrNull(payload.compression_target_budget),
|
|
52
|
+
compression_target_ratio: numberOrNull(payload.compression_target_ratio),
|
|
53
|
+
threshold_used: numberOrNull(payload.threshold_used)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function normalizeContextWindow(value) {
|
|
57
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
58
|
+
const payload = value;
|
|
59
|
+
const rawSource = String(payload.source || "").trim();
|
|
60
|
+
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";
|
|
61
|
+
const rawStage = String(payload.stage || "").trim();
|
|
62
|
+
const stage = rawStage === "preflight" || rawStage === "sent_prompt" || rawStage === "final_usage" ? rawStage : "sent_prompt";
|
|
63
|
+
const rawConfidence = String(payload.confidence || "").trim();
|
|
64
|
+
const confidence = rawConfidence === "exact" || rawConfidence === "high" || rawConfidence === "medium" || rawConfidence === "low" || rawConfidence === "unknown" ? rawConfidence : null;
|
|
65
|
+
return {
|
|
66
|
+
source,
|
|
67
|
+
run_id: typeof payload.run_id === "string" ? payload.run_id : null,
|
|
68
|
+
stage,
|
|
69
|
+
confidence,
|
|
70
|
+
counter: typeof payload.counter === "string" ? payload.counter : null,
|
|
71
|
+
model_id: typeof payload.model_id === "string" ? payload.model_id : null,
|
|
72
|
+
max_tokens: numberOrNull(payload.max_tokens),
|
|
73
|
+
max_tokens_source: typeof payload.max_tokens_source === "string" ? payload.max_tokens_source : null,
|
|
74
|
+
input_tokens: numberOrNull(payload.input_tokens),
|
|
75
|
+
remaining_tokens: numberOrNull(payload.remaining_tokens),
|
|
76
|
+
usage_ratio: numberOrNull(payload.usage_ratio),
|
|
77
|
+
assembly: payload.assembly && typeof payload.assembly === "object" && !Array.isArray(payload.assembly) ? payload.assembly : null,
|
|
78
|
+
context_compression: normalizeContextCompression(payload.context_compression)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function mergeContextWindow(current, incoming) {
|
|
82
|
+
if (!incoming) return current ?? null;
|
|
83
|
+
if (!current) return incoming;
|
|
84
|
+
if (hasRenderableContextWindow(current) && !hasRenderableContextWindow(incoming)) return current;
|
|
85
|
+
const currentRunId = current.run_id || null;
|
|
86
|
+
const incomingRunId = incoming.run_id || null;
|
|
87
|
+
if (incomingRunId && currentRunId && incomingRunId !== currentRunId) {
|
|
88
|
+
return stagePriority(incoming) >= STAGE_PRIORITY.sent_prompt ? incoming : current;
|
|
89
|
+
}
|
|
90
|
+
const currentWeight = windowWeight(current);
|
|
91
|
+
const incomingWeight = windowWeight(incoming);
|
|
92
|
+
return incomingWeight[0] > currentWeight[0] || incomingWeight[0] === currentWeight[0] && incomingWeight[1] >= currentWeight[1] ? incoming : current;
|
|
93
|
+
}
|
|
94
|
+
function mergeContextWindowUpdate(current, incoming) {
|
|
95
|
+
return mergeContextWindow(current, normalizeContextWindow(incoming));
|
|
96
|
+
}
|
|
97
|
+
function compressionFromContextWindow(contextWindow) {
|
|
98
|
+
if (!contextWindow) return null;
|
|
99
|
+
if (contextWindow.context_compression) return contextWindow.context_compression;
|
|
100
|
+
const assembly = contextWindow.assembly;
|
|
101
|
+
if (!assembly || typeof assembly !== "object" || Array.isArray(assembly)) return null;
|
|
102
|
+
const artifactRefs = Array.isArray(assembly.artifact_refs) ? assembly.artifact_refs : [];
|
|
103
|
+
const compactFrameCount = numberOrNull(assembly.compact_frame_count) ?? 0;
|
|
104
|
+
const droppedFrameCount = numberOrNull(assembly.dropped_frame_count) ?? 0;
|
|
105
|
+
const reason = typeof assembly.compaction_reason === "string" ? assembly.compaction_reason : "none";
|
|
106
|
+
return {
|
|
107
|
+
active: reason !== "none" || compactFrameCount > 0 || droppedFrameCount > 0,
|
|
108
|
+
reason,
|
|
109
|
+
input_tokens: contextWindow.input_tokens ?? null,
|
|
110
|
+
max_tokens: contextWindow.max_tokens ?? null,
|
|
111
|
+
usage_ratio: contextWindow.usage_ratio ?? null,
|
|
112
|
+
full_frame_count: numberOrNull(assembly.full_frame_count),
|
|
113
|
+
compact_frame_count: compactFrameCount,
|
|
114
|
+
dropped_frame_count: droppedFrameCount,
|
|
115
|
+
artifact_ref_count: artifactRefs.length,
|
|
116
|
+
compression_trigger_budget: numberOrNull(assembly.compression_trigger_budget),
|
|
117
|
+
compression_target_budget: numberOrNull(assembly.compression_target_budget),
|
|
118
|
+
compression_target_ratio: numberOrNull(assembly.compression_target_ratio),
|
|
119
|
+
threshold_used: numberOrNull(assembly.threshold_used)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
4
123
|
// src/model.ts
|
|
5
124
|
var DEFAULT_THREAD_PAGE_SIZE = 5;
|
|
6
|
-
var isRunningThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running"])).has(String(status || "").toLowerCase());
|
|
125
|
+
var isRunningThreadStatus = (status) => (/* @__PURE__ */ new Set(["queued", "running", "cancelling"])).has(String(status || "").toLowerCase());
|
|
7
126
|
var createChatId = () => {
|
|
8
127
|
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
9
128
|
return `chat-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -42,6 +161,16 @@ var attachmentsFromTurn = (turn) => (turn.attachments || []).map((attachment, in
|
|
|
42
161
|
mediaType: String(record.mime_type || record.mediaType || record.type || "application/octet-stream")
|
|
43
162
|
};
|
|
44
163
|
});
|
|
164
|
+
var tokenUsageFromTurn = (turn) => {
|
|
165
|
+
const usage = turn.run_usage;
|
|
166
|
+
if (!usage || typeof usage !== "object") return void 0;
|
|
167
|
+
return {
|
|
168
|
+
inputTokens: usage.input_tokens ?? null,
|
|
169
|
+
outputTokens: usage.output_tokens ?? null,
|
|
170
|
+
totalTokens: usage.total_tokens ?? null,
|
|
171
|
+
usageSource: usage.source ?? null
|
|
172
|
+
};
|
|
173
|
+
};
|
|
45
174
|
var latestEventPayloadValue = (events, key) => {
|
|
46
175
|
for (let index = (events || []).length - 1; index >= 0; index -= 1) {
|
|
47
176
|
const payload = events?.[index]?.payload;
|
|
@@ -243,7 +372,8 @@ var turnToMessages = (turn, activeRunId) => {
|
|
|
243
372
|
createdAt: turn.completed_at ? new Date(turn.completed_at) : createdAt,
|
|
244
373
|
parts,
|
|
245
374
|
reasoningSteps: mergeReasoningSteps(reasoningStepsFromParts(parts), { finalize: !isRunning }),
|
|
246
|
-
isFinal: !isRunning
|
|
375
|
+
isFinal: !isRunning,
|
|
376
|
+
tokenUsage: tokenUsageFromTurn(turn)
|
|
247
377
|
});
|
|
248
378
|
}
|
|
249
379
|
return messages;
|
|
@@ -260,10 +390,11 @@ var threadPaging = (thread) => {
|
|
|
260
390
|
};
|
|
261
391
|
};
|
|
262
392
|
var latestContextWindowFromThread = (thread) => {
|
|
263
|
-
|
|
393
|
+
const threadContextWindow = normalizeContextWindow(thread.context_window);
|
|
394
|
+
if (threadContextWindow) return threadContextWindow;
|
|
264
395
|
for (let index = (thread.turns || []).length - 1; index >= 0; index -= 1) {
|
|
265
|
-
const
|
|
266
|
-
if (
|
|
396
|
+
const contextWindow = normalizeContextWindow(thread.turns?.[index]?.context_window);
|
|
397
|
+
if (contextWindow) return contextWindow;
|
|
267
398
|
}
|
|
268
399
|
return null;
|
|
269
400
|
};
|
|
@@ -294,10 +425,6 @@ var activeRunIdFromThreadDetail = (thread) => {
|
|
|
294
425
|
};
|
|
295
426
|
|
|
296
427
|
// src/controller.ts
|
|
297
|
-
var mergeContextWindow = (current, incoming) => {
|
|
298
|
-
if (!incoming || typeof incoming !== "object") return current;
|
|
299
|
-
return { ...current || {}, ...incoming };
|
|
300
|
-
};
|
|
301
428
|
var threadSummaryToStored = (thread) => ({
|
|
302
429
|
...thread,
|
|
303
430
|
id: String(thread.id),
|
|
@@ -315,7 +442,9 @@ function useAgents24ChatController({
|
|
|
315
442
|
createId = createChatId,
|
|
316
443
|
onActiveThreadIdChange,
|
|
317
444
|
onSourceClick,
|
|
318
|
-
onStreamErrorMessage
|
|
445
|
+
onStreamErrorMessage,
|
|
446
|
+
onRuntimeEvent,
|
|
447
|
+
onThreadDetailLoaded
|
|
319
448
|
}) {
|
|
320
449
|
const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
|
|
321
450
|
const initialCached = activeThreadId ? storage.getThread(activeThreadId) : void 0;
|
|
@@ -332,6 +461,9 @@ function useAgents24ChatController({
|
|
|
332
461
|
const [disliked, setDisliked] = useState({});
|
|
333
462
|
const [copiedMessageId, setCopiedMessageId] = useState(null);
|
|
334
463
|
const [lastThinkingDurationMs, setLastThinkingDurationMs] = useState(null);
|
|
464
|
+
const [threads, setThreads] = useState(() => storage.listThreads());
|
|
465
|
+
const [isRefreshingThreads, setIsRefreshingThreads] = useState(false);
|
|
466
|
+
const [activeRunId, setActiveRunId] = useState(null);
|
|
335
467
|
const textareaRef = useRef(null);
|
|
336
468
|
const activeThreadIdRef = useRef(activeThreadId);
|
|
337
469
|
const messagesRef = useRef(messages);
|
|
@@ -348,6 +480,20 @@ function useAgents24ChatController({
|
|
|
348
480
|
const streamingMessageIdRef = useRef(null);
|
|
349
481
|
const reasoningRef = useRef([]);
|
|
350
482
|
const liveVoiceIdsRef = useRef({});
|
|
483
|
+
const setActiveRunIdValue = useCallback((runId) => {
|
|
484
|
+
activeRunIdRef.current = runId;
|
|
485
|
+
setActiveRunId(runId);
|
|
486
|
+
}, []);
|
|
487
|
+
const syncThreadsFromStorage = useCallback(() => {
|
|
488
|
+
setThreads(storage.listThreads());
|
|
489
|
+
}, [storage]);
|
|
490
|
+
const upsertStoredThread = useCallback(
|
|
491
|
+
(thread) => {
|
|
492
|
+
storage.upsertThread(thread);
|
|
493
|
+
syncThreadsFromStorage();
|
|
494
|
+
},
|
|
495
|
+
[storage, syncThreadsFromStorage]
|
|
496
|
+
);
|
|
351
497
|
useEffect(() => {
|
|
352
498
|
messagesRef.current = messages;
|
|
353
499
|
}, [messages]);
|
|
@@ -355,7 +501,7 @@ function useAgents24ChatController({
|
|
|
355
501
|
(threadId, nextMessages, paging, options) => {
|
|
356
502
|
const existing = storage.getThread(threadId);
|
|
357
503
|
const firstUser = nextMessages.find((message) => message.role === "user");
|
|
358
|
-
|
|
504
|
+
upsertStoredThread({
|
|
359
505
|
...existing || {},
|
|
360
506
|
id: threadId,
|
|
361
507
|
title: existing?.title || (firstUser ? titleFromMessage(firstUser.content, firstUser.attachments || []) : "New chat"),
|
|
@@ -366,13 +512,13 @@ function useAgents24ChatController({
|
|
|
366
512
|
nextBeforeTurnIndex: paging?.nextBeforeTurnIndex ?? nextBeforeTurnIndexRef.current
|
|
367
513
|
});
|
|
368
514
|
},
|
|
369
|
-
[storage]
|
|
515
|
+
[storage, upsertStoredThread]
|
|
370
516
|
);
|
|
371
517
|
const markThreadRunStatus = useCallback(
|
|
372
518
|
(threadId, runId, status, lastEventSeq) => {
|
|
373
519
|
const existing = storage.getThread(threadId);
|
|
374
520
|
if (!existing || !runId) return;
|
|
375
|
-
|
|
521
|
+
upsertStoredThread({
|
|
376
522
|
...existing,
|
|
377
523
|
last_run_id: runId,
|
|
378
524
|
last_run_status: status,
|
|
@@ -389,14 +535,25 @@ function useAgents24ChatController({
|
|
|
389
535
|
created_at: existing.activeRun?.created_at ?? existing.active_run?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
390
536
|
},
|
|
391
537
|
lastEventSeq: typeof lastEventSeq === "number" ? lastEventSeq : existing.lastEventSeq ?? null,
|
|
392
|
-
isRunning:
|
|
538
|
+
isRunning: activeRunIdFromThread({
|
|
539
|
+
lastRunId: runId,
|
|
540
|
+
lastRunStatus: status,
|
|
541
|
+
activeRun: { run_id: runId, status }
|
|
542
|
+
}) !== null
|
|
393
543
|
});
|
|
394
544
|
},
|
|
395
|
-
[storage]
|
|
545
|
+
[storage, upsertStoredThread]
|
|
396
546
|
);
|
|
397
547
|
const refresh = useCallback(async () => {
|
|
398
|
-
|
|
399
|
-
|
|
548
|
+
setIsRefreshingThreads(true);
|
|
549
|
+
try {
|
|
550
|
+
const data = await transport.listThreads();
|
|
551
|
+
const nextThreads = (data.items || []).map((item) => threadSummaryToStored(item));
|
|
552
|
+
storage.setThreads(nextThreads);
|
|
553
|
+
setThreads(storage.listThreads());
|
|
554
|
+
} finally {
|
|
555
|
+
setIsRefreshingThreads(false);
|
|
556
|
+
}
|
|
400
557
|
}, [storage, transport]);
|
|
401
558
|
const applyThreadId = useCallback(
|
|
402
559
|
(threadId, baseMessages) => {
|
|
@@ -424,7 +581,7 @@ function useAgents24ChatController({
|
|
|
424
581
|
const controller = abortControllerRef.current;
|
|
425
582
|
abortControllerRef.current = null;
|
|
426
583
|
controller?.abort();
|
|
427
|
-
|
|
584
|
+
setActiveRunIdValue(null);
|
|
428
585
|
reattachedRunIdRef.current = null;
|
|
429
586
|
streamingMessageIdRef.current = null;
|
|
430
587
|
streamingContentRef.current = "";
|
|
@@ -433,7 +590,7 @@ function useAgents24ChatController({
|
|
|
433
590
|
setIsLoading(false);
|
|
434
591
|
setStreamingContent("");
|
|
435
592
|
setCurrentReasoning([]);
|
|
436
|
-
}, []);
|
|
593
|
+
}, [setActiveRunIdValue]);
|
|
437
594
|
const setLiveAssistantMessage = useCallback(
|
|
438
595
|
(input) => {
|
|
439
596
|
setMessages((prev) => {
|
|
@@ -503,7 +660,7 @@ function useAgents24ChatController({
|
|
|
503
660
|
persistThread(input.threadId, completed);
|
|
504
661
|
const existingThread = storage.getThread(input.threadId);
|
|
505
662
|
if (existingThread && input.runId) {
|
|
506
|
-
|
|
663
|
+
upsertStoredThread({
|
|
507
664
|
...existingThread,
|
|
508
665
|
messages: completed,
|
|
509
666
|
last_run_id: input.runId,
|
|
@@ -518,7 +675,7 @@ function useAgents24ChatController({
|
|
|
518
675
|
}
|
|
519
676
|
return completed;
|
|
520
677
|
},
|
|
521
|
-
[createId, persistThread, storage]
|
|
678
|
+
[createId, persistThread, storage, upsertStoredThread]
|
|
522
679
|
);
|
|
523
680
|
const loadThread = useCallback(
|
|
524
681
|
async (threadId) => {
|
|
@@ -533,6 +690,7 @@ function useAgents24ChatController({
|
|
|
533
690
|
includeRunEvents: false
|
|
534
691
|
});
|
|
535
692
|
if (activeThreadIdRef.current !== threadId || seq !== requestSeqRef.current) return;
|
|
693
|
+
void onThreadDetailLoaded?.(detail);
|
|
536
694
|
const nextMessages = threadDetailToMessages(detail);
|
|
537
695
|
const paging = threadPaging(detail);
|
|
538
696
|
setContextStatus(latestContextWindowFromThread(detail));
|
|
@@ -542,7 +700,7 @@ function useAgents24ChatController({
|
|
|
542
700
|
hasOlderTurnsRef.current = paging.hasOlderTurns;
|
|
543
701
|
nextBeforeTurnIndexRef.current = paging.nextBeforeTurnIndex;
|
|
544
702
|
loadedThreadIdRef.current = threadId;
|
|
545
|
-
|
|
703
|
+
upsertStoredThread({
|
|
546
704
|
...threadSummaryToStored(detail),
|
|
547
705
|
messages: nextMessages,
|
|
548
706
|
isHydrated: true,
|
|
@@ -554,7 +712,7 @@ function useAgents24ChatController({
|
|
|
554
712
|
if (activeThreadIdRef.current === threadId) setLoadingHistory(false);
|
|
555
713
|
}
|
|
556
714
|
},
|
|
557
|
-
[pageSize, setLoadingHistory,
|
|
715
|
+
[onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
|
|
558
716
|
);
|
|
559
717
|
const loadOlderTurns = useCallback(async () => {
|
|
560
718
|
const threadId = activeThreadIdRef.current;
|
|
@@ -586,11 +744,18 @@ function useAgents24ChatController({
|
|
|
586
744
|
}, [pageSize, persistThread, transport]);
|
|
587
745
|
const handleStreamEvent = useCallback(
|
|
588
746
|
(input) => {
|
|
589
|
-
const { event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
|
|
747
|
+
const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
|
|
590
748
|
const payload = event.payload || {};
|
|
591
749
|
const responseBlocks = Array.isArray(payload.response_blocks) ? payload.response_blocks : null;
|
|
592
|
-
if (event.run_id)
|
|
593
|
-
|
|
750
|
+
if (event.run_id) setActiveRunIdValue(event.run_id);
|
|
751
|
+
void onRuntimeEvent?.(event, {
|
|
752
|
+
mode,
|
|
753
|
+
assistantMessageId,
|
|
754
|
+
threadId: streamThreadIdRef.current,
|
|
755
|
+
runId: event.run_id || activeRunIdRef.current,
|
|
756
|
+
startedAt
|
|
757
|
+
});
|
|
758
|
+
setContextStatus((current) => mergeContextWindowUpdate(current, payload.context_window));
|
|
594
759
|
if (responseBlocks) {
|
|
595
760
|
const blockText = String(payload.assistant_output_text || "") || assistantTextFromResponseBlocks(responseBlocks) || streamingContentRef.current;
|
|
596
761
|
if (blockText) setStreamingText(blockText);
|
|
@@ -636,7 +801,7 @@ function useAgents24ChatController({
|
|
|
636
801
|
error: isFailed ? String(payload.message || payload.error || event.diagnostics?.[0]?.message || onStreamErrorMessage?.(event) || "The chat run failed.") : void 0
|
|
637
802
|
});
|
|
638
803
|
},
|
|
639
|
-
[applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onStreamErrorMessage, setLiveAssistantMessage, setReasoningSteps]
|
|
804
|
+
[applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
|
|
640
805
|
);
|
|
641
806
|
const runStream = useCallback(
|
|
642
807
|
async (input) => {
|
|
@@ -698,16 +863,16 @@ function useAgents24ChatController({
|
|
|
698
863
|
}
|
|
699
864
|
try {
|
|
700
865
|
if (input.mode === "attach") {
|
|
701
|
-
|
|
866
|
+
setActiveRunIdValue(input.runId);
|
|
702
867
|
reattachedRunIdRef.current = input.runId;
|
|
703
868
|
await transport.attachRun(
|
|
704
869
|
{ runId: input.runId, signal: controller.signal },
|
|
705
|
-
(event) => handleStreamEvent({ event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
|
|
870
|
+
(event) => handleStreamEvent({ mode: "attach", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
|
|
706
871
|
);
|
|
707
872
|
} else {
|
|
708
873
|
await transport.streamMessage(
|
|
709
|
-
{
|
|
710
|
-
(event) => handleStreamEvent({ event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
|
|
874
|
+
{ ...input.message, files: input.message.files || [], threadId: activeThreadIdRef.current, signal: controller.signal },
|
|
875
|
+
(event) => handleStreamEvent({ mode: "submit", event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages })
|
|
711
876
|
);
|
|
712
877
|
}
|
|
713
878
|
if (streamingContentRef.current.trim() && streamingMessageIdRef.current) {
|
|
@@ -739,7 +904,7 @@ function useAgents24ChatController({
|
|
|
739
904
|
const isCurrentStream = abortControllerRef.current === controller || streamingMessageIdRef.current === assistantMessageId;
|
|
740
905
|
if (abortControllerRef.current === controller) abortControllerRef.current = null;
|
|
741
906
|
if (isCurrentStream) {
|
|
742
|
-
|
|
907
|
+
setActiveRunIdValue(null);
|
|
743
908
|
streamingMessageIdRef.current = null;
|
|
744
909
|
reattachedRunIdRef.current = null;
|
|
745
910
|
setStreamingMessageId(null);
|
|
@@ -750,7 +915,7 @@ function useAgents24ChatController({
|
|
|
750
915
|
}
|
|
751
916
|
}
|
|
752
917
|
},
|
|
753
|
-
[createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setReasoningSteps, transport]
|
|
918
|
+
[createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
|
|
754
919
|
);
|
|
755
920
|
const handleSubmit = useCallback(
|
|
756
921
|
async (message) => {
|
|
@@ -765,7 +930,7 @@ function useAgents24ChatController({
|
|
|
765
930
|
const liveMessageId = streamingMessageIdRef.current;
|
|
766
931
|
abortControllerRef.current?.abort();
|
|
767
932
|
abortControllerRef.current = null;
|
|
768
|
-
|
|
933
|
+
setActiveRunIdValue(null);
|
|
769
934
|
streamingMessageIdRef.current = null;
|
|
770
935
|
setStreamingMessageId(null);
|
|
771
936
|
setIsLoading(false);
|
|
@@ -782,10 +947,16 @@ function useAgents24ChatController({
|
|
|
782
947
|
messageId: liveMessageId
|
|
783
948
|
});
|
|
784
949
|
}
|
|
785
|
-
}, [finalizeAssistantMessage, lastThinkingDurationMs, setReasoningSteps, transport]);
|
|
950
|
+
}, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
|
|
786
951
|
useEffect(() => {
|
|
787
|
-
refresh().catch(() =>
|
|
952
|
+
refresh().catch(() => {
|
|
953
|
+
storage.setThreads([]);
|
|
954
|
+
setThreads([]);
|
|
955
|
+
});
|
|
788
956
|
}, [refresh, storage]);
|
|
957
|
+
useEffect(() => {
|
|
958
|
+
syncThreadsFromStorage();
|
|
959
|
+
}, [storageKey, syncThreadsFromStorage]);
|
|
789
960
|
useEffect(() => {
|
|
790
961
|
const previous = activeThreadIdRef.current;
|
|
791
962
|
activeThreadIdRef.current = activeThreadId;
|
|
@@ -893,7 +1064,40 @@ function useAgents24ChatController({
|
|
|
893
1064
|
return next;
|
|
894
1065
|
});
|
|
895
1066
|
}, [createId, persistThread]);
|
|
1067
|
+
const startNewThread = useCallback(() => {
|
|
1068
|
+
detachActiveStream();
|
|
1069
|
+
requestSeqRef.current += 1;
|
|
1070
|
+
activeThreadIdRef.current = null;
|
|
1071
|
+
loadedThreadIdRef.current = null;
|
|
1072
|
+
nextBeforeTurnIndexRef.current = null;
|
|
1073
|
+
hasOlderTurnsRef.current = false;
|
|
1074
|
+
storage.setActiveThreadId?.(null);
|
|
1075
|
+
onActiveThreadIdChange?.(null);
|
|
1076
|
+
setMessages([]);
|
|
1077
|
+
messagesRef.current = [];
|
|
1078
|
+
setHasOlderTurns(false);
|
|
1079
|
+
setIsLoadingOlder(false);
|
|
1080
|
+
setContextStatus(null);
|
|
1081
|
+
setLoadingHistory(false);
|
|
1082
|
+
}, [detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage]);
|
|
1083
|
+
const loadThreadById = useCallback(
|
|
1084
|
+
async (threadId) => {
|
|
1085
|
+
if (!threadId) return;
|
|
1086
|
+
if (activeThreadIdRef.current !== threadId) {
|
|
1087
|
+
if (abortControllerRef.current) detachActiveStream();
|
|
1088
|
+
activeThreadIdRef.current = threadId;
|
|
1089
|
+
storage.setActiveThreadId?.(threadId);
|
|
1090
|
+
onActiveThreadIdChange?.(threadId);
|
|
1091
|
+
}
|
|
1092
|
+
await loadThread(threadId);
|
|
1093
|
+
},
|
|
1094
|
+
[detachActiveStream, loadThread, onActiveThreadIdChange, storage]
|
|
1095
|
+
);
|
|
1096
|
+
const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
|
|
896
1097
|
return useMemo(() => ({
|
|
1098
|
+
threads,
|
|
1099
|
+
activeThreadId,
|
|
1100
|
+
activeThread,
|
|
897
1101
|
messages,
|
|
898
1102
|
streamingContent,
|
|
899
1103
|
streamingMessageId,
|
|
@@ -902,12 +1106,13 @@ function useAgents24ChatController({
|
|
|
902
1106
|
isLoading,
|
|
903
1107
|
isLoadingHistory,
|
|
904
1108
|
isLoadingOlder,
|
|
1109
|
+
isRefreshingThreads,
|
|
905
1110
|
hasOlderTurns,
|
|
906
1111
|
liked,
|
|
907
1112
|
disliked,
|
|
908
1113
|
copiedMessageId,
|
|
909
1114
|
lastThinkingDurationMs,
|
|
910
|
-
activeRunId
|
|
1115
|
+
activeRunId,
|
|
911
1116
|
handleSubmit,
|
|
912
1117
|
handleStop,
|
|
913
1118
|
handleCopy,
|
|
@@ -916,11 +1121,16 @@ function useAgents24ChatController({
|
|
|
916
1121
|
handleRetry,
|
|
917
1122
|
handleSourceClick: (citations) => onSourceClick?.(citations),
|
|
918
1123
|
upsertLiveVoiceMessage,
|
|
1124
|
+
startNewThread,
|
|
1125
|
+
loadThreadById,
|
|
919
1126
|
loadOlderTurns,
|
|
920
1127
|
refresh,
|
|
921
1128
|
textareaRef
|
|
922
1129
|
}), [
|
|
923
1130
|
copiedMessageId,
|
|
1131
|
+
activeRunId,
|
|
1132
|
+
activeThread,
|
|
1133
|
+
activeThreadId,
|
|
924
1134
|
contextStatus,
|
|
925
1135
|
currentReasoning,
|
|
926
1136
|
disliked,
|
|
@@ -934,14 +1144,18 @@ function useAgents24ChatController({
|
|
|
934
1144
|
isLoading,
|
|
935
1145
|
isLoadingHistory,
|
|
936
1146
|
isLoadingOlder,
|
|
1147
|
+
isRefreshingThreads,
|
|
937
1148
|
lastThinkingDurationMs,
|
|
938
1149
|
liked,
|
|
1150
|
+
loadThreadById,
|
|
939
1151
|
loadOlderTurns,
|
|
940
1152
|
messages,
|
|
941
1153
|
onSourceClick,
|
|
942
1154
|
refresh,
|
|
943
1155
|
streamingContent,
|
|
944
1156
|
streamingMessageId,
|
|
1157
|
+
startNewThread,
|
|
1158
|
+
threads,
|
|
945
1159
|
upsertLiveVoiceMessage
|
|
946
1160
|
]);
|
|
947
1161
|
}
|
|
@@ -1282,6 +1496,18 @@ var nextLatestFollowStateOnScroll = ({
|
|
|
1282
1496
|
if (isProgrammatic) return current;
|
|
1283
1497
|
return atLatest ? "following" : "detached";
|
|
1284
1498
|
};
|
|
1499
|
+
var isUserScrollIntentAwayFromLatest = (intent) => {
|
|
1500
|
+
if (!intent) return false;
|
|
1501
|
+
if ("deltaY" in intent && typeof intent.deltaY === "number") {
|
|
1502
|
+
return intent.deltaY < 0;
|
|
1503
|
+
}
|
|
1504
|
+
if ("key" in intent && typeof intent.key === "string") {
|
|
1505
|
+
if (intent.key === "ArrowUp" || intent.key === "PageUp" || intent.key === "Home") return true;
|
|
1506
|
+
if ((intent.key === " " || intent.key === "Spacebar") && intent.shiftKey) return true;
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
return "touches" in intent;
|
|
1510
|
+
};
|
|
1285
1511
|
function useLatestThreadViewport({
|
|
1286
1512
|
itemCount,
|
|
1287
1513
|
hasOlder,
|
|
@@ -1331,10 +1557,11 @@ function useLatestThreadViewport({
|
|
|
1331
1557
|
const reattachToLatest = useCallback2(() => {
|
|
1332
1558
|
scrollToLatest({ reattach: true });
|
|
1333
1559
|
}, [scrollToLatest]);
|
|
1334
|
-
const handleUserScrollIntent = useCallback2(() => {
|
|
1560
|
+
const handleUserScrollIntent = useCallback2((intent) => {
|
|
1335
1561
|
const element = scrollContainerRef.current;
|
|
1336
1562
|
if (!activeStreamKey || !element) return;
|
|
1337
|
-
|
|
1563
|
+
const atLatest = isAtTimelineLatestEdge(element, isTopOrigin);
|
|
1564
|
+
if (!atLatest || isUserScrollIntentAwayFromLatest(intent)) detachFromLatest();
|
|
1338
1565
|
}, [activeStreamKey, detachFromLatest, isTopOrigin]);
|
|
1339
1566
|
useEffect3(() => {
|
|
1340
1567
|
return () => {
|
|
@@ -1418,9 +1645,22 @@ function useLatestThreadViewport({
|
|
|
1418
1645
|
};
|
|
1419
1646
|
intrinsicResizePreserveRef.current.frame = requestAnimationFrame(preserveFrame);
|
|
1420
1647
|
}, [isTopOrigin, markProgrammaticScroll]);
|
|
1421
|
-
|
|
1648
|
+
useLayoutEffect(() => {
|
|
1422
1649
|
const key = activeStreamKey || null;
|
|
1423
|
-
|
|
1650
|
+
const previousKey = activeStreamKeyRef.current;
|
|
1651
|
+
if (!key) {
|
|
1652
|
+
activeStreamKeyRef.current = null;
|
|
1653
|
+
if (previousKey && shouldAutoFollow && followStateRef.current === "following" && !isLoadingOlder && !olderPageRequestInFlightRef.current) {
|
|
1654
|
+
const frame2 = requestAnimationFrame(() => {
|
|
1655
|
+
if (followStateRef.current === "following" && !isLoadingOlder && !olderPageRequestInFlightRef.current) {
|
|
1656
|
+
scrollToLatest({ reattach: false });
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
return () => cancelAnimationFrame(frame2);
|
|
1660
|
+
}
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
if (previousKey === key) {
|
|
1424
1664
|
activeStreamKeyRef.current = key;
|
|
1425
1665
|
return;
|
|
1426
1666
|
}
|
|
@@ -1428,7 +1668,7 @@ function useLatestThreadViewport({
|
|
|
1428
1668
|
const frame = requestAnimationFrame(() => scrollToLatest());
|
|
1429
1669
|
activeStreamKeyRef.current = key;
|
|
1430
1670
|
return () => cancelAnimationFrame(frame);
|
|
1431
|
-
}, [activeStreamKey, scrollToLatest, setLatestFollowState]);
|
|
1671
|
+
}, [activeStreamKey, isLoadingOlder, scrollToLatest, setLatestFollowState, shouldAutoFollow]);
|
|
1432
1672
|
useLayoutEffect(() => {
|
|
1433
1673
|
if (!activeStreamKey || !shouldAutoFollow || followStateRef.current !== "following") return;
|
|
1434
1674
|
if (isLoadingOlder || olderPageRequestInFlightRef.current) return;
|
|
@@ -1499,10 +1739,10 @@ function LatestThreadViewport({
|
|
|
1499
1739
|
{
|
|
1500
1740
|
ref: viewport.scrollContainerRef,
|
|
1501
1741
|
className,
|
|
1502
|
-
|
|
1742
|
+
onKeyDownCapture: viewport.handleUserScrollIntent,
|
|
1503
1743
|
onScroll: viewport.handleScroll,
|
|
1504
|
-
|
|
1505
|
-
|
|
1744
|
+
onTouchMoveCapture: viewport.handleUserScrollIntent,
|
|
1745
|
+
onWheelCapture: viewport.handleUserScrollIntent,
|
|
1506
1746
|
role: "log",
|
|
1507
1747
|
style: { overflowAnchor: "none" },
|
|
1508
1748
|
children: [
|
|
@@ -1522,6 +1762,7 @@ export {
|
|
|
1522
1762
|
activeRunIdFromThreadDetail,
|
|
1523
1763
|
assistantTextFromParts,
|
|
1524
1764
|
assistantTextFromResponseBlocks,
|
|
1765
|
+
compressionFromContextWindow,
|
|
1525
1766
|
consumeSseResponse,
|
|
1526
1767
|
createChatId,
|
|
1527
1768
|
createFetchChatTransport,
|
|
@@ -1533,9 +1774,14 @@ export {
|
|
|
1533
1774
|
isAtTimelineLatestEdge,
|
|
1534
1775
|
isRunningThreadStatus,
|
|
1535
1776
|
isScrollable,
|
|
1777
|
+
isUserScrollIntentAwayFromLatest,
|
|
1536
1778
|
latestContextWindowFromThread,
|
|
1779
|
+
mergeContextWindow,
|
|
1780
|
+
mergeContextWindowUpdate,
|
|
1537
1781
|
mergeReasoningSteps,
|
|
1538
1782
|
nextLatestFollowStateOnScroll,
|
|
1783
|
+
normalizeContextCompression,
|
|
1784
|
+
normalizeContextWindow,
|
|
1539
1785
|
parseSseBlock,
|
|
1540
1786
|
partsFromResponseBlocks,
|
|
1541
1787
|
reasoningStepsFromParts,
|