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
@@ -0,0 +1,244 @@
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
+ function buildClientRequestId() {
17
+ const random = Math.random().toString(36).slice(2, 10);
18
+ return `req_${Date.now()}_${random}`;
19
+ }
20
+
21
+ export class SSEClient {
22
+ constructor({ endpoint = DEFAULT_ENDPOINT, baseUrl = API_BASE_URL } = {}) {
23
+ this.abortController = null;
24
+ this.endpoint = endpoint;
25
+ this.baseUrl = baseUrl;
26
+ this.activeRequestId = '';
27
+ this.authReadyPromise = null;
28
+ this.authReadyAtMs = 0;
29
+ }
30
+
31
+ async sendMessage(sessionOrParams, contentArg, onEventArg, onCompleteArg, onErrorArg, optionsArg = {}) {
32
+ const isObjectCall = typeof sessionOrParams === 'object' && sessionOrParams !== null;
33
+ const params = isObjectCall
34
+ ? sessionOrParams
35
+ : {
36
+ sessionId: sessionOrParams,
37
+ content: contentArg,
38
+ enabledCapabilities: optionsArg.enabledCapabilities || [],
39
+ };
40
+
41
+ const sessionId = params.sessionId;
42
+ const content = params.content;
43
+ const enabledCapabilities = Array.isArray(params.enabledCapabilities) ? params.enabledCapabilities : [];
44
+ const modeKey = typeof params.modeKey === 'string' ? params.modeKey.trim() : '';
45
+ const manualModeKey = typeof params.manualModeKey === 'string' ? params.manualModeKey.trim() : '';
46
+ const payloadExtra = (
47
+ params.payloadExtra
48
+ && typeof params.payloadExtra === 'object'
49
+ && !Array.isArray(params.payloadExtra)
50
+ )
51
+ ? params.payloadExtra
52
+ : {};
53
+ const onEvent = isObjectCall ? contentArg : onEventArg;
54
+ const onComplete = isObjectCall ? onEventArg : onCompleteArg;
55
+ const onError = isObjectCall ? onCompleteArg : onErrorArg;
56
+ const url = params.url || joinUrl(params.baseUrl ?? this.baseUrl, params.endpoint || this.endpoint);
57
+ const command = typeof params.command === 'string' && params.command.trim()
58
+ ? params.command.trim()
59
+ : (typeof payloadExtra.command === 'string' && payloadExtra.command.trim() ? payloadExtra.command.trim() : 'send_message');
60
+ const clientRequestId = typeof params.clientRequestId === 'string' && params.clientRequestId.trim()
61
+ ? params.clientRequestId.trim()
62
+ : buildClientRequestId();
63
+ const contractVersion = typeof params.contractVersion === 'string' && params.contractVersion.trim()
64
+ ? params.contractVersion.trim()
65
+ : 'flare.v1';
66
+ const lastTurnId = typeof params.lastTurnId === 'string' ? params.lastTurnId.trim() : '';
67
+ const token = typeof params.token === 'string' ? params.token.trim() : '';
68
+ const onTiming = typeof params.onTiming === 'function' ? params.onTiming : null;
69
+ const authProvider = (
70
+ params.auth
71
+ && typeof params.auth === 'object'
72
+ && !Array.isArray(params.auth)
73
+ ) ? params.auth : null;
74
+
75
+ const nowMs = Date.now();
76
+ onTiming?.({
77
+ point: 't_submit',
78
+ at_ms: nowMs,
79
+ request_id: clientRequestId,
80
+ session_id: String(sessionId || '').trim(),
81
+ });
82
+
83
+ const waitAuthReady = async () => {
84
+ if (!authProvider || typeof authProvider.ensureReady !== 'function') {
85
+ return {
86
+ authorizationToken: token,
87
+ authReadyAtMs: nowMs,
88
+ };
89
+ }
90
+ if (!this.authReadyPromise) {
91
+ this.authReadyPromise = Promise.resolve()
92
+ .then(() => authProvider.ensureReady())
93
+ .then((resolved) => {
94
+ this.authReadyAtMs = Date.now();
95
+ return resolved || null;
96
+ })
97
+ .finally(() => {
98
+ this.authReadyPromise = null;
99
+ });
100
+ }
101
+ const authResult = await this.authReadyPromise;
102
+ const tokenFromAuthResult = typeof authResult?.token === 'string' ? authResult.token.trim() : '';
103
+ const tokenFromProvider = typeof authProvider.getToken === 'function'
104
+ ? String(authProvider.getToken() || '').trim()
105
+ : '';
106
+ return {
107
+ authorizationToken: tokenFromAuthResult || tokenFromProvider || token,
108
+ authReadyAtMs: this.authReadyAtMs || Date.now(),
109
+ };
110
+ };
111
+
112
+ const currentRequestId = clientRequestId;
113
+ if (this.abortController) {
114
+ this.abortController.abort();
115
+ this.abortController = null;
116
+ }
117
+ this.abortController = new AbortController();
118
+ this.activeRequestId = currentRequestId;
119
+
120
+ try {
121
+ const authResolved = await waitAuthReady();
122
+ onTiming?.({
123
+ point: 't_auth_ready',
124
+ at_ms: authResolved.authReadyAtMs || Date.now(),
125
+ request_id: currentRequestId,
126
+ session_id: String(sessionId || '').trim(),
127
+ });
128
+ if (this.activeRequestId !== currentRequestId) {
129
+ return;
130
+ }
131
+ const response = await fetch(url, {
132
+ method: 'POST',
133
+ headers: {
134
+ 'Content-Type': 'application/json',
135
+ ...(authResolved.authorizationToken ? { Authorization: `Bearer ${authResolved.authorizationToken}` } : {}),
136
+ },
137
+ body: JSON.stringify({
138
+ contract_version: contractVersion,
139
+ client_request_id: clientRequestId,
140
+ command,
141
+ last_turn_id: lastTurnId || null,
142
+ message: content,
143
+ session_id: sessionId,
144
+ enabled_capabilities: enabledCapabilities,
145
+ ...(modeKey ? { mode: modeKey } : {}),
146
+ ...(manualModeKey ? { manual_mode: manualModeKey } : {}),
147
+ payload: {
148
+ message: content,
149
+ ...(modeKey ? { mode: modeKey } : {}),
150
+ ...(manualModeKey ? { manual_mode: manualModeKey } : {}),
151
+ ...payloadExtra,
152
+ },
153
+ }),
154
+ signal: this.abortController.signal,
155
+ });
156
+ onTiming?.({
157
+ point: 't_stream_connected',
158
+ at_ms: Date.now(),
159
+ request_id: currentRequestId,
160
+ session_id: String(sessionId || '').trim(),
161
+ });
162
+
163
+ if (!response.ok) {
164
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
165
+ }
166
+
167
+ const reader = response.body.getReader();
168
+ const decoder = new TextDecoder();
169
+ let buffer = '';
170
+ let currentEvent = '';
171
+
172
+ while (true) {
173
+ const { done, value } = await reader.read();
174
+ if (done) break;
175
+
176
+ buffer += decoder.decode(value, { stream: true });
177
+ const lines = buffer.split('\n');
178
+ buffer = lines.pop() || '';
179
+
180
+ for (const line of lines) {
181
+ if (line.startsWith('event:')) {
182
+ currentEvent = line.substring(6).trim();
183
+ continue;
184
+ }
185
+
186
+ if (!line.startsWith('data:')) {
187
+ continue;
188
+ }
189
+
190
+ const data = line.substring(5).trim();
191
+ if (!data || !currentEvent) {
192
+ continue;
193
+ }
194
+
195
+ try {
196
+ const eventData = JSON.parse(data);
197
+ if (this.activeRequestId !== currentRequestId) {
198
+ return;
199
+ }
200
+ onEvent?.({
201
+ type: currentEvent,
202
+ data: eventData,
203
+ });
204
+
205
+ if (currentEvent === 'complete' || currentEvent === 'done') {
206
+ await onComplete?.();
207
+ if (this.activeRequestId === currentRequestId) {
208
+ this.abortController = null;
209
+ this.activeRequestId = '';
210
+ }
211
+ return;
212
+ }
213
+ } catch (error) {
214
+ console.error('SSE 数据解析失败:', error, 'data:', data);
215
+ }
216
+ }
217
+ }
218
+
219
+ await onComplete?.();
220
+ if (this.activeRequestId === currentRequestId) {
221
+ this.abortController = null;
222
+ this.activeRequestId = '';
223
+ }
224
+ } catch (error) {
225
+ if (error?.name === 'AbortError') {
226
+ return;
227
+ }
228
+
229
+ onError?.(error instanceof Error ? error.message : '发送消息失败');
230
+ if (this.activeRequestId === currentRequestId) {
231
+ this.abortController = null;
232
+ this.activeRequestId = '';
233
+ }
234
+ }
235
+ }
236
+
237
+ abort() {
238
+ if (this.abortController) {
239
+ this.abortController.abort();
240
+ this.abortController = null;
241
+ }
242
+ this.activeRequestId = '';
243
+ }
244
+ }
@@ -0,0 +1,121 @@
1
+ import {
2
+ parseAck,
3
+ parseAgentStatus,
4
+ parseThinkingTrace,
5
+ parseExecutionTrace,
6
+ parseContent,
7
+ parseDone,
8
+ parsePatch,
9
+ parsePhaseEvent,
10
+ parseTextDelta,
11
+ parseTextReplace,
12
+ parseTrace,
13
+ parseFieldProgress,
14
+ parseDoc,
15
+ parseRequirementDraft,
16
+ parseNextActions,
17
+ parseSourcingCandidates,
18
+ parseRiskSummary,
19
+ parseShortlistUpdated,
20
+ parseEvaluationReportReady,
21
+ parseWorkspaceActivation,
22
+ parseUICards,
23
+ parseKnowledgeBaseState,
24
+ parseCategoryIdentified,
25
+ parseCapabilitySuggestion,
26
+ parseFieldsUpdated,
27
+ parseInstanceProfile,
28
+ parseModeRuntime,
29
+ parseAgentRuntime,
30
+ parseSkillRuntime,
31
+ parseModeSwitchReason,
32
+ parseKnowledgeSearch,
33
+ parseKnowledgeCitation,
34
+ parseOrchestrationStatus,
35
+ parseCanvasState,
36
+ parseCanvasRevision,
37
+ parsePlanBlock,
38
+ parsePatchEvent,
39
+ parseSSEError,
40
+ } from '../contracts/sse-events.js';
41
+
42
+ function handlePatchEvent(event, context) {
43
+ const parsedPatchEvent = parsePatchEvent(event.data);
44
+ const patchSessionId = String(parsedPatchEvent?.session?.session_id || '').trim();
45
+ if (patchSessionId) {
46
+ context.setLatestSessionId(patchSessionId);
47
+ }
48
+ context.handlers.onPatchEvent?.(parsedPatchEvent);
49
+ }
50
+
51
+ export function dispatchSSEEvent(event, context) {
52
+ const handlersByType = {
53
+ ack: () => context.handlers.onAck?.(parseAck(event.data)),
54
+ 'phase.start': () => context.handlers.onPhaseStart?.(parsePhaseEvent(event.data)),
55
+ 'phase.update': () => context.handlers.onPhaseUpdate?.(parsePhaseEvent(event.data)),
56
+ 'phase.end': () => context.handlers.onPhaseEnd?.(parsePhaseEvent(event.data)),
57
+ patch: () => context.handlers.onPatch?.(parsePatch(event.data)),
58
+ agent_status: () => context.handlers.onAgentStatus?.(parseAgentStatus(event.data)),
59
+ thinking_trace: () => context.handlers.onThinkingTrace?.(parseThinkingTrace(event.data)),
60
+ execution_trace: () => context.handlers.onExecutionTrace?.(parseExecutionTrace(event.data)),
61
+ step_started: () => context.handlers.onExecutionTrace?.(parseExecutionTrace(event.data)),
62
+ step_updated: () => context.handlers.onExecutionTrace?.(parseExecutionTrace(event.data)),
63
+ step_completed: () => context.handlers.onExecutionTrace?.(parseExecutionTrace(event.data)),
64
+ step_failed: () => context.handlers.onExecutionTrace?.(parseExecutionTrace(event.data)),
65
+ content: () => {
66
+ const { chunk } = parseContent(event.data);
67
+ context.appendChunk(chunk);
68
+ context.handlers.onContent?.(chunk);
69
+ },
70
+ 'text.delta': () => {
71
+ const parsed = parseTextDelta(event.data);
72
+ if (parsed.channel === 'assistant' && parsed.delta) {
73
+ context.appendChunk(parsed.delta);
74
+ context.handlers.onContent?.(parsed.delta);
75
+ }
76
+ },
77
+ 'text.replace': () => {
78
+ const parsed = parseTextReplace(event.data);
79
+ if (parsed.channel === 'assistant') {
80
+ context.replaceChunk(parsed.content);
81
+ context.handlers.onTextReplace?.(parsed.content);
82
+ }
83
+ },
84
+ done: () => context.handlers.onDone?.(parseDone(event.data)),
85
+ trace: () => context.handlers.onTrace?.(parseTrace(event.data)),
86
+ workspace_activation: () => context.handlers.onWorkspaceActivation?.(parseWorkspaceActivation(event.data)),
87
+ ui_cards: () => context.handlers.onUICards?.(parseUICards(event.data)),
88
+ knowledge_base_state: () => context.handlers.onKnowledgeBaseState?.(parseKnowledgeBaseState(event.data)),
89
+ field_progress: () => context.handlers.onFieldProgress?.(parseFieldProgress(event.data)),
90
+ requirement_draft: () => context.handlers.onRequirementDraft?.(parseRequirementDraft(event.data)),
91
+ next_actions: () => context.handlers.onNextActions?.(parseNextActions(event.data)),
92
+ sourcing_candidates: () => context.handlers.onSourcingCandidates?.(parseSourcingCandidates(event.data)),
93
+ risk_summary: () => context.handlers.onRiskSummary?.(parseRiskSummary(event.data)),
94
+ shortlist_updated: () => context.handlers.onShortlistUpdated?.(parseShortlistUpdated(event.data)),
95
+ evaluation_report_ready: () => context.handlers.onEvaluationReportReady?.(parseEvaluationReportReady(event.data)),
96
+ category_identified: () => context.handlers.onCategoryIdentified?.(parseCategoryIdentified(event.data)),
97
+ fields_updated: () => context.handlers.onFieldsUpdated?.(parseFieldsUpdated(event.data)),
98
+ instance_profile: () => context.handlers.onInstanceProfile?.(parseInstanceProfile(event.data)),
99
+ mode_runtime: () => context.handlers.onModeRuntime?.(parseModeRuntime(event.data)),
100
+ agent_runtime: () => context.handlers.onAgentRuntime?.(parseAgentRuntime(event.data)),
101
+ skill_runtime: () => context.handlers.onSkillRuntime?.(parseSkillRuntime(event.data)),
102
+ mode_switch_reason: () => context.handlers.onModeSwitchReason?.(parseModeSwitchReason(event.data)),
103
+ knowledge_search: () => context.handlers.onKnowledgeSearch?.(parseKnowledgeSearch(event.data)),
104
+ knowledge_citation: () => context.handlers.onKnowledgeCitation?.(parseKnowledgeCitation(event.data)),
105
+ orchestration_status: () => context.handlers.onOrchestrationStatus?.(parseOrchestrationStatus(event.data)),
106
+ canvas_state: () => context.handlers.onCanvasState?.(parseCanvasState(event.data)),
107
+ canvas_revision: () => context.handlers.onCanvasRevision?.(parseCanvasRevision(event.data)),
108
+ plan_block: () => context.handlers.onPlanBlock?.(parsePlanBlock(event.data)),
109
+ doc: () => context.handlers.onDoc?.(parseDoc(event.data)),
110
+ capability_suggestion: () => context.handlers.onCapabilitySuggestion?.(parseCapabilitySuggestion(event.data)),
111
+ patch_event: () => handlePatchEvent(event, context),
112
+ error: () => context.handlers.onError?.(parseSSEError(event.data)),
113
+ };
114
+
115
+ const handler = handlersByType[event.type];
116
+ if (!handler) {
117
+ console.warn('[SSE] unknown event type:', event.type);
118
+ return;
119
+ }
120
+ handler();
121
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import AppProviders from './AppProviders.jsx';
3
+ import ChatWorkspaceScreen from './ChatWorkspaceScreen.jsx';
4
+
5
+ export default function App(props = {}) {
6
+ return (
7
+ <AppProviders>
8
+ <ChatWorkspaceScreen {...props} />
9
+ </AppProviders>
10
+ );
11
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { App as AntdApp, ConfigProvider } from 'antd';
3
+
4
+ export default function AppProviders({ children }) {
5
+ return (
6
+ <ConfigProvider>
7
+ <AntdApp>
8
+ {children}
9
+ </AntdApp>
10
+ </ConfigProvider>
11
+ );
12
+ }
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { useWorkspaceController } from './core-chat-entry/useWorkspaceController.jsx';
3
+ import WorkspaceLayout from './WorkspaceLayout.jsx';
4
+
5
+ export default function ChatWorkspaceScreen({
6
+ functionType = 'chat_component_debug',
7
+ defaultSessionTitle = '调试会话',
8
+ projectId = 'project_demo_001',
9
+ userId = 'user_demo_001',
10
+ backendMode = 'real',
11
+ kernelBaseUrl = 'http://127.0.0.1:18002',
12
+ apiBaseUrl = '',
13
+ apiToken = '',
14
+ defaultProjectName = '本地演示项目',
15
+ sessionAPI,
16
+ messageAPI,
17
+ } = {}) {
18
+ const workspace = useWorkspaceController({
19
+ functionType,
20
+ defaultSessionTitle,
21
+ projectId,
22
+ userId,
23
+ backendMode,
24
+ kernelBaseUrl,
25
+ apiBaseUrl,
26
+ apiToken,
27
+ defaultProjectName,
28
+ sessionAPI,
29
+ messageAPI,
30
+ });
31
+
32
+ return <WorkspaceLayout {...workspace} />;
33
+ }
@@ -0,0 +1,190 @@
1
+ import React from 'react';
2
+ import { Drawer } from 'antd';
3
+ import {
4
+ WORKSPACE_CONTENT_MAX_WIDTH,
5
+ RESOLVED_UI_LABELS,
6
+ createWorkspaceStyle,
7
+ } from './core-chat-entry/constants.js';
8
+ import WorkspaceSessionPane from './components/WorkspaceSessionPane.jsx';
9
+ import WorkspaceMainPane from './components/WorkspaceMainPane.jsx';
10
+
11
+ const COMPACT_LAYOUT_QUERY = '(max-width: 767px)';
12
+
13
+ export default function WorkspaceLayout({
14
+ themeTokens,
15
+ viewModel,
16
+ actionGuards,
17
+ apiReadinessGate,
18
+ hasProject,
19
+ projectItems,
20
+ projectSlot,
21
+ activeSession,
22
+ sessions,
23
+ projectNameEditing,
24
+ projectNameDraft,
25
+ setProjectNameDraft,
26
+ handleProjectNameSave,
27
+ handleProjectNameCancel,
28
+ projectDisplayName,
29
+ handleProjectNameStartEdit,
30
+ activeWorkspaceTab,
31
+ setActiveWorkspaceTab,
32
+ knowledgeHubPopoverContent,
33
+ knowledgeHubPopoverOpen,
34
+ setKnowledgeHubPopoverOpen,
35
+ handleOpenKnowledgeHub,
36
+ showCanvasPanel,
37
+ handleToggleWorkspacePanel,
38
+ composerNode,
39
+ handleOpenSourcePicker,
40
+ handleRemoveSource,
41
+ handleRetrySource,
42
+ handleViewSourceDetail,
43
+ sourceActionError,
44
+ sourceItems,
45
+ sourceRemovingId,
46
+ sourceSyncLoading,
47
+ sourceUploadLoading,
48
+ canvasPanelNode,
49
+ renderedTimelineItems,
50
+ handleUICardAction,
51
+ generativeRegistry,
52
+ showAllScenarios,
53
+ setShowAllScenarios,
54
+ visibleScenarios,
55
+ sourceFileInputRef,
56
+ handleSourceFileChange,
57
+ }) {
58
+ const [isCompactLayout, setIsCompactLayout] = React.useState(false);
59
+ const [sidebarDrawerOpen, setSidebarDrawerOpen] = React.useState(false);
60
+
61
+ React.useEffect(() => {
62
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
63
+ return undefined;
64
+ }
65
+
66
+ const mediaQuery = window.matchMedia(COMPACT_LAYOUT_QUERY);
67
+ const handleChange = (event) => {
68
+ const nextCompact = Boolean(event.matches);
69
+ setIsCompactLayout(nextCompact);
70
+ if (!nextCompact) {
71
+ setSidebarDrawerOpen(false);
72
+ }
73
+ };
74
+
75
+ setIsCompactLayout(mediaQuery.matches);
76
+
77
+ if (typeof mediaQuery.addEventListener === 'function') {
78
+ mediaQuery.addEventListener('change', handleChange);
79
+ return () => mediaQuery.removeEventListener('change', handleChange);
80
+ }
81
+
82
+ mediaQuery.addListener(handleChange);
83
+ return () => mediaQuery.removeListener(handleChange);
84
+ }, []);
85
+
86
+ const showInlineSidebar = !isCompactLayout;
87
+ const workspaceStyle = {
88
+ ...createWorkspaceStyle(themeTokens),
89
+ gridTemplateColumns: showInlineSidebar ? '300px minmax(0, 1fr)' : 'minmax(0, 1fr)',
90
+ };
91
+
92
+ return (
93
+ <div
94
+ className="flare-chat-workspace"
95
+ style={workspaceStyle}
96
+ >
97
+ {showInlineSidebar ? (
98
+ <WorkspaceSessionPane
99
+ themeTokens={themeTokens}
100
+ viewModel={viewModel}
101
+ actionGuards={actionGuards}
102
+ apiReadinessGate={apiReadinessGate}
103
+ hasProject={hasProject}
104
+ projectItems={projectItems}
105
+ projectSlot={projectSlot}
106
+ sessions={sessions}
107
+ resolvedUILabels={RESOLVED_UI_LABELS}
108
+ />
109
+ ) : null}
110
+
111
+ <WorkspaceMainPane
112
+ themeTokens={themeTokens}
113
+ hasProject={hasProject}
114
+ viewModel={viewModel}
115
+ projectSlot={projectSlot}
116
+ projectNameEditing={projectNameEditing}
117
+ projectNameDraft={projectNameDraft}
118
+ setProjectNameDraft={setProjectNameDraft}
119
+ handleProjectNameSave={handleProjectNameSave}
120
+ handleProjectNameCancel={handleProjectNameCancel}
121
+ projectDisplayName={projectDisplayName}
122
+ handleProjectNameStartEdit={handleProjectNameStartEdit}
123
+ activeWorkspaceTab={activeWorkspaceTab}
124
+ setActiveWorkspaceTab={setActiveWorkspaceTab}
125
+ resolvedUILabels={RESOLVED_UI_LABELS}
126
+ activeSession={activeSession}
127
+ knowledgeHubPopoverContent={knowledgeHubPopoverContent}
128
+ knowledgeHubPopoverOpen={knowledgeHubPopoverOpen}
129
+ setKnowledgeHubPopoverOpen={setKnowledgeHubPopoverOpen}
130
+ handleOpenKnowledgeHub={handleOpenKnowledgeHub}
131
+ showCanvasPanel={showCanvasPanel}
132
+ handleToggleWorkspacePanel={handleToggleWorkspacePanel}
133
+ projectItems={projectItems}
134
+ actionGuards={actionGuards}
135
+ composerNode={composerNode}
136
+ contentMaxWidth={WORKSPACE_CONTENT_MAX_WIDTH}
137
+ handleOpenSourcePicker={handleOpenSourcePicker}
138
+ handleRemoveSource={handleRemoveSource}
139
+ handleRetrySource={handleRetrySource}
140
+ handleViewSourceDetail={handleViewSourceDetail}
141
+ sourceActionError={sourceActionError}
142
+ sourceItems={sourceItems}
143
+ sourceRemovingId={sourceRemovingId}
144
+ sourceSyncLoading={sourceSyncLoading}
145
+ sourceUploadLoading={sourceUploadLoading}
146
+ canvasPanelNode={canvasPanelNode}
147
+ renderedTimelineItems={renderedTimelineItems}
148
+ handleUICardAction={handleUICardAction}
149
+ generativeRegistry={generativeRegistry}
150
+ showAllScenarios={showAllScenarios}
151
+ setShowAllScenarios={setShowAllScenarios}
152
+ visibleScenarios={visibleScenarios}
153
+ isCompactLayout={isCompactLayout}
154
+ showSidebarMenuButton={isCompactLayout}
155
+ handleOpenSidebarMenu={() => setSidebarDrawerOpen(true)}
156
+ />
157
+
158
+ <Drawer
159
+ onClose={() => setSidebarDrawerOpen(false)}
160
+ open={isCompactLayout && sidebarDrawerOpen}
161
+ placement="left"
162
+ styles={{
163
+ body: { padding: 0 },
164
+ wrapper: { width: '86vw' },
165
+ }}
166
+ >
167
+ <WorkspaceSessionPane
168
+ themeTokens={themeTokens}
169
+ viewModel={viewModel}
170
+ actionGuards={actionGuards}
171
+ apiReadinessGate={apiReadinessGate}
172
+ hasProject={hasProject}
173
+ projectItems={projectItems}
174
+ projectSlot={projectSlot}
175
+ sessions={sessions}
176
+ resolvedUILabels={RESOLVED_UI_LABELS}
177
+ onAfterSidebarAction={() => setSidebarDrawerOpen(false)}
178
+ />
179
+ </Drawer>
180
+
181
+ <input
182
+ ref={sourceFileInputRef}
183
+ type="file"
184
+ multiple
185
+ style={{ display: 'none' }}
186
+ onChange={handleSourceFileChange}
187
+ />
188
+ </div>
189
+ );
190
+ }
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import { ChatWorkspaceCanvasSection } from 'flare-chat-ui';
3
+
4
+ export default function AppCanvasPanel({
5
+ canvasActiveTabKey,
6
+ showCanvasPanel,
7
+ resolvedUILabels,
8
+ canvasFullscreenOpen,
9
+ canvasTabs,
10
+ resolvedCanvasEvidenceTabKey,
11
+ canvasEvidenceSummary,
12
+ canvasEvidenceItems,
13
+ canvasFocusEvidenceAnchorId,
14
+ canvasSourcingHasMore,
15
+ canvasSourcingLoadingMore,
16
+ onCanvasSourcingLoadMore,
17
+ canvasReportTemplate,
18
+ inputState,
19
+ setCanvasActiveTabKey,
20
+ setCanvasFullscreenOpen,
21
+ setShowCanvasPanel,
22
+ themeTokens,
23
+ }) {
24
+ return (
25
+ <ChatWorkspaceCanvasSection
26
+ canvasPanelErrorFallback={null}
27
+ handleCanvasRenderError={() => {}}
28
+ canvasActiveTabKey={canvasActiveTabKey}
29
+ showCanvasPanel={showCanvasPanel}
30
+ canvasCollectedCount={0}
31
+ canvasDisplayLines={[]}
32
+ canvasDraftEditing={false}
33
+ canvasDraftEditorValue=""
34
+ resolvedUILabels={resolvedUILabels}
35
+ canvasFieldRows={[]}
36
+ canvasFullscreenOpen={canvasFullscreenOpen}
37
+ canvasManualControlsEnabled={false}
38
+ canvasProgressPercent={0}
39
+ canvasTabs={canvasTabs}
40
+ canvasEvidenceTabKey={resolvedCanvasEvidenceTabKey}
41
+ canvasEvidenceSummary={canvasEvidenceSummary}
42
+ canvasEvidenceItems={canvasEvidenceItems}
43
+ canvasFocusEvidenceAnchorId={canvasFocusEvidenceAnchorId}
44
+ canvasSourcingLoading={false}
45
+ canvasSourcingHasMore={canvasSourcingHasMore}
46
+ canvasSourcingLoadingMore={canvasSourcingLoadingMore}
47
+ onCanvasSourcingLoadMore={onCanvasSourcingLoadMore}
48
+ canvasReportTemplate={canvasReportTemplate}
49
+ resolvedCanvasAnalysisPayload={null}
50
+ inputState={inputState}
51
+ canvasWorkspaceFullscreenMode={false}
52
+ isCompactLayout={false}
53
+ handleCanvasApplyRevision={() => {}}
54
+ setCanvasActiveTabKey={setCanvasActiveTabKey}
55
+ setCanvasDraftEditorValue={() => {}}
56
+ handleCanvasCancelEdit={() => {}}
57
+ setCanvasFullscreenOpen={setCanvasFullscreenOpen}
58
+ setCanvasPanelOpen={setShowCanvasPanel}
59
+ handleCanvasSaveEdit={() => {}}
60
+ handleCanvasStartEdit={() => {}}
61
+ themeTokens={themeTokens}
62
+ />
63
+ );
64
+ }