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,291 +0,0 @@
|
|
|
1
|
-
const MAX_PREVIEW_LENGTH = 80;
|
|
2
|
-
const MAX_AUTO_TITLE_LENGTH = 24;
|
|
3
|
-
const DEFAULT_TITLE_SOURCE = 'default';
|
|
4
|
-
const AUTO_TITLE_SOURCE = 'auto';
|
|
5
|
-
const MANUAL_TITLE_SOURCE = 'manual';
|
|
6
|
-
|
|
7
|
-
function buildId(prefix) {
|
|
8
|
-
const randomPart = Math.random().toString(36).slice(2, 10);
|
|
9
|
-
return `${prefix}_${Date.now()}_${randomPart}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function nowISO() {
|
|
13
|
-
return new Date().toISOString();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function summarize(messages) {
|
|
17
|
-
if (!Array.isArray(messages) || messages.length === 0) {
|
|
18
|
-
return '';
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const lastContent = messages[messages.length - 1]?.content || '';
|
|
22
|
-
if (lastContent.length <= MAX_PREVIEW_LENGTH) {
|
|
23
|
-
return lastContent;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return `${lastContent.slice(0, MAX_PREVIEW_LENGTH)}...`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function extractSessionPreview({ userMessage = '', assistantMessage = '' } = {}) {
|
|
30
|
-
const normalizedUserMessage = String(userMessage || '').replace(/\s+/g, ' ').trim();
|
|
31
|
-
if (normalizedUserMessage) {
|
|
32
|
-
if (normalizedUserMessage.length <= MAX_PREVIEW_LENGTH) {
|
|
33
|
-
return normalizedUserMessage;
|
|
34
|
-
}
|
|
35
|
-
return `${normalizedUserMessage.slice(0, MAX_PREVIEW_LENGTH)}...`;
|
|
36
|
-
}
|
|
37
|
-
const normalizedAssistantMessage = String(assistantMessage || '').replace(/\s+/g, ' ').trim();
|
|
38
|
-
if (!normalizedAssistantMessage) {
|
|
39
|
-
return '';
|
|
40
|
-
}
|
|
41
|
-
if (normalizedAssistantMessage.length <= MAX_PREVIEW_LENGTH) {
|
|
42
|
-
return normalizedAssistantMessage;
|
|
43
|
-
}
|
|
44
|
-
return `${normalizedAssistantMessage.slice(0, MAX_PREVIEW_LENGTH)}...`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function createMessage(role, content) {
|
|
48
|
-
return {
|
|
49
|
-
message_id: buildId(`msg_${role}`),
|
|
50
|
-
role,
|
|
51
|
-
content,
|
|
52
|
-
created_at: nowISO(),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function extractAutoSessionTitle(content) {
|
|
57
|
-
const normalized = String(content || '')
|
|
58
|
-
.replace(/\s+/g, ' ')
|
|
59
|
-
.trim();
|
|
60
|
-
if (!normalized) {
|
|
61
|
-
return '';
|
|
62
|
-
}
|
|
63
|
-
if (normalized.length <= MAX_AUTO_TITLE_LENGTH) {
|
|
64
|
-
return normalized;
|
|
65
|
-
}
|
|
66
|
-
return `${normalized.slice(0, MAX_AUTO_TITLE_LENGTH)}…`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function createMockRuntime({
|
|
70
|
-
defaultFunctionType = 'chat_component_debug',
|
|
71
|
-
defaultSessionTitle = '新会话',
|
|
72
|
-
} = {}) {
|
|
73
|
-
const sessions = new Map();
|
|
74
|
-
const messagesBySession = new Map();
|
|
75
|
-
|
|
76
|
-
function getMessages(sessionId) {
|
|
77
|
-
return messagesBySession.get(sessionId) || [];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function upsertSession(sessionRecord) {
|
|
81
|
-
sessions.set(sessionRecord.sessionId, sessionRecord);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function getSessionOrThrow(sessionId) {
|
|
85
|
-
const session = sessions.get(sessionId);
|
|
86
|
-
if (!session) {
|
|
87
|
-
throw new Error(`Session not found: ${sessionId}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return session;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function touchSession(sessionId, options = {}) {
|
|
94
|
-
const session = getSessionOrThrow(sessionId);
|
|
95
|
-
const previewOverride = String(options?.preview || '').trim();
|
|
96
|
-
upsertSession({
|
|
97
|
-
...session,
|
|
98
|
-
updatedAt: nowISO(),
|
|
99
|
-
preview: previewOverride || summarize(getMessages(sessionId)),
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function createSessionRecord({ functionType, projectId = null, title, userId = null }) {
|
|
104
|
-
const sessionId = buildId('sess');
|
|
105
|
-
const now = nowISO();
|
|
106
|
-
const nextRecord = {
|
|
107
|
-
sessionId,
|
|
108
|
-
functionType: functionType || defaultFunctionType,
|
|
109
|
-
projectId: projectId || null,
|
|
110
|
-
title: title || defaultSessionTitle,
|
|
111
|
-
titleSource: DEFAULT_TITLE_SOURCE,
|
|
112
|
-
status: 'active',
|
|
113
|
-
userId: userId || null,
|
|
114
|
-
createdAt: now,
|
|
115
|
-
updatedAt: now,
|
|
116
|
-
preview: '',
|
|
117
|
-
workspaceState: null,
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
upsertSession(nextRecord);
|
|
121
|
-
messagesBySession.set(sessionId, []);
|
|
122
|
-
return nextRecord;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function ensureSessionRecord(sessionId, options = {}) {
|
|
126
|
-
const normalizedSessionId = String(sessionId || '').trim();
|
|
127
|
-
if (normalizedSessionId && sessions.has(normalizedSessionId)) {
|
|
128
|
-
return normalizedSessionId;
|
|
129
|
-
}
|
|
130
|
-
const created = createSessionRecord({
|
|
131
|
-
functionType: options.functionType,
|
|
132
|
-
projectId: options.projectId,
|
|
133
|
-
title: options.title,
|
|
134
|
-
userId: options.userId,
|
|
135
|
-
});
|
|
136
|
-
return created.sessionId;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const sessionAPI = {
|
|
140
|
-
async list(params = {}) {
|
|
141
|
-
const functionType = params.function_type;
|
|
142
|
-
const projectId = params.project_id ? String(params.project_id) : null;
|
|
143
|
-
const userId = params.user_id ? String(params.user_id) : null;
|
|
144
|
-
const filteredSessions = Array.from(sessions.values())
|
|
145
|
-
.filter((item) => !functionType || item.functionType === functionType)
|
|
146
|
-
.filter((item) => !projectId || item.projectId === projectId)
|
|
147
|
-
.filter((item) => !userId || item.userId === userId)
|
|
148
|
-
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())
|
|
149
|
-
.map((item) => ({
|
|
150
|
-
sessionId: item.sessionId,
|
|
151
|
-
project_id: item.projectId,
|
|
152
|
-
title: item.title,
|
|
153
|
-
preview: item.preview,
|
|
154
|
-
title_source: item.titleSource,
|
|
155
|
-
status: item.status,
|
|
156
|
-
updatedAt: item.updatedAt,
|
|
157
|
-
user_id: item.userId,
|
|
158
|
-
}));
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
sessions: filteredSessions,
|
|
162
|
-
};
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
async get(sessionId) {
|
|
166
|
-
const session = getSessionOrThrow(sessionId);
|
|
167
|
-
return {
|
|
168
|
-
sessionId: session.sessionId,
|
|
169
|
-
project_id: session.projectId,
|
|
170
|
-
title: session.title,
|
|
171
|
-
preview: session.preview,
|
|
172
|
-
title_source: session.titleSource,
|
|
173
|
-
status: session.status,
|
|
174
|
-
user_id: session.userId,
|
|
175
|
-
function_type: session.functionType,
|
|
176
|
-
workspace_state: session.workspaceState,
|
|
177
|
-
};
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
async create(payload = {}) {
|
|
181
|
-
const session = createSessionRecord({
|
|
182
|
-
functionType: payload.function_type,
|
|
183
|
-
projectId: payload.project_id,
|
|
184
|
-
title: payload.title,
|
|
185
|
-
userId: payload.user_id,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
sessionId: session.sessionId,
|
|
190
|
-
project_id: session.projectId,
|
|
191
|
-
title: session.title,
|
|
192
|
-
preview: session.preview,
|
|
193
|
-
title_source: session.titleSource,
|
|
194
|
-
status: session.status,
|
|
195
|
-
user_id: session.userId,
|
|
196
|
-
workspace_state: session.workspaceState,
|
|
197
|
-
};
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
async update(sessionId, payload = {}) {
|
|
201
|
-
const session = getSessionOrThrow(sessionId);
|
|
202
|
-
const nextTitle = String(payload.title || '').trim();
|
|
203
|
-
const hasWorkspaceState = Object.prototype.hasOwnProperty.call(payload, 'workspace_state');
|
|
204
|
-
|
|
205
|
-
upsertSession({
|
|
206
|
-
...session,
|
|
207
|
-
title: nextTitle || session.title,
|
|
208
|
-
titleSource: nextTitle ? MANUAL_TITLE_SOURCE : session.titleSource,
|
|
209
|
-
...(hasWorkspaceState ? { workspaceState: payload.workspace_state || null } : {}),
|
|
210
|
-
updatedAt: nowISO(),
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
return {
|
|
214
|
-
sessionId,
|
|
215
|
-
title: nextTitle || session.title,
|
|
216
|
-
preview: session.preview,
|
|
217
|
-
title_source: nextTitle ? MANUAL_TITLE_SOURCE : session.titleSource,
|
|
218
|
-
workspace_state: hasWorkspaceState ? (payload.workspace_state || null) : session.workspaceState,
|
|
219
|
-
};
|
|
220
|
-
},
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const messageAPI = {
|
|
224
|
-
async list(sessionId) {
|
|
225
|
-
const messages = getMessages(sessionId);
|
|
226
|
-
return {
|
|
227
|
-
messages: messages.map((item) => ({ ...item })),
|
|
228
|
-
};
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
function appendExchange(sessionId, userMessage, assistantMessage, options = {}) {
|
|
233
|
-
const resolvedSessionId = ensureSessionRecord(sessionId, options);
|
|
234
|
-
const messages = [...getMessages(resolvedSessionId)];
|
|
235
|
-
const session = getSessionOrThrow(resolvedSessionId);
|
|
236
|
-
const currentTitleSource = String(session?.titleSource || DEFAULT_TITLE_SOURCE).trim() || DEFAULT_TITLE_SOURCE;
|
|
237
|
-
const hasUserMessage = messages.some((item) => String(item?.role || '').trim() === 'user');
|
|
238
|
-
const nextAutoTitle = extractAutoSessionTitle(userMessage);
|
|
239
|
-
const shouldAutoTitle = (
|
|
240
|
-
!hasUserMessage
|
|
241
|
-
&& nextAutoTitle
|
|
242
|
-
&& currentTitleSource !== MANUAL_TITLE_SOURCE
|
|
243
|
-
&& String(session?.title || '').trim() === String(defaultSessionTitle || '').trim()
|
|
244
|
-
);
|
|
245
|
-
const nextPreview = extractSessionPreview({ userMessage, assistantMessage });
|
|
246
|
-
|
|
247
|
-
messages.push(createMessage('user', userMessage));
|
|
248
|
-
messages.push(createMessage('assistant', assistantMessage));
|
|
249
|
-
|
|
250
|
-
messagesBySession.set(resolvedSessionId, messages);
|
|
251
|
-
upsertSession({
|
|
252
|
-
...session,
|
|
253
|
-
title: shouldAutoTitle ? nextAutoTitle : session.title,
|
|
254
|
-
titleSource: shouldAutoTitle ? AUTO_TITLE_SOURCE : currentTitleSource,
|
|
255
|
-
preview: nextPreview || session.preview || '',
|
|
256
|
-
updatedAt: nowISO(),
|
|
257
|
-
});
|
|
258
|
-
touchSession(resolvedSessionId, { preview: nextPreview });
|
|
259
|
-
const latestSession = getSessionOrThrow(resolvedSessionId);
|
|
260
|
-
return {
|
|
261
|
-
sessionId: resolvedSessionId,
|
|
262
|
-
title: String(latestSession?.title || '').trim(),
|
|
263
|
-
preview: String(latestSession?.preview || '').trim(),
|
|
264
|
-
title_source: String(latestSession?.titleSource || '').trim(),
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function buildAssistantReply(content, enabledCapabilities = []) {
|
|
269
|
-
const lines = [
|
|
270
|
-
'这是本地开发模式的模拟响应。',
|
|
271
|
-
'用于调试会话、流式渲染、模式切换与生成式界面插槽。',
|
|
272
|
-
'',
|
|
273
|
-
`你输入的是:${content}`,
|
|
274
|
-
];
|
|
275
|
-
|
|
276
|
-
if (enabledCapabilities.length > 0) {
|
|
277
|
-
lines.push('', `当前启用能力:${enabledCapabilities.join('、')}`);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
lines.push('', '下一步可以继续输入,观察多轮对话行为。');
|
|
281
|
-
return lines.join('\n');
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
sessionAPI,
|
|
286
|
-
messageAPI,
|
|
287
|
-
appendExchange,
|
|
288
|
-
ensureSessionRecord,
|
|
289
|
-
buildAssistantReply,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { buildRealStreamRequestBody, resolveStreamRefs } from '../pipelines/build-stream-request.js';
|
|
3
|
-
import { sendMockStreamMessage } from '../pipelines/transport/send-mock-stream.js';
|
|
4
|
-
import { sendRealStreamAction, sendRealStreamMessage } from '../pipelines/transport/send-real-stream.js';
|
|
5
|
-
|
|
6
|
-
function useMockStream(runtime, scope = {}, persistExchange) {
|
|
7
|
-
const [loading, setLoading] = useState(false);
|
|
8
|
-
const [error, setError] = useState(null);
|
|
9
|
-
const lastRequestRef = useRef(null);
|
|
10
|
-
const timersRef = useRef([]);
|
|
11
|
-
|
|
12
|
-
const clearTimers = useCallback(() => {
|
|
13
|
-
timersRef.current.forEach((timer) => clearTimeout(timer));
|
|
14
|
-
timersRef.current = [];
|
|
15
|
-
}, []);
|
|
16
|
-
|
|
17
|
-
useEffect(() => clearTimers, [clearTimers]);
|
|
18
|
-
|
|
19
|
-
const schedule = useCallback((delay, callback) => {
|
|
20
|
-
const timer = setTimeout(callback, delay);
|
|
21
|
-
timersRef.current.push(timer);
|
|
22
|
-
}, []);
|
|
23
|
-
|
|
24
|
-
const send = useCallback(async (content, handlers = {}, options = {}) => {
|
|
25
|
-
clearTimers();
|
|
26
|
-
setLoading(true);
|
|
27
|
-
setError(null);
|
|
28
|
-
lastRequestRef.current = { content, handlers, options };
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
await sendMockStreamMessage({
|
|
32
|
-
runtime,
|
|
33
|
-
content,
|
|
34
|
-
handlers,
|
|
35
|
-
options,
|
|
36
|
-
scope,
|
|
37
|
-
persistExchange,
|
|
38
|
-
schedule,
|
|
39
|
-
});
|
|
40
|
-
} finally {
|
|
41
|
-
setLoading(false);
|
|
42
|
-
}
|
|
43
|
-
}, [clearTimers, persistExchange, runtime, schedule, scope]);
|
|
44
|
-
|
|
45
|
-
const retry = useCallback(async () => {
|
|
46
|
-
if (!lastRequestRef.current) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const { content, handlers, options } = lastRequestRef.current;
|
|
51
|
-
await send(content, handlers, options);
|
|
52
|
-
}, [send]);
|
|
53
|
-
|
|
54
|
-
const abort = useCallback(() => {
|
|
55
|
-
clearTimers();
|
|
56
|
-
setLoading(false);
|
|
57
|
-
}, [clearTimers]);
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
send,
|
|
61
|
-
retry,
|
|
62
|
-
abort,
|
|
63
|
-
loading,
|
|
64
|
-
error,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function useRealStream(apiBaseUrl, scope = {}, persistExchange) {
|
|
69
|
-
const [loading, setLoading] = useState(false);
|
|
70
|
-
const [error, setError] = useState(null);
|
|
71
|
-
const lastRequestRef = useRef(null);
|
|
72
|
-
const abortControllerRef = useRef(null);
|
|
73
|
-
const resolvedProjectId = String(scope?.projectId || '').trim();
|
|
74
|
-
const resolvedUserId = String(scope?.userId || '').trim();
|
|
75
|
-
|
|
76
|
-
const abort = useCallback(() => {
|
|
77
|
-
if (abortControllerRef.current) {
|
|
78
|
-
abortControllerRef.current.abort();
|
|
79
|
-
abortControllerRef.current = null;
|
|
80
|
-
}
|
|
81
|
-
setLoading(false);
|
|
82
|
-
}, []);
|
|
83
|
-
|
|
84
|
-
const send = useCallback(async (content, handlers = {}, options = {}) => {
|
|
85
|
-
setError(null);
|
|
86
|
-
setLoading(true);
|
|
87
|
-
lastRequestRef.current = { content, handlers, options };
|
|
88
|
-
abortControllerRef.current = new AbortController();
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
await sendRealStreamMessage({
|
|
92
|
-
apiBaseUrl,
|
|
93
|
-
content,
|
|
94
|
-
handlers,
|
|
95
|
-
options,
|
|
96
|
-
scope: {
|
|
97
|
-
projectId: resolvedProjectId,
|
|
98
|
-
userId: resolvedUserId,
|
|
99
|
-
},
|
|
100
|
-
persistExchange,
|
|
101
|
-
signal: abortControllerRef.current.signal,
|
|
102
|
-
});
|
|
103
|
-
} catch (streamError) {
|
|
104
|
-
if (streamError?.name !== 'AbortError') {
|
|
105
|
-
const nextError = {
|
|
106
|
-
message: streamError?.message || 'chat stream failed',
|
|
107
|
-
type: 'api',
|
|
108
|
-
};
|
|
109
|
-
setError(nextError);
|
|
110
|
-
handlers.onError?.(nextError);
|
|
111
|
-
}
|
|
112
|
-
} finally {
|
|
113
|
-
setLoading(false);
|
|
114
|
-
abortControllerRef.current = null;
|
|
115
|
-
}
|
|
116
|
-
}, [apiBaseUrl, persistExchange, resolvedProjectId, resolvedUserId]);
|
|
117
|
-
|
|
118
|
-
const sendAction = useCallback(async (action, handlers = {}, options = {}) => {
|
|
119
|
-
setError(null);
|
|
120
|
-
setLoading(true);
|
|
121
|
-
abortControllerRef.current = new AbortController();
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
return await sendRealStreamAction({
|
|
125
|
-
apiBaseUrl,
|
|
126
|
-
action,
|
|
127
|
-
handlers,
|
|
128
|
-
options,
|
|
129
|
-
scope: {
|
|
130
|
-
projectId: resolvedProjectId,
|
|
131
|
-
userId: resolvedUserId,
|
|
132
|
-
},
|
|
133
|
-
signal: abortControllerRef.current.signal,
|
|
134
|
-
});
|
|
135
|
-
} catch (actionError) {
|
|
136
|
-
if (actionError?.name !== 'AbortError') {
|
|
137
|
-
const nextError = {
|
|
138
|
-
message: actionError?.message || 'chat action failed',
|
|
139
|
-
type: 'api',
|
|
140
|
-
};
|
|
141
|
-
setError(nextError);
|
|
142
|
-
handlers.onError?.(nextError);
|
|
143
|
-
}
|
|
144
|
-
return null;
|
|
145
|
-
} finally {
|
|
146
|
-
setLoading(false);
|
|
147
|
-
abortControllerRef.current = null;
|
|
148
|
-
}
|
|
149
|
-
}, [apiBaseUrl, resolvedProjectId, resolvedUserId]);
|
|
150
|
-
|
|
151
|
-
const retry = useCallback(async () => {
|
|
152
|
-
if (!lastRequestRef.current) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
const { content, handlers, options } = lastRequestRef.current;
|
|
156
|
-
await send(content, handlers, options);
|
|
157
|
-
}, [send]);
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
send,
|
|
161
|
-
sendAction,
|
|
162
|
-
retry,
|
|
163
|
-
abort,
|
|
164
|
-
loading,
|
|
165
|
-
error,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function useAppStream({
|
|
170
|
-
runtime,
|
|
171
|
-
backendMode = 'real',
|
|
172
|
-
apiBaseUrl,
|
|
173
|
-
kernelBaseUrl,
|
|
174
|
-
scope,
|
|
175
|
-
persistExchange,
|
|
176
|
-
}) {
|
|
177
|
-
const fallbackPersistExchange = (
|
|
178
|
-
typeof persistExchange === 'function'
|
|
179
|
-
? persistExchange
|
|
180
|
-
: runtime?.appendExchange
|
|
181
|
-
);
|
|
182
|
-
const mockStream = useMockStream(runtime, scope, fallbackPersistExchange);
|
|
183
|
-
const realStream = useRealStream(apiBaseUrl, scope, fallbackPersistExchange);
|
|
184
|
-
return backendMode === 'real' ? realStream : mockStream;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export { resolveStreamRefs, buildRealStreamRequestBody };
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { resolveStreamRefs } from './useAppStream.js';
|
|
4
|
-
|
|
5
|
-
test('resolveStreamRefs prefers top-level refs over payloadExtra refs', () => {
|
|
6
|
-
const refs = resolveStreamRefs({
|
|
7
|
-
context_refs: [{ source_id: 'ctx-top' }],
|
|
8
|
-
knowledge_refs: [{ source_id: 'kn-top' }],
|
|
9
|
-
payloadExtra: {
|
|
10
|
-
context_refs: [{ source_id: 'ctx-payload' }],
|
|
11
|
-
knowledge_refs: [{ source_id: 'kn-payload' }],
|
|
12
|
-
},
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
assert.deepEqual(refs.context_refs, [{ source_id: 'ctx-top' }]);
|
|
16
|
-
assert.deepEqual(refs.knowledge_refs, [{ source_id: 'kn-top' }]);
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test('resolveStreamRefs falls back to payloadExtra refs when top-level refs are absent', () => {
|
|
20
|
-
const refs = resolveStreamRefs({
|
|
21
|
-
payloadExtra: {
|
|
22
|
-
context_refs: [{ source_id: 'ctx-payload' }],
|
|
23
|
-
knowledge_refs: [{ source_id: 'kn-payload' }],
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
assert.deepEqual(refs.context_refs, [{ source_id: 'ctx-payload' }]);
|
|
28
|
-
assert.deepEqual(refs.knowledge_refs, [{ source_id: 'kn-payload' }]);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('resolveStreamRefs returns empty arrays for invalid refs input', () => {
|
|
32
|
-
const refs = resolveStreamRefs({
|
|
33
|
-
context_refs: 'invalid',
|
|
34
|
-
knowledge_refs: null,
|
|
35
|
-
payloadExtra: {
|
|
36
|
-
context_refs: 'invalid',
|
|
37
|
-
knowledge_refs: { source_id: 'bad' },
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
assert.deepEqual(refs.context_refs, []);
|
|
42
|
-
assert.deepEqual(refs.knowledge_refs, []);
|
|
43
|
-
});
|
|
44
|
-
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { buildRealStreamRequestBody } from './useAppStream.js';
|
|
4
|
-
|
|
5
|
-
test('buildRealStreamRequestBody includes source refs in request body', () => {
|
|
6
|
-
const requestBody = buildRealStreamRequestBody({
|
|
7
|
-
content: 'hello',
|
|
8
|
-
scope: {
|
|
9
|
-
projectId: 'project-1',
|
|
10
|
-
userId: 'user-1',
|
|
11
|
-
},
|
|
12
|
-
options: {
|
|
13
|
-
sessionIdOverride: 'session-1',
|
|
14
|
-
knowledge_refs: ['src-1', 'src-2'],
|
|
15
|
-
context_refs: [{ source_id: 'src-1' }],
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
assert.deepEqual(requestBody.knowledge_refs, ['src-1', 'src-2']);
|
|
20
|
-
assert.deepEqual(requestBody.context_refs, [{ source_id: 'src-1' }]);
|
|
21
|
-
assert.equal(requestBody.session_id, 'session-1');
|
|
22
|
-
assert.equal(requestBody.project_id, 'project-1');
|
|
23
|
-
assert.equal(requestBody.user_id, 'user-1');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('buildRealStreamRequestBody falls back refs from payloadExtra', () => {
|
|
27
|
-
const requestBody = buildRealStreamRequestBody({
|
|
28
|
-
content: 'hello',
|
|
29
|
-
options: {
|
|
30
|
-
payloadExtra: {
|
|
31
|
-
knowledge_refs: ['src-payload'],
|
|
32
|
-
context_refs: [{ source_id: 'ctx-payload' }],
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
assert.deepEqual(requestBody.knowledge_refs, ['src-payload']);
|
|
38
|
-
assert.deepEqual(requestBody.context_refs, [{ source_id: 'ctx-payload' }]);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('buildRealStreamRequestBody keeps valid source refs stable with mixed invalid values', () => {
|
|
42
|
-
const requestBody = buildRealStreamRequestBody({
|
|
43
|
-
content: 'hello',
|
|
44
|
-
options: {
|
|
45
|
-
context_refs: [
|
|
46
|
-
{ source_id: 'ctx-valid-1' },
|
|
47
|
-
null,
|
|
48
|
-
undefined,
|
|
49
|
-
{ source_id: 'ctx-valid-2' },
|
|
50
|
-
'',
|
|
51
|
-
0,
|
|
52
|
-
{},
|
|
53
|
-
],
|
|
54
|
-
knowledge_refs: [
|
|
55
|
-
'src-valid-1',
|
|
56
|
-
null,
|
|
57
|
-
undefined,
|
|
58
|
-
'src-valid-2',
|
|
59
|
-
'',
|
|
60
|
-
1,
|
|
61
|
-
{ source_id: 'src-valid-3' },
|
|
62
|
-
],
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
assert.deepEqual(requestBody.context_refs, [
|
|
67
|
-
{ source_id: 'ctx-valid-1' },
|
|
68
|
-
{ source_id: 'ctx-valid-2' },
|
|
69
|
-
'',
|
|
70
|
-
0,
|
|
71
|
-
{},
|
|
72
|
-
]);
|
|
73
|
-
assert.deepEqual(requestBody.knowledge_refs, [
|
|
74
|
-
'src-valid-1',
|
|
75
|
-
'src-valid-2',
|
|
76
|
-
'',
|
|
77
|
-
1,
|
|
78
|
-
{ source_id: 'src-valid-3' },
|
|
79
|
-
]);
|
|
80
|
-
|
|
81
|
-
assert.ok(
|
|
82
|
-
requestBody.context_refs.some(
|
|
83
|
-
(item) => item && typeof item === 'object' && item.source_id === 'ctx-valid-1',
|
|
84
|
-
),
|
|
85
|
-
);
|
|
86
|
-
assert.ok(
|
|
87
|
-
requestBody.knowledge_refs.includes('src-valid-1'),
|
|
88
|
-
);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test('buildRealStreamRequestBody includes extracted entities in payload', () => {
|
|
92
|
-
const requestBody = buildRealStreamRequestBody({
|
|
93
|
-
content: '请对比 SAP 和 Oracle 的 ERP 方案',
|
|
94
|
-
options: {},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
assert.ok(Array.isArray(requestBody.payload.entities));
|
|
98
|
-
const normalized = requestBody.payload.entities.map((item) => String(item?.normalized || '').trim());
|
|
99
|
-
assert.ok(normalized.includes('sap'));
|
|
100
|
-
assert.ok(normalized.includes('oracle'));
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('buildRealStreamRequestBody merges provided entities with extracted entities', () => {
|
|
104
|
-
const requestBody = buildRealStreamRequestBody({
|
|
105
|
-
content: '请推荐金蝶 ERP 供应商',
|
|
106
|
-
options: {
|
|
107
|
-
payloadExtra: {
|
|
108
|
-
entities: [{ text: '用友', normalized: 'yonyou', type: 'vendor' }],
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
const normalized = requestBody.payload.entities.map((item) => String(item?.normalized || '').trim());
|
|
114
|
-
assert.ok(normalized.includes('yonyou'));
|
|
115
|
-
assert.ok(normalized.includes('金蝶'));
|
|
116
|
-
});
|