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.
Files changed (122) hide show
  1. package/dist/index.js +6343 -0
  2. package/package.json +19 -22
  3. package/docs/CAPABILITY-INVENTORY.md +0 -42
  4. package/docs/CHAT-CORE-BOUNDARY.md +0 -47
  5. package/docs/CORE-APP-REALIGNMENT-WORKLOAD-2026-04-18.md +0 -86
  6. package/docs/SSOT-CHAT-CORE-BOUNDARY.md +0 -73
  7. package/docs/SSOT-CHAT-CORE-DATAFLOW.md +0 -97
  8. package/index.html +0 -12
  9. package/scripts/check.sh +0 -15
  10. package/src/adapters/index.js +0 -6
  11. package/src/adapters/message-api.adapter.js +0 -59
  12. package/src/adapters/session-api.adapter.js +0 -133
  13. package/src/adapters/session-message-api.http.js +0 -161
  14. package/src/adapters/session-message-api.js +0 -34
  15. package/src/adapters/session-message-api.normalize-source-record.test.mjs +0 -180
  16. package/src/adapters/session-message-api.normalizers.js +0 -153
  17. package/src/adapters/source-api.adapter.js +0 -135
  18. package/src/adapters/sse-client.js +0 -244
  19. package/src/adapters/sse-event-dispatcher.js +0 -121
  20. package/src/app/App.jsx +0 -11
  21. package/src/app/AppProviders.jsx +0 -12
  22. package/src/app/ChatWorkspaceScreen.jsx +0 -33
  23. package/src/app/WorkspaceLayout.jsx +0 -125
  24. package/src/app/components/AppCanvasPanel.jsx +0 -64
  25. package/src/app/components/TriggerThresholdPopoverContent.jsx +0 -122
  26. package/src/app/components/WorkspaceBodySection.jsx +0 -109
  27. package/src/app/components/WorkspaceMainPane.jsx +0 -113
  28. package/src/app/components/WorkspaceSessionPane.jsx +0 -48
  29. package/src/app/components/WorkspaceTopBarSection.jsx +0 -65
  30. package/src/app/core-chat-entry/ComposerSectionNode.jsx +0 -241
  31. package/src/app/core-chat-entry/attachmentSendRefs.js +0 -154
  32. package/src/app/core-chat-entry/attachmentSendRefs.test.mjs +0 -101
  33. package/src/app/core-chat-entry/composerActionRouter.js +0 -26
  34. package/src/app/core-chat-entry/constants.js +0 -108
  35. package/src/app/core-chat-entry/selectors.js +0 -28
  36. package/src/app/core-chat-entry/useAppActionErrorGuards.js +0 -68
  37. package/src/app/core-chat-entry/useChatCorePipelines.js +0 -110
  38. package/src/app/core-chat-entry/useComposerModeSuggestion.js +0 -89
  39. package/src/app/core-chat-entry/useDevCapabilityStatusNote.js +0 -22
  40. package/src/app/core-chat-entry/useProjectNameEditing.js +0 -41
  41. package/src/app/core-chat-entry/useProjectSourceUpload.js +0 -341
  42. package/src/app/core-chat-entry/useRealApiReadinessGate.js +0 -103
  43. package/src/app/core-chat-entry/useUnavailableActionError.js +0 -29
  44. package/src/app/core-chat-entry/useWorkspaceCanvasController.jsx +0 -177
  45. package/src/app/core-chat-entry/useWorkspaceCanvasProjection.jsx +0 -171
  46. package/src/app/core-chat-entry/useWorkspaceComposerController.jsx +0 -199
  47. package/src/app/core-chat-entry/useWorkspaceController.jsx +0 -226
  48. package/src/app/core-chat-entry/useWorkspacePanels.js +0 -55
  49. package/src/app/hooks/useComposerAttachmentSync.js +0 -223
  50. package/src/app/hooks/useComposerChooserHandlers.js +0 -52
  51. package/src/app/hooks/useSendWithContextRefs.js +0 -140
  52. package/src/app/hooks/useSendWithContextRefs.test.mjs +0 -29
  53. package/src/app/hooks/useUserThresholdProfile.js +0 -121
  54. package/src/app/index.js +0 -1
  55. package/src/app/selectors/assistantTextSelector.js +0 -73
  56. package/src/app/selectors/canvasEvidenceSummarySelector.js +0 -28
  57. package/src/app/selectors/canvasReportTemplateSelector.js +0 -28
  58. package/src/app/selectors/canvasTabsSelector.js +0 -58
  59. package/src/app/selectors/evidenceProjectionSelector.js +0 -175
  60. package/src/app/selectors/evidenceProjectionSelector.test.mjs +0 -107
  61. package/src/app/selectors/modeSuggestionSelector.js +0 -50
  62. package/src/chat-core/app/mockRuntime.js +0 -291
  63. package/src/chat-core/app/useAppStream.js +0 -187
  64. package/src/chat-core/app/useAppStream.refs.test.mjs +0 -44
  65. package/src/chat-core/app/useAppStream.request-body.test.mjs +0 -116
  66. package/src/chat-core/app/useCoreChatApp.js +0 -115
  67. package/src/chat-core/facade/useBasicConversationFacade.js +0 -280
  68. package/src/chat-core/index.js +0 -14
  69. package/src/chat-core/input/useChatInput.js +0 -103
  70. package/src/chat-core/messages/buildTimelineItems.analysis-route.test.mjs +0 -36
  71. package/src/chat-core/messages/buildTimelineItems.js +0 -233
  72. package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +0 -183
  73. package/src/chat-core/messages/contextUsageDefaults.js +0 -3
  74. package/src/chat-core/messages/contextUsageViewModel.js +0 -147
  75. package/src/chat-core/messages/contextUsageViewModel.test.mjs +0 -74
  76. package/src/chat-core/messages/useContextUsageViewModel.js +0 -41
  77. package/src/chat-core/orchestration/useBasicSendHandler.js +0 -55
  78. package/src/chat-core/pipelines/build-action-request.js +0 -46
  79. package/src/chat-core/pipelines/build-stream-request.js +0 -74
  80. package/src/chat-core/pipelines/entity-extraction.js +0 -159
  81. package/src/chat-core/pipelines/preprocess-message.js +0 -16
  82. package/src/chat-core/pipelines/stream-persist-utils.js +0 -32
  83. package/src/chat-core/pipelines/transport/send-mock-stream.js +0 -86
  84. package/src/chat-core/pipelines/transport/send-real-stream.js +0 -330
  85. package/src/chat-core/pipelines/transport/send-real-stream.test.mjs +0 -27
  86. package/src/chat-core/pipelines/transport/send-sourcing-search.js +0 -86
  87. package/src/chat-core/pipelines/transport/send-sourcing-search.test.mjs +0 -14
  88. package/src/chat-core/pipelines/transport/sourcing-response-templates.js +0 -55
  89. package/src/chat-core/pipelines/transport/sourcing-search-api.js +0 -155
  90. package/src/chat-core/runtime/runtimeMode.js +0 -69
  91. package/src/chat-core/session/chatSessionActionTypes.js +0 -24
  92. package/src/chat-core/session/chatSessionReducer.js +0 -352
  93. package/src/chat-core/session/chatSessionReducer.streaming-done.test.mjs +0 -39
  94. package/src/chat-core/session/index.js +0 -2
  95. package/src/chat-core/session/sessionActionsMessages.js +0 -44
  96. package/src/chat-core/session/sessionActionsSessionCrud.js +0 -131
  97. package/src/chat-core/session/sessionActionsStreaming.js +0 -80
  98. package/src/chat-core/session/sessionActionsUiState.js +0 -51
  99. package/src/chat-core/session/useChatSessionReducer.js +0 -131
  100. package/src/chat-core/session/useSessionListController.js +0 -67
  101. package/src/chat-core/stream/sse-client.js +0 -1
  102. package/src/chat-core/stream/sse-event-dispatcher.js +0 -1
  103. package/src/chat-core/stream/sse-events.js +0 -1
  104. package/src/chat-core/stream/useSSEStream.js +0 -1
  105. package/src/chat-core/stream/useStreamSendController.js +0 -46
  106. package/src/contracts/context-ssot.js +0 -47
  107. package/src/contracts/index.js +0 -1
  108. package/src/contracts/sse-events/base-parsers.js +0 -79
  109. package/src/contracts/sse-events/domain-parsers.js +0 -3
  110. package/src/contracts/sse-events/internal-normalizers.js +0 -143
  111. package/src/contracts/sse-events/parsers-intake.js +0 -235
  112. package/src/contracts/sse-events/parsers-runtime.js +0 -37
  113. package/src/contracts/sse-events/parsers-sourcing.js +0 -179
  114. package/src/contracts/sse-events/patch-event-parser.js +0 -121
  115. package/src/contracts/sse-events/runtime-parsers.js +0 -79
  116. package/src/contracts/sse-events.js +0 -4
  117. package/src/index.js +0 -6
  118. package/src/main.jsx +0 -28
  119. package/src/orchestration/index.js +0 -6
  120. package/src/orchestration/useSSEStream.js +0 -221
  121. package/src/state/index.js +0 -4
  122. package/vite.config.js +0 -36
