flare-chat-core 0.2.3 → 0.2.5

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 (122) hide show
  1. package/dist/index.js +6372 -0
  2. package/package.json +19 -22
  3. package/docs/CAPABILITY-INVENTORY.md +0 -42
  4. package/docs/CHAT-CORE-BOUNDARY.md +0 -47
  5. package/docs/CORE-APP-REALIGNMENT-WORKLOAD-2026-04-18.md +0 -86
  6. package/docs/SSOT-CHAT-CORE-BOUNDARY.md +0 -73
  7. package/docs/SSOT-CHAT-CORE-DATAFLOW.md +0 -97
  8. package/index.html +0 -12
  9. package/scripts/check.sh +0 -15
  10. package/src/adapters/index.js +0 -6
  11. package/src/adapters/message-api.adapter.js +0 -59
  12. package/src/adapters/session-api.adapter.js +0 -133
  13. package/src/adapters/session-message-api.http.js +0 -161
  14. package/src/adapters/session-message-api.js +0 -34
  15. package/src/adapters/session-message-api.normalize-source-record.test.mjs +0 -180
  16. package/src/adapters/session-message-api.normalizers.js +0 -153
  17. package/src/adapters/source-api.adapter.js +0 -135
  18. package/src/adapters/sse-client.js +0 -244
  19. package/src/adapters/sse-event-dispatcher.js +0 -121
  20. package/src/app/App.jsx +0 -11
  21. package/src/app/AppProviders.jsx +0 -12
  22. package/src/app/ChatWorkspaceScreen.jsx +0 -33
  23. package/src/app/WorkspaceLayout.jsx +0 -190
  24. package/src/app/components/AppCanvasPanel.jsx +0 -64
  25. package/src/app/components/TriggerThresholdPopoverContent.jsx +0 -122
  26. package/src/app/components/WorkspaceBodySection.jsx +0 -156
  27. package/src/app/components/WorkspaceMainPane.jsx +0 -121
  28. package/src/app/components/WorkspaceSessionPane.jsx +0 -70
  29. package/src/app/components/WorkspaceTopBarSection.jsx +0 -71
  30. package/src/app/core-chat-entry/ComposerSectionNode.jsx +0 -241
  31. package/src/app/core-chat-entry/attachmentSendRefs.js +0 -154
  32. package/src/app/core-chat-entry/attachmentSendRefs.test.mjs +0 -101
  33. package/src/app/core-chat-entry/composerActionRouter.js +0 -26
  34. package/src/app/core-chat-entry/constants.js +0 -108
  35. package/src/app/core-chat-entry/selectors.js +0 -28
  36. package/src/app/core-chat-entry/useAppActionErrorGuards.js +0 -68
  37. package/src/app/core-chat-entry/useChatCorePipelines.js +0 -110
  38. package/src/app/core-chat-entry/useComposerModeSuggestion.js +0 -89
  39. package/src/app/core-chat-entry/useDevCapabilityStatusNote.js +0 -22
  40. package/src/app/core-chat-entry/useProjectNameEditing.js +0 -41
  41. package/src/app/core-chat-entry/useProjectSourceUpload.js +0 -341
  42. package/src/app/core-chat-entry/useRealApiReadinessGate.js +0 -103
  43. package/src/app/core-chat-entry/useUnavailableActionError.js +0 -29
  44. package/src/app/core-chat-entry/useWorkspaceCanvasController.jsx +0 -177
  45. package/src/app/core-chat-entry/useWorkspaceCanvasProjection.jsx +0 -171
  46. package/src/app/core-chat-entry/useWorkspaceComposerController.jsx +0 -199
  47. package/src/app/core-chat-entry/useWorkspaceController.jsx +0 -226
  48. package/src/app/core-chat-entry/useWorkspacePanels.js +0 -55
  49. package/src/app/hooks/useComposerAttachmentSync.js +0 -223
  50. package/src/app/hooks/useComposerChooserHandlers.js +0 -52
  51. package/src/app/hooks/useSendWithContextRefs.js +0 -140
  52. package/src/app/hooks/useSendWithContextRefs.test.mjs +0 -29
  53. package/src/app/hooks/useUserThresholdProfile.js +0 -121
  54. package/src/app/index.js +0 -1
  55. package/src/app/selectors/assistantTextSelector.js +0 -73
  56. package/src/app/selectors/canvasEvidenceSummarySelector.js +0 -28
  57. package/src/app/selectors/canvasReportTemplateSelector.js +0 -28
  58. package/src/app/selectors/canvasTabsSelector.js +0 -58
  59. package/src/app/selectors/evidenceProjectionSelector.js +0 -175
  60. package/src/app/selectors/evidenceProjectionSelector.test.mjs +0 -107
  61. package/src/app/selectors/modeSuggestionSelector.js +0 -50
  62. package/src/chat-core/app/mockRuntime.js +0 -291
  63. package/src/chat-core/app/useAppStream.js +0 -187
  64. package/src/chat-core/app/useAppStream.refs.test.mjs +0 -44
  65. package/src/chat-core/app/useAppStream.request-body.test.mjs +0 -116
  66. package/src/chat-core/app/useCoreChatApp.js +0 -115
  67. package/src/chat-core/facade/useBasicConversationFacade.js +0 -280
  68. package/src/chat-core/index.js +0 -14
  69. package/src/chat-core/input/useChatInput.js +0 -103
  70. package/src/chat-core/messages/buildTimelineItems.analysis-route.test.mjs +0 -36
  71. package/src/chat-core/messages/buildTimelineItems.js +0 -201
  72. package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +0 -182
  73. package/src/chat-core/messages/contextUsageDefaults.js +0 -3
  74. package/src/chat-core/messages/contextUsageViewModel.js +0 -147
  75. package/src/chat-core/messages/contextUsageViewModel.test.mjs +0 -74
  76. package/src/chat-core/messages/useContextUsageViewModel.js +0 -41
  77. package/src/chat-core/orchestration/useBasicSendHandler.js +0 -55
  78. package/src/chat-core/pipelines/build-action-request.js +0 -46
  79. package/src/chat-core/pipelines/build-stream-request.js +0 -74
  80. package/src/chat-core/pipelines/entity-extraction.js +0 -159
  81. package/src/chat-core/pipelines/preprocess-message.js +0 -16
  82. package/src/chat-core/pipelines/stream-persist-utils.js +0 -32
  83. package/src/chat-core/pipelines/transport/send-mock-stream.js +0 -86
  84. package/src/chat-core/pipelines/transport/send-real-stream.js +0 -330
  85. package/src/chat-core/pipelines/transport/send-real-stream.test.mjs +0 -27
  86. package/src/chat-core/pipelines/transport/send-sourcing-search.js +0 -86
  87. package/src/chat-core/pipelines/transport/send-sourcing-search.test.mjs +0 -14
  88. package/src/chat-core/pipelines/transport/sourcing-response-templates.js +0 -55
  89. package/src/chat-core/pipelines/transport/sourcing-search-api.js +0 -155
  90. package/src/chat-core/runtime/runtimeMode.js +0 -69
  91. package/src/chat-core/session/chatSessionActionTypes.js +0 -24
  92. package/src/chat-core/session/chatSessionReducer.js +0 -352
  93. package/src/chat-core/session/chatSessionReducer.streaming-done.test.mjs +0 -39
  94. package/src/chat-core/session/index.js +0 -2
  95. package/src/chat-core/session/sessionActionsMessages.js +0 -44
  96. package/src/chat-core/session/sessionActionsSessionCrud.js +0 -131
  97. package/src/chat-core/session/sessionActionsStreaming.js +0 -80
  98. package/src/chat-core/session/sessionActionsUiState.js +0 -51
  99. package/src/chat-core/session/useChatSessionReducer.js +0 -131
  100. package/src/chat-core/session/useSessionListController.js +0 -67
  101. package/src/chat-core/stream/sse-client.js +0 -1
  102. package/src/chat-core/stream/sse-event-dispatcher.js +0 -1
  103. package/src/chat-core/stream/sse-events.js +0 -1
  104. package/src/chat-core/stream/useSSEStream.js +0 -1
  105. package/src/chat-core/stream/useStreamSendController.js +0 -46
  106. package/src/contracts/context-ssot.js +0 -47
  107. package/src/contracts/index.js +0 -1
  108. package/src/contracts/sse-events/base-parsers.js +0 -79
  109. package/src/contracts/sse-events/domain-parsers.js +0 -3
  110. package/src/contracts/sse-events/internal-normalizers.js +0 -143
  111. package/src/contracts/sse-events/parsers-intake.js +0 -235
  112. package/src/contracts/sse-events/parsers-runtime.js +0 -37
  113. package/src/contracts/sse-events/parsers-sourcing.js +0 -179
  114. package/src/contracts/sse-events/patch-event-parser.js +0 -121
  115. package/src/contracts/sse-events/runtime-parsers.js +0 -79
  116. package/src/contracts/sse-events.js +0 -4
  117. package/src/index.js +0 -6
  118. package/src/main.jsx +0 -28
  119. package/src/orchestration/index.js +0 -6
  120. package/src/orchestration/useSSEStream.js +0 -221
  121. package/src/state/index.js +0 -4
  122. package/vite.config.js +0 -36
