flare-chat-core 0.2.0 → 0.2.2

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 +125 -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 +109 -0
  26. package/src/app/components/WorkspaceMainPane.jsx +113 -0
  27. package/src/app/components/WorkspaceSessionPane.jsx +48 -0
  28. package/src/app/components/WorkspaceTopBarSection.jsx +65 -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 +172 -11
  70. package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +183 -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 +67 -390
  98. package/src/chat-core/session/useSessionListController.js +67 -0
  99. package/src/chat-core/stream/sse-client.js +1 -142
  100. package/src/chat-core/stream/sse-event-dispatcher.js +1 -0
  101. package/src/chat-core/stream/sse-events.js +1 -598
  102. package/src/chat-core/stream/useSSEStream.js +1 -273
  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,402 +1,69 @@
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 AGENT_STATUS_SET = 'AGENT_STATUS_SET';
12
- const THINKING_TRACE_SET = 'THINKING_TRACE_SET';
13
- const EXECUTION_TRACE_SET = 'EXECUTION_TRACE_SET';
14
- const STREAMING_DONE = 'STREAMING_DONE';
15
- const DOC_GENERATED = 'DOC_GENERATED';
16
- const LEGEND_HINT_SHOWN = 'LEGEND_HINT_SHOWN';
17
- const EXECUTION_CARD_UPSERTED = 'EXECUTION_CARD_UPSERTED';
18
- const UI_CARDS_SET = 'UI_CARDS_SET';
19
- const LAST_USER_MESSAGE_SET = 'LAST_USER_MESSAGE_SET';
20
- const UI_CARD_UPDATED = 'UI_CARD_UPDATED';
21
- const INSTANCE_PROFILE_SET = 'INSTANCE_PROFILE_SET';
22
-
23
- const initialStreaming = {
24
- content: '',
25
- agentStatus: null,
26
- thinkingTrace: '',
27
- executionTrace: null,
28
- };
29
-
30
- export const initialState = {
31
- sessionId: null,
32
- sessionTitle: '',
33
- sessionStatus: 'active',
34
- instanceProfile: null,
35
- messages: [],
36
- executionCards: [],
37
- uiCards: [],
38
- lastUserMessage: '',
39
- generatedDoc: null,
40
- streaming: initialStreaming,
41
- sessionError: null,
42
- legendHintsShown: {
43
- knowledge_base: false,
44
- ai: false,
45
- },
46
- };
47
-
48
- function reducer(state, { type, payload }) {
49
- switch (type) {
50
- case SESSION_LOADED: {
51
- const { sessionId, title, status, messages } = payload;
52
- return {
53
- ...state,
54
- sessionId,
55
- sessionTitle: title,
56
- sessionStatus: status || 'active',
57
- instanceProfile: state.instanceProfile,
58
- messages,
59
- executionCards: [],
60
- uiCards: [],
61
- lastUserMessage: '',
62
- generatedDoc: null,
63
- streaming: initialStreaming,
64
- legendHintsShown: initialState.legendHintsShown,
65
- };
66
- }
67
-
68
- case SESSION_RESET:
69
- return { ...initialState, instanceProfile: state.instanceProfile };
70
-
71
- case SESSION_ERROR:
72
- return { ...state, sessionError: payload };
73
-
74
- case TITLE_UPDATED:
75
- return { ...state, sessionTitle: payload };
76
-
77
- case MESSAGES_REFRESHED:
78
- return { ...state, messages: payload };
79
-
80
- case MESSAGE_APPENDED:
81
- return {
82
- ...state,
83
- messages: [...state.messages, payload],
84
- lastUserMessage: payload.role === 'user' ? payload.content : state.lastUserMessage,
85
- };
86
-
87
- case LAST_USER_MESSAGE_SET:
88
- return { ...state, lastUserMessage: payload };
89
-
90
- case UI_CARDS_SET:
91
- return { ...state, uiCards: payload };
92
-
93
- case UI_CARD_UPDATED: {
94
- const nextCard = payload;
95
- return {
96
- ...state,
97
- uiCards: state.uiCards.map((card) =>
98
- card.id === nextCard.id ? { ...card, ...nextCard } : card
99
- ),
100
- };
101
- }
102
-
103
- case INSTANCE_PROFILE_SET:
104
- return { ...state, instanceProfile: payload };
105
-
106
- case EXECUTION_CARD_UPSERTED: {
107
- const nextCard = payload;
108
- const cardKey = nextCard.step_id || `${nextCard.agent}-${nextCard.label}-${nextCard.stage}`;
109
- const existingIndex = state.executionCards.findIndex((card) => {
110
- const existingKey = card.step_id || `${card.agent}-${card.label}-${card.stage}`;
111
- return existingKey === cardKey;
112
- });
113
-
114
- const updatedCards = existingIndex >= 0
115
- ? state.executionCards.map((card, index) =>
116
- index === existingIndex ? { ...card, ...nextCard } : card
117
- )
118
- : [...state.executionCards, nextCard];
119
-
120
- return {
121
- ...state,
122
- executionCards: updatedCards.slice(-6),
123
- };
124
- }
125
-
126
- case STREAMING_RESET:
127
- return { ...state, streaming: initialStreaming };
128
-
129
- case STREAMING_CHUNK:
130
- return {
131
- ...state,
132
- streaming: {
133
- ...state.streaming,
134
- content: state.streaming.content + payload,
135
- },
136
- };
137
-
138
- case AGENT_STATUS_SET:
139
- return {
140
- ...state,
141
- streaming: { ...state.streaming, agentStatus: payload },
142
- };
143
-
144
- case THINKING_TRACE_SET:
145
- return {
146
- ...state,
147
- streaming: { ...state.streaming, thinkingTrace: payload },
148
- };
149
-
150
- case EXECUTION_TRACE_SET:
151
- return {
152
- ...state,
153
- streaming: {
154
- ...state.streaming,
155
- executionTrace: {
156
- ...state.streaming.executionTrace,
157
- ...payload,
158
- },
159
- },
160
- };
161
-
162
- case STREAMING_DONE: {
163
- const newMessages = payload
164
- ? [
165
- ...state.messages,
166
- {
167
- message_id: `temp-assistant-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
168
- role: 'assistant',
169
- content: payload,
170
- created_at: new Date().toISOString(),
171
- },
172
- ]
173
- : state.messages;
174
-
175
- return {
176
- ...state,
177
- messages: newMessages,
178
- streaming: initialStreaming,
179
- };
180
- }
181
-
182
- case DOC_GENERATED:
183
- return { ...state, generatedDoc: payload };
184
-
185
- case LEGEND_HINT_SHOWN:
186
- return {
187
- ...state,
188
- legendHintsShown: {
189
- ...state.legendHintsShown,
190
- [payload]: true,
191
- },
192
- };
193
-
194
- default:
195
- return state;
196
- }
197
- }
198
-
199
- function requireAPI(api, name) {
200
- if (!api) {
201
- throw new Error(`${name} is required`);
202
- }
203
- return api;
204
- }
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';
205
12
 
206
13
  export default function useChatSessionReducer(deps = {}) {
207
14
  const { sessionAPI, messageAPI } = deps;
208
15
  const [state, dispatch] = useReducer(reducer, initialState);
209
16
 
210
- const loadSession = useCallback(async (sessionId) => {
211
- const sessionApi = requireAPI(sessionAPI, 'sessionAPI');
212
- const messageApi = requireAPI(messageAPI, 'messageAPI');
213
-
214
- const [detail, msgs] = await Promise.all([
215
- sessionApi.get(sessionId),
216
- messageApi.list(sessionId),
217
- ]);
218
-
219
- dispatch({
220
- type: SESSION_LOADED,
221
- payload: {
222
- sessionId,
223
- title: detail.title,
224
- status: detail.status || 'active',
225
- messages: msgs.messages,
226
- },
227
- });
228
- }, [sessionAPI, messageAPI]);
229
-
230
- const createSession = useCallback(async ({
231
- function_type,
232
- title,
233
- ...extraPayload
234
- } = {}) => {
235
- const sessionApi = requireAPI(sessionAPI, 'sessionAPI');
236
- const response = await sessionApi.create({ function_type, title, ...extraPayload });
237
-
238
- dispatch({
239
- type: SESSION_LOADED,
240
- payload: {
241
- sessionId: response.sessionId,
242
- title,
243
- status: response.status || 'active',
244
- messages: [],
245
- },
246
- });
247
-
248
- return response.sessionId;
249
- }, [sessionAPI]);
250
-
251
- const createOrLoadSession = useCallback(async ({
252
- function_type,
253
- defaultTitle,
254
- forceNew = false,
255
- ...extraPayload
256
- } = {}) => {
257
- const sessionApi = requireAPI(sessionAPI, 'sessionAPI');
258
- const messageApi = requireAPI(messageAPI, 'messageAPI');
259
-
260
- let sessionId;
261
- let title;
262
-
263
- if (forceNew) {
264
- title = defaultTitle;
265
- const response = await sessionApi.create({ function_type, title, ...extraPayload });
266
- sessionId = response.sessionId;
267
- } else {
268
- const sessionList = await sessionApi.list({
269
- function_type,
270
- status: 'active',
271
- page: 1,
272
- page_size: 1,
273
- ...extraPayload,
274
- });
275
-
276
- if (sessionList.sessions.length > 0) {
277
- sessionId = sessionList.sessions[0].sessionId;
278
- title = sessionList.sessions[0].title;
279
- } else {
280
- title = defaultTitle;
281
- const response = await sessionApi.create({ function_type, title, ...extraPayload });
282
- sessionId = response.sessionId;
283
- }
284
- }
285
-
286
- const [msgs, detail] = await Promise.all([
287
- messageApi.list(sessionId),
288
- sessionApi.get(sessionId),
289
- ]);
290
-
291
- dispatch({
292
- type: SESSION_LOADED,
293
- payload: {
294
- sessionId,
295
- title,
296
- status: detail.status || 'active',
297
- messages: msgs.messages,
298
- },
299
- });
300
-
301
- return sessionId;
302
- }, [sessionAPI, messageAPI]);
303
-
304
- const resetSession = useCallback(() => {
305
- dispatch({ type: SESSION_RESET });
306
- }, []);
307
-
308
- const updateTitle = useCallback(async (sessionId, title) => {
309
- const sessionApi = requireAPI(sessionAPI, 'sessionAPI');
310
- await sessionApi.update(sessionId, { title });
311
- dispatch({ type: TITLE_UPDATED, payload: title });
312
- }, [sessionAPI]);
313
-
314
- const refreshMessages = useCallback(async (sessionId) => {
315
- const messageApi = requireAPI(messageAPI, 'messageAPI');
316
- const response = await messageApi.list(sessionId);
317
- const messagesWithIds = response.messages.map((msg, index) => ({
318
- ...msg,
319
- message_id: msg.message_id || `fallback-${Date.now()}-${index}`,
320
- }));
321
- dispatch({ type: MESSAGES_REFRESHED, payload: messagesWithIds });
322
- }, [messageAPI]);
323
-
324
- const appendUserMessage = useCallback((content, metadata = {}) => {
325
- const payloadMeta = (
326
- metadata
327
- && typeof metadata === 'object'
328
- && !Array.isArray(metadata)
329
- )
330
- ? metadata
331
- : {};
332
- dispatch({
333
- type: MESSAGE_APPENDED,
334
- payload: {
335
- message_id: `temp-user-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
336
- role: 'user',
337
- content,
338
- created_at: new Date().toISOString(),
339
- ...payloadMeta,
340
- },
341
- });
342
- }, []);
343
-
344
- const setUICards = useCallback((cards) => {
345
- dispatch({ type: UI_CARDS_SET, payload: Array.isArray(cards) ? cards : [] });
346
- }, []);
347
-
348
- const updateUICard = useCallback((card) => {
349
- dispatch({ type: UI_CARD_UPDATED, payload: card });
350
- }, []);
351
-
352
- const setLastUserMessage = useCallback((content) => {
353
- dispatch({ type: LAST_USER_MESSAGE_SET, payload: content || '' });
354
- }, []);
355
-
356
- const resetStreaming = useCallback(() => {
357
- dispatch({ type: STREAMING_RESET });
358
- }, []);
359
-
360
- const appendStreamChunk = useCallback((chunk) => {
361
- dispatch({ type: STREAMING_CHUNK, payload: chunk });
362
- }, []);
363
-
364
- const setAgentStatus = useCallback((agentStatus) => {
365
- dispatch({ type: AGENT_STATUS_SET, payload: agentStatus });
366
- }, []);
367
-
368
- const setThinkingTrace = useCallback((trace) => {
369
- dispatch({ type: THINKING_TRACE_SET, payload: trace });
370
- }, []);
371
-
372
- const setExecutionTrace = useCallback((executionTrace) => {
373
- dispatch({ type: EXECUTION_TRACE_SET, payload: executionTrace });
374
- dispatch({ type: EXECUTION_CARD_UPSERTED, payload: executionTrace });
375
- }, []);
376
-
377
- const completeStreaming = useCallback((finalContent) => {
378
- dispatch({ type: STREAMING_DONE, payload: finalContent });
379
- }, []);
380
-
381
- const setGeneratedDoc = useCallback((doc) => {
382
- dispatch({ type: DOC_GENERATED, payload: doc });
383
- }, []);
384
-
385
- const markLegendHintShown = useCallback((sourceType) => {
386
- dispatch({ type: LEGEND_HINT_SHOWN, payload: sourceType });
387
- }, []);
388
-
389
- const setSessionError = useCallback((error) => {
390
- dispatch({ type: SESSION_ERROR, payload: error });
391
- }, []);
392
-
393
- const setInstanceProfile = useCallback((profile) => {
394
- dispatch({ type: INSTANCE_PROFILE_SET, payload: profile || null });
395
- }, []);
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;
396
62
 