@@ -1,154 +0,0 @@
1
- function normalizeSourceId(item) {
2
- if (typeof item === 'string') {
3
- return String(item).trim();
4
- }
5
- if (item && typeof item === 'object' && !Array.isArray(item)) {
6
- return String(item.source_id || item.id || '').trim();
7
- }
8
- return '';
9
- }
10
-
11
- export function mergeSourceReferenceLists(...lists) {
12
- const refs = [];
13
- const seen = new Set();
14
- lists.forEach((list) => {
15
- if (!Array.isArray(list)) {
16
- return;
17
- }
18
- list.forEach((item) => {
19
- const sourceId = normalizeSourceId(item);
20
- if (!sourceId || seen.has(sourceId)) {
21
- return;
22
- }
23
- seen.add(sourceId);
24
- refs.push({ source_id: sourceId });
25
- });
26
- });
27
- return refs;
28
- }
29
-
30
- export function collectSourceIdStrings(...lists) {
31
- const ids = [];
32
- const seen = new Set();
33
- lists.forEach((list) => {
34
- if (!Array.isArray(list)) {
35
- return;
36
- }
37
- list.forEach((item) => {
38
- const sourceId = normalizeSourceId(item);
39
- if (!sourceId || seen.has(sourceId)) {
40
- return;
41
- }
42
- seen.add(sourceId);
43
- ids.push(sourceId);
44
- });
45
- });
46
- return ids;
47
- }
48
-
49
- function normalizeAttachmentStatus(item) {
50
- return String(item?.status || '').trim().toLowerCase();
51
- }
52
-
53
- export function collectReadyAttachmentRefs(fileList = []) {
54
- if (!Array.isArray(fileList)) {
55
- return [];
56
- }
57
- const readyRefs = fileList.filter((item) => {
58
- const sourceId = normalizeSourceId(item);
59
- if (!sourceId) {
60
- return false;
61
- }
62
- return normalizeAttachmentStatus(item) === 'ready';
63
- });
64
- return mergeSourceReferenceLists(readyRefs);
65
- }
66
-
67
- export function hasNonReadyComposerAttachments(fileList = []) {
68
- if (!Array.isArray(fileList)) {
69
- return false;
70
- }
71
- return fileList.some((item) => {
72
- const sourceId = normalizeSourceId(item);
73
- const status = normalizeAttachmentStatus(item);
74
- return Boolean(!sourceId || status !== 'ready');
75
- });
76
- }
77
-
78
- function buildAttachmentSnapshot(fileList = []) {
79
- if (!Array.isArray(fileList)) {
80
- return [];
81
- }
82
- return fileList.map((item) => ({ ...item }));
83
- }
84
-
85
- function resolveUploadableFile(item) {
86
- if (!item || typeof item !== 'object' || Array.isArray(item)) {
87
- return null;
88
- }
89
- const sourceId = String(item.source_id || '').trim();
90
- if (sourceId) {
91
- return null;
92
- }
93
- const status = String(item.status || '').trim().toLowerCase();
94
- if (status === 'failed') {
95
- return null;
96
- }
97
- return item.originFileObj || null;
98
- }
99
-
100
- export async function prepareComposerAttachmentSendRefs({
101
- fileList = [],
102
- uploadSourceFile,
103
- projectId = '',
104
- sessionId = '',
105
- } = {}) {
106
- const attachedSessionFilesSnapshot = buildAttachmentSnapshot(fileList);
107
- const resolvedProjectId = String(projectId || '').trim();
108
- const canUpload = typeof uploadSourceFile === 'function' && Boolean(resolvedProjectId);
109
-
110
- if (canUpload) {
111
- await Promise.all(attachedSessionFilesSnapshot.map(async (attachment, index) => {
112
- const uploadableFile = resolveUploadableFile(attachment);
113
- if (!uploadableFile) {
114
- return;
115
- }
116
- try {
117
- const uploaded = await uploadSourceFile({
118
- file: uploadableFile,
119
- filename: attachment?.name || attachment?.filename || uploadableFile?.name || 'upload.bin',
120
- project_id: resolvedProjectId,
121
- session_id: String(sessionId || '').trim() || null,
122
- });
123
- const sourceId = String(uploaded?.source_id || uploaded?.id || '').trim();
124
- if (!sourceId) {
125
- attachedSessionFilesSnapshot[index] = {
126
- ...attachment,
127
- status: 'failed',
128
- error_message: '上传成功但未返回 source_id',
129
- };
130
- return;
131
- }
132
- attachedSessionFilesSnapshot[index] = {
133
- ...attachment,
134
- ...uploaded,
135
- source_id: sourceId,
136
- status: String(uploaded?.status || attachment?.status || 'ready'),
137
- error_message: String(uploaded?.error_message || ''),
138
- };
139
- } catch (error) {
140
- attachedSessionFilesSnapshot[index] = {
141
- ...attachment,
142
- status: 'failed',
143
- error_message: String(error?.message || error || '上传失败'),
144
- };
145
- }
146
- }));
147
- }
148
-
149
- const refs = mergeSourceReferenceLists(attachedSessionFilesSnapshot.filter((item) => String(item?.status || '').trim().toLowerCase() !== 'failed'));
150
- return {
151
- attachedSessionFilesSnapshot,
152
- refs,
153
- };
154
- }
@@ -1,101 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import {
4
- collectSourceIdStrings,
5
- collectReadyAttachmentRefs,
6
- hasNonReadyComposerAttachments,
7
- mergeSourceReferenceLists,
8
- prepareComposerAttachmentSendRefs,
9
- } from './attachmentSendRefs.js';
10
-
11
- test('mergeSourceReferenceLists keeps unique refs from strings and objects', () => {
12
- const refs = mergeSourceReferenceLists(
13
- ['src-1', '', 'src-1'],
14
- [{ source_id: 'src-2' }, { id: 'src-3' }, null],
15
- );
16
- assert.deepEqual(refs, [
17
- { source_id: 'src-1' },
18
- { source_id: 'src-2' },
19
- { source_id: 'src-3' },
20
- ]);
21
- });
22
-
23
- test('collectSourceIdStrings keeps unique source ids from mixed refs', () => {
24
- const ids = collectSourceIdStrings(
25
- ['src-1', { source_id: 'src-2' }, { id: 'src-3' }],
26
- [{ source_id: 'src-1' }, '', null, { source_id: '' }],
27
- );
28
- assert.deepEqual(ids, ['src-1', 'src-2', 'src-3']);
29
- });
30
-
31
- test('prepareComposerAttachmentSendRefs uploads pending files and skips failed refs', async () => {
32
- const uploaded = [];
33
- const payload = await prepareComposerAttachmentSendRefs({
34
- fileList: [
35
- {
36
- uid: 'f1',
37
- name: 'a.docx',
38
- originFileObj: { name: 'a.docx' },
39
- status: 'ready',
40
- },
41
- {
42
- uid: 'f2',
43
- name: 'b.pdf',
44
- originFileObj: { name: 'b.pdf' },
45
- status: 'failed',
46
- },
47
- {
48
- uid: 'f3',
49
- name: 'c.txt',
50
- source_id: 'src-existing',
51
- originFileObj: { name: 'c.txt' },
52
- status: 'ready',
53
- },
54
- ],
55
- uploadSourceFile: async (input) => {
56
- uploaded.push(input.filename);
57
- return { source_id: 'src-uploaded' };
58
- },
59
- projectId: 'project-1',
60
- sessionId: 'session-1',
61
- });
62
-
63
- assert.deepEqual(uploaded, ['a.docx']);
64
- assert.deepEqual(payload.refs, [
65
- { source_id: 'src-uploaded' },
66
- { source_id: 'src-existing' },
67
- ]);
68
- assert.equal(payload.attachedSessionFilesSnapshot[0].source_id, 'src-uploaded');
69
- assert.equal(payload.attachedSessionFilesSnapshot[1].status, 'failed');
70
- });
71
-
72
- test('collectReadyAttachmentRefs includes only ready source_id entries', () => {
73
- const refs = collectReadyAttachmentRefs([
74
- { source_id: 'src-ready-1', status: 'ready' },
75
- { source_id: 'src-uploaded', status: 'uploaded' },
76
- { source_id: 'src-ready-2', status: 'ready' },
77
- { source_id: 'src-failed', status: 'failed' },
78
- { source_id: '', status: 'ready' },
79
- ]);
80
-
81
- assert.deepEqual(refs, [
82
- { source_id: 'src-ready-1' },
83
- { source_id: 'src-ready-2' },
84
- ]);
85
- });
86
-
87
- test('hasNonReadyComposerAttachments blocks when any file is not ready', () => {
88
- assert.equal(hasNonReadyComposerAttachments([
89
- { source_id: 'src-1', status: 'ready' },
90
- { source_id: 'src-2', status: 'ready' },
91
- ]), false);
92
-
93
- assert.equal(hasNonReadyComposerAttachments([
94
- { source_id: 'src-1', status: 'ready' },
95
- { source_id: 'src-2', status: 'indexing' },
96
- ]), true);
97
-
98
- assert.equal(hasNonReadyComposerAttachments([
99
- { source_id: '', status: 'ready' },
100
- ]), true);
101
- });
@@ -1,26 +0,0 @@
1
- function toText(value) {
2
- return String(value || '').trim();
3
- }
4
-
5
- export function buildComposerDraftRoundKey({
6
- activeSessionId,
7
- currentInput,
8
- }) {
9
- const sessionPart = toText(activeSessionId) || 'draft';
10
- const inputPart = toText(currentInput);
11
- return `${sessionPart}:${inputPart}`;
12
- }
13
-
14
- export function resolveComposerActionIntent(choice) {
15
- const actionPayload = choice?.action && typeof choice.action === 'object'
16
- ? choice.action
17
- : null;
18
- const actionKey = toText(actionPayload?.action_key);
19
- if (actionKey === 'open_sourcing') {
20
- return { kind: 'open_sourcing' };
21
- }
22
- if (actionKey === 'dismiss_sourcing_once') {
23
- return { kind: 'dismiss_sourcing_once' };
24
- }
25
- return { kind: 'noop' };
26
- }
@@ -1,108 +0,0 @@
1
- export const WORKSPACE_CONTENT_MAX_WIDTH = 980;
2
-
3
- export const RESOLVED_UI_LABELS = {
4
- tab_chats: '会话',
5
- tab_sources: '资料',
6
- edit_project_button: '编辑',
7
- sidebar_operations_title: '常规操作',
8
- project_select_hint: '请从左侧项目列表选择一个项目。',
9
- project_create_hint: '先创建一个项目,然后开始会话。',
10
- project_list_empty: '还没有项目,点击“创建项目”开始。',
11
- new_project_button: '创建项目',
12
- new_session_button: '新会话',
13
- project_sort_by_name: '按名称',
14
- project_sort_by_updated: '按最近更新',
15
- empty_state_title: '欢迎使用 FLARE',
16
- empty_state_description: '开始一个新对话。',
17
- scenario_expand_label: '查看更多',
18
- scenario_collapse_label: '收起',
19
- composer_attach_label: '上传文件',
20
- composer_hint: '描述你的目标、背景或约束,系统会先帮你判断下一步。',
21
- canvas_workspace_title: '需求梳理工作区',
22
- sources_add_button: '添加资料',
23
- sources_loading: '资料加载中...',
24
- sources_empty_description: '上传到项目资料池后,后续该项目下的对话和分析都可以复用这些资料。',
25
- };
26
-
27
- export const STARTER_SCENARIOS = [
28
- {
29
- key: 'starter-1',
30
- label: '帮我梳理一个需求',
31
- description: '先补齐目标、约束和验收标准。',
32
- prompt: '帮我梳理一个需求',
33
- },
34
- {
35
- key: 'starter-2',
36
- label: '帮我做方案对比',
37
- description: '从关键维度对比候选方案。',
38
- prompt: '帮我做方案对比',
39
- },
40
- {
41
- key: 'starter-3',
42
- label: '帮我整理执行计划',
43
- description: '拆分阶段任务并明确里程碑。',
44
- prompt: '帮我整理执行计划',
45
- },
46
- ];
47
-
48
- export const MODE_SWITCH_OPTIONS = [
49
- { value: 'requirement_canvas', label: 'Plan 模式' },
50
- { value: 'intelligent_sourcing', label: '寻源模式' },
51
- { value: 'analysis_mode', label: '分析模式' },
52
- ];
53
-
54
- export const MODE_SUGGESTION_TIP_RULES = [
55
- {
56
- id: 'analysis',
57
- modeKey: 'analysis_mode',
58
- label: '分析模式',
59
- reason: '识别到分析或模板诉求,建议开启分析模式。',
60
- actionText: '开始需求分析',
61
- keywords: ['分析', '模板', '模版', '清单模板', '报告'],
62
- },
63
- {
64
- id: 'sourcing',
65
- modeKey: 'intelligent_sourcing',
66
- label: '寻源模式',
67
- reason: '识别到找供应商/检索诉求,建议开启寻源模式。',
68
- actionText: '启用寻源模式',
69
- keywords: ['找', '寻源', '搜索', '检索', '供应商', '比价'],
70
- },
71
- {
72
- id: 'plan',
73
- modeKey: 'requirement_canvas',
74
- label: 'Plan 模式',
75
- reason: '识别到需求规划/整理诉求,建议开启 Plan 模式。',
76
- actionText: '开始需求梳理',
77
- keywords: ['规划', '整理', '梳理', '需求', '采购', '收口'],
78
- },
79
- ];
80
-
81
- export const RESPONSE_STYLE_POLICY = {
82
- friendlyPrefix: '好的,我们先把关键信息理清楚:',
83
- questionReplyHint: '你可以直接回复序号或补充信息,我来继续整理。',
84
- };
85
-
86
- export function createWorkspaceStyle(themeTokens) {
87
- return {
88
- '--flare-accent': themeTokens.bubbleUserBg,
89
- '--flare-accent-hover': themeTokens.surfaceBorderActive,
90
- '--flare-focus-outline': themeTokens.focusOutline,
91
- '--flare-font-body': themeTokens.fontFamilyBody,
92
- '--flare-font-heading': themeTokens.fontFamilyHeading,
93
- '--flare-hover-bg': themeTokens.surfaceBgActive,
94
- '--flare-scrollbar-thumb': themeTokens.divider,
95
- '--flare-surface-bg-active': themeTokens.surfaceBgActive,
96
- '--flare-surface-border-active': themeTokens.surfaceBorderActive,
97
- '--flare-text-primary': themeTokens.textPrimary,
98
- '--flare-text-secondary': themeTokens.textSecondary,
99
- background: themeTokens.appBg,
100
- boxShadow: themeTokens.appShadow,
101
- display: 'grid',
102
- gridTemplateColumns: '300px minmax(0, 1fr)',
103
- gridTemplateRows: 'minmax(0, 1fr)',
104
- height: '100vh',
105
- overflow: 'hidden',
106
- position: 'relative',
107
- };
108
- }
@@ -1,28 +0,0 @@
1
- function getSessionId(item) {
2
- return String(item?.sessionId || item?.session_id || item?.id || '').trim();
3
- }
4
-
5
- export function selectActiveSession(sessions, activeSessionId) {
6
- return sessions.find((item) => {
7
- const sessionId = getSessionId(item);
8
- return Boolean(sessionId && sessionId === activeSessionId);
9
- }) || null;
10
- }
11
-
12
- export function selectProjectItems({
13
- projectId,
14
- projectDisplayName,
15
- sessions,
16
- }) {
17
- const sessionItems = Array.isArray(sessions) ? sessions : [];
18
- if (sessionItems.length <= 0) {
19
- return [];
20
- }
21
- return [{
22
- key: 'local_demo_project',
23
- project_id: projectId,
24
- name: projectDisplayName,
25
- updatedAt: sessionItems[0]?.updatedAt || sessionItems[0]?.updated_at || new Date().toISOString(),
26
- createdAt: sessionItems[0]?.createdAt || sessionItems[0]?.created_at || new Date().toISOString(),
27
- }];
28
- }
@@ -1,68 +0,0 @@
1
- import { useCallback, useMemo, useState } from 'react';
2
-
3
- function normalizeError(error, fallbackMessage) {
4
- if (error instanceof Error) {
5
- return error;
6
- }
7
- return new Error(String(error || fallbackMessage));
8
- }
9
-
10
- export function useAppActionErrorGuards({
11
- blocked = false,
12
- blockedError,
13
- sessionListError = null,
14
- composerError = null,
15
- onCreateProject,
16
- onCreateSession,
17
- onSend,
18
- } = {}) {
19
- const [actionError, setActionError] = useState(null);
20
-
21
- const handleCreateProject = useCallback(async () => {
22
- if (blocked) {
23
- setActionError(blockedError);
24
- return;
25
- }
26
- try {
27
- setActionError(null);
28
- await onCreateProject?.();
29
- } catch (nextError) {
30
- setActionError(normalizeError(nextError, '创建项目失败'));
31
- }
32
- }, [blocked, blockedError, onCreateProject]);
33
-
34
- const handleCreateSession = useCallback(async () => {
35
- if (blocked) {
36
- setActionError(blockedError);
37
- return;
38
- }
39
- try {
40
- setActionError(null);
41
- await onCreateSession?.();
42
- } catch (nextError) {
43
- setActionError(normalizeError(nextError, '创建会话失败'));
44
- }
45
- }, [blocked, blockedError, onCreateSession]);
46
-
47
- const handleSend = useCallback(async (options = {}) => {
48
- if (blocked) {
49
- setActionError(blockedError);
50
- return;
51
- }
52
- setActionError(null);
53
- const sendResult = await onSend?.(options);
54
- if (sendResult instanceof Error) {
55
- setActionError(sendResult);
56
- }
57
- return sendResult;
58
- }, [blocked, blockedError, onSend]);
59
-
60
- return useMemo(() => ({
61
- actionError,
62
- handleCreateProject,
63
- handleCreateSession,
64
- handleSend,
65
- sessionPaneError: actionError || sessionListError || null,
66
- streamError: actionError || composerError || null,
67
- }), [actionError, composerError, handleCreateProject, handleCreateSession, handleSend, sessionListError]);
68
- }
@@ -1,110 +0,0 @@
1
- import { useMemo } from 'react';
2
- import { useCoreChatApp } from '../../chat-core/app/useCoreChatApp.js';
3
- import { useAppStream } from '../../chat-core/app/useAppStream.js';
4
- import { createMockRuntime } from '../../chat-core/app/mockRuntime.js';
5
- import { createSessionMessageAPI } from '../../adapters/session-message-api.js';
6
- import { useRealApiReadinessGate } from './useRealApiReadinessGate.js';
7
-
8
- export function useChatCorePipelines({
9
- functionType,
10
- defaultSessionTitle,
11
- projectId,
12
- userId,
13
- backendMode,
14
- kernelBaseUrl,
15
- apiBaseUrl,
16
- apiToken,
17
- sessionAPI,
18
- messageAPI,
19
- }) {
20
- const normalizedApiBaseUrl = String(apiBaseUrl || '').trim();
21
- const runtime = useMemo(() => createMockRuntime({
22
- defaultFunctionType: functionType,
23
- defaultSessionTitle,
24
- }), [defaultSessionTitle, functionType]);
25
-
26
- const scope = useMemo(() => ({
27
- projectId,
28
- userId,
29
- }), [projectId, userId]);
30
-
31
- const listParams = useMemo(() => ({
32
- status: 'active',
33
- page: 1,
34
- page_size: 20,
35
- function_type: functionType,
36
- project_id: projectId,
37
- }), [functionType, projectId]);
38
-
39
- const shouldUseRealAPI = backendMode === 'real' && !sessionAPI && !messageAPI;
40
- const apiReadinessGate = useRealApiReadinessGate({
41
- enabled: shouldUseRealAPI,
42
- apiBaseUrl: normalizedApiBaseUrl,
43
- apiToken,
44
- });
45
- const blockedApiError = useMemo(() => (
46
- apiReadinessGate.error || new Error('API 不可用,请检查配置与服务状态')
47
- ), [apiReadinessGate.error]);
48
- const blockedSessionAPI = useMemo(() => ({
49
- list: async () => ({ sessions: [] }),
50
- get: async () => {
51
- throw blockedApiError;
52
- },
53
- create: async () => {
54
- throw blockedApiError;
55
- },
56
- update: async () => {
57
- throw blockedApiError;
58
- },
59
- }), [blockedApiError]);
60
- const blockedMessageAPI = useMemo(() => ({
61
- list: async () => {
62
- throw blockedApiError;
63
- },
64
- }), [blockedApiError]);
65
- const realApi = useMemo(() => {
66
- if (!shouldUseRealAPI || apiReadinessGate.blocked) {
67
- return null;
68
- }
69
- return createSessionMessageAPI({
70
- baseUrl: normalizedApiBaseUrl,
71
- token: apiToken,
72
- });
73
- }, [apiReadinessGate.blocked, apiToken, normalizedApiBaseUrl, shouldUseRealAPI]);
74
- const resolvedSessionAPI = sessionAPI
75
- || realApi?.sessionAPI
76
- || (shouldUseRealAPI ? blockedSessionAPI : runtime.sessionAPI);
77
- const resolvedMessageAPI = messageAPI
78
- || realApi?.messageAPI
79
- || (shouldUseRealAPI ? blockedMessageAPI : runtime.messageAPI);
80
- const useRuntimePersistence = !shouldUseRealAPI && !sessionAPI && !messageAPI;
81
-
82
- const stream = useAppStream({
83
- runtime,
84
- backendMode,
85
- apiBaseUrl: normalizedApiBaseUrl,
86
- kernelBaseUrl,
87
- scope,
88
- persistExchange: useRuntimePersistence ? runtime.appendExchange : null,
89
- });
90
-
91
- const { viewModel } = useCoreChatApp({
92
- sessionAPI: resolvedSessionAPI,
93
- messageAPI: resolvedMessageAPI,
94
- stream,
95
- functionType,
96
- defaultSessionTitle,
97
- listParams,
98
- createSessionPayload: {
99
- project_id: projectId,
100
- user_id: userId,
101
- },
102
- });
103
-
104
- return {
105
- apiReadinessGate,
106
- blockedApiError,
107
- realApi,
108
- viewModel,
109
- };
110
- }
@@ -1,89 +0,0 @@
1
- import { useMemo } from 'react';
2
-
3
- function normalizeText(value) {
4
- return String(value || '').trim().toLowerCase();
5
- }
6
-
7
- function hasAnyKeyword(content, keywords = []) {
8
- return keywords.some((keyword) => content.includes(String(keyword || '').trim().toLowerCase()));
9
- }
10
-
11
- function buildSuggestionFromInput({
12
- inputValue,
13
- activeModeKey,
14
- tipRules = [],
15
- contextFlags = {},
16
- }) {
17
- const resolvedModeKey = String(activeModeKey || '').trim();
18
- if (resolvedModeKey !== 'auto') {
19
- return null;
20
- }
21
- const normalizedInput = normalizeText(inputValue);
22
- const normalizedRules = Array.isArray(tipRules) ? tipRules : [];
23
- if (!normalizedInput && normalizedRules.length === 0) {
24
- return null;
25
- }
26
- const matchedRule = normalizedRules.find((rule) => {
27
- const keywords = Array.isArray(rule?.keywords) ? rule.keywords : [];
28
- const hasKeywordMatch = normalizedInput && hasAnyKeyword(normalizedInput, keywords);
29
- const contextKey = String(rule?.whenContextKey || '').trim();
30
- const hasContextMatch = contextKey ? contextFlags?.[contextKey] === true : false;
31
- return hasKeywordMatch || hasContextMatch;
32
- });
33
- if (!matchedRule) {
34
- return null;
35
- }
36
- return {
37
- source: String(matchedRule.source || 'local_input_tip'),
38
- modeKey: String(matchedRule.modeKey || '').trim(),
39
- label: String(matchedRule.label || '').trim(),
40
- reason: String(matchedRule.reason || '').trim(),
41
- actionText: String(matchedRule.actionText || '').trim(),
42
- statusOnly: matchedRule.statusOnly === true,
43
- };
44
- }
45
-
46
- export default function useComposerModeSuggestion({
47
- inputValue,
48
- activeModeKey,
49
- modeSuggestion,
50
- modeSuggestionAction,
51
- tipRules = [],
52
- contextFlags = {},
53
- }) {
54
- return useMemo(() => {
55
- if (modeSuggestionAction && typeof modeSuggestionAction === 'object') {
56
- return {
57
- modeSuggestion: modeSuggestion || null,
58
- modeSuggestionAction,
59
- fromFallback: false,
60
- };
61
- }
62
- const fallbackSuggestion = buildSuggestionFromInput({
63
- inputValue,
64
- activeModeKey,
65
- tipRules,
66
- contextFlags,
67
- });
68
- if (!fallbackSuggestion) {
69
- return {
70
- modeSuggestion: modeSuggestion || null,
71
- modeSuggestionAction: null,
72
- fromFallback: false,
73
- };
74
- }
75
- return {
76
- modeSuggestion: fallbackSuggestion,
77
- modeSuggestionAction: {
78
- source: 'mode_suggestion',
79
- text: fallbackSuggestion.actionText || `启用${fallbackSuggestion.label || fallbackSuggestion.modeKey || ''}`,
80
- action: {
81
- action_type: 'switch_mode',
82
- target_mode: fallbackSuggestion.modeKey,
83
- label: fallbackSuggestion.actionText || '',
84
- },
85
- },
86
- fromFallback: true,
87
- };
88
- }, [activeModeKey, contextFlags, inputValue, modeSuggestion, modeSuggestionAction, tipRules]);
89
- }