flare-chat-core 0.2.2 → 0.2.4
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 +6343 -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 -125
- package/src/app/components/AppCanvasPanel.jsx +0 -64
- package/src/app/components/TriggerThresholdPopoverContent.jsx +0 -122
- package/src/app/components/WorkspaceBodySection.jsx +0 -109
- package/src/app/components/WorkspaceMainPane.jsx +0 -113
- package/src/app/components/WorkspaceSessionPane.jsx +0 -48
- package/src/app/components/WorkspaceTopBarSection.jsx +0 -65
- 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 -233
- package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +0 -183
- 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,22 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
|
|
3
|
-
export function useDevCapabilityStatusNote({
|
|
4
|
-
isDev = false,
|
|
5
|
-
mode = '',
|
|
6
|
-
labels = {},
|
|
7
|
-
} = {}) {
|
|
8
|
-
return useMemo(() => {
|
|
9
|
-
if (!isDev) {
|
|
10
|
-
return '';
|
|
11
|
-
}
|
|
12
|
-
const normalizedMode = String(mode || '').trim().toLowerCase();
|
|
13
|
-
if (normalizedMode === 'persistent') {
|
|
14
|
-
return String(labels.persistent || '开发态:资料能力已接入持久化链路');
|
|
15
|
-
}
|
|
16
|
-
if (normalizedMode === 'local_fallback') {
|
|
17
|
-
return String(labels.localFallback || '开发态:资料能力当前为本地回退模式');
|
|
18
|
-
}
|
|
19
|
-
return String(labels.unknown || '开发态:资料能力状态未知');
|
|
20
|
-
}, [isDev, labels.localFallback, labels.persistent, labels.unknown, mode]);
|
|
21
|
-
}
|
|
22
|
-
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export function useProjectNameEditing({ defaultProjectName } = {}) {
|
|
4
|
-
const initialName = String(defaultProjectName || '').trim() || '本地演示项目';
|
|
5
|
-
const [projectNameEditing, setProjectNameEditing] = useState(false);
|
|
6
|
-
const [projectNameDraft, setProjectNameDraft] = useState(initialName);
|
|
7
|
-
const [projectDisplayName, setProjectDisplayName] = useState(initialName);
|
|
8
|
-
|
|
9
|
-
const handleProjectNameStartEdit = (projectSlot) => {
|
|
10
|
-
if (!projectSlot?.key) {
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
setProjectNameDraft(String(projectDisplayName || '').trim());
|
|
14
|
-
setProjectNameEditing(true);
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const handleProjectNameCancel = () => {
|
|
18
|
-
setProjectNameEditing(false);
|
|
19
|
-
setProjectNameDraft('');
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const handleProjectNameSave = () => {
|
|
23
|
-
const nextName = String(projectNameDraft || '').trim();
|
|
24
|
-
if (nextName) {
|
|
25
|
-
setProjectDisplayName(nextName);
|
|
26
|
-
}
|
|
27
|
-
handleProjectNameCancel();
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
projectNameEditing,
|
|
32
|
-
projectNameDraft,
|
|
33
|
-
setProjectNameDraft,
|
|
34
|
-
projectDisplayName,
|
|
35
|
-
handleProjectNameStartEdit,
|
|
36
|
-
handleProjectNameCancel,
|
|
37
|
-
handleProjectNameSave,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default useProjectNameEditing;
|
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const projectSourceListSingleflight = new Map();
|
|
4
|
-
const SOURCE_STATUS_TERMINAL = new Set(['ready', 'failed', 'error']);
|
|
5
|
-
|
|
6
|
-
function normalizeError(error, fallbackMessage) {
|
|
7
|
-
if (error instanceof Error) {
|
|
8
|
-
return error;
|
|
9
|
-
}
|
|
10
|
-
return new Error(String(error || fallbackMessage));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function buildSourceItem(file, index, scopedProjectId) {
|
|
14
|
-
return {
|
|
15
|
-
id: `project-source-${Date.now()}-${index}`,
|
|
16
|
-
project_id: scopedProjectId,
|
|
17
|
-
message_id: null,
|
|
18
|
-
scope: 'project',
|
|
19
|
-
source: 'local_upload',
|
|
20
|
-
filename: String(file?.name || '').trim() || `资料 ${index + 1}`,
|
|
21
|
-
mime_type: String(file?.type || '').trim(),
|
|
22
|
-
size: Number.isFinite(file?.size) ? file.size : null,
|
|
23
|
-
created_at: new Date().toISOString(),
|
|
24
|
-
status: 'ready',
|
|
25
|
-
error_message: '',
|
|
26
|
-
persisted_to_project: false,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function buildSourceFileSignature(item = {}) {
|
|
31
|
-
return `${item.filename || ''}::${item.size || ''}::${item.mime_type || ''}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function parseSourceList(response) {
|
|
35
|
-
if (Array.isArray(response?.sources)) {
|
|
36
|
-
return response.sources;
|
|
37
|
-
}
|
|
38
|
-
if (Array.isArray(response?.data?.sources)) {
|
|
39
|
-
return response.data.sources;
|
|
40
|
-
}
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function normalizeSourceStatus(status) {
|
|
45
|
-
return String(status || '').trim().toLowerCase();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function hasPendingSourceProcessing(items = []) {
|
|
49
|
-
if (!Array.isArray(items) || items.length === 0) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
return items.some((item) => !SOURCE_STATUS_TERMINAL.has(normalizeSourceStatus(item?.status)));
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function createUploadProgressItem(file, index) {
|
|
56
|
-
return {
|
|
57
|
-
id: `upload-progress-${Date.now()}-${index}`,
|
|
58
|
-
filename: String(file?.name || '').trim() || `资料 ${index + 1}`,
|
|
59
|
-
progress: 0,
|
|
60
|
-
stage: 'reading',
|
|
61
|
-
error_message: '',
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function readFileWithProgress(file, onProgress) {
|
|
66
|
-
return new Promise((resolve, reject) => {
|
|
67
|
-
const reader = new FileReader();
|
|
68
|
-
reader.onprogress = (event) => {
|
|
69
|
-
if (!event.lengthComputable) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const percent = (event.loaded / event.total) * 80;
|
|
73
|
-
onProgress(percent);
|
|
74
|
-
};
|
|
75
|
-
reader.onload = () => {
|
|
76
|
-
onProgress(80);
|
|
77
|
-
resolve();
|
|
78
|
-
};
|
|
79
|
-
reader.onerror = () => {
|
|
80
|
-
reject(reader.error || new Error('读取文件失败'));
|
|
81
|
-
};
|
|
82
|
-
reader.readAsArrayBuffer(file);
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function useProjectSourceUpload({
|
|
87
|
-
projectId = '',
|
|
88
|
-
userId = '',
|
|
89
|
-
sourceAPI = null,
|
|
90
|
-
shouldLoadSources = true,
|
|
91
|
-
} = {}) {
|
|
92
|
-
const fileInputRef = useRef(null);
|
|
93
|
-
const [sourceItems, setSourceItems] = useState([]);
|
|
94
|
-
const [sourceActionError, setSourceActionError] = useState(null);
|
|
95
|
-
const [sourceUploadLoading, setSourceUploadLoading] = useState(false);
|
|
96
|
-
const [sourceUploadProgressItems, setSourceUploadProgressItems] = useState([]);
|
|
97
|
-
const [sourceSyncLoading, setSourceSyncLoading] = useState(false);
|
|
98
|
-
const [sourceRemovingId, setSourceRemovingId] = useState('');
|
|
99
|
-
const [sourceEndpointUnavailable, setSourceEndpointUnavailable] = useState(false);
|
|
100
|
-
|
|
101
|
-
const canUsePersistentSourceAPI = (
|
|
102
|
-
typeof sourceAPI?.listProjectSources === 'function'
|
|
103
|
-
&& typeof sourceAPI?.uploadSourceFile === 'function'
|
|
104
|
-
&& typeof sourceAPI?.deleteProjectSource === 'function'
|
|
105
|
-
&& !sourceEndpointUnavailable
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const loadProjectSources = useCallback(async ({ silent = false } = {}) => {
|
|
109
|
-
const scopedProjectId = String(projectId || '').trim();
|
|
110
|
-
if (!canUsePersistentSourceAPI || !scopedProjectId) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (!silent) {
|
|
114
|
-
setSourceSyncLoading(true);
|
|
115
|
-
setSourceActionError(null);
|
|
116
|
-
}
|
|
117
|
-
try {
|
|
118
|
-
const requestKey = scopedProjectId;
|
|
119
|
-
let pendingRequest = projectSourceListSingleflight.get(requestKey);
|
|
120
|
-
if (!pendingRequest) {
|
|
121
|
-
pendingRequest = sourceAPI.listProjectSources(scopedProjectId)
|
|
122
|
-
.finally(() => {
|
|
123
|
-
projectSourceListSingleflight.delete(requestKey);
|
|
124
|
-
});
|
|
125
|
-
projectSourceListSingleflight.set(requestKey, pendingRequest);
|
|
126
|
-
}
|
|
127
|
-
const response = await pendingRequest;
|
|
128
|
-
setSourceItems(parseSourceList(response));
|
|
129
|
-
setSourceEndpointUnavailable(false);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
if (Number(error?.status) === 404) {
|
|
132
|
-
setSourceEndpointUnavailable(true);
|
|
133
|
-
if (!silent) {
|
|
134
|
-
setSourceActionError(null);
|
|
135
|
-
}
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
if (!silent) {
|
|
139
|
-
setSourceActionError(normalizeError(error, '加载资料失败'));
|
|
140
|
-
}
|
|
141
|
-
} finally {
|
|
142
|
-
if (!silent) {
|
|
143
|
-
setSourceSyncLoading(false);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}, [canUsePersistentSourceAPI, projectId, sourceAPI]);
|
|
147
|
-
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
if (!shouldLoadSources) {
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
loadProjectSources();
|
|
153
|
-
}, [loadProjectSources, shouldLoadSources]);
|
|
154
|
-
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
if (!shouldLoadSources) {
|
|
157
|
-
return undefined;
|
|
158
|
-
}
|
|
159
|
-
if (!canUsePersistentSourceAPI) {
|
|
160
|
-
return undefined;
|
|
161
|
-
}
|
|
162
|
-
if (!hasPendingSourceProcessing(sourceItems)) {
|
|
163
|
-
return undefined;
|
|
164
|
-
}
|
|
165
|
-
const timer = setInterval(() => {
|
|
166
|
-
void loadProjectSources({ silent: true });
|
|
167
|
-
}, 1500);
|
|
168
|
-
return () => {
|
|
169
|
-
clearInterval(timer);
|
|
170
|
-
};
|
|
171
|
-
}, [canUsePersistentSourceAPI, loadProjectSources, shouldLoadSources, sourceItems]);
|
|
172
|
-
|
|
173
|
-
const appendLocalSourceFiles = useCallback((nativeFiles = []) => {
|
|
174
|
-
setSourceItems((prev) => {
|
|
175
|
-
const existingSignatures = new Set(prev.map((item) => buildSourceFileSignature(item)));
|
|
176
|
-
const nextItems = nativeFiles
|
|
177
|
-
.map((file, index) => buildSourceItem(file, index, projectId))
|
|
178
|
-
.filter((item) => {
|
|
179
|
-
const signature = buildSourceFileSignature(item);
|
|
180
|
-
if (existingSignatures.has(signature)) {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
existingSignatures.add(signature);
|
|
184
|
-
return true;
|
|
185
|
-
});
|
|
186
|
-
return [...nextItems, ...prev];
|
|
187
|
-
});
|
|
188
|
-
}, [projectId]);
|
|
189
|
-
|
|
190
|
-
const uploadSourceFiles = useCallback(async (nativeFiles = []) => {
|
|
191
|
-
const scopedProjectId = String(projectId || '').trim();
|
|
192
|
-
if (nativeFiles.length === 0) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
setSourceUploadLoading(true);
|
|
196
|
-
setSourceActionError(null);
|
|
197
|
-
const progressItems = nativeFiles.map((file, index) => createUploadProgressItem(file, index));
|
|
198
|
-
setSourceUploadProgressItems((prev) => [...progressItems, ...prev]);
|
|
199
|
-
const updateProgressItem = (itemId, patch = {}) => {
|
|
200
|
-
setSourceUploadProgressItems((prev) => prev.map((item) => (item.id === itemId ? { ...item, ...patch } : item)));
|
|
201
|
-
};
|
|
202
|
-
try {
|
|
203
|
-
if (canUsePersistentSourceAPI && scopedProjectId) {
|
|
204
|
-
for (let index = 0; index < nativeFiles.length; index += 1) {
|
|
205
|
-
const file = nativeFiles[index];
|
|
206
|
-
const progressItem = progressItems[index];
|
|
207
|
-
await readFileWithProgress(file, (percent) => {
|
|
208
|
-
updateProgressItem(progressItem.id, {
|
|
209
|
-
stage: 'reading',
|
|
210
|
-
progress: percent,
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
updateProgressItem(progressItem.id, {
|
|
214
|
-
stage: 'uploading',
|
|
215
|
-
progress: 80,
|
|
216
|
-
});
|
|
217
|
-
await sourceAPI.uploadSourceFile({
|
|
218
|
-
project_id: scopedProjectId,
|
|
219
|
-
user_id: String(userId || '').trim() || null,
|
|
220
|
-
file,
|
|
221
|
-
filename: file.name,
|
|
222
|
-
onProgress: (percent) => {
|
|
223
|
-
const safePercent = Number.isFinite(percent) ? percent : 0;
|
|
224
|
-
const mappedPercent = 80 + (Math.max(0, Math.min(100, safePercent)) * 0.2);
|
|
225
|
-
updateProgressItem(progressItem.id, {
|
|
226
|
-
stage: 'uploading',
|
|
227
|
-
progress: mappedPercent,
|
|
228
|
-
});
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
updateProgressItem(progressItem.id, {
|
|
232
|
-
stage: 'done',
|
|
233
|
-
progress: 100,
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
await loadProjectSources();
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
for (let index = 0; index < nativeFiles.length; index += 1) {
|
|
240
|
-
const file = nativeFiles[index];
|
|
241
|
-
const progressItem = progressItems[index];
|
|
242
|
-
await readFileWithProgress(file, (percent) => {
|
|
243
|
-
updateProgressItem(progressItem.id, {
|
|
244
|
-
stage: 'reading',
|
|
245
|
-
progress: percent,
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
updateProgressItem(progressItem.id, {
|
|
249
|
-
stage: 'uploading',
|
|
250
|
-
progress: 95,
|
|
251
|
-
});
|
|
252
|
-
appendLocalSourceFiles([file]);
|
|
253
|
-
updateProgressItem(progressItem.id, {
|
|
254
|
-
stage: 'done',
|
|
255
|
-
progress: 100,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
} catch (error) {
|
|
259
|
-
const errorMessage = String(error?.message || '添加资料失败');
|
|
260
|
-
setSourceUploadProgressItems((prev) => prev.map((item) => (item.stage === 'done'
|
|
261
|
-
? item
|
|
262
|
-
: {
|
|
263
|
-
...item,
|
|
264
|
-
stage: 'error',
|
|
265
|
-
error_message: errorMessage,
|
|
266
|
-
})));
|
|
267
|
-
setSourceActionError(normalizeError(error, '添加资料失败'));
|
|
268
|
-
} finally {
|
|
269
|
-
setSourceUploadLoading(false);
|
|
270
|
-
}
|
|
271
|
-
}, [appendLocalSourceFiles, canUsePersistentSourceAPI, loadProjectSources, projectId, sourceAPI, userId]);
|
|
272
|
-
|
|
273
|
-
const handleOpenSourcePicker = useCallback((nativeFiles = []) => {
|
|
274
|
-
if (sourceUploadLoading) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
if (Array.isArray(nativeFiles) && nativeFiles.length > 0) {
|
|
278
|
-
void uploadSourceFiles(nativeFiles);
|
|
279
|
-
return;
|
|
280
|
-
}
|
|
281
|
-
setSourceActionError(null);
|
|
282
|
-
fileInputRef.current?.click();
|
|
283
|
-
}, [sourceUploadLoading, uploadSourceFiles]);
|
|
284
|
-
|
|
285
|
-
const handleSourceFileChange = useCallback((event) => {
|
|
286
|
-
const nativeFiles = Array.from(event?.target?.files || []);
|
|
287
|
-
event.target.value = '';
|
|
288
|
-
void uploadSourceFiles(nativeFiles);
|
|
289
|
-
}, [uploadSourceFiles]);
|
|
290
|
-
|
|
291
|
-
const handleRemoveSource = useCallback(async (sourceId) => {
|
|
292
|
-
const scopedProjectId = String(projectId || '').trim();
|
|
293
|
-
const normalizedSourceId = String(sourceId || '').trim();
|
|
294
|
-
if (!normalizedSourceId) {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
setSourceActionError(null);
|
|
298
|
-
setSourceRemovingId(normalizedSourceId);
|
|
299
|
-
try {
|
|
300
|
-
if (canUsePersistentSourceAPI && scopedProjectId) {
|
|
301
|
-
await sourceAPI.deleteProjectSource(scopedProjectId, normalizedSourceId);
|
|
302
|
-
await loadProjectSources();
|
|
303
|
-
} else {
|
|
304
|
-
setSourceItems((prev) => prev.filter((item) => {
|
|
305
|
-
const currentSourceId = String(item?.source_id || item?.id || '').trim();
|
|
306
|
-
return currentSourceId !== normalizedSourceId;
|
|
307
|
-
}));
|
|
308
|
-
}
|
|
309
|
-
} catch (error) {
|
|
310
|
-
setSourceActionError(normalizeError(error, '删除资料失败'));
|
|
311
|
-
} finally {
|
|
312
|
-
setSourceRemovingId('');
|
|
313
|
-
}
|
|
314
|
-
}, [canUsePersistentSourceAPI, loadProjectSources, projectId, sourceAPI]);
|
|
315
|
-
|
|
316
|
-
const handleRetrySource = useCallback(() => {
|
|
317
|
-
if (canUsePersistentSourceAPI) {
|
|
318
|
-
loadProjectSources();
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
setSourceActionError(null);
|
|
322
|
-
}, [canUsePersistentSourceAPI, loadProjectSources]);
|
|
323
|
-
|
|
324
|
-
const handleViewSourceDetail = useCallback(() => {}, []);
|
|
325
|
-
|
|
326
|
-
return {
|
|
327
|
-
fileInputRef,
|
|
328
|
-
sourceCapabilityMode: canUsePersistentSourceAPI ? 'persistent' : 'local_fallback',
|
|
329
|
-
sourceActionError,
|
|
330
|
-
sourceItems,
|
|
331
|
-
sourceRemovingId,
|
|
332
|
-
sourceSyncLoading,
|
|
333
|
-
sourceUploadLoading,
|
|
334
|
-
sourceUploadProgressItems,
|
|
335
|
-
handleOpenSourcePicker,
|
|
336
|
-
handleSourceFileChange,
|
|
337
|
-
handleRemoveSource,
|
|
338
|
-
handleRetrySource,
|
|
339
|
-
handleViewSourceDetail,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
function joinUrl(baseUrl = '', path = '') {
|
|
4
|
-
if (!baseUrl) {
|
|
5
|
-
return path || '';
|
|
6
|
-
}
|
|
7
|
-
if (!path) {
|
|
8
|
-
return baseUrl;
|
|
9
|
-
}
|
|
10
|
-
return `${baseUrl.replace(/\/+$/, '')}/${path.replace(/^\/+/, '')}`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function createGateError({ message, status = 0, method = 'GET', url = '' }) {
|
|
14
|
-
const error = new Error(message);
|
|
15
|
-
error.status = status;
|
|
16
|
-
error.method = method;
|
|
17
|
-
error.url = url;
|
|
18
|
-
return error;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useRealApiReadinessGate({
|
|
22
|
-
enabled = false,
|
|
23
|
-
apiBaseUrl = '',
|
|
24
|
-
apiToken = '',
|
|
25
|
-
} = {}) {
|
|
26
|
-
const [checking, setChecking] = useState(false);
|
|
27
|
-
const [ready, setReady] = useState(false);
|
|
28
|
-
const [error, setError] = useState(null);
|
|
29
|
-
|
|
30
|
-
const normalizedApiBaseUrl = String(apiBaseUrl || '').trim();
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
let cancelled = false;
|
|
34
|
-
|
|
35
|
-
const run = async () => {
|
|
36
|
-
if (!enabled) {
|
|
37
|
-
setChecking(false);
|
|
38
|
-
setReady(true);
|
|
39
|
-
setError(null);
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!normalizedApiBaseUrl) {
|
|
44
|
-
setChecking(false);
|
|
45
|
-
setReady(false);
|
|
46
|
-
setError(createGateError({
|
|
47
|
-
message: 'real 模式要求配置 VITE_FLARE_CHAT_API_BASE_URL',
|
|
48
|
-
}));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
setChecking(true);
|
|
53
|
-
setReady(false);
|
|
54
|
-
setError(null);
|
|
55
|
-
|
|
56
|
-
const url = joinUrl(normalizedApiBaseUrl, '/chat/sessions?page=1&page_size=1');
|
|
57
|
-
const headers = new Headers();
|
|
58
|
-
if (apiToken) {
|
|
59
|
-
headers.set('Authorization', `Bearer ${String(apiToken || '').trim()}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const response = await fetch(url, {
|
|
64
|
-
method: 'GET',
|
|
65
|
-
headers,
|
|
66
|
-
});
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
throw createGateError({
|
|
69
|
-
message: `API 预检失败: HTTP ${response.status} ${response.statusText || ''}`.trim(),
|
|
70
|
-
status: response.status,
|
|
71
|
-
method: 'GET',
|
|
72
|
-
url,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
if (!cancelled) {
|
|
76
|
-
setReady(true);
|
|
77
|
-
}
|
|
78
|
-
} catch (nextError) {
|
|
79
|
-
if (!cancelled) {
|
|
80
|
-
setReady(false);
|
|
81
|
-
setError(nextError);
|
|
82
|
-
}
|
|
83
|
-
} finally {
|
|
84
|
-
if (!cancelled) {
|
|
85
|
-
setChecking(false);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
run();
|
|
91
|
-
|
|
92
|
-
return () => {
|
|
93
|
-
cancelled = true;
|
|
94
|
-
};
|
|
95
|
-
}, [apiToken, enabled, normalizedApiBaseUrl]);
|
|
96
|
-
|
|
97
|
-
return useMemo(() => ({
|
|
98
|
-
checking,
|
|
99
|
-
ready,
|
|
100
|
-
error,
|
|
101
|
-
blocked: enabled && Boolean(error),
|
|
102
|
-
}), [checking, enabled, error, ready]);
|
|
103
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { useCallback, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
function createUnavailableError(message, fallbackMessage) {
|
|
4
|
-
if (message instanceof Error) {
|
|
5
|
-
return message;
|
|
6
|
-
}
|
|
7
|
-
return new Error(String(message || fallbackMessage));
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function useUnavailableActionError({
|
|
11
|
-
fallbackMessage = '当前能力尚未接入。',
|
|
12
|
-
} = {}) {
|
|
13
|
-
const [error, setError] = useState(null);
|
|
14
|
-
|
|
15
|
-
const raiseUnavailableError = useCallback((message) => {
|
|
16
|
-
setError(createUnavailableError(message, fallbackMessage));
|
|
17
|
-
}, [fallbackMessage]);
|
|
18
|
-
|
|
19
|
-
const clearUnavailableError = useCallback(() => {
|
|
20
|
-
setError(null);
|
|
21
|
-
}, []);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
error,
|
|
25
|
-
raiseUnavailableError,
|
|
26
|
-
clearUnavailableError,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|