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
@@ -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,125 @@
1
+ import React from 'react';
2
+ import {
3
+ WORKSPACE_CONTENT_MAX_WIDTH,
4
+ RESOLVED_UI_LABELS,
5
+ createWorkspaceStyle,
6
+ } from './core-chat-entry/constants.js';
7
+ import WorkspaceSessionPane from './components/WorkspaceSessionPane.jsx';
8
+ import WorkspaceMainPane from './components/WorkspaceMainPane.jsx';
9
+
10
+ export default function WorkspaceLayout({
11
+ themeTokens,
12
+ viewModel,
13
+ actionGuards,
14
+ apiReadinessGate,
15
+ hasProject,
16
+ projectItems,
17
+ projectSlot,
18
+ activeSession,
19
+ sessions,
20
+ projectNameEditing,
21
+ projectNameDraft,
22
+ setProjectNameDraft,
23
+ handleProjectNameSave,
24
+ handleProjectNameCancel,
25
+ projectDisplayName,
26
+ handleProjectNameStartEdit,
27
+ activeWorkspaceTab,
28
+ setActiveWorkspaceTab,
29
+ knowledgeHubPopoverContent,
30
+ knowledgeHubPopoverOpen,
31
+ setKnowledgeHubPopoverOpen,
32
+ handleOpenKnowledgeHub,
33
+ showCanvasPanel,
34
+ handleToggleWorkspacePanel,
35
+ composerNode,
36
+ handleOpenSourcePicker,
37
+ handleRemoveSource,
38
+ handleRetrySource,
39
+ handleViewSourceDetail,
40
+ sourceActionError,
41
+ sourceItems,
42
+ sourceRemovingId,
43
+ sourceSyncLoading,
44
+ sourceUploadLoading,
45
+ canvasPanelNode,
46
+ renderedTimelineItems,
47
+ handleUICardAction,
48
+ generativeRegistry,
49
+ showAllScenarios,
50
+ setShowAllScenarios,
51
+ visibleScenarios,
52
+ sourceFileInputRef,
53
+ handleSourceFileChange,
54
+ }) {
55
+ return (
56
+ <div
57
+ className="flare-chat-workspace"
58
+ style={createWorkspaceStyle(themeTokens)}
59
+ >
60
+ <WorkspaceSessionPane
61
+ themeTokens={themeTokens}
62
+ viewModel={viewModel}
63
+ actionGuards={actionGuards}
64
+ apiReadinessGate={apiReadinessGate}
65
+ hasProject={hasProject}
66
+ projectItems={projectItems}
67
+ projectSlot={projectSlot}
68
+ sessions={sessions}
69
+ resolvedUILabels={RESOLVED_UI_LABELS}
70
+ />
71
+
72
+ <WorkspaceMainPane
73
+ themeTokens={themeTokens}
74
+ hasProject={hasProject}
75
+ viewModel={viewModel}
76
+ projectSlot={projectSlot}
77
+ projectNameEditing={projectNameEditing}
78
+ projectNameDraft={projectNameDraft}
79
+ setProjectNameDraft={setProjectNameDraft}
80
+ handleProjectNameSave={handleProjectNameSave}
81
+ handleProjectNameCancel={handleProjectNameCancel}
82
+ projectDisplayName={projectDisplayName}
83
+ handleProjectNameStartEdit={handleProjectNameStartEdit}
84
+ activeWorkspaceTab={activeWorkspaceTab}
85
+ setActiveWorkspaceTab={setActiveWorkspaceTab}
86
+ resolvedUILabels={RESOLVED_UI_LABELS}
87
+ activeSession={activeSession}
88
+ knowledgeHubPopoverContent={knowledgeHubPopoverContent}
89
+ knowledgeHubPopoverOpen={knowledgeHubPopoverOpen}
90
+ setKnowledgeHubPopoverOpen={setKnowledgeHubPopoverOpen}
91
+ handleOpenKnowledgeHub={handleOpenKnowledgeHub}
92
+ showCanvasPanel={showCanvasPanel}
93
+ handleToggleWorkspacePanel={handleToggleWorkspacePanel}
94
+ projectItems={projectItems}
95
+ actionGuards={actionGuards}
96
+ composerNode={composerNode}
97
+ contentMaxWidth={WORKSPACE_CONTENT_MAX_WIDTH}
98
+ handleOpenSourcePicker={handleOpenSourcePicker}
99
+ handleRemoveSource={handleRemoveSource}
100
+ handleRetrySource={handleRetrySource}
101
+ handleViewSourceDetail={handleViewSourceDetail}
102
+ sourceActionError={sourceActionError}
103
+ sourceItems={sourceItems}
104
+ sourceRemovingId={sourceRemovingId}
105
+ sourceSyncLoading={sourceSyncLoading}
106
+ sourceUploadLoading={sourceUploadLoading}
107
+ canvasPanelNode={canvasPanelNode}
108
+ renderedTimelineItems={renderedTimelineItems}
109
+ handleUICardAction={handleUICardAction}
110
+ generativeRegistry={generativeRegistry}
111
+ showAllScenarios={showAllScenarios}
112
+ setShowAllScenarios={setShowAllScenarios}
113
+ visibleScenarios={visibleScenarios}
114
+ />
115
+
116
+ <input
117
+ ref={sourceFileInputRef}
118
+ type="file"
119
+ multiple
120
+ style={{ display: 'none' }}
121
+ onChange={handleSourceFileChange}
122
+ />
123
+ </div>
124
+ );
125
+ }
@@ -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
+ }