flare-chat-core 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +28 -0
  2. package/docs/CAPABILITY-INVENTORY.md +42 -0
  3. package/docs/CHAT-CORE-BOUNDARY.md +47 -0
  4. package/docs/CORE-APP-REALIGNMENT-WORKLOAD-2026-04-18.md +86 -0
  5. package/docs/SSOT-CHAT-CORE-BOUNDARY.md +73 -0
  6. package/docs/SSOT-CHAT-CORE-DATAFLOW.md +97 -0
  7. package/index.html +12 -0
  8. package/package.json +24 -2
  9. package/src/adapters/index.js +6 -0
  10. package/src/adapters/message-api.adapter.js +59 -0
  11. package/src/adapters/session-api.adapter.js +133 -0
  12. package/src/adapters/session-message-api.http.js +161 -0
  13. package/src/adapters/session-message-api.js +34 -0
  14. package/src/adapters/session-message-api.normalize-source-record.test.mjs +180 -0
  15. package/src/adapters/session-message-api.normalizers.js +153 -0
  16. package/src/adapters/source-api.adapter.js +135 -0
  17. package/src/adapters/sse-client.js +244 -0
  18. package/src/adapters/sse-event-dispatcher.js +121 -0
  19. package/src/app/App.jsx +11 -0
  20. package/src/app/AppProviders.jsx +12 -0
  21. package/src/app/ChatWorkspaceScreen.jsx +33 -0
  22. package/src/app/WorkspaceLayout.jsx +190 -0
  23. package/src/app/components/AppCanvasPanel.jsx +64 -0
  24. package/src/app/components/TriggerThresholdPopoverContent.jsx +122 -0
  25. package/src/app/components/WorkspaceBodySection.jsx +156 -0
  26. package/src/app/components/WorkspaceMainPane.jsx +121 -0
  27. package/src/app/components/WorkspaceSessionPane.jsx +70 -0
  28. package/src/app/components/WorkspaceTopBarSection.jsx +71 -0
  29. package/src/app/core-chat-entry/ComposerSectionNode.jsx +241 -0
  30. package/src/app/core-chat-entry/attachmentSendRefs.js +154 -0
  31. package/src/app/core-chat-entry/attachmentSendRefs.test.mjs +101 -0
  32. package/src/app/core-chat-entry/composerActionRouter.js +26 -0
  33. package/src/app/core-chat-entry/constants.js +108 -0
  34. package/src/app/core-chat-entry/selectors.js +28 -0
  35. package/src/app/core-chat-entry/useAppActionErrorGuards.js +68 -0
  36. package/src/app/core-chat-entry/useChatCorePipelines.js +110 -0
  37. package/src/app/core-chat-entry/useComposerModeSuggestion.js +89 -0
  38. package/src/app/core-chat-entry/useDevCapabilityStatusNote.js +22 -0
  39. package/src/app/core-chat-entry/useProjectNameEditing.js +41 -0
  40. package/src/app/core-chat-entry/useProjectSourceUpload.js +341 -0
  41. package/src/app/core-chat-entry/useRealApiReadinessGate.js +103 -0
  42. package/src/app/core-chat-entry/useUnavailableActionError.js +29 -0
  43. package/src/app/core-chat-entry/useWorkspaceCanvasController.jsx +177 -0
  44. package/src/app/core-chat-entry/useWorkspaceCanvasProjection.jsx +171 -0
  45. package/src/app/core-chat-entry/useWorkspaceComposerController.jsx +199 -0
  46. package/src/app/core-chat-entry/useWorkspaceController.jsx +226 -0
  47. package/src/app/core-chat-entry/useWorkspacePanels.js +55 -0
  48. package/src/app/hooks/useComposerAttachmentSync.js +223 -0
  49. package/src/app/hooks/useComposerChooserHandlers.js +52 -0
  50. package/src/app/hooks/useSendWithContextRefs.js +140 -0
  51. package/src/app/hooks/useSendWithContextRefs.test.mjs +29 -0
  52. package/src/app/hooks/useUserThresholdProfile.js +121 -0
  53. package/src/app/index.js +1 -0
  54. package/src/app/selectors/assistantTextSelector.js +73 -0
  55. package/src/app/selectors/canvasEvidenceSummarySelector.js +28 -0
  56. package/src/app/selectors/canvasReportTemplateSelector.js +28 -0
  57. package/src/app/selectors/canvasTabsSelector.js +58 -0
  58. package/src/app/selectors/evidenceProjectionSelector.js +175 -0
  59. package/src/app/selectors/evidenceProjectionSelector.test.mjs +107 -0
  60. package/src/app/selectors/modeSuggestionSelector.js +50 -0
  61. package/src/chat-core/app/mockRuntime.js +291 -0
  62. package/src/chat-core/app/useAppStream.js +187 -0
  63. package/src/chat-core/app/useAppStream.refs.test.mjs +44 -0
  64. package/src/chat-core/app/useAppStream.request-body.test.mjs +116 -0
  65. package/src/chat-core/app/useCoreChatApp.js +115 -0
  66. package/src/chat-core/facade/useBasicConversationFacade.js +280 -0
  67. package/src/chat-core/index.js +9 -1
  68. package/src/chat-core/messages/buildTimelineItems.analysis-route.test.mjs +36 -0
  69. package/src/chat-core/messages/buildTimelineItems.js +139 -13
  70. package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +182 -0
  71. package/src/chat-core/messages/contextUsageDefaults.js +3 -0
  72. package/src/chat-core/messages/contextUsageViewModel.js +147 -0
  73. package/src/chat-core/messages/contextUsageViewModel.test.mjs +74 -0
  74. package/src/chat-core/messages/useContextUsageViewModel.js +41 -0
  75. package/src/chat-core/orchestration/useBasicSendHandler.js +55 -0
  76. package/src/chat-core/pipelines/build-action-request.js +46 -0
  77. package/src/chat-core/pipelines/build-stream-request.js +74 -0
  78. package/src/chat-core/pipelines/entity-extraction.js +159 -0
  79. package/src/chat-core/pipelines/preprocess-message.js +16 -0
  80. package/src/chat-core/pipelines/stream-persist-utils.js +32 -0
  81. package/src/chat-core/pipelines/transport/send-mock-stream.js +86 -0
  82. package/src/chat-core/pipelines/transport/send-real-stream.js +330 -0
  83. package/src/chat-core/pipelines/transport/send-real-stream.test.mjs +27 -0
  84. package/src/chat-core/pipelines/transport/send-sourcing-search.js +86 -0
  85. package/src/chat-core/pipelines/transport/send-sourcing-search.test.mjs +14 -0
  86. package/src/chat-core/pipelines/transport/sourcing-response-templates.js +55 -0
  87. package/src/chat-core/pipelines/transport/sourcing-search-api.js +155 -0
  88. package/src/chat-core/runtime/runtimeMode.js +69 -0
  89. package/src/chat-core/session/chatSessionActionTypes.js +24 -0
  90. package/src/chat-core/session/chatSessionReducer.js +352 -0
  91. package/src/chat-core/session/chatSessionReducer.streaming-done.test.mjs +39 -0
  92. package/src/chat-core/session/index.js +2 -0
  93. package/src/chat-core/session/sessionActionsMessages.js +44 -0
  94. package/src/chat-core/session/sessionActionsSessionCrud.js +131 -0
  95. package/src/chat-core/session/sessionActionsStreaming.js +80 -0
  96. package/src/chat-core/session/sessionActionsUiState.js +51 -0
  97. package/src/chat-core/session/useChatSessionReducer.js +62 -455
  98. package/src/chat-core/session/useSessionListController.js +67 -0
  99. package/src/chat-core/stream/sse-client.js +1 -244
  100. package/src/chat-core/stream/sse-event-dispatcher.js +1 -0
  101. package/src/chat-core/stream/sse-events.js +1 -867
  102. package/src/chat-core/stream/useSSEStream.js +1 -356
  103. package/src/chat-core/stream/useStreamSendController.js +46 -0
  104. package/src/contracts/context-ssot.js +47 -0
  105. package/src/contracts/index.js +1 -0
  106. package/src/contracts/sse-events/base-parsers.js +79 -0
  107. package/src/contracts/sse-events/domain-parsers.js +3 -0
  108. package/src/contracts/sse-events/internal-normalizers.js +143 -0
  109. package/src/contracts/sse-events/parsers-intake.js +235 -0
  110. package/src/contracts/sse-events/parsers-runtime.js +37 -0
  111. package/src/contracts/sse-events/parsers-sourcing.js +179 -0
  112. package/src/contracts/sse-events/patch-event-parser.js +121 -0
  113. package/src/contracts/sse-events/runtime-parsers.js +79 -0
  114. package/src/contracts/sse-events.js +4 -0
  115. package/src/index.js +5 -0
  116. package/src/main.jsx +28 -0
  117. package/src/orchestration/index.js +6 -0
  118. package/src/orchestration/useSSEStream.js +221 -0
  119. package/src/state/index.js +4 -0
  120. package/vite.config.js +36 -0