@@ -1,241 +0,0 @@
1
- import React, { useMemo, useState } from 'react';
2
- import { ChatWorkspaceComposerSection } from 'flare-chat-ui';
3
- import useComposerModeSuggestion from './useComposerModeSuggestion.js';
4
-
5
- const MODE_STATUS_IDLE = {
6
- requirement_canvas: 'idle',
7
- intelligent_sourcing: 'idle',
8
- analysis_mode: 'idle',
9
- };
10
-
11
- function buildModeStatusByActiveKey(modeKey) {
12
- if (!modeKey || modeKey === 'auto') {
13
- return MODE_STATUS_IDLE;
14
- }
15
- return {
16
- requirement_canvas: modeKey === 'requirement_canvas' ? 'active' : 'idle',
17
- intelligent_sourcing: modeKey === 'intelligent_sourcing' ? 'active' : 'idle',
18
- analysis_mode: modeKey === 'analysis_mode' ? 'active' : 'idle',
19
- };
20
- }
21
-
22
- export default function ComposerSectionNode({
23
- themeTokens,
24
- contentMaxWidth,
25
- streamLoading,
26
- resolvedUILabels,
27
- modeSwitchOptions,
28
- inputState,
29
- onAttachFiles,
30
- onSend,
31
- effectiveComposerChooser = null,
32
- onComposerActionSelect = () => {},
33
- onComposerChooserDismiss = () => {},
34
- runtimeComposerActions = [],
35
- initialModeKey = 'auto',
36
- onModeChanged,
37
- userId,
38
- intentEscalationPolicyPopoverContent,
39
- contextGaugePopoverContent,
40
- shouldRenderModeSuggestion = true,
41
- modeSuggestion = null,
42
- modeSuggestionAction = null,
43
- tipRules = [],
44
- suggestionContextFlags = {},
45
- }) {
46
- const [activeModeKey, setActiveModeKey] = useState(initialModeKey);
47
- const [statusOnlyTipHidden, setStatusOnlyTipHidden] = useState(false);
48
- const [intentEscalationPolicyPopoverOpen, setIntentEscalationPolicyPopoverOpen] = useState(false);
49
- React.useEffect(() => {
50
- setActiveModeKey(String(initialModeKey || 'auto').trim() || 'auto');
51
- }, [initialModeKey]);
52
- React.useEffect(() => {
53
- setStatusOnlyTipHidden(false);
54
- }, [activeModeKey, inputState?.inputValue]);
55
- const modeStatusByKey = useMemo(
56
- () => buildModeStatusByActiveKey(activeModeKey),
57
- [activeModeKey],
58
- );
59
- const {
60
- modeSuggestion: resolvedModeSuggestion,
61
- modeSuggestionAction: resolvedModeSuggestionAction,
62
- fromFallback: modeSuggestionFromFallback,
63
- } = useComposerModeSuggestion({
64
- inputValue: String(inputState?.inputValue || ''),
65
- activeModeKey,
66
- modeSuggestion,
67
- modeSuggestionAction,
68
- tipRules,
69
- contextFlags: suggestionContextFlags,
70
- });
71
- const resolvedModeKey = String(activeModeKey || '').trim();
72
- const recommendationPanelState = 'hidden';
73
- const handleModeSuggestionAction = (actionItem) => {
74
- const actionPayload = actionItem?.action && typeof actionItem.action === 'object'
75
- ? actionItem.action
76
- : null;
77
- const actionType = String(actionPayload?.action_type || actionPayload?.type || '').trim();
78
- const targetMode = String(
79
- actionPayload?.target_mode
80
- || actionPayload?.mode
81
- || actionItem?.modeKey
82
- || ''
83
- ).trim();
84
- if ((actionType === 'switch_mode' || actionType === 'activate_mode') && targetMode) {
85
- const normalizedModeKey = targetMode || 'auto';
86
- setActiveModeKey(normalizedModeKey);
87
- onModeChanged?.(normalizedModeKey);
88
- return;
89
- }
90
- onComposerActionSelect?.(actionItem);
91
- };
92
- const handleCollectingAnalysisAction = (collectingAnalysisAction) => {
93
- if (!collectingAnalysisAction) {
94
- return;
95
- }
96
- onComposerActionSelect?.({
97
- source: 'runtime_action',
98
- text: '开始需求分析',
99
- action: collectingAnalysisAction,
100
- });
101
- };
102
- const handleComposerChooserSubmit = (payload) => {
103
- const selectedChoice = payload?.selectedChoice && typeof payload.selectedChoice === 'object'
104
- ? payload.selectedChoice
105
- : null;
106
- if (!selectedChoice) {
107
- return;
108
- }
109
- const composerChooser = payload?.composerChooser && typeof payload.composerChooser === 'object'
110
- ? payload.composerChooser
111
- : null;
112
- const currentQuestion = composerChooser?.currentQuestion && typeof composerChooser.currentQuestion === 'object'
113
- ? composerChooser.currentQuestion
114
- : null;
115
- const isCustomInputChoice = selectedChoice?.allowCustomInput === true || selectedChoice?.requiresCustomInput === true;
116
- const answerValue = String(payload?.answerValue || '').trim();
117
- if (!answerValue) {
118
- return;
119
- }
120
- const fieldKey = String(currentQuestion?.fieldKey || '').trim();
121
- const questionKind = String(currentQuestion?.questionKind || 'field').trim();
122
- const branchNodeKey = String(currentQuestion?.branchNodeKey || '').trim();
123
- const questionText = String(currentQuestion?.questionText || composerChooser?.title || '').trim();
124
- const selectedOptionText = String(selectedChoice?.text || '').trim();
125
- const selectedOptionKey = String(selectedChoice?.key || '').trim();
126
- const selectedOptionValue = isCustomInputChoice
127
- ? answerValue
128
- : String(selectedChoice?.answerValue || selectedOptionText || '').trim();
129
- const selectedOptionDisplayText = isCustomInputChoice
130
- ? answerValue
131
- : selectedOptionText;
132
- const answerText = String(answerValue || selectedOptionDisplayText).trim();
133
- const hasCurrentQuestionContext = Boolean(currentQuestion && (fieldKey || branchNodeKey));
134
- if (hasCurrentQuestionContext) {
135
- const action = selectedChoice?.action && typeof selectedChoice.action === 'object'
136
- ? {
137
- ...selectedChoice.action,
138
- payload: {
139
- ...(selectedChoice.action.payload && typeof selectedChoice.action.payload === 'object'
140
- ? selectedChoice.action.payload
141
- : {}),
142
- question_answer: {
143
- field_key: fieldKey,
144
- question_kind: questionKind,
145
- branch_node_key: branchNodeKey,
146
- question_text: questionText,
147
- selected_option_key: selectedOptionKey,
148
- selected_option_text: selectedOptionDisplayText,
149
- selected_option_value: selectedOptionValue,
150
- field_value: answerValue,
151
- },
152
- prefill_prompt: answerText,
153
- },
154
- }
155
- : null;
156
- onComposerActionSelect?.({
157
- ...selectedChoice,
158
- source: 'question_answer',
159
- text: answerText,
160
- answerValue,
161
- action,
162
- });
163
- return;
164
- }
165
- onComposerActionSelect?.({
166
- ...selectedChoice,
167
- source: String(selectedChoice?.source || 'action'),
168
- text: String(selectedChoice?.text || answerValue).trim(),
169
- answerValue,
170
- action: selectedChoice?.action && typeof selectedChoice.action === 'object'
171
- ? selectedChoice.action
172
- : null,
173
- });
174
- };
175
-
176
- return (
177
- <ChatWorkspaceComposerSection
178
- canvasWorkspaceFullscreenMode={false}
179
- themeTokens={themeTokens}
180
- isCompactLayout={false}
181
- contentMaxWidth={contentMaxWidth}
182
- recommendationPanelState={recommendationPanelState}
183
- effectiveComposerChooser={effectiveComposerChooser}
184
- streamLoading={streamLoading}
185
- shouldRenderModeSuggestion={shouldRenderModeSuggestion}
186
- modeSuggestion={resolvedModeSuggestion}
187
- modeSuggestionAction={resolvedModeSuggestionAction}
188
- modeSuggestionFromFallback={modeSuggestionFromFallback}
189
- onModeSuggestionAction={handleModeSuggestionAction}
190
- handleComposerActionSelect={onComposerActionSelect}
191
- isStatusOnlyModeSuggestion={false}
192
- statusOnlyTipHidden={statusOnlyTipHidden}
193
- setStatusOnlyTipHidden={setStatusOnlyTipHidden}
194
- applyBehaviorEvent={() => {}}
195
- isRequirementCanvasMode
196
- chooserAwaitingAuthoritative={false}
197
- canonicalAwaitingReason=""
198
- patchEventHealth={{ ok: true }}
199
- showCollectingAnalysisCta={false}
200
- collectingAnalysisAction={null}
201
- onCollectingAnalysisAction={handleCollectingAnalysisAction}
202
- attachmentActionError={null}
203
- resolvedUILabels={resolvedUILabels}
204
- attachmentConstraintsHint="当前附件仅用于本次消息。最多 10 个,单个不超过 20MB。"
205
- composerInputRef={null}
206
- modeSwitchOptions={modeSwitchOptions}
207
- resolvedActiveModeKey={activeModeKey}
208
- modeStatusByKey={modeStatusByKey}
209
- handleAttachFiles={onAttachFiles}
210
- inputState={inputState}
211
- handleModeSelect={(nextModeKey) => {
212
- const normalizedModeKey = String(nextModeKey || '').trim() || 'auto';
213
- setActiveModeKey(normalizedModeKey);
214
- onModeChanged?.(normalizedModeKey);
215
- }}
216
- handleSend={() => onSend({
217
- streamOptions: {
218
- modeKey: resolvedModeKey && resolvedModeKey !== 'auto' ? resolvedModeKey : '',
219
- manualModeKey: resolvedModeKey && resolvedModeKey !== 'auto' ? resolvedModeKey : '',
220
- payloadExtra: (
221
- resolvedModeKey === 'intelligent_sourcing'
222
- ? { sourcing_enabled: true, source_scope: 'hybrid', top_k: 8 }
223
- : {}
224
- ),
225
- },
226
- })}
227
- resolvedComposerPlaceholder="请输入你的需求"
228
- runtimeComposerActions={runtimeComposerActions}
229
- onComposerChooserSubmit={handleComposerChooserSubmit}
230
- onComposerChooserDismiss={onComposerChooserDismiss}
231
- sendButtonLabel="发送"
232
- resolvedUserDisplayName={userId}
233
- intentEscalationPolicyPopoverContent={intentEscalationPolicyPopoverContent}
234
- intentEscalationPolicyPopoverOpen={intentEscalationPolicyPopoverOpen}
235
- handleIntentEscalationPolicyPopoverOpenChange={setIntentEscalationPolicyPopoverOpen}
236
- intentEscalationConfigEntryLabel="触发阈值"
237
- contextGaugePopoverContent={contextGaugePopoverContent}
238
- contextWindowPercent={8}
239
- />
240
- );
241
- }
@@ -1,154 +0,0 @@
1
- function normalizeSourceId(item) {
2
- if (typeof item === 'string') {
3
- return String(item).trim();
4
- }
5
- if (item && typeof item === 'object' && !Array.isArray(item)) {
6
- return String(item.source_id || item.id || '').trim();
7
- }
8
- return '';
9
- }
10
-
11
- export function mergeSourceReferenceLists(...lists) {
12
- const refs = [];
13
- const seen = new Set();
14
- lists.forEach((list) => {
15
- if (!Array.isArray(list)) {
16
- return;
17
- }
18
- list.forEach((item) => {
19
- const sourceId = normalizeSourceId(item);
20
- if (!sourceId || seen.has(sourceId)) {
21
- return;
22
- }
23
- seen.add(sourceId);
24
- refs.push({ source_id: sourceId });
25
- });
26
- });
27
- return refs;
28
- }
29
-
30
- export function collectSourceIdStrings(...lists) {
31
- const ids = [];
32
- const seen = new Set();
33
- lists.forEach((list) => {
34
- if (!Array.isArray(list)) {
35
- return;
36
- }
37
- list.forEach((item) => {
38
- const sourceId = normalizeSourceId(item);
39
- if (!sourceId || seen.has(sourceId)) {
40
- return;
41
- }
42
- seen.add(sourceId);
43
- ids.push(sourceId);
44
- });
45
- });
46
- return ids;
47
- }
48
-
49
- function normalizeAttachmentStatus(item) {
50
- return String(item?.status || '').trim().toLowerCase();
51
- }
52
-
53
- export function collectReadyAttachmentRefs(fileList = []) {
54
- if (!Array.isArray(fileList)) {
55
- return [];
56
- }
57
- const readyRefs = fileList.filter((item) => {
58
- const sourceId = normalizeSourceId(item);
59
- if (!sourceId) {
60
- return false;
61
- }
62
- return normalizeAttachmentStatus(item) === 'ready';
63
- });
64
- return mergeSourceReferenceLists(readyRefs);
65
- }
66
-
67
- export function hasNonReadyComposerAttachments(fileList = []) {
68
- if (!Array.isArray(fileList)) {
69
- return false;
70
- }
71
- return fileList.some((item) => {
72
- const sourceId = normalizeSourceId(item);
73
- const status = normalizeAttachmentStatus(item);
74
- return Boolean(!sourceId || status !== 'ready');
75
- });
76
- }
77
-
78
- function buildAttachmentSnapshot(fileList = []) {
79
- if (!Array.isArray(fileList)) {
80
- return [];
81
- }
82
- return fileList.map((item) => ({ ...item }));
83
- }
84
-
85
- function resolveUploadableFile(item) {
86
- if (!item || typeof item !== 'object' || Array.isArray(item)) {
87
- return null;
88
- }
89
- const sourceId = String(item.source_id || '').trim();
90
- if (sourceId) {
91
- return null;
92
- }
93
- const status = String(item.status || '').trim().toLowerCase();
94
- if (status === 'failed') {
95
- return null;
96
- }
97
- return item.originFileObj || null;
98
- }
99
-
100
- export async function prepareComposerAttachmentSendRefs({
101
- fileList = [],
102
- uploadSourceFile,
103
- projectId = '',
104
- sessionId = '',
105
- } = {}) {
106
- const attachedSessionFilesSnapshot = buildAttachmentSnapshot(fileList);
107
- const resolvedProjectId = String(projectId || '').trim();
108
- const canUpload = typeof uploadSourceFile === 'function' && Boolean(resolvedProjectId);
109
-
110
- if (canUpload) {
111
- await Promise.all(attachedSessionFilesSnapshot.map(async (attachment, index) => {
112
- const uploadableFile = resolveUploadableFile(attachment);
113
- if (!uploadableFile) {
114
- return;
115
- }
116
- try {
117
- const uploaded = await uploadSourceFile({
118
- file: uploadableFile,
119
- filename: attachment?.name || attachment?.filename || uploadableFile?.name || 'upload.bin',
120
- project_id: resolvedProjectId,
121
- session_id: String(sessionId || '').trim() || null,
122
- });
123
- const sourceId = String(uploaded?.source_id || uploaded?.id || '').trim();
124
- if (!sourceId) {
125
- attachedSessionFilesSnapshot[index] = {
126
- ...attachment,
127
- status: 'failed',
128
- error_message: '上传成功但未返回 source_id',
129
- };
130
- return;
131
- }
132
- attachedSessionFilesSnapshot[index] = {
133
- ...attachment,
134
- ...uploaded,
135
- source_id: sourceId,
136
- status: String(uploaded?.status || attachment?.status || 'ready'),
137
- error_message: String(uploaded?.error_message || ''),
138
- };
139
- } catch (error) {
140
- attachedSessionFilesSnapshot[index] = {
141
- ...attachment,
142
- status: 'failed',
143
- error_message: String(error?.message || error || '上传失败'),
144
- };
145
- }
146
- }));
147
- }
148
-
149
- const refs = mergeSourceReferenceLists(attachedSessionFilesSnapshot.filter((item) => String(item?.status || '').trim().toLowerCase() !== 'failed'));
150
- return {
151
- attachedSessionFilesSnapshot,
152
- refs,
153
- };
154
- }
@@ -1,101 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {
4
- collectSourceIdStrings,
5
- collectReadyAttachmentRefs,
6
- hasNonReadyComposerAttachments,
7
- mergeSourceReferenceLists,
8
- prepareComposerAttachmentSendRefs,
9
- } from './attachmentSendRefs.js';
10
-
11
- test('mergeSourceReferenceLists keeps unique refs from strings and objects', () => {
12
- const refs = mergeSourceReferenceLists(
13
- ['src-1', '', 'src-1'],
14
- [{ source_id: 'src-2' }, { id: 'src-3' }, null],
15
- );
16
- assert.deepEqual(refs, [
17
- { source_id: 'src-1' },
18
- { source_id: 'src-2' },
19
- { source_id: 'src-3' },
20
- ]);
21
- });
22
-
23
- test('collectSourceIdStrings keeps unique source ids from mixed refs', () => {
24
- const ids = collectSourceIdStrings(
25
- ['src-1', { source_id: 'src-2' }, { id: 'src-3' }],
26
- [{ source_id: 'src-1' }, '', null, { source_id: '' }],
27
- );
28
- assert.deepEqual(ids, ['src-1', 'src-2', 'src-3']);
29
- });
30
-
31
- test('prepareComposerAttachmentSendRefs uploads pending files and skips failed refs', async () => {
32
- const uploaded = [];
33
- const payload = await prepareComposerAttachmentSendRefs({
34
- fileList: [
35
- {
36
- uid: 'f1',
37
- name: 'a.docx',
38
- originFileObj: { name: 'a.docx' },
39
- status: 'ready',
40
- },
41
- {
42
- uid: 'f2',
43
- name: 'b.pdf',
44
- originFileObj: { name: 'b.pdf' },
45
- status: 'failed',
46
- },
47
- {
48
- uid: 'f3',
49
- name: 'c.txt',
50
- source_id: 'src-existing',
51
- originFileObj: { name: 'c.txt' },
52
- status: 'ready',
53
- },
54
- ],
55
- uploadSourceFile: async (input) => {
56
- uploaded.push(input.filename);
57
- return { source_id: 'src-uploaded' };
58
- },
59
- projectId: 'project-1',
60
- sessionId: 'session-1',
61
- });
62
-
63
- assert.deepEqual(uploaded, ['a.docx']);
64
- assert.deepEqual(payload.refs, [
65
- { source_id: 'src-uploaded' },
66
- { source_id: 'src-existing' },
67
- ]);
68
- assert.equal(payload.attachedSessionFilesSnapshot[0].source_id, 'src-uploaded');
69
- assert.equal(payload.attachedSessionFilesSnapshot[1].status, 'failed');
70
- });
71
-
72
- test('collectReadyAttachmentRefs includes only ready source_id entries', () => {
73
- const refs = collectReadyAttachmentRefs([
74
- { source_id: 'src-ready-1', status: 'ready' },
75
- { source_id: 'src-uploaded', status: 'uploaded' },
76
- { source_id: 'src-ready-2', status: 'ready' },
77
- { source_id: 'src-failed', status: 'failed' },
78
- { source_id: '', status: 'ready' },
79
- ]);
80
-
81
- assert.deepEqual(refs, [
82
- { source_id: 'src-ready-1' },
83
- { source_id: 'src-ready-2' },
84
- ]);
85
- });
86
-
87
- test('hasNonReadyComposerAttachments blocks when any file is not ready', () => {
88
- assert.equal(hasNonReadyComposerAttachments([
89
- { source_id: 'src-1', status: 'ready' },
90
- { source_id: 'src-2', status: 'ready' },
91
- ]), false);
92
-
93
- assert.equal(hasNonReadyComposerAttachments([
94
- { source_id: 'src-1', status: 'ready' },
95
- { source_id: 'src-2', status: 'indexing' },
96
- ]), true);
97
-
98
- assert.equal(hasNonReadyComposerAttachments([
99
- { source_id: '', status: 'ready' },
100
- ]), true);
101
- });
@@ -1,26 +0,0 @@
1
- function toText(value) {
2
- return String(value || '').trim();
3
- }
4
-
5
- export function buildComposerDraftRoundKey({
6
- activeSessionId,
7
- currentInput,
8
- }) {
9
- const sessionPart = toText(activeSessionId) || 'draft';
10
- const inputPart = toText(currentInput);
11
- return `${sessionPart}:${inputPart}`;
12
- }
13
-
14
- export function resolveComposerActionIntent(choice) {
15
- const actionPayload = choice?.action && typeof choice.action === 'object'
16
- ? choice.action
17
- : null;
18
- const actionKey = toText(actionPayload?.action_key);
19
- if (actionKey === 'open_sourcing') {
20
- return { kind: 'open_sourcing' };
21
- }
22
- if (actionKey === 'dismiss_sourcing_once') {
23
- return { kind: 'dismiss_sourcing_once' };
24
- }
25
- return { kind: 'noop' };
26
- }
@@ -1,108 +0,0 @@
1
- export const WORKSPACE_CONTENT_MAX_WIDTH = 980;
2
-
3
- export const RESOLVED_UI_LABELS = {
4
- tab_chats: '会话',
5
- tab_sources: '资料',
6
- edit_project_button: '编辑',
7
- sidebar_operations_title: '常规操作',
8
- project_select_hint: '请从左侧项目列表选择一个项目。',
9
- project_create_hint: '先创建一个项目,然后开始会话。',
10
- project_list_empty: '还没有项目,点击“创建项目”开始。',
11
- new_project_button: '创建项目',
12
- new_session_button: '新会话',
13
- project_sort_by_name: '按名称',
14
- project_sort_by_updated: '按最近更新',
15
- empty_state_title: '欢迎使用 FLARE',
16
- empty_state_description: '开始一个新对话。',
17
- scenario_expand_label: '查看更多',
18
- scenario_collapse_label: '收起',
19
- composer_attach_label: '上传文件',
20
- composer_hint: '描述你的目标、背景或约束,系统会先帮你判断下一步。',
21
- canvas_workspace_title: '需求梳理工作区',
22
- sources_add_button: '添加资料',
23
- sources_loading: '资料加载中...',
24
- sources_empty_description: '上传到项目资料池后,后续该项目下的对话和分析都可以复用这些资料。',
25
- };
26
-
27
- export const STARTER_SCENARIOS = [
28
- {
29
- key: 'starter-1',
30
- label: '帮我梳理一个需求',
31
- description: '先补齐目标、约束和验收标准。',
32
- prompt: '帮我梳理一个需求',
33
- },
34
- {
35
- key: 'starter-2',
36
- label: '帮我做方案对比',
37
- description: '从关键维度对比候选方案。',
38
- prompt: '帮我做方案对比',
39
- },
40
- {
41
- key: 'starter-3',
42
- label: '帮我整理执行计划',
43
- description: '拆分阶段任务并明确里程碑。',
44
- prompt: '帮我整理执行计划',
45
- },
46
- ];
47
-
48
- export const MODE_SWITCH_OPTIONS = [
49
- { value: 'requirement_canvas', label: 'Plan 模式' },
50
- { value: 'intelligent_sourcing', label: '寻源模式' },
51
- { value: 'analysis_mode', label: '分析模式' },
52
- ];
53
-
54
- export const MODE_SUGGESTION_TIP_RULES = [
55
- {
56
- id: 'analysis',
57
- modeKey: 'analysis_mode',
58
- label: '分析模式',
59
- reason: '识别到分析或模板诉求,建议开启分析模式。',
60
- actionText: '开始需求分析',
61
- keywords: ['分析', '模板', '模版', '清单模板', '报告'],
62
- },
63
- {
64
- id: 'sourcing',
65
- modeKey: 'intelligent_sourcing',
66
- label: '寻源模式',
67
- reason: '识别到找供应商/检索诉求,建议开启寻源模式。',
68
- actionText: '启用寻源模式',
69
- keywords: ['找', '寻源', '搜索', '检索', '供应商', '比价'],
70
- },
71
- {
72
- id: 'plan',
73
- modeKey: 'requirement_canvas',
74
- label: 'Plan 模式',
75
- reason: '识别到需求规划/整理诉求,建议开启 Plan 模式。',
76
- actionText: '开始需求梳理',
77
- keywords: ['规划', '整理', '梳理', '需求', '采购', '收口'],
78
- },
79
- ];
80
-
81
- export const RESPONSE_STYLE_POLICY = {
82
- friendlyPrefix: '好的,我们先把关键信息理清楚:',
83
- questionReplyHint: '你可以直接回复序号或补充信息,我来继续整理。',
84
- };
85
-
86
- export function createWorkspaceStyle(themeTokens) {
87
- return {
88
- '--flare-accent': themeTokens.bubbleUserBg,
89
- '--flare-accent-hover': themeTokens.surfaceBorderActive,
90
- '--flare-focus-outline': themeTokens.focusOutline,
91
- '--flare-font-body': themeTokens.fontFamilyBody,
92
- '--flare-font-heading': themeTokens.fontFamilyHeading,
93
- '--flare-hover-bg': themeTokens.surfaceBgActive,
94
- '--flare-scrollbar-thumb': themeTokens.divider,
95
- '--flare-surface-bg-active': themeTokens.surfaceBgActive,
96
- '--flare-surface-border-active': themeTokens.surfaceBorderActive,
97
- '--flare-text-primary': themeTokens.textPrimary,
98
- '--flare-text-secondary': themeTokens.textSecondary,
99
- background: themeTokens.appBg,
100
- boxShadow: themeTokens.appShadow,
101
- display: 'grid',
102
- gridTemplateColumns: '300px minmax(0, 1fr)',
103
- gridTemplateRows: 'minmax(0, 1fr)',
104
- height: '100vh',
105
- overflow: 'hidden',
106
- position: 'relative',
107
- };
108
- }
@@ -1,28 +0,0 @@
1
- function getSessionId(item) {
2
- return String(item?.sessionId || item?.session_id || item?.id || '').trim();
3
- }
4
-
5
- export function selectActiveSession(sessions, activeSessionId) {
6
- return sessions.find((item) => {
7
- const sessionId = getSessionId(item);
8
- return Boolean(sessionId && sessionId === activeSessionId);
9
- }) || null;
10
- }
11
-
12
- export function selectProjectItems({
13
- projectId,
14
- projectDisplayName,
15
- sessions,
16
- }) {
17
- const sessionItems = Array.isArray(sessions) ? sessions : [];
18
- if (sessionItems.length <= 0) {
19
- return [];
20
- }
21
- return [{
22
- key: 'local_demo_project',
23
- project_id: projectId,
24
- name: projectDisplayName,
25
- updatedAt: sessionItems[0]?.updatedAt || sessionItems[0]?.updated_at || new Date().toISOString(),
26
- createdAt: sessionItems[0]?.createdAt || sessionItems[0]?.created_at || new Date().toISOString(),
27
- }];
28
- }