@djangocfg/ui-tools 2.1.336 → 2.1.338
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/dist/ChatRoot-BT3CLXI4.cjs +14 -0
- package/dist/{ChatRoot-IIYQEWUU.mjs.map → ChatRoot-BT3CLXI4.cjs.map} +1 -1
- package/dist/ChatRoot-R5YQTOLP.mjs +5 -0
- package/dist/{ChatRoot-UUKTYM4N.cjs.map → ChatRoot-R5YQTOLP.mjs.map} +1 -1
- package/dist/{chunk-NRXYYO5V.cjs → chunk-5T4K3VUV.cjs} +147 -26
- package/dist/chunk-5T4K3VUV.cjs.map +1 -0
- package/dist/{chunk-KRETIZU6.mjs → chunk-QE25H6DP.mjs} +148 -28
- package/dist/chunk-QE25H6DP.mjs.map +1 -0
- package/dist/index.cjs +49 -45
- package/dist/index.d.cts +55 -2
- package/dist/index.d.ts +55 -2
- package/dist/index.mjs +3 -3
- package/package.json +6 -6
- package/src/tools/Chat/components/ChatRoot.tsx +9 -2
- package/src/tools/Chat/context/ChatProvider.tsx +4 -0
- package/src/tools/Chat/core/logger.ts +73 -0
- package/src/tools/Chat/hooks/useChat.ts +149 -20
- package/src/tools/Chat/index.ts +3 -0
- package/dist/ChatRoot-IIYQEWUU.mjs +0 -5
- package/dist/ChatRoot-UUKTYM4N.cjs +0 -14
- package/dist/chunk-KRETIZU6.mjs.map +0 -1
- package/dist/chunk-NRXYYO5V.cjs.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat dev logger.
|
|
3
|
+
*
|
|
4
|
+
* A thin namespaced wrapper over `consola` that no-ops in production unless
|
|
5
|
+
* the host app explicitly opts in via `<ChatRoot debug />`. The default
|
|
6
|
+
* detection uses `isDev` from `@djangocfg/ui-core/lib/env` (NODE_ENV).
|
|
7
|
+
*
|
|
8
|
+
* Why a dedicated module: chat is async and event-heavy (bootstrap, transport,
|
|
9
|
+
* SSE chunks, tool calls, regenerate, …). Inline `console.log`s rot fast and
|
|
10
|
+
* leak into prod. A single `getChatLogger()` call gives every layer the same
|
|
11
|
+
* namespaced sub-logger and keeps zero-cost gating in one place.
|
|
12
|
+
*
|
|
13
|
+
* Sub-loggers:
|
|
14
|
+
* bootstrap — initial session bootstrap (createSession / loadHistory)
|
|
15
|
+
* transport — outbound transport calls + responses
|
|
16
|
+
* stream — SSE chunk / tool / message_end events
|
|
17
|
+
* lifecycle — sendMessage, regenerate, newSession, edits
|
|
18
|
+
* tools — tool_call_start / _delta / _end specifics
|
|
19
|
+
* error — caught errors (always emitted as `error` level)
|
|
20
|
+
*/
|
|
21
|
+
import { consola, type ConsolaInstance } from 'consola';
|
|
22
|
+
|
|
23
|
+
import { isDev } from '@djangocfg/ui-core/lib';
|
|
24
|
+
|
|
25
|
+
export type ChatLogScope = 'bootstrap' | 'transport' | 'stream' | 'lifecycle' | 'tools' | 'error';
|
|
26
|
+
|
|
27
|
+
export interface ChatLogger {
|
|
28
|
+
bootstrap: ConsolaInstance;
|
|
29
|
+
transport: ConsolaInstance;
|
|
30
|
+
stream: ConsolaInstance;
|
|
31
|
+
lifecycle: ConsolaInstance;
|
|
32
|
+
tools: ConsolaInstance;
|
|
33
|
+
error: ConsolaInstance;
|
|
34
|
+
/** True when this logger is actually emitting (host opted in or NODE_ENV=development). */
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SCOPES: ChatLogScope[] = ['bootstrap', 'transport', 'stream', 'lifecycle', 'tools', 'error'];
|
|
39
|
+
|
|
40
|
+
/** Module-level cache so all hooks/components share the same logger instance per `enabled` mode. */
|
|
41
|
+
const cache = new Map<boolean, ChatLogger>();
|
|
42
|
+
|
|
43
|
+
function buildLogger(enabled: boolean): ChatLogger {
|
|
44
|
+
const root = consola.withTag('chat');
|
|
45
|
+
const subs = Object.fromEntries(
|
|
46
|
+
SCOPES.map((scope) => [scope, root.withTag(scope)]),
|
|
47
|
+
) as Record<ChatLogScope, ConsolaInstance>;
|
|
48
|
+
|
|
49
|
+
if (!enabled) {
|
|
50
|
+
// Silence everything except `error` — surfaced errors should never go
|
|
51
|
+
// missing even if the host didn't opt in to debug logs.
|
|
52
|
+
for (const scope of SCOPES) {
|
|
53
|
+
if (scope === 'error') continue;
|
|
54
|
+
subs[scope].level = -999;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { ...subs, enabled };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the chat logger.
|
|
63
|
+
* @param debug Explicit override from the host. `undefined` falls back to `isDev`.
|
|
64
|
+
*/
|
|
65
|
+
export function getChatLogger(debug?: boolean): ChatLogger {
|
|
66
|
+
const enabled = debug ?? isDev;
|
|
67
|
+
let logger = cache.get(enabled);
|
|
68
|
+
if (!logger) {
|
|
69
|
+
logger = buildLogger(enabled);
|
|
70
|
+
cache.set(enabled, logger);
|
|
71
|
+
}
|
|
72
|
+
return logger;
|
|
73
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
type ChatAction,
|
|
19
19
|
} from '../core/reducer';
|
|
20
20
|
import { createId } from '../core/ids';
|
|
21
|
+
import { getChatLogger } from '../core/logger';
|
|
21
22
|
import { createTokenBuffer } from '../core/markdown';
|
|
22
23
|
|
|
23
24
|
export interface UseChatConfig {
|
|
@@ -36,6 +37,12 @@ export interface UseChatConfig {
|
|
|
36
37
|
metadata?: Record<string, unknown>;
|
|
37
38
|
/** Stamped on outgoing user messages as `message.sender`. */
|
|
38
39
|
userPersona?: ChatPersona;
|
|
40
|
+
/**
|
|
41
|
+
* Enable verbose dev-mode logging (consola, namespace `chat:*`).
|
|
42
|
+
* Defaults to `isDev` from `@djangocfg/ui-core/lib`. Pass `false` to silence
|
|
43
|
+
* even in development; `true` to force on in production.
|
|
44
|
+
*/
|
|
45
|
+
debug?: boolean;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
export interface UseChatReturn extends ChatState {
|
|
@@ -57,37 +64,84 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
57
64
|
|
|
58
65
|
const abortRef = useRef<AbortController | null>(null);
|
|
59
66
|
const lastErrorRef = useRef<Error | null>(null);
|
|
60
|
-
const initRef = useRef(false);
|
|
61
67
|
const streamingMsgIdRef = useRef<string | null>(null);
|
|
68
|
+
// Promise resolved once the initial session is available (or `null` when the
|
|
69
|
+
// bootstrap finished without producing one — e.g. autoCreateSession=false).
|
|
70
|
+
// Action methods (sendMessage, regenerate, …) await this so users who type
|
|
71
|
+
// before the first network round-trip resolves don't hit "No active session".
|
|
72
|
+
const bootstrapRef = useRef<Promise<string | null> | null>(null);
|
|
62
73
|
|
|
63
74
|
const { transport, autoCreateSession = true, streaming = true, pageSize = LIMITS.pageSize } =
|
|
64
75
|
config;
|
|
76
|
+
const log = getChatLogger(config.debug);
|
|
65
77
|
|
|
66
78
|
// Initial session bootstrap.
|
|
79
|
+
//
|
|
80
|
+
// Strict Mode quirk: this effect runs twice in dev (mount → unmount → mount).
|
|
81
|
+
// Previous design used `initRef` to skip the second run, but the first run's
|
|
82
|
+
// cleanup sets `cancelled = true`, so its dispatch never lands — and the
|
|
83
|
+
// second run was blocked. Result: bootstrap silently completed the network
|
|
84
|
+
// call but never wrote sessionId to state.
|
|
85
|
+
//
|
|
86
|
+
// Fix: drop `initRef`. On the second mount we DO re-run, but `bootstrapRef`
|
|
87
|
+
// is preserved across renders so we don't re-fetch if a previous run already
|
|
88
|
+
// succeeded — we just resolve from existing state.
|
|
67
89
|
useEffect(() => {
|
|
68
|
-
if (initRef.current) return;
|
|
69
|
-
initRef.current = true;
|
|
70
|
-
|
|
71
90
|
let cancelled = false;
|
|
72
|
-
|
|
91
|
+
|
|
92
|
+
// If a prior run already produced a sessionId, skip.
|
|
93
|
+
if (stateRef.current.sessionId) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Show "loading" state immediately so the UI doesn't look idle while we
|
|
98
|
+
// wait for createSession / loadHistory to come back.
|
|
99
|
+
if (config.initialSessionId || autoCreateSession) {
|
|
100
|
+
dispatch({ type: 'HISTORY_LOAD_START' });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
log.bootstrap.info('start', {
|
|
104
|
+
mode: config.initialSessionId ? 'resume' : autoCreateSession ? 'create' : 'idle',
|
|
105
|
+
initialSessionId: config.initialSessionId,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const run = async (): Promise<string | null> => {
|
|
109
|
+
const t0 = performance.now();
|
|
73
110
|
try {
|
|
74
111
|
if (config.initialSessionId) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
112
|
+
if (!cancelled) {
|
|
113
|
+
dispatch({
|
|
114
|
+
type: 'SESSION_SET',
|
|
115
|
+
sessionId: config.initialSessionId,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
80
118
|
const page = await transport.loadHistory(config.initialSessionId, null, pageSize);
|
|
81
|
-
if (cancelled)
|
|
119
|
+
if (cancelled) {
|
|
120
|
+
log.bootstrap.debug('cancelled (post-loadHistory)');
|
|
121
|
+
// Even though *this* effect is cancelled, the network call did
|
|
122
|
+
// succeed — return the sessionId so awaitSession() doesn't see
|
|
123
|
+
// a phantom null when the next mount picks up.
|
|
124
|
+
return config.initialSessionId;
|
|
125
|
+
}
|
|
82
126
|
dispatch({
|
|
83
127
|
type: 'HISTORY_LOAD_DONE',
|
|
84
128
|
messages: page.messages,
|
|
85
129
|
hasMore: page.hasMore,
|
|
86
130
|
cursor: page.nextCursor,
|
|
87
131
|
});
|
|
88
|
-
|
|
132
|
+
log.bootstrap.success('resumed', {
|
|
133
|
+
sessionId: config.initialSessionId,
|
|
134
|
+
messages: page.messages.length,
|
|
135
|
+
hasMore: page.hasMore,
|
|
136
|
+
elapsedMs: Math.round(performance.now() - t0),
|
|
137
|
+
});
|
|
138
|
+
return config.initialSessionId;
|
|
139
|
+
}
|
|
140
|
+
if (autoCreateSession) {
|
|
89
141
|
const info = await transport.createSession({ metadata: config.metadata });
|
|
90
|
-
if
|
|
142
|
+
// We always commit the session to state even if this effect was
|
|
143
|
+
// cancelled — the network call already succeeded, throwing away the
|
|
144
|
+
// sessionId would just trigger a duplicate createSession on remount.
|
|
91
145
|
dispatch({
|
|
92
146
|
type: 'SESSION_SET',
|
|
93
147
|
sessionId: info.sessionId,
|
|
@@ -95,21 +149,53 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
95
149
|
hasMore: info.hasMore ?? false,
|
|
96
150
|
cursor: info.cursor ?? null,
|
|
97
151
|
});
|
|
152
|
+
dispatch({
|
|
153
|
+
type: 'HISTORY_LOAD_DONE',
|
|
154
|
+
messages: info.messages ?? [],
|
|
155
|
+
hasMore: info.hasMore ?? false,
|
|
156
|
+
cursor: info.cursor ?? null,
|
|
157
|
+
});
|
|
158
|
+
log.bootstrap.success(cancelled ? 'created (post-cancel)' : 'created', {
|
|
159
|
+
sessionId: info.sessionId,
|
|
160
|
+
resumed: info.resumed ?? false,
|
|
161
|
+
cancelled,
|
|
162
|
+
elapsedMs: Math.round(performance.now() - t0),
|
|
163
|
+
});
|
|
164
|
+
return info.sessionId;
|
|
98
165
|
}
|
|
166
|
+
log.bootstrap.debug('idle (no initialSessionId, autoCreateSession=false)');
|
|
167
|
+
return null;
|
|
99
168
|
} catch (err) {
|
|
100
169
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
170
|
+
if (cancelled) {
|
|
171
|
+
log.bootstrap.debug('cancelled (in catch)', { message: e.message });
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
101
174
|
lastErrorRef.current = e;
|
|
102
175
|
dispatch({ type: 'ERROR_SET', error: e.message });
|
|
103
176
|
config.onError?.(e);
|
|
177
|
+
log.error.error('bootstrap failed', { message: e.message, elapsedMs: Math.round(performance.now() - t0) });
|
|
178
|
+
return null;
|
|
104
179
|
}
|
|
105
180
|
};
|
|
106
|
-
|
|
181
|
+
bootstrapRef.current = run();
|
|
107
182
|
return () => {
|
|
108
183
|
cancelled = true;
|
|
109
184
|
};
|
|
110
185
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
111
186
|
}, []);
|
|
112
187
|
|
|
188
|
+
/** Wait for the initial session bootstrap to settle, then return whatever
|
|
189
|
+
* sessionId is now in state. Safe to call multiple times. */
|
|
190
|
+
const awaitSession = useCallback(async (): Promise<string | null> => {
|
|
191
|
+
if (stateRef.current.sessionId) return stateRef.current.sessionId;
|
|
192
|
+
if (bootstrapRef.current) {
|
|
193
|
+
const id = await bootstrapRef.current;
|
|
194
|
+
if (id) return id;
|
|
195
|
+
}
|
|
196
|
+
return stateRef.current.sessionId;
|
|
197
|
+
}, []);
|
|
198
|
+
|
|
113
199
|
const consumeStream = useCallback(
|
|
114
200
|
async (
|
|
115
201
|
sessionId: string,
|
|
@@ -123,12 +209,16 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
123
209
|
|
|
124
210
|
dispatch({ type: 'STREAM_START', id: assistantId });
|
|
125
211
|
config.onStreamStart?.(assistantId);
|
|
212
|
+
log.stream.info('start', { sessionId, assistantId, chars: content.length });
|
|
126
213
|
|
|
127
214
|
const tokenBuffer = createTokenBuffer((delta) =>
|
|
128
215
|
dispatch({ type: 'STREAM_CHUNK', delta }),
|
|
129
216
|
);
|
|
130
217
|
|
|
131
218
|
let serverMessageId: string | null = null;
|
|
219
|
+
let chunkCount = 0;
|
|
220
|
+
let charsReceived = 0;
|
|
221
|
+
const t0 = performance.now();
|
|
132
222
|
|
|
133
223
|
try {
|
|
134
224
|
const iterator = transport.stream(sessionId, content, {
|
|
@@ -150,18 +240,26 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
150
240
|
|
|
151
241
|
const finalMsg = stateRef.current.messages.find((m) => m.id === assistantId);
|
|
152
242
|
if (finalMsg) config.onMessageEnd?.(finalMsg);
|
|
243
|
+
log.stream.success('done', {
|
|
244
|
+
assistantId,
|
|
245
|
+
chunks: chunkCount,
|
|
246
|
+
chars: charsReceived,
|
|
247
|
+
elapsedMs: Math.round(performance.now() - t0),
|
|
248
|
+
});
|
|
153
249
|
} catch (err) {
|
|
154
250
|
tokenBuffer.close();
|
|
155
251
|
if (ctrl.signal.aborted) {
|
|
156
252
|
const partial =
|
|
157
253
|
stateRef.current.messages.find((m) => m.id === assistantId)?.content ?? '';
|
|
158
254
|
dispatch({ type: 'STREAM_CANCELLED', id: assistantId, partialText: partial });
|
|
255
|
+
log.stream.warn('cancelled', { assistantId, partialChars: partial.length });
|
|
159
256
|
return;
|
|
160
257
|
}
|
|
161
258
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
162
259
|
lastErrorRef.current = e;
|
|
163
260
|
dispatch({ type: 'STREAM_ERROR', id: assistantId, message: e.message });
|
|
164
261
|
config.onError?.(e);
|
|
262
|
+
log.error.error('stream failed', { assistantId, message: e.message });
|
|
165
263
|
} finally {
|
|
166
264
|
tokenBuffer.close();
|
|
167
265
|
if (abortRef.current === ctrl) abortRef.current = null;
|
|
@@ -172,13 +270,17 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
172
270
|
switch (ev.type) {
|
|
173
271
|
case 'message_start':
|
|
174
272
|
serverMessageId = ev.messageId;
|
|
273
|
+
log.stream.debug('message_start', { messageId: ev.messageId });
|
|
175
274
|
return;
|
|
176
275
|
case 'chunk':
|
|
177
276
|
tokenBuffer.push(ev.delta);
|
|
277
|
+
chunkCount += 1;
|
|
278
|
+
charsReceived += ev.delta.length;
|
|
178
279
|
return;
|
|
179
280
|
case 'tool_activity':
|
|
180
281
|
tokenBuffer.flush();
|
|
181
282
|
dispatch({ type: 'STREAM_TOOL_ACTIVITY', tool: ev.tool });
|
|
283
|
+
log.tools.debug('activity', { tool: ev.tool, status: ev.status });
|
|
182
284
|
return;
|
|
183
285
|
case 'tool_call_start': {
|
|
184
286
|
tokenBuffer.flush();
|
|
@@ -195,6 +297,7 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
195
297
|
messageId: assistantId,
|
|
196
298
|
toolCall,
|
|
197
299
|
});
|
|
300
|
+
log.tools.info('call_start', { toolId: ev.toolId, name: ev.name });
|
|
198
301
|
return;
|
|
199
302
|
}
|
|
200
303
|
case 'tool_call_delta':
|
|
@@ -213,6 +316,7 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
213
316
|
output: ev.output,
|
|
214
317
|
status: ev.status,
|
|
215
318
|
});
|
|
319
|
+
log.tools.info('call_end', { toolId: ev.toolId, status: ev.status });
|
|
216
320
|
return;
|
|
217
321
|
case 'message_end':
|
|
218
322
|
tokenBuffer.flush();
|
|
@@ -223,6 +327,11 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
223
327
|
tokensOut: ev.tokensOut,
|
|
224
328
|
sources: ev.sources,
|
|
225
329
|
});
|
|
330
|
+
log.stream.debug('message_end', {
|
|
331
|
+
tokensIn: ev.tokensIn,
|
|
332
|
+
tokensOut: ev.tokensOut,
|
|
333
|
+
sources: ev.sources?.length ?? 0,
|
|
334
|
+
});
|
|
226
335
|
return;
|
|
227
336
|
case 'error':
|
|
228
337
|
tokenBuffer.flush();
|
|
@@ -231,6 +340,7 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
231
340
|
id: assistantId,
|
|
232
341
|
message: ev.message,
|
|
233
342
|
});
|
|
343
|
+
log.error.error('stream event error', { code: ev.code, message: ev.message });
|
|
234
344
|
return;
|
|
235
345
|
}
|
|
236
346
|
// unreachable; prevents unused-var on serverMessageId
|
|
@@ -270,16 +380,31 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
270
380
|
|
|
271
381
|
const sendMessage = useCallback(
|
|
272
382
|
async (content: string, attachments?: ChatAttachment[]) => {
|
|
273
|
-
|
|
383
|
+
// Wait for the initial session bootstrap if it's still in flight.
|
|
384
|
+
// Without this, fast typers hit "No active session" before
|
|
385
|
+
// transport.createSession resolves.
|
|
386
|
+
log.lifecycle.info('sendMessage', {
|
|
387
|
+
chars: content.length,
|
|
388
|
+
attachments: attachments?.length ?? 0,
|
|
389
|
+
hasSession: !!stateRef.current.sessionId,
|
|
390
|
+
});
|
|
391
|
+
const sessionId = await awaitSession();
|
|
274
392
|
if (!sessionId) {
|
|
275
393
|
const e = new Error('No active session');
|
|
276
394
|
lastErrorRef.current = e;
|
|
277
395
|
dispatch({ type: 'ERROR_SET', error: e.message });
|
|
278
396
|
config.onError?.(e);
|
|
397
|
+
log.error.error('sendMessage aborted: no session');
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (!content.trim() && !(attachments && attachments.length > 0)) {
|
|
401
|
+
log.lifecycle.debug('sendMessage skipped (empty)');
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (stateRef.current.isStreaming) {
|
|
405
|
+
log.lifecycle.debug('sendMessage skipped (already streaming)');
|
|
279
406
|
return;
|
|
280
407
|
}
|
|
281
|
-
if (!content.trim() && !(attachments && attachments.length > 0)) return;
|
|
282
|
-
if (stateRef.current.isStreaming) return;
|
|
283
408
|
|
|
284
409
|
const userMsg: ChatMessage = {
|
|
285
410
|
id: createId('u'),
|
|
@@ -298,7 +423,7 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
298
423
|
await consumeBuffered(sessionId, content, attachments);
|
|
299
424
|
}
|
|
300
425
|
},
|
|
301
|
-
[streaming, consumeStream, consumeBuffered, config],
|
|
426
|
+
[streaming, consumeStream, consumeBuffered, config, awaitSession],
|
|
302
427
|
);
|
|
303
428
|
|
|
304
429
|
const cancelStream = useCallback(() => {
|
|
@@ -307,6 +432,7 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
307
432
|
|
|
308
433
|
const regenerate = useCallback(
|
|
309
434
|
async (messageId?: string) => {
|
|
435
|
+
log.lifecycle.info('regenerate', { messageId: messageId ?? '(last)' });
|
|
310
436
|
const messages = stateRef.current.messages;
|
|
311
437
|
let targetUserIdx = -1;
|
|
312
438
|
if (messageId) {
|
|
@@ -324,7 +450,7 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
324
450
|
for (let i = messages.length - 1; i > targetUserIdx; i -= 1) {
|
|
325
451
|
dispatch({ type: 'MESSAGE_DELETE', id: messages[i].id });
|
|
326
452
|
}
|
|
327
|
-
const sessionId =
|
|
453
|
+
const sessionId = await awaitSession();
|
|
328
454
|
if (!sessionId) return;
|
|
329
455
|
if (streaming) {
|
|
330
456
|
await consumeStream(sessionId, userMsg.content, userMsg.attachments);
|
|
@@ -332,7 +458,7 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
332
458
|
await consumeBuffered(sessionId, userMsg.content, userMsg.attachments);
|
|
333
459
|
}
|
|
334
460
|
},
|
|
335
|
-
[streaming, consumeStream, consumeBuffered],
|
|
461
|
+
[streaming, consumeStream, consumeBuffered, awaitSession],
|
|
336
462
|
);
|
|
337
463
|
|
|
338
464
|
const editMessage = useCallback(
|
|
@@ -381,6 +507,7 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
381
507
|
}, [transport, pageSize, config]);
|
|
382
508
|
|
|
383
509
|
const newSession = useCallback(async () => {
|
|
510
|
+
log.lifecycle.info('newSession', { previous: stateRef.current.sessionId });
|
|
384
511
|
abortRef.current?.abort();
|
|
385
512
|
const previous = stateRef.current.sessionId;
|
|
386
513
|
if (previous) {
|
|
@@ -400,11 +527,13 @@ export function useChat(config: UseChatConfig): UseChatReturn {
|
|
|
400
527
|
hasMore: info.hasMore ?? false,
|
|
401
528
|
cursor: info.cursor ?? null,
|
|
402
529
|
});
|
|
530
|
+
log.lifecycle.success('newSession ok', { sessionId: info.sessionId });
|
|
403
531
|
} catch (err) {
|
|
404
532
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
405
533
|
lastErrorRef.current = e;
|
|
406
534
|
dispatch({ type: 'ERROR_SET', error: e.message });
|
|
407
535
|
config.onError?.(e);
|
|
536
|
+
log.error.error('newSession failed', { message: e.message });
|
|
408
537
|
}
|
|
409
538
|
}, [transport, config]);
|
|
410
539
|
|
package/src/tools/Chat/index.ts
CHANGED
|
@@ -109,6 +109,9 @@ export {
|
|
|
109
109
|
export { useChatLightbox, type UseChatLightboxReturn, type ChatLightboxState } from './hooks';
|
|
110
110
|
export { collectImageAttachments } from './utils/collectImageAttachments';
|
|
111
111
|
|
|
112
|
+
// Dev logger (consola-based, namespace "chat:*")
|
|
113
|
+
export { getChatLogger, type ChatLogger, type ChatLogScope } from './core/logger';
|
|
114
|
+
|
|
112
115
|
// Context
|
|
113
116
|
export {
|
|
114
117
|
ChatProvider,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkNRXYYO5V_cjs = require('./chunk-NRXYYO5V.cjs');
|
|
4
|
-
require('./chunk-B5AWZOHJ.cjs');
|
|
5
|
-
require('./chunk-OLISEQHS.cjs');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Object.defineProperty(exports, "ChatRoot", {
|
|
10
|
-
enumerable: true,
|
|
11
|
-
get: function () { return chunkNRXYYO5V_cjs.ChatRoot; }
|
|
12
|
-
});
|
|
13
|
-
//# sourceMappingURL=ChatRoot-UUKTYM4N.cjs.map
|
|
14
|
-
//# sourceMappingURL=ChatRoot-UUKTYM4N.cjs.map
|