@@ -1,463 +1,64 @@
1
- import { useReducer, useCallback, useMemo } from 'react';
2
-
3
- const SESSION_LOADED = 'SESSION_LOADED';
4
- const SESSION_RESET = 'SESSION_RESET';
5
- const SESSION_ERROR = 'SESSION_ERROR';
6
- const TITLE_UPDATED = 'TITLE_UPDATED';
7
- const MESSAGES_REFRESHED = 'MESSAGES_REFRESHED';
8
- const MESSAGE_APPENDED = 'MESSAGE_APPENDED';
9
- const STREAMING_RESET = 'STREAMING_RESET';
10
- const STREAMING_CHUNK = 'STREAMING_CHUNK';
11
- const STREAMING_REPLACE = 'STREAMING_REPLACE';
12
- const AGENT_STATUS_SET = 'AGENT_STATUS_SET';
13
- const THINKING_TRACE_SET = 'THINKING_TRACE_SET';
14
- const EXECUTION_TRACE_SET = 'EXECUTION_TRACE_SET';
15
- const STREAMING_DONE = 'STREAMING_DONE';
16
- const DOC_GENERATED = 'DOC_GENERATED';
17
- const LEGEND_HINT_SHOWN = 'LEGEND_HINT_SHOWN';
18
- const EXECUTION_CARD_UPSERTED = 'EXECUTION_CARD_UPSERTED';
19
- const UI_CARDS_SET = 'UI_CARDS_SET';
20
- const LAST_USER_MESSAGE_SET = 'LAST_USER_MESSAGE_SET';
21
- const UI_CARD_UPDATED = 'UI_CARD_UPDATED';
22
- const INSTANCE_PROFILE_SET = 'INSTANCE_PROFILE_SET';
23
- const SESSION_PROMOTED = 'SESSION_PROMOTED';
24
-
25
- const initialStreaming = {
26
- content: '',
27
- agentStatus: null,
28
- thinkingTrace: '',
29
- executionTrace: null,
30
- };
31
-
32
- export const initialState = {
33
- sessionId: null,
34
- sessionTitle: '',
35
- sessionStatus: 'active',
36
- sessionDetail: null,
37
- instanceProfile: null,
38
- messages: [],
39
- executionCards: [],
40
- uiCards: [],
41
- lastUserMessage: '',
42
- generatedDoc: null,
43
- streaming: initialStreaming,
44
- sessionError: null,
45
- legendHintsShown: {
46
- knowledge_base: false,
47
- ai: false,
48
- },
49
- };
50
-
51
- function reducer(state, { type, payload }) {
52
- switch (type) {
53
- case SESSION_LOADED: {
54
- const {
55
- sessionId,
56
- title,
57
- status,
58
- messages,
59
- detail,
60
- } = payload;
61
- return {
62
- ...state,
63
- sessionId,
64
- sessionTitle: title,
65
- sessionStatus: status || 'active',
66
- sessionDetail: (detail && typeof detail === 'object' && !Array.isArray(detail)) ? detail : null,
67
- instanceProfile: state.instanceProfile,
68
- messages,
69
- executionCards: [],
70
- uiCards: [],
71
- lastUserMessage: '',
72
- generatedDoc: null,
73
- streaming: initialStreaming,
74
- legendHintsShown: initialState.legendHintsShown,
75
- };
76
- }
77
-
78
- case SESSION_RESET:
79
- return { ...initialState, instanceProfile: state.instanceProfile };
80
-
81
- case SESSION_ERROR:
82
- return { ...state, sessionError: payload };
83
-
84
- case TITLE_UPDATED:
85
- return { ...state, sessionTitle: payload };
86
-
87
- case MESSAGES_REFRESHED:
88
- return { ...state, messages: payload };
89
-
90
- case MESSAGE_APPENDED:
91
- return {
92
- ...state,
93
- messages: [...state.messages, payload],
94
- lastUserMessage: payload.role === 'user' ? payload.content : state.lastUserMessage,
95
- };
96
-
97
- case LAST_USER_MESSAGE_SET:
98
- return { ...state, lastUserMessage: payload };
99
-
100
- case UI_CARDS_SET:
101
- return { ...state, uiCards: payload };
102
-
103
- case UI_CARD_UPDATED: {
104
- const nextCard = payload;
105
- return {
106
- ...state,
107
- uiCards: state.uiCards.map((card) =>
108
- card.id === nextCard.id ? { ...card, ...nextCard } : card
109
- ),
110
- };
111
- }
112
-
113
- case INSTANCE_PROFILE_SET:
114
- return { ...state, instanceProfile: payload };
115
-
116
- case SESSION_PROMOTED: {
117
- const sessionId = String(payload?.sessionId || '').trim();
118
- if (!sessionId) {
119
- return state;
120
- }
121
- const title = String(payload?.title || '').trim();
122
- const detailPayload = (
123
- payload?.detail
124
- && typeof payload.detail === 'object'
125
- && !Array.isArray(payload.detail)
126
- ) ? payload.detail : null;
127
- const nextDetail = detailPayload
128
- ? {
129
- ...(state.sessionDetail && typeof state.sessionDetail === 'object' ? state.sessionDetail : {}),
130
- ...detailPayload,
131
- sessionId,
132
- }
133
- : (
134
- state.sessionDetail && typeof state.sessionDetail === 'object'
135
- ? {
136
- ...state.sessionDetail,
137
- sessionId,
138
- }
139
- : null
140
- );
141
- return {
142
- ...state,
143
- sessionId,
144
- sessionTitle: title || state.sessionTitle,
145
- sessionStatus: String(payload?.status || state.sessionStatus || 'active'),
146
- sessionDetail: nextDetail,
147
- };
148
- }
149
-
150
- case EXECUTION_CARD_UPSERTED: {
151
- const nextCard = payload;
152
- const cardKey = nextCard.step_id || `${nextCard.agent}-${nextCard.label}-${nextCard.stage}`;
153
- const existingIndex = state.executionCards.findIndex((card) => {
154
- const existingKey = card.step_id || `${card.agent}-${card.label}-${card.stage}`;
155
- return existingKey === cardKey;
156
- });
157
-
158
- const updatedCards = existingIndex >= 0
159
- ? state.executionCards.map((card, index) =>
160
- index === existingIndex ? { ...card, ...nextCard } : card
161
- )
162
- : [...state.executionCards, nextCard];
163
-
164
- return {
165
- ...state,
166
- executionCards: updatedCards.slice(-6),
167
- };
168
- }
169
-
170
- case STREAMING_RESET:
171
- return { ...state, streaming: initialStreaming };
172
-
173
- case STREAMING_CHUNK:
174
- return {
175
- ...state,
176
- streaming: {
177
- ...state.streaming,
178
- content: state.streaming.content + payload,
179
- },
180
- };
181
-
182
- case STREAMING_REPLACE:
183
- return {
184
- ...state,
185
- streaming: {
186
- ...state.streaming,
187
- content: String(payload || ''),
188
- },
189
- };
190
-
191
- case AGENT_STATUS_SET:
192
- return {
193
- ...state,
194
- streaming: { ...state.streaming, agentStatus: payload },
195
- };
196
-
197
- case THINKING_TRACE_SET:
198
- return {
199
- ...state,
200
- streaming: { ...state.streaming, thinkingTrace: payload },
201
- };
202
-
203
- case EXECUTION_TRACE_SET:
204
- return {
205
- ...state,
206
- streaming: {
207
- ...state.streaming,
208
- executionTrace: {
209
- ...state.streaming.executionTrace,
210
- ...payload,
211
- },
212
- },
213
- };
214
-
215
- case STREAMING_DONE: {
216
- const newMessages = payload
217
- ? [
218
- ...state.messages,
219
- {
220
- message_id: `temp-assistant-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
221
- role: 'assistant',
222
- content: payload,
223
- created_at: new Date().toISOString(),
224
- },
225
- ]
226
- : state.messages;
227
-
228
- return {
229
- ...state,
230
- messages: newMessages,
231
- streaming: initialStreaming,
232
- };
233
- }
234
-
235
- case DOC_GENERATED:
236
- return { ...state, generatedDoc: payload };
237
-
238
- case LEGEND_HINT_SHOWN:
239
- return {
240
- ...state,
241
- legendHintsShown: {
242
- ...state.legendHintsShown,
243
- [payload]: true,
244
- },
245
- };
246
-
247
- default:
248
- return state;
249
- }
250
- }
251
-
252
- function requireAPI(api, name) {
253
- if (!api) {
254
- throw new Error(`${name} is required`);
255
- }
256
- return api;
257
- }
1
+ import { useReducer, useMemo } from 'react';
2
+ import {
3
+ initialState,
4
+ reducer,
5
+ } from './chatSessionReducer.js';
6
+ import { useSessionCrudActions } from './sessionActionsSessionCrud.js';
7
+ import { useMessageActions } from './sessionActionsMessages.js';
8
+ import { useStreamingActions } from './sessionActionsStreaming.js';
9
+ import { useUiStateActions } from './sessionActionsUiState.js';
10
+
11
+ export { initialState } from './chatSessionReducer.js';
258
12
 
259
13
  export default function useChatSessionReducer(deps = {}) {
260
14
  const { sessionAPI, messageAPI } = deps;
261
15
  const [state, dispatch] = useReducer(reducer, initialState);
262
16
 
263
- const loadSession = useCallback(async (sessionId) => {
264
- const sessionApi = requireAPI(sessionAPI, 'sessionAPI');
265
- const messageApi = requireAPI(messageAPI, 'messageAPI');
266
-
267
- const [detail, msgs] = await Promise.all([
268
- sessionApi.get(sessionId),
269
- messageApi.list(sessionId),
270
- ]);
271
-
272
- dispatch({
273
- type: SESSION_LOADED,
274
- payload: {
275
- sessionId,
276
- title: detail.title,
277
- status: detail.status || 'active',
278
- messages: msgs.messages,
279
- detail,
280
- },
281
- });
282
- return detail;
283
- }, [sessionAPI, messageAPI]);
284
-
285
- const createSession = useCallback(async ({
286
- function_type,
287
- title,
288
- ...extraPayload
289
- } = {}) => {
290
- const sessionApi = requireAPI(sessionAPI, 'sessionAPI');
291
- const response = await sessionApi.create({ function_type, title, ...extraPayload });
292
-
293
- dispatch({
294
- type: SESSION_LOADED,
295
- payload: {
296
- sessionId: response.sessionId,
297
- title,
298
- status: response.status || 'active',
299
- messages: [],
300
- detail: response,
301
- },
302
- });
303
-
304
- return response.sessionId;
305
- }, [sessionAPI]);
306
-
307
- const createOrLoadSession = useCallback(async ({
308
- function_type,
309
- defaultTitle,
310
- forceNew = false,
311
- ...extraPayload
312
- } = {}) => {
313
- const sessionApi = requireAPI(sessionAPI, 'sessionAPI');
314
- const messageApi = requireAPI(messageAPI, 'messageAPI');
315
-
316
- let sessionId;
317
- let title;
318
-
319
- if (forceNew) {
320
- title = defaultTitle;
321
- const response = await sessionApi.create({ function_type, title, ...extraPayload });
322
- sessionId = response.sessionId;
323
- } else {
324
- const sessionList = await sessionApi.list({
325
- function_type,
326
- status: 'active',
327
- page: 1,
328
- page_size: 1,
329
- ...extraPayload,
330
- });
331
-
332
- if (sessionList.sessions.length > 0) {
333
- sessionId = sessionList.sessions[0].sessionId;
334
- title = sessionList.sessions[0].title;
335
- } else {
336
- title = defaultTitle;
337
- const response = await sessionApi.create({ function_type, title, ...extraPayload });
338
- sessionId = response.sessionId;
339
- }
340
- }
341
-
342
- const [msgs, detail] = await Promise.all([
343
- messageApi.list(sessionId),
344
- sessionApi.get(sessionId),
345
- ]);
346
-
347
- dispatch({
348
- type: SESSION_LOADED,
349
- payload: {
350
- sessionId,
351
- title,
352
- status: detail.status || 'active',
353
- messages: msgs.messages,
354
- detail,
355
- },
356
- });
357
-
358
- return sessionId;
359
- }, [sessionAPI, messageAPI]);
360
-
361
- const resetSession = useCallback(() => {
362
- dispatch({ type: SESSION_RESET });
363
- }, []);
364
-
365
- const updateTitle = useCallback(async (sessionId, title) => {
366
- const sessionApi = requireAPI(sessionAPI, 'sessionAPI');
367
- await sessionApi.update(sessionId, { title });
368
- dispatch({ type: TITLE_UPDATED, payload: title });
369
- }, [sessionAPI]);
370
-
371
- const promoteSession = useCallback((payload = {}) => {
372
- dispatch({ type: SESSION_PROMOTED, payload });
373
- }, []);
374
-
375
- const refreshMessages = useCallback(async (sessionId) => {
376
- const messageApi = requireAPI(messageAPI, 'messageAPI');
377
- const response = await messageApi.list(sessionId);
378
- const messagesWithIds = response.messages.map((msg, index) => ({
379
- ...msg,
380
- message_id: msg.message_id || `fallback-${Date.now()}-${index}`,
381
- }));
382
- dispatch({ type: MESSAGES_REFRESHED, payload: messagesWithIds });
383
- }, [messageAPI]);
384
-
385
- const appendUserMessage = useCallback((content, metadata = {}) => {
386
- const payloadMeta = (
387
- metadata
388
- && typeof metadata === 'object'
389
- && !Array.isArray(metadata)
390
- )
391
- ? metadata
392
- : {};
393
- dispatch({
394
- type: MESSAGE_APPENDED,
395
- payload: {
396
- message_id: `temp-user-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
397
- role: 'user',
398
- content,
399
- created_at: new Date().toISOString(),
400
- ...payloadMeta,
401
- },
402
- });
403
- }, []);
404
-
405
- const setUICards = useCallback((cards) => {
406
- dispatch({ type: UI_CARDS_SET, payload: Array.isArray(cards) ? cards : [] });
407
- }, []);
408
-
409
- const updateUICard = useCallback((card) => {
410
- dispatch({ type: UI_CARD_UPDATED, payload: card });
411
- }, []);
412
-
413
- const setLastUserMessage = useCallback((content) => {
414
- dispatch({ type: LAST_USER_MESSAGE_SET, payload: content || '' });
415
- }, []);
416
-
417
- const resetStreaming = useCallback(() => {
418
- dispatch({ type: STREAMING_RESET });
419
- }, []);
420
-
421
- const appendStreamChunk = useCallback((chunk) => {
422
- dispatch({ type: STREAMING_CHUNK, payload: chunk });
423
- }, []);
424
-
425
- const replaceStreamContent = useCallback((content) => {
426
- dispatch({ type: STREAMING_REPLACE, payload: content });
427
- }, []);
428
-
429
- const setAgentStatus = useCallback((agentStatus) => {
430
- dispatch({ type: AGENT_STATUS_SET, payload: agentStatus });
431
- }, []);
432
-
433
- const setThinkingTrace = useCallback((trace) => {
434
- dispatch({ type: THINKING_TRACE_SET, payload: trace });
435
- }, []);
436
-
437
- const setExecutionTrace = useCallback((executionTrace) => {
438
- dispatch({ type: EXECUTION_TRACE_SET, payload: executionTrace });
439
- dispatch({ type: EXECUTION_CARD_UPSERTED, payload: executionTrace });
440
- }, []);
441
-
442
- const completeStreaming = useCallback((finalContent) => {
443
- dispatch({ type: STREAMING_DONE, payload: finalContent });
444
- }, []);
445
-
446
- const setGeneratedDoc = useCallback((doc) => {
447
- dispatch({ type: DOC_GENERATED, payload: doc });
448
- }, []);
449
-
450
- const markLegendHintShown = useCallback((sourceType) => {
451
- dispatch({ type: LEGEND_HINT_SHOWN, payload: sourceType });
452
- }, []);
453
-
454
- const setSessionError = useCallback((error) => {
455
- dispatch({ type: SESSION_ERROR, payload: error });
456
- }, []);
457
-
458
- const setInstanceProfile = useCallback((profile) => {
459
- dispatch({ type: INSTANCE_PROFILE_SET, payload: profile || null });
460
- }, []);
17
+ const sessionCrudActions = useSessionCrudActions({
18
+ dispatch,
19
+ sessionAPI,
20
+ messageAPI,
21
+ });
22
+ const messageActions = useMessageActions({
23
+ dispatch,
24
+ messageAPI,
25
+ });
26
+ const streamingActions = useStreamingActions({ dispatch });
27
+ const uiStateActions = useUiStateActions({ dispatch });
28
+
29
+ const {
30
+ loadSession,
31
+ createSession,
32
+ createOrLoadSession,
33
+ resetSession,
34
+ updateTitle,
35
+ promoteSession,
36
+ } = sessionCrudActions;
37
+ const {
38
+ refreshMessages,
39
+ appendUserMessage,
40
+ } = messageActions;
41
+ const {
42
+ resetStreaming,
43
+ appendStreamChunk,
44
+ replaceStreamContent,
45
+ setAgentStatus,
46
+ setThinkingTrace,
47
+ setExecutionTrace,
48
+ setKnowledgeSearch,
49
+ setSourcingCandidates,
50
+ setKnowledgeCitation,
51
+ completeStreaming,
52
+ } = streamingActions;
53
+ const {
54
+ setUICards,
55
+ updateUICard,
56
+ setLastUserMessage,
57
+ setGeneratedDoc,
58
+ markLegendHintShown,
59
+ setSessionError,
60
+ setInstanceProfile,
61
+ } = uiStateActions;
461
62
 