397
63
  return useMemo(() => ({
398
64
  sessionId: state.sessionId,
399
65
  sessionTitle: state.sessionTitle,
66
+ sessionDetail: state.sessionDetail,
400
67
  instanceProfile: state.instanceProfile,
401
68
  messages: state.messages,
402
69
  executionCards: state.executionCards,
@@ -411,6 +78,7 @@ export default function useChatSessionReducer(deps = {}) {
411
78
  createOrLoadSession,
412
79
  resetSession,
413
80
  updateTitle,
81
+ promoteSession,
414
82
  refreshMessages,
415
83
  setSessionError,
416
84
  appendUserMessage,
@@ -419,9 +87,13 @@ export default function useChatSessionReducer(deps = {}) {
419
87
  setLastUserMessage,
420
88
  resetStreaming,
421
89
  appendStreamChunk,
90
+ replaceStreamContent,
422
91
  setAgentStatus,
423
92
  setThinkingTrace,
424
93
  setExecutionTrace,
94
+ setKnowledgeSearch,
95
+ setSourcingCandidates,
96
+ setKnowledgeCitation,
425
97
  completeStreaming,
426
98
  setGeneratedDoc,
427
99
  setInstanceProfile,
@@ -433,6 +105,7 @@ export default function useChatSessionReducer(deps = {}) {
433
105
  createOrLoadSession,
434
106
  resetSession,
435
107
  updateTitle,
108
+ promoteSession,
436
109
  refreshMessages,
437
110
  setSessionError,
438
111
  appendUserMessage,
@@ -441,9 +114,13 @@ export default function useChatSessionReducer(deps = {}) {
441
114
  setLastUserMessage,
442
115
  resetStreaming,
443
116
  appendStreamChunk,
117
+ replaceStreamContent,
444
118
  setAgentStatus,
445
119
  setThinkingTrace,
446
120
  setExecutionTrace,
121
+ setKnowledgeSearch,
122
+ setSourcingCandidates,
123
+ setKnowledgeCitation,
447
124
  completeStreaming,
448
125
  setGeneratedDoc,
449
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 };
@@ -1,142 +1 @@
1
- const API_BASE_URL = import.meta.env.DEV ? '' : import.meta.env.VITE_API_URL || '';
2
- const DEFAULT_ENDPOINT = '/api/v1/chat/stream';
3
-
4
- function joinUrl(baseUrl, endpoint) {
5
- if (!baseUrl) {
6
- return endpoint;
7
- }
8
-
9
- if (!endpoint) {
10
- return baseUrl;
11
- }
12
-
13
- return `${baseUrl.replace(/\/+$/, '')}/${endpoint.replace(/^\/+/, '')}`;
14
- }
15
-
16
- export class SSEClient {
17
- constructor({ endpoint = DEFAULT_ENDPOINT, baseUrl = API_BASE_URL } = {}) {
18
- this.abortController = null;
19
- this.endpoint = endpoint;
20
- this.baseUrl = baseUrl;
21
- }
22
-
23
- async sendMessage(sessionOrParams, contentArg, onEventArg, onCompleteArg, onErrorArg, optionsArg = {}) {
24
- const isObjectCall = typeof sessionOrParams === 'object' && sessionOrParams !== null;
25
- const params = isObjectCall
26
- ? sessionOrParams
27
- : {
28
- sessionId: sessionOrParams,
29
- content: contentArg,
30
- enabledCapabilities: optionsArg.enabledCapabilities || [],
31
- };
32
-
33
- const sessionId = params.sessionId;
34
- const content = params.content;
35
- const enabledCapabilities = Array.isArray(params.enabledCapabilities) ? params.enabledCapabilities : [];
36
- const modeKey = typeof params.modeKey === 'string' ? params.modeKey.trim() : '';
37
- const manualModeKey = typeof params.manualModeKey === 'string' ? params.manualModeKey.trim() : '';
38
- const currentModeKey = typeof params.currentModeKey === 'string' ? params.currentModeKey.trim() : '';
39
- const payloadExtra = (
40
- params.payloadExtra
41
- && typeof params.payloadExtra === 'object'
42
- && !Array.isArray(params.payloadExtra)
43
- )
44
- ? params.payloadExtra
45
- : {};
46
- const onEvent = isObjectCall ? contentArg : onEventArg;
47
- const onComplete = isObjectCall ? onEventArg : onCompleteArg;
48
- const onError = isObjectCall ? onCompleteArg : onErrorArg;
49
- const url = params.url || joinUrl(params.baseUrl ?? this.baseUrl, params.endpoint || this.endpoint);
50
-
51
- this.abortController = new AbortController();
52
-
53
- try {
54
- const response = await fetch(url, {
55
- method: 'POST',
56
- headers: {
57
- 'Content-Type': 'application/json',
58
- },
59
- body: JSON.stringify({
60
- message: content,
61
- session_id: sessionId,
62
- enabled_capabilities: enabledCapabilities,
63
- ...(modeKey ? { mode: modeKey } : {}),
64
- ...(manualModeKey ? { manual_mode: manualModeKey } : {}),
65
- ...(currentModeKey ? { current_mode: currentModeKey } : {}),
66
- payload: {
67
- message: content,
68
- ...(modeKey ? { mode: modeKey } : {}),
69
- ...(manualModeKey ? { manual_mode: manualModeKey } : {}),
70
- ...(currentModeKey ? { current_mode: currentModeKey } : {}),
71
- ...payloadExtra,
72
- },
73
- }),
74
- signal: this.abortController.signal,
75
- });
76
-
77
- if (!response.ok) {
78
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
79
- }
80
-
81
- const reader = response.body.getReader();
82
- const decoder = new TextDecoder();
83
- let buffer = '';
84
- let currentEvent = '';
85
-
86
- while (true) {
87
- const { done, value } = await reader.read();
88
- if (done) break;
89
-
90
- buffer += decoder.decode(value, { stream: true });
91
- const lines = buffer.split('\n');
92
- buffer = lines.pop() || '';
93
-
94
- for (const line of lines) {
95
- if (line.startsWith('event:')) {
96
- currentEvent = line.substring(6).trim();
97
- continue;
98
- }
99
-
100
- if (!line.startsWith('data:')) {
101
- continue;
102
- }
103
-
104
- const data = line.substring(5).trim();
105
- if (!data || !currentEvent) {
106
- continue;
107
- }
108
-
109
- try {
110
- const eventData = JSON.parse(data);
111
- onEvent?.({
112
- type: currentEvent,
113
- data: eventData,
114
- });
115
-
116
- if (currentEvent === 'complete') {
117
- onComplete?.();
118
- return;
119
- }
120
- } catch (error) {
121
- console.error('SSE 数据解析失败:', error, 'data:', data);
122
- }
123
- }
124
- }
125
-
126
- onComplete?.();
127
- } catch (error) {
128
- if (error?.name === 'AbortError') {
129
- return;
130
- }
131
-
132
- onError?.(error instanceof Error ? error.message : '发送消息失败');
133
- }
134
- }
135
-
136
- abort() {
137
- if (this.abortController) {
138
- this.abortController.abort();
139
- this.abortController = null;
140
- }
141
- }
142
- }
1
+ export { SSEClient } from '../../adapters/sse-client.js';