flare-chat-core 0.2.3 → 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 -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,29 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import {
|
|
4
|
-
isSourcingRefreshCommand,
|
|
5
|
-
resolveSourcingPayloadExtra,
|
|
6
|
-
} from './useSendWithContextRefs.js';
|
|
7
|
-
|
|
8
|
-
test('isSourcingRefreshCommand detects supported refresh commands', () => {
|
|
9
|
-
assert.equal(isSourcingRefreshCommand('换一批'), true);
|
|
10
|
-
assert.equal(isSourcingRefreshCommand('能不能换一批'), true);
|
|
11
|
-
assert.equal(isSourcingRefreshCommand('继续'), true);
|
|
12
|
-
assert.equal(isSourcingRefreshCommand('重新检索'), false);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('resolveSourcingPayloadExtra pins sourcing_query for refresh command', () => {
|
|
16
|
-
const payloadExtra = resolveSourcingPayloadExtra({
|
|
17
|
-
streamOptions: {
|
|
18
|
-
payloadExtra: { source_scope: 'hybrid' },
|
|
19
|
-
},
|
|
20
|
-
isSourcingMode: true,
|
|
21
|
-
currentInput: '换一批',
|
|
22
|
-
latestSourcingQuery: '上海 ERP 供应商',
|
|
23
|
-
latestSourcingResultIds: ['r1', 'r2', ''],
|
|
24
|
-
});
|
|
25
|
-
assert.equal(payloadExtra.sourcing_query, '上海 ERP 供应商');
|
|
26
|
-
assert.equal(payloadExtra.append_mode, true);
|
|
27
|
-
assert.equal(payloadExtra.lock_existing_order, true);
|
|
28
|
-
assert.deepEqual(payloadExtra.base_result_ids, ['r1', 'r2']);
|
|
29
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_THRESHOLD_PROFILE = Object.freeze({
|
|
4
|
-
evidenceStrictMode: true,
|
|
5
|
-
evidenceHitScoreThreshold: 0.05,
|
|
6
|
-
languageGuardEnabled: true,
|
|
7
|
-
friendlyToneEnabled: true,
|
|
8
|
-
questionModeSuggestionEnabled: true,
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
function resolveStorageKey({ userId, projectId }) {
|
|
12
|
-
return `flare:threshold-profile:${String(userId || 'anonymous').trim()}:${String(projectId || 'default').trim()}`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function sanitizeBoolean(value, fallback) {
|
|
16
|
-
if (typeof value !== 'boolean') {
|
|
17
|
-
return fallback;
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function sanitizeThreshold(value) {
|
|
23
|
-
const normalized = Number(value);
|
|
24
|
-
if (!Number.isFinite(normalized)) {
|
|
25
|
-
return DEFAULT_THRESHOLD_PROFILE.evidenceHitScoreThreshold;
|
|
26
|
-
}
|
|
27
|
-
if (normalized < 0) {
|
|
28
|
-
return 0;
|
|
29
|
-
}
|
|
30
|
-
if (normalized > 1) {
|
|
31
|
-
return 1;
|
|
32
|
-
}
|
|
33
|
-
return normalized;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function sanitizeProfile(payload) {
|
|
37
|
-
const source = payload && typeof payload === 'object' ? payload : {};
|
|
38
|
-
return {
|
|
39
|
-
evidenceStrictMode: sanitizeBoolean(
|
|
40
|
-
source.evidenceStrictMode,
|
|
41
|
-
DEFAULT_THRESHOLD_PROFILE.evidenceStrictMode,
|
|
42
|
-
),
|
|
43
|
-
evidenceHitScoreThreshold: sanitizeThreshold(source.evidenceHitScoreThreshold),
|
|
44
|
-
languageGuardEnabled: sanitizeBoolean(
|
|
45
|
-
source.languageGuardEnabled,
|
|
46
|
-
DEFAULT_THRESHOLD_PROFILE.languageGuardEnabled,
|
|
47
|
-
),
|
|
48
|
-
friendlyToneEnabled: sanitizeBoolean(
|
|
49
|
-
source.friendlyToneEnabled,
|
|
50
|
-
DEFAULT_THRESHOLD_PROFILE.friendlyToneEnabled,
|
|
51
|
-
),
|
|
52
|
-
questionModeSuggestionEnabled: sanitizeBoolean(
|
|
53
|
-
source.questionModeSuggestionEnabled,
|
|
54
|
-
DEFAULT_THRESHOLD_PROFILE.questionModeSuggestionEnabled,
|
|
55
|
-
),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function loadProfileFromStorage(storageKey) {
|
|
60
|
-
if (typeof window === 'undefined' || !window.localStorage) {
|
|
61
|
-
return DEFAULT_THRESHOLD_PROFILE;
|
|
62
|
-
}
|
|
63
|
-
const raw = window.localStorage.getItem(storageKey);
|
|
64
|
-
if (!raw) {
|
|
65
|
-
return DEFAULT_THRESHOLD_PROFILE;
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
return sanitizeProfile(JSON.parse(raw));
|
|
69
|
-
} catch (_error) {
|
|
70
|
-
return DEFAULT_THRESHOLD_PROFILE;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function useUserThresholdProfile({
|
|
75
|
-
userId,
|
|
76
|
-
projectId,
|
|
77
|
-
} = {}) {
|
|
78
|
-
const storageKey = useMemo(() => resolveStorageKey({ userId, projectId }), [projectId, userId]);
|
|
79
|
-
const [profile, setProfile] = useState(() => loadProfileFromStorage(storageKey));
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
setProfile(loadProfileFromStorage(storageKey));
|
|
83
|
-
}, [storageKey]);
|
|
84
|
-
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
if (typeof window === 'undefined' || !window.localStorage) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
window.localStorage.setItem(storageKey, JSON.stringify(profile));
|
|
90
|
-
}, [profile, storageKey]);
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
evidenceStrictMode: profile.evidenceStrictMode,
|
|
94
|
-
setEvidenceStrictMode: (value) => setProfile((prev) => ({
|
|
95
|
-
...prev,
|
|
96
|
-
evidenceStrictMode: Boolean(value),
|
|
97
|
-
})),
|
|
98
|
-
evidenceHitScoreThreshold: profile.evidenceHitScoreThreshold,
|
|
99
|
-
setEvidenceHitScoreThreshold: (value) => setProfile((prev) => ({
|
|
100
|
-
...prev,
|
|
101
|
-
evidenceHitScoreThreshold: sanitizeThreshold(value),
|
|
102
|
-
})),
|
|
103
|
-
languageGuardEnabled: profile.languageGuardEnabled,
|
|
104
|
-
setLanguageGuardEnabled: (value) => setProfile((prev) => ({
|
|
105
|
-
...prev,
|
|
106
|
-
languageGuardEnabled: Boolean(value),
|
|
107
|
-
})),
|
|
108
|
-
friendlyToneEnabled: profile.friendlyToneEnabled,
|
|
109
|
-
setFriendlyToneEnabled: (value) => setProfile((prev) => ({
|
|
110
|
-
...prev,
|
|
111
|
-
friendlyToneEnabled: Boolean(value),
|
|
112
|
-
})),
|
|
113
|
-
questionModeSuggestionEnabled: profile.questionModeSuggestionEnabled,
|
|
114
|
-
setQuestionModeSuggestionEnabled: (value) => setProfile((prev) => ({
|
|
115
|
-
...prev,
|
|
116
|
-
questionModeSuggestionEnabled: Boolean(value),
|
|
117
|
-
})),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export default useUserThresholdProfile;
|
package/src/app/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as App } from './App.jsx';
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
const SYSTEM_TRACE_TOKENS = ['mode=', 'intent=', 'source=', 'provider_status', 'profile=', 'tokens≈', 'goal='];
|
|
2
|
-
|
|
3
|
-
export function sanitizeAssistantText(rawText) {
|
|
4
|
-
const lines = String(rawText || '')
|
|
5
|
-
.split('\n')
|
|
6
|
-
.map((line) => String(line || '').trim())
|
|
7
|
-
.filter(Boolean);
|
|
8
|
-
const cleaned = lines.filter((line) => !SYSTEM_TRACE_TOKENS.some((token) => line.includes(token)));
|
|
9
|
-
return cleaned.join('\n').trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function hasAssistantQuestionCue(text = '') {
|
|
13
|
-
const normalized = String(text || '').trim();
|
|
14
|
-
if (!normalized) {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
const hasQuestionMark = normalized.includes('?') || normalized.includes('?');
|
|
18
|
-
const hasReplyCue = ['请告诉', '请确认', '请选择', '请补充', '请回复'].some((cue) => normalized.includes(cue));
|
|
19
|
-
return hasQuestionMark || hasReplyCue;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function applyFriendlyTone(text = '', responseStylePolicy = {}) {
|
|
23
|
-
const normalized = String(text || '').trim();
|
|
24
|
-
if (!normalized) {
|
|
25
|
-
return '';
|
|
26
|
-
}
|
|
27
|
-
const lines = normalized.split('\n');
|
|
28
|
-
const firstLine = String(lines[0] || '').trim();
|
|
29
|
-
const hasOpening = ['好的', '收到', '明白', '了解'].some((prefix) => firstLine.startsWith(prefix));
|
|
30
|
-
const withOpening = hasOpening
|
|
31
|
-
? normalized
|
|
32
|
-
: `${String(responseStylePolicy.friendlyPrefix || '').trim()}\n${normalized}`;
|
|
33
|
-
const hasQuestion = hasAssistantQuestionCue(withOpening);
|
|
34
|
-
const questionReplyHint = String(responseStylePolicy.questionReplyHint || '').trim();
|
|
35
|
-
const hasEndingHint = questionReplyHint && withOpening.includes(questionReplyHint);
|
|
36
|
-
if (hasQuestion && questionReplyHint && !hasEndingHint) {
|
|
37
|
-
return `${withOpening}\n\n${questionReplyHint}`;
|
|
38
|
-
}
|
|
39
|
-
return withOpening;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function selectRenderedTimelineItems({
|
|
43
|
-
timelineItems,
|
|
44
|
-
languageGuardEnabled,
|
|
45
|
-
friendlyToneEnabled,
|
|
46
|
-
responseStylePolicy,
|
|
47
|
-
} = {}) {
|
|
48
|
-
const resolvedTimeline = Array.isArray(timelineItems) ? timelineItems : [];
|
|
49
|
-
if (!languageGuardEnabled && !friendlyToneEnabled) {
|
|
50
|
-
return resolvedTimeline;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return resolvedTimeline.map((item) => {
|
|
54
|
-
if (!item || typeof item !== 'object') {
|
|
55
|
-
return item;
|
|
56
|
-
}
|
|
57
|
-
const shouldProcess = (item.type === 'message' && item.role === 'assistant') || item.type === 'streaming';
|
|
58
|
-
if (!shouldProcess) {
|
|
59
|
-
return item;
|
|
60
|
-
}
|
|
61
|
-
const normalizedContent = languageGuardEnabled
|
|
62
|
-
? sanitizeAssistantText(item.content)
|
|
63
|
-
: String(item.content || '');
|
|
64
|
-
return {
|
|
65
|
-
...item,
|
|
66
|
-
content: friendlyToneEnabled
|
|
67
|
-
? applyFriendlyTone(normalizedContent, responseStylePolicy)
|
|
68
|
-
: normalizedContent,
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export default selectRenderedTimelineItems;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
function normalizeSourceType(value) {
|
|
2
|
-
const rawType = String(value || '').trim().toLowerCase();
|
|
3
|
-
if (rawType.includes('web')) {
|
|
4
|
-
return 'web';
|
|
5
|
-
}
|
|
6
|
-
if (rawType.includes('mcp')) {
|
|
7
|
-
return 'mcp';
|
|
8
|
-
}
|
|
9
|
-
return 'local';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function selectCanvasEvidenceSummary(canvasEvidenceItems = []) {
|
|
13
|
-
const sourceBreakdown = { local: 0, mcp: 0, web: 0 };
|
|
14
|
-
const evidenceRows = Array.isArray(canvasEvidenceItems) ? canvasEvidenceItems : [];
|
|
15
|
-
|
|
16
|
-
evidenceRows.forEach((row) => {
|
|
17
|
-
const sourceType = normalizeSourceType(row?.source_type || row?.source);
|
|
18
|
-
sourceBreakdown[sourceType] += 1;
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
exploredCount: evidenceRows.length,
|
|
23
|
-
hitCount: evidenceRows.length,
|
|
24
|
-
sourceBreakdown,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export default selectCanvasEvidenceSummary;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export function selectCanvasReportTemplate(timelineItems = []) {
|
|
2
|
-
const items = Array.isArray(timelineItems) ? timelineItems : [];
|
|
3
|
-
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
4
|
-
const item = items[index];
|
|
5
|
-
const payload = item?.payload && typeof item.payload === 'object' ? item.payload : null;
|
|
6
|
-
if (!payload) {
|
|
7
|
-
continue;
|
|
8
|
-
}
|
|
9
|
-
const rawTemplate = (
|
|
10
|
-
payload.canvas_report_template
|
|
11
|
-
|| payload.report_template
|
|
12
|
-
|| payload.prompt_summary_template
|
|
13
|
-
);
|
|
14
|
-
if (!rawTemplate || typeof rawTemplate !== 'object' || Array.isArray(rawTemplate)) {
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
const sectionOrder = Array.isArray(rawTemplate.sectionOrder)
|
|
18
|
-
? rawTemplate.sectionOrder
|
|
19
|
-
: (Array.isArray(rawTemplate.section_order) ? rawTemplate.section_order : undefined);
|
|
20
|
-
return {
|
|
21
|
-
...rawTemplate,
|
|
22
|
-
...(sectionOrder ? { sectionOrder } : {}),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export default selectCanvasReportTemplate;
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
function isSourcingEvidence(row) {
|
|
2
|
-
return String(row?.evidence_kind || '').trim() === 'sourcing';
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function selectCanvasEvidencePresence(canvasEvidenceItems = []) {
|
|
6
|
-
const evidenceRows = Array.isArray(canvasEvidenceItems) ? canvasEvidenceItems : [];
|
|
7
|
-
return {
|
|
8
|
-
hasSourcingEvidenceRows: evidenceRows.some((row) => isSourcingEvidence(row)),
|
|
9
|
-
hasRetrievalEvidenceRows: evidenceRows.some((row) => !isSourcingEvidence(row)),
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function selectCanvasTabs({
|
|
14
|
-
isPlanMode,
|
|
15
|
-
canvasReportTemplate,
|
|
16
|
-
hasSourcingEvidenceRows,
|
|
17
|
-
hasRetrievalEvidenceRows,
|
|
18
|
-
resolvedUILabels,
|
|
19
|
-
} = {}) {
|
|
20
|
-
return [
|
|
21
|
-
...(isPlanMode ? [{ key: 'requirement', label: resolvedUILabels?.canvas_tab_result || '工作内容' }] : []),
|
|
22
|
-
...(canvasReportTemplate ? [{ key: 'analysis', label: '分析报告' }] : []),
|
|
23
|
-
...(hasSourcingEvidenceRows ? [{ key: 'sourcing', label: '寻源结果' }] : []),
|
|
24
|
-
...(hasRetrievalEvidenceRows ? [{ key: 'evidence', label: '检索证据' }] : []),
|
|
25
|
-
];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function selectResolvedCanvasEvidenceTabKey({ hasSourcingEvidenceRows, canvasActiveTabKey } = {}) {
|
|
29
|
-
return hasSourcingEvidenceRows && canvasActiveTabKey === 'sourcing'
|
|
30
|
-
? 'sourcing'
|
|
31
|
-
: 'retrieval';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function selectCanvasAutoTab({
|
|
35
|
-
canvasEvidenceItems,
|
|
36
|
-
canvasFocusEvidenceAnchorId,
|
|
37
|
-
showCanvasPanel,
|
|
38
|
-
canvasActiveTabKey,
|
|
39
|
-
} = {}) {
|
|
40
|
-
const evidenceRows = Array.isArray(canvasEvidenceItems) ? canvasEvidenceItems : [];
|
|
41
|
-
if (!evidenceRows.length || !showCanvasPanel) {
|
|
42
|
-
return '';
|
|
43
|
-
}
|
|
44
|
-
if (String(canvasFocusEvidenceAnchorId || '').trim()) {
|
|
45
|
-
return 'evidence';
|
|
46
|
-
}
|
|
47
|
-
if (String(canvasActiveTabKey || '').trim()) {
|
|
48
|
-
return '';
|
|
49
|
-
}
|
|
50
|
-
return evidenceRows.some((row) => isSourcingEvidence(row)) ? 'sourcing' : 'evidence';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function selectShouldAutoOpenCanvasForSourcing({
|
|
54
|
-
hasSourcingEvidenceRows,
|
|
55
|
-
hadSourcingEvidence,
|
|
56
|
-
} = {}) {
|
|
57
|
-
return Boolean(hasSourcingEvidenceRows && !hadSourcingEvidence);
|
|
58
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
const DEFAULT_EVIDENCE_HIT_SCORE_THRESHOLD = 0.05;
|
|
2
|
-
const NOISY_EVIDENCE_TERMS = new Set(['周年庆', '方案', '采购', '礼品', '需求', '项目']);
|
|
3
|
-
|
|
4
|
-
function extractInformativeTerms(value) {
|
|
5
|
-
const normalized = String(value || '').toLowerCase();
|
|
6
|
-
if (!normalized) {
|
|
7
|
-
return [];
|
|
8
|
-
}
|
|
9
|
-
const chinese = normalized.match(/[\u4e00-\u9fa5]{2,}/g) || [];
|
|
10
|
-
const english = normalized.match(/[a-z][a-z0-9_]{2,}/g) || [];
|
|
11
|
-
return Array.from(new Set([...chinese, ...english]))
|
|
12
|
-
.map((term) => term.trim())
|
|
13
|
-
.filter((term) => term && !NOISY_EVIDENCE_TERMS.has(term));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function resolveEvidenceHitScoreThreshold(value) {
|
|
17
|
-
const normalized = Number(value);
|
|
18
|
-
if (!Number.isFinite(normalized)) {
|
|
19
|
-
return DEFAULT_EVIDENCE_HIT_SCORE_THRESHOLD;
|
|
20
|
-
}
|
|
21
|
-
if (normalized < 0) {
|
|
22
|
-
return 0;
|
|
23
|
-
}
|
|
24
|
-
if (normalized > 1) {
|
|
25
|
-
return 1;
|
|
26
|
-
}
|
|
27
|
-
return normalized;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function isEvidenceHit({ score, snippet, query, matchedTerms = [] }, strictMode = true, scoreThreshold = DEFAULT_EVIDENCE_HIT_SCORE_THRESHOLD) {
|
|
31
|
-
if (Number.isFinite(Number(score))) {
|
|
32
|
-
return Number(score) >= resolveEvidenceHitScoreThreshold(scoreThreshold);
|
|
33
|
-
}
|
|
34
|
-
const normalizedSnippet = String(snippet || '').trim();
|
|
35
|
-
const normalizedQuery = String(query || '').trim();
|
|
36
|
-
if (!normalizedSnippet || normalizedSnippet.length < 12) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
const matchedTermList = Array.isArray(matchedTerms)
|
|
40
|
-
? matchedTerms.map((item) => String(item || '').trim()).filter(Boolean)
|
|
41
|
-
: [];
|
|
42
|
-
if (!strictMode) {
|
|
43
|
-
if (!normalizedQuery || normalizedQuery.length < 2) {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
return normalizedSnippet.toLowerCase().includes(normalizedQuery.toLowerCase());
|
|
47
|
-
}
|
|
48
|
-
if (matchedTermList.length > 0) {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
const queryTerms = extractInformativeTerms(normalizedQuery);
|
|
52
|
-
if (queryTerms.length === 0) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
const snippetTerms = extractInformativeTerms(normalizedSnippet);
|
|
56
|
-
if (snippetTerms.length === 0) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
const overlapCount = queryTerms.filter((term) => snippetTerms.includes(term)).length;
|
|
60
|
-
return overlapCount >= 2;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function normalizeSourceType(value) {
|
|
64
|
-
const raw = String(value || '').trim().toLowerCase();
|
|
65
|
-
if (raw.includes('web')) {
|
|
66
|
-
return 'web';
|
|
67
|
-
}
|
|
68
|
-
if (raw.includes('mcp')) {
|
|
69
|
-
return 'mcp';
|
|
70
|
-
}
|
|
71
|
-
return 'local';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function mapKnowledgeResultToEvidenceRow(result, index, query, runId) {
|
|
75
|
-
return {
|
|
76
|
-
key: String(result.id || result.record_id || result.source_id || `${runId}-${index}`),
|
|
77
|
-
citation_id: String(result.citation_id || result.citationId || '').trim() || undefined,
|
|
78
|
-
evidence_kind: 'retrieval',
|
|
79
|
-
title: String(result.title || result.name || result.vendor_name || '证据条目'),
|
|
80
|
-
source_type: normalizeSourceType(result.source_type || result.source),
|
|
81
|
-
score: Number.isFinite(Number(result.score)) ? Number(result.score) : undefined,
|
|
82
|
-
run_id: runId,
|
|
83
|
-
query,
|
|
84
|
-
snippet: String(result.snippet || result.summary || result.excerpt || result.content || '').trim(),
|
|
85
|
-
matchedTerms: Array.isArray(result.matched_terms) ? result.matched_terms : [],
|
|
86
|
-
source_url: String(result.source_url || result.url || '').trim() || undefined,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function mapSourcingCandidateToEvidenceRow(candidate, index, query, runId) {
|
|
91
|
-
const reasons = Array.isArray(candidate.match_reasons) ? candidate.match_reasons : [];
|
|
92
|
-
const summary = String(candidate.summary || reasons[0] || '').trim();
|
|
93
|
-
const firstRef = Array.isArray(candidate.evidence_refs) ? candidate.evidence_refs[0] : null;
|
|
94
|
-
const canonicalUrl = String(candidate.canonical_url || firstRef?.url || candidate.source_url || '').trim();
|
|
95
|
-
return {
|
|
96
|
-
key: String(candidate.id || candidate.supplier_id || `${runId}-candidate-${index}`),
|
|
97
|
-
result_id: String(candidate.result_id || candidate.id || candidate.supplier_id || `${runId}-candidate-${index}`),
|
|
98
|
-
evidence_kind: 'sourcing',
|
|
99
|
-
title: String(candidate.source_title || candidate.title || candidate.supplier_name || candidate.vendor_name || '候选供应商'),
|
|
100
|
-
supplier_name: String(candidate.supplier_name || candidate.source_title || candidate.title || candidate.vendor_name || '候选供应商'),
|
|
101
|
-
source_title: String(candidate.source_title || candidate.title || candidate.supplier_name || ''),
|
|
102
|
-
source_domain: String(candidate.source_domain || ''),
|
|
103
|
-
source_meta: (
|
|
104
|
-
candidate?.source_meta
|
|
105
|
-
&& typeof candidate.source_meta === 'object'
|
|
106
|
-
&& !Array.isArray(candidate.source_meta)
|
|
107
|
-
) ? candidate.source_meta : {},
|
|
108
|
-
canonical_url: canonicalUrl || undefined,
|
|
109
|
-
source_type: normalizeSourceType(candidate.source_type || candidate.source),
|
|
110
|
-
list_group: 'pending_verify',
|
|
111
|
-
score: Number.isFinite(Number(candidate.match_score || candidate.score))
|
|
112
|
-
? Number(candidate.match_score || candidate.score)
|
|
113
|
-
: undefined,
|
|
114
|
-
confidence: candidate?.confidence ?? '',
|
|
115
|
-
confidence_detail: (
|
|
116
|
-
candidate?.confidence_detail
|
|
117
|
-
&& typeof candidate.confidence_detail === 'object'
|
|
118
|
-
&& !Array.isArray(candidate.confidence_detail)
|
|
119
|
-
) ? candidate.confidence_detail : null,
|
|
120
|
-
run_id: runId,
|
|
121
|
-
query,
|
|
122
|
-
snippet: summary,
|
|
123
|
-
matchedTerms: reasons,
|
|
124
|
-
source_url: canonicalUrl || undefined,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export function selectProjectedCanvasEvidenceItems({
|
|
129
|
-
timelineItems,
|
|
130
|
-
composerModeKey,
|
|
131
|
-
evidenceStrictMode,
|
|
132
|
-
evidenceHitScoreThreshold = DEFAULT_EVIDENCE_HIT_SCORE_THRESHOLD,
|
|
133
|
-
} = {}) {
|
|
134
|
-
const rows = [];
|
|
135
|
-
const resolvedTimeline = Array.isArray(timelineItems) ? timelineItems : [];
|
|
136
|
-
|
|
137
|
-
resolvedTimeline.forEach((item) => {
|
|
138
|
-
const itemType = String(item?.type || '').trim();
|
|
139
|
-
const payload = item?.payload && typeof item.payload === 'object' ? item.payload : {};
|
|
140
|
-
|
|
141
|
-
if (itemType === 'knowledge_search') {
|
|
142
|
-
const query = String(payload?.query || '').trim();
|
|
143
|
-
const runId = String(payload?.run_id || '').trim();
|
|
144
|
-
const results = Array.isArray(payload?.results) ? payload.results : [];
|
|
145
|
-
results.forEach((result, index) => {
|
|
146
|
-
if (!result || typeof result !== 'object') {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const nextRow = mapKnowledgeResultToEvidenceRow(result, index, query, runId);
|
|
150
|
-
if (!isEvidenceHit(nextRow, evidenceStrictMode, evidenceHitScoreThreshold)) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
rows.push(nextRow);
|
|
154
|
-
});
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (itemType === 'sourcing_candidates') {
|
|
159
|
-
const query = String(payload?.query || payload?.message_excerpt || '').trim();
|
|
160
|
-
const runId = String(payload?.run_id || '').trim();
|
|
161
|
-
const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
|
|
162
|
-
candidates.forEach((candidate, index) => {
|
|
163
|
-
if (!candidate || typeof candidate !== 'object') {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const nextRow = mapSourcingCandidateToEvidenceRow(candidate, index, query, runId);
|
|
167
|
-
rows.push(nextRow);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
return rows;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export default selectProjectedCanvasEvidenceItems;
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import test from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { selectProjectedCanvasEvidenceItems } from './evidenceProjectionSelector.js';
|
|
4
|
-
|
|
5
|
-
test('selectProjectedCanvasEvidenceItems rebuilds evidence rows from knowledge_citation timeline item', () => {
|
|
6
|
-
const rows = selectProjectedCanvasEvidenceItems({
|
|
7
|
-
timelineItems: [
|
|
8
|
-
{
|
|
9
|
-
type: 'knowledge_citation',
|
|
10
|
-
payload: {
|
|
11
|
-
run_id: 'retrieval-run-1',
|
|
12
|
-
query: '总结附件',
|
|
13
|
-
citations: [
|
|
14
|
-
{
|
|
15
|
-
citation_id: 'cite_1',
|
|
16
|
-
title: '采购需求文档.pdf',
|
|
17
|
-
source_id: 'src-123',
|
|
18
|
-
source_type: 'local',
|
|
19
|
-
score: 0.9,
|
|
20
|
-
matched_text: '预算 50 万元',
|
|
21
|
-
snippet: '预算 50 万元,交付截止 2026-05-30',
|
|
22
|
-
},
|
|
23
|
-
],
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
evidenceStrictMode: false,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
assert.equal(rows.length, 1);
|
|
31
|
-
assert.equal(rows[0].citation_id, 'cite_1');
|
|
32
|
-
assert.equal(rows[0].run_id, 'retrieval-run-1');
|
|
33
|
-
assert.equal(rows[0].title, '采购需求文档.pdf');
|
|
34
|
-
assert.equal(rows[0].snippet.includes('预算'), true);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('selectProjectedCanvasEvidenceItems de-duplicates duplicated citation/result keys', () => {
|
|
38
|
-
const rows = selectProjectedCanvasEvidenceItems({
|
|
39
|
-
timelineItems: [
|
|
40
|
-
{
|
|
41
|
-
type: 'knowledge_search',
|
|
42
|
-
payload: {
|
|
43
|
-
run_id: 'retrieval-run-2',
|
|
44
|
-
query: '风险',
|
|
45
|
-
results: [
|
|
46
|
-
{ id: 'same-key', title: 'A', snippet: '交付风险', score: 0.8 },
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
type: 'knowledge_citation',
|
|
52
|
-
payload: {
|
|
53
|
-
run_id: 'retrieval-run-2',
|
|
54
|
-
query: '风险',
|
|
55
|
-
citations: [
|
|
56
|
-
{ citation_id: 'same-key', title: 'A', matched_text: '交付风险', score: 0.9 },
|
|
57
|
-
],
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
evidenceStrictMode: false,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
assert.equal(rows.length, 1);
|
|
65
|
-
assert.equal(rows[0].key, 'same-key');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('selectProjectedCanvasEvidenceItems prefers attachment filename and mime over opaque source_id title', () => {
|
|
69
|
-
const rows = selectProjectedCanvasEvidenceItems({
|
|
70
|
-
timelineItems: [
|
|
71
|
-
{
|
|
72
|
-
type: 'message',
|
|
73
|
-
role: 'user',
|
|
74
|
-
attachments: [
|
|
75
|
-
{
|
|
76
|
-
source_id: 'src-abc123456789',
|
|
77
|
-
filename: '公司采购需求.docx',
|
|
78
|
-
mime_type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
type: 'knowledge_citation',
|
|
84
|
-
payload: {
|
|
85
|
-
run_id: 'retrieval-run-3',
|
|
86
|
-
query: '总结',
|
|
87
|
-
citations: [
|
|
88
|
-
{
|
|
89
|
-
citation_id: 'cite_2',
|
|
90
|
-
title: 'src-abc123456789',
|
|
91
|
-
source_id: 'src-abc123456789',
|
|
92
|
-
score: 0.8,
|
|
93
|
-
matched_text: '主要风险',
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
evidenceStrictMode: false,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
assert.equal(rows.length, 1);
|
|
103
|
-
assert.equal(
|
|
104
|
-
rows[0].title,
|
|
105
|
-
'公司采购需求.docx · Word 文档',
|
|
106
|
-
);
|
|
107
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
function shouldSuggestPlanFromAssistantQuestion(text = '') {
|
|
2
|
-
const normalized = String(text || '').trim();
|
|
3
|
-
if (!normalized) {
|
|
4
|
-
return false;
|
|
5
|
-
}
|
|
6
|
-
const hasQuestionMark = normalized.includes('?') || normalized.includes('?');
|
|
7
|
-
const hasReplyCue = ['请告诉', '请确认', '请选择', '请补充', '请回复'].some((cue) => normalized.includes(cue));
|
|
8
|
-
return hasQuestionMark || hasReplyCue;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function selectLatestAssistantQuestionPendingReply(timelineItems = []) {
|
|
12
|
-
const items = Array.isArray(timelineItems) ? timelineItems : [];
|
|
13
|
-
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
14
|
-
const item = items[index];
|
|
15
|
-
if (item?.type !== 'message' || item?.role !== 'assistant') {
|
|
16
|
-
continue;
|
|
17
|
-
}
|
|
18
|
-
return shouldSuggestPlanFromAssistantQuestion(item?.content || '');
|
|
19
|
-
}
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function selectModeSuggestionTipRules({
|
|
24
|
-
baseRules,
|
|
25
|
-
questionModeSuggestionEnabled,
|
|
26
|
-
} = {}) {
|
|
27
|
-
const resolvedBaseRules = Array.isArray(baseRules) ? baseRules : [];
|
|
28
|
-
if (!questionModeSuggestionEnabled) {
|
|
29
|
-
return resolvedBaseRules;
|
|
30
|
-
}
|
|
31
|
-
return [
|
|
32
|
-
...resolvedBaseRules,
|
|
33
|
-
{
|
|
34
|
-
id: 'assistant_question_followup',
|
|
35
|
-
modeKey: 'requirement_canvas',
|
|
36
|
-
label: 'Plan 模式',
|
|
37
|
-
reason: '当前回复需要你补充信息,建议开启 Plan 模式,按模板快速回答。',
|
|
38
|
-
actionText: '打开梳理模式',
|
|
39
|
-
whenContextKey: 'assistant_question_pending_reply',
|
|
40
|
-
},
|
|
41
|
-
];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function selectModeSuggestionContextFlags({ latestAssistantQuestionPendingReply } = {}) {
|
|
45
|
-
return {
|
|
46
|
-
assistant_question_pending_reply: Boolean(latestAssistantQuestionPendingReply),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default selectLatestAssistantQuestionPendingReply;
|