462
63
  return useMemo(() => ({
463
64
  sessionId: state.sessionId,
@@ -490,6 +91,9 @@ export default function useChatSessionReducer(deps = {}) {
490
91
  setAgentStatus,
491
92
  setThinkingTrace,
492
93
  setExecutionTrace,
94
+ setKnowledgeSearch,
95
+ setSourcingCandidates,
96
+ setKnowledgeCitation,
493
97
  completeStreaming,
494
98
  setGeneratedDoc,
495
99
  setInstanceProfile,
@@ -514,6 +118,9 @@ export default function useChatSessionReducer(deps = {}) {
514
118
  setAgentStatus,
515
119
  setThinkingTrace,
516
120
  setExecutionTrace,
121
+ setKnowledgeSearch,
122
+ setSourcingCandidates,
123
+ setKnowledgeCitation,
517
124
  completeStreaming,
518
125
  setGeneratedDoc,
519
126
  setInstanceProfile,
@@ -0,0 +1,67 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ /**
4
+ * Session list lifecycle controller.
5
+ *
6
+ * @param {object} params - Hook params.
7
+ * @param {object} params.sessionAPI - Session API provider.
8
+ * @param {object} params.listParams - Session list query params.
9
+ * @param {Function} params.onStarted - Optional started callback.
10
+ * @param {Function} params.onSucceeded - Optional succeeded callback.
11
+ * @param {Function} params.onFailed - Optional failed callback.
12
+ * @returns {{sessions: Array<object>, loading: boolean, error: any, loadSessionList: Function}}
13
+ */
14
+ export default function useSessionListController({
15
+ sessionAPI,
16
+ listParams = {},
17
+ onStarted,
18
+ onSucceeded,
19
+ onFailed,
20
+ } = {}) {
21
+ const [sessions, setSessions] = useState([]);
22
+ const [loading, setLoading] = useState(false);
23
+ const [error, setError] = useState(null);
24
+ const [hasLoaded, setHasLoaded] = useState(false);
25
+
26
+ const loadSessionList = useCallback(async (options = {}) => {
27
+ if (!sessionAPI?.list) {
28
+ return [];
29
+ }
30
+ const background = options?.background === true;
31
+ const shouldShowLoading = !background && !hasLoaded;
32
+
33
+ onStarted?.();
34
+ if (shouldShowLoading) {
35
+ setLoading(true);
36
+ }
37
+ if (!background) {
38
+ setError(null);
39
+ }
40
+
41
+ try {
42
+ const response = await sessionAPI.list(listParams);
43
+ const resolvedSessions = Array.isArray(response?.sessions) ? response.sessions : [];
44
+ setSessions(resolvedSessions);
45
+ setHasLoaded(true);
46
+ onSucceeded?.(resolvedSessions);
47
+ return resolvedSessions;
48
+ } catch (nextError) {
49
+ setError(nextError);
50
+ onFailed?.(nextError);
51
+ throw nextError;
52
+ } finally {
53
+ if (shouldShowLoading) {
54
+ setLoading(false);
55
+ }
56
+ }
57
+ }, [hasLoaded, listParams, onFailed, onStarted, onSucceeded, sessionAPI]);
58
+
59
+ return {
60
+ sessions,
61
+ loading,
62
+ error,
63
+ loadSessionList,
64
+ };
65
+ }
66
+
67
+ export { useSessionListController };