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.
- package/dist/index.js +6372 -0
- package/package.json +19 -22
- package/docs/CAPABILITY-INVENTORY.md +0 -42
- package/docs/CHAT-CORE-BOUNDARY.md +0 -47
- package/docs/CORE-APP-REALIGNMENT-WORKLOAD-2026-04-18.md +0 -86
- package/docs/SSOT-CHAT-CORE-BOUNDARY.md +0 -73
- package/docs/SSOT-CHAT-CORE-DATAFLOW.md +0 -97
- package/index.html +0 -12
- package/scripts/check.sh +0 -15
- package/src/adapters/index.js +0 -6
- package/src/adapters/message-api.adapter.js +0 -59
- package/src/adapters/session-api.adapter.js +0 -133
- package/src/adapters/session-message-api.http.js +0 -161
- package/src/adapters/session-message-api.js +0 -34
- package/src/adapters/session-message-api.normalize-source-record.test.mjs +0 -180
- package/src/adapters/session-message-api.normalizers.js +0 -153
- package/src/adapters/source-api.adapter.js +0 -135
- package/src/adapters/sse-client.js +0 -244
- package/src/adapters/sse-event-dispatcher.js +0 -121
- package/src/app/App.jsx +0 -11
- package/src/app/AppProviders.jsx +0 -12
- package/src/app/ChatWorkspaceScreen.jsx +0 -33
- package/src/app/WorkspaceLayout.jsx +0 -190
- package/src/app/components/AppCanvasPanel.jsx +0 -64
- package/src/app/components/TriggerThresholdPopoverContent.jsx +0 -122
- package/src/app/components/WorkspaceBodySection.jsx +0 -156
- package/src/app/components/WorkspaceMainPane.jsx +0 -121
- package/src/app/components/WorkspaceSessionPane.jsx +0 -70
- package/src/app/components/WorkspaceTopBarSection.jsx +0 -71
- package/src/app/core-chat-entry/ComposerSectionNode.jsx +0 -241
- package/src/app/core-chat-entry/attachmentSendRefs.js +0 -154
- package/src/app/core-chat-entry/attachmentSendRefs.test.mjs +0 -101
- package/src/app/core-chat-entry/composerActionRouter.js +0 -26
- package/src/app/core-chat-entry/constants.js +0 -108
- package/src/app/core-chat-entry/selectors.js +0 -28
- package/src/app/core-chat-entry/useAppActionErrorGuards.js +0 -68
- package/src/app/core-chat-entry/useChatCorePipelines.js +0 -110
- package/src/app/core-chat-entry/useComposerModeSuggestion.js +0 -89
- package/src/app/core-chat-entry/useDevCapabilityStatusNote.js +0 -22
- package/src/app/core-chat-entry/useProjectNameEditing.js +0 -41
- package/src/app/core-chat-entry/useProjectSourceUpload.js +0 -341
- package/src/app/core-chat-entry/useRealApiReadinessGate.js +0 -103
- package/src/app/core-chat-entry/useUnavailableActionError.js +0 -29
- package/src/app/core-chat-entry/useWorkspaceCanvasController.jsx +0 -177
- package/src/app/core-chat-entry/useWorkspaceCanvasProjection.jsx +0 -171
- package/src/app/core-chat-entry/useWorkspaceComposerController.jsx +0 -199
- package/src/app/core-chat-entry/useWorkspaceController.jsx +0 -226
- package/src/app/core-chat-entry/useWorkspacePanels.js +0 -55
- package/src/app/hooks/useComposerAttachmentSync.js +0 -223
- package/src/app/hooks/useComposerChooserHandlers.js +0 -52
- package/src/app/hooks/useSendWithContextRefs.js +0 -140
- package/src/app/hooks/useSendWithContextRefs.test.mjs +0 -29
- package/src/app/hooks/useUserThresholdProfile.js +0 -121
- package/src/app/index.js +0 -1
- package/src/app/selectors/assistantTextSelector.js +0 -73
- package/src/app/selectors/canvasEvidenceSummarySelector.js +0 -28
- package/src/app/selectors/canvasReportTemplateSelector.js +0 -28
- package/src/app/selectors/canvasTabsSelector.js +0 -58
- package/src/app/selectors/evidenceProjectionSelector.js +0 -175
- package/src/app/selectors/evidenceProjectionSelector.test.mjs +0 -107
- package/src/app/selectors/modeSuggestionSelector.js +0 -50
- package/src/chat-core/app/mockRuntime.js +0 -291
- package/src/chat-core/app/useAppStream.js +0 -187
- package/src/chat-core/app/useAppStream.refs.test.mjs +0 -44
- package/src/chat-core/app/useAppStream.request-body.test.mjs +0 -116
- package/src/chat-core/app/useCoreChatApp.js +0 -115
- package/src/chat-core/facade/useBasicConversationFacade.js +0 -280
- package/src/chat-core/index.js +0 -14
- package/src/chat-core/input/useChatInput.js +0 -103
- package/src/chat-core/messages/buildTimelineItems.analysis-route.test.mjs +0 -36
- package/src/chat-core/messages/buildTimelineItems.js +0 -201
- package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +0 -182
- package/src/chat-core/messages/contextUsageDefaults.js +0 -3
- package/src/chat-core/messages/contextUsageViewModel.js +0 -147
- package/src/chat-core/messages/contextUsageViewModel.test.mjs +0 -74
- package/src/chat-core/messages/useContextUsageViewModel.js +0 -41
- package/src/chat-core/orchestration/useBasicSendHandler.js +0 -55
- package/src/chat-core/pipelines/build-action-request.js +0 -46
- package/src/chat-core/pipelines/build-stream-request.js +0 -74
- package/src/chat-core/pipelines/entity-extraction.js +0 -159
- package/src/chat-core/pipelines/preprocess-message.js +0 -16
- package/src/chat-core/pipelines/stream-persist-utils.js +0 -32
- package/src/chat-core/pipelines/transport/send-mock-stream.js +0 -86
- package/src/chat-core/pipelines/transport/send-real-stream.js +0 -330
- package/src/chat-core/pipelines/transport/send-real-stream.test.mjs +0 -27
- package/src/chat-core/pipelines/transport/send-sourcing-search.js +0 -86
- package/src/chat-core/pipelines/transport/send-sourcing-search.test.mjs +0 -14
- package/src/chat-core/pipelines/transport/sourcing-response-templates.js +0 -55
- package/src/chat-core/pipelines/transport/sourcing-search-api.js +0 -155
- package/src/chat-core/runtime/runtimeMode.js +0 -69
- package/src/chat-core/session/chatSessionActionTypes.js +0 -24
- package/src/chat-core/session/chatSessionReducer.js +0 -352
- package/src/chat-core/session/chatSessionReducer.streaming-done.test.mjs +0 -39
- package/src/chat-core/session/index.js +0 -2
- package/src/chat-core/session/sessionActionsMessages.js +0 -44
- package/src/chat-core/session/sessionActionsSessionCrud.js +0 -131
- package/src/chat-core/session/sessionActionsStreaming.js +0 -80
- package/src/chat-core/session/sessionActionsUiState.js +0 -51
- package/src/chat-core/session/useChatSessionReducer.js +0 -131
- package/src/chat-core/session/useSessionListController.js +0 -67
- package/src/chat-core/stream/sse-client.js +0 -1
- package/src/chat-core/stream/sse-event-dispatcher.js +0 -1
- package/src/chat-core/stream/sse-events.js +0 -1
- package/src/chat-core/stream/useSSEStream.js +0 -1
- package/src/chat-core/stream/useStreamSendController.js +0 -46
- package/src/contracts/context-ssot.js +0 -47
- package/src/contracts/index.js +0 -1
- package/src/contracts/sse-events/base-parsers.js +0 -79
- package/src/contracts/sse-events/domain-parsers.js +0 -3
- package/src/contracts/sse-events/internal-normalizers.js +0 -143
- package/src/contracts/sse-events/parsers-intake.js +0 -235
- package/src/contracts/sse-events/parsers-runtime.js +0 -37
- package/src/contracts/sse-events/parsers-sourcing.js +0 -179
- package/src/contracts/sse-events/patch-event-parser.js +0 -121
- package/src/contracts/sse-events/runtime-parsers.js +0 -79
- package/src/contracts/sse-events.js +0 -4
- package/src/index.js +0 -6
- package/src/main.jsx +0 -28
- package/src/orchestration/index.js +0 -6
- package/src/orchestration/useSSEStream.js +0 -221
- package/src/state/index.js +0 -4
- 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
|
-
}
|