flare-chat-core 0.2.1 → 0.2.2

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 (120) hide show
  1. package/README.md +28 -0
  2. package/docs/CAPABILITY-INVENTORY.md +42 -0
  3. package/docs/CHAT-CORE-BOUNDARY.md +47 -0
  4. package/docs/CORE-APP-REALIGNMENT-WORKLOAD-2026-04-18.md +86 -0
  5. package/docs/SSOT-CHAT-CORE-BOUNDARY.md +73 -0
  6. package/docs/SSOT-CHAT-CORE-DATAFLOW.md +97 -0
  7. package/index.html +12 -0
  8. package/package.json +24 -2
  9. package/src/adapters/index.js +6 -0
  10. package/src/adapters/message-api.adapter.js +59 -0
  11. package/src/adapters/session-api.adapter.js +133 -0
  12. package/src/adapters/session-message-api.http.js +161 -0
  13. package/src/adapters/session-message-api.js +34 -0
  14. package/src/adapters/session-message-api.normalize-source-record.test.mjs +180 -0
  15. package/src/adapters/session-message-api.normalizers.js +153 -0
  16. package/src/adapters/source-api.adapter.js +135 -0
  17. package/src/adapters/sse-client.js +244 -0
  18. package/src/adapters/sse-event-dispatcher.js +121 -0
  19. package/src/app/App.jsx +11 -0
  20. package/src/app/AppProviders.jsx +12 -0
  21. package/src/app/ChatWorkspaceScreen.jsx +33 -0
  22. package/src/app/WorkspaceLayout.jsx +125 -0
  23. package/src/app/components/AppCanvasPanel.jsx +64 -0
  24. package/src/app/components/TriggerThresholdPopoverContent.jsx +122 -0
  25. package/src/app/components/WorkspaceBodySection.jsx +109 -0
  26. package/src/app/components/WorkspaceMainPane.jsx +113 -0
  27. package/src/app/components/WorkspaceSessionPane.jsx +48 -0
  28. package/src/app/components/WorkspaceTopBarSection.jsx +65 -0
  29. package/src/app/core-chat-entry/ComposerSectionNode.jsx +241 -0
  30. package/src/app/core-chat-entry/attachmentSendRefs.js +154 -0
  31. package/src/app/core-chat-entry/attachmentSendRefs.test.mjs +101 -0
  32. package/src/app/core-chat-entry/composerActionRouter.js +26 -0
  33. package/src/app/core-chat-entry/constants.js +108 -0
  34. package/src/app/core-chat-entry/selectors.js +28 -0
  35. package/src/app/core-chat-entry/useAppActionErrorGuards.js +68 -0
  36. package/src/app/core-chat-entry/useChatCorePipelines.js +110 -0
  37. package/src/app/core-chat-entry/useComposerModeSuggestion.js +89 -0
  38. package/src/app/core-chat-entry/useDevCapabilityStatusNote.js +22 -0
  39. package/src/app/core-chat-entry/useProjectNameEditing.js +41 -0
  40. package/src/app/core-chat-entry/useProjectSourceUpload.js +341 -0
  41. package/src/app/core-chat-entry/useRealApiReadinessGate.js +103 -0
  42. package/src/app/core-chat-entry/useUnavailableActionError.js +29 -0
  43. package/src/app/core-chat-entry/useWorkspaceCanvasController.jsx +177 -0
  44. package/src/app/core-chat-entry/useWorkspaceCanvasProjection.jsx +171 -0
  45. package/src/app/core-chat-entry/useWorkspaceComposerController.jsx +199 -0
  46. package/src/app/core-chat-entry/useWorkspaceController.jsx +226 -0
  47. package/src/app/core-chat-entry/useWorkspacePanels.js +55 -0
  48. package/src/app/hooks/useComposerAttachmentSync.js +223 -0
  49. package/src/app/hooks/useComposerChooserHandlers.js +52 -0
  50. package/src/app/hooks/useSendWithContextRefs.js +140 -0
  51. package/src/app/hooks/useSendWithContextRefs.test.mjs +29 -0
  52. package/src/app/hooks/useUserThresholdProfile.js +121 -0
  53. package/src/app/index.js +1 -0
  54. package/src/app/selectors/assistantTextSelector.js +73 -0
  55. package/src/app/selectors/canvasEvidenceSummarySelector.js +28 -0
  56. package/src/app/selectors/canvasReportTemplateSelector.js +28 -0
  57. package/src/app/selectors/canvasTabsSelector.js +58 -0
  58. package/src/app/selectors/evidenceProjectionSelector.js +175 -0
  59. package/src/app/selectors/evidenceProjectionSelector.test.mjs +107 -0
  60. package/src/app/selectors/modeSuggestionSelector.js +50 -0
  61. package/src/chat-core/app/mockRuntime.js +291 -0
  62. package/src/chat-core/app/useAppStream.js +187 -0
  63. package/src/chat-core/app/useAppStream.refs.test.mjs +44 -0
  64. package/src/chat-core/app/useAppStream.request-body.test.mjs +116 -0
  65. package/src/chat-core/app/useCoreChatApp.js +115 -0
  66. package/src/chat-core/facade/useBasicConversationFacade.js +280 -0
  67. package/src/chat-core/index.js +9 -1
  68. package/src/chat-core/messages/buildTimelineItems.analysis-route.test.mjs +36 -0
  69. package/src/chat-core/messages/buildTimelineItems.js +170 -12
  70. package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +183 -0
  71. package/src/chat-core/messages/contextUsageDefaults.js +3 -0
  72. package/src/chat-core/messages/contextUsageViewModel.js +147 -0
  73. package/src/chat-core/messages/contextUsageViewModel.test.mjs +74 -0
  74. package/src/chat-core/messages/useContextUsageViewModel.js +41 -0
  75. package/src/chat-core/orchestration/useBasicSendHandler.js +55 -0
  76. package/src/chat-core/pipelines/build-action-request.js +46 -0
  77. package/src/chat-core/pipelines/build-stream-request.js +74 -0
  78. package/src/chat-core/pipelines/entity-extraction.js +159 -0
  79. package/src/chat-core/pipelines/preprocess-message.js +16 -0
  80. package/src/chat-core/pipelines/stream-persist-utils.js +32 -0
  81. package/src/chat-core/pipelines/transport/send-mock-stream.js +86 -0
  82. package/src/chat-core/pipelines/transport/send-real-stream.js +330 -0
  83. package/src/chat-core/pipelines/transport/send-real-stream.test.mjs +27 -0
  84. package/src/chat-core/pipelines/transport/send-sourcing-search.js +86 -0
  85. package/src/chat-core/pipelines/transport/send-sourcing-search.test.mjs +14 -0
  86. package/src/chat-core/pipelines/transport/sourcing-response-templates.js +55 -0
  87. package/src/chat-core/pipelines/transport/sourcing-search-api.js +155 -0
  88. package/src/chat-core/runtime/runtimeMode.js +69 -0
  89. package/src/chat-core/session/chatSessionActionTypes.js +24 -0
  90. package/src/chat-core/session/chatSessionReducer.js +352 -0
  91. package/src/chat-core/session/chatSessionReducer.streaming-done.test.mjs +39 -0
  92. package/src/chat-core/session/index.js +2 -0
  93. package/src/chat-core/session/sessionActionsMessages.js +44 -0
  94. package/src/chat-core/session/sessionActionsSessionCrud.js +131 -0
  95. package/src/chat-core/session/sessionActionsStreaming.js +80 -0
  96. package/src/chat-core/session/sessionActionsUiState.js +51 -0
  97. package/src/chat-core/session/useChatSessionReducer.js +62 -455
  98. package/src/chat-core/session/useSessionListController.js +67 -0
  99. package/src/chat-core/stream/sse-client.js +1 -244
  100. package/src/chat-core/stream/sse-event-dispatcher.js +1 -0
  101. package/src/chat-core/stream/sse-events.js +1 -867
  102. package/src/chat-core/stream/useSSEStream.js +1 -356
  103. package/src/chat-core/stream/useStreamSendController.js +46 -0
  104. package/src/contracts/context-ssot.js +47 -0
  105. package/src/contracts/index.js +1 -0
  106. package/src/contracts/sse-events/base-parsers.js +79 -0
  107. package/src/contracts/sse-events/domain-parsers.js +3 -0
  108. package/src/contracts/sse-events/internal-normalizers.js +143 -0
  109. package/src/contracts/sse-events/parsers-intake.js +235 -0
  110. package/src/contracts/sse-events/parsers-runtime.js +37 -0
  111. package/src/contracts/sse-events/parsers-sourcing.js +179 -0
  112. package/src/contracts/sse-events/patch-event-parser.js +121 -0
  113. package/src/contracts/sse-events/runtime-parsers.js +79 -0
  114. package/src/contracts/sse-events.js +4 -0
  115. package/src/index.js +5 -0
  116. package/src/main.jsx +28 -0
  117. package/src/orchestration/index.js +6 -0
  118. package/src/orchestration/useSSEStream.js +221 -0
  119. package/src/state/index.js +4 -0
  120. package/vite.config.js +36 -0
@@ -0,0 +1,122 @@
1
+ import React from 'react';
2
+ import { InfoCircleOutlined } from '@ant-design/icons';
3
+ import { Divider, Popover, Slider, Space, Switch, Typography } from 'antd';
4
+
5
+ const { Text } = Typography;
6
+
7
+ export default function TriggerThresholdPopoverContent({
8
+ evidenceStrictMode,
9
+ setEvidenceStrictMode,
10
+ evidenceHitScoreThreshold,
11
+ setEvidenceHitScoreThreshold,
12
+ languageGuardEnabled,
13
+ setLanguageGuardEnabled,
14
+ friendlyToneEnabled,
15
+ setFriendlyToneEnabled,
16
+ questionModeSuggestionEnabled,
17
+ setQuestionModeSuggestionEnabled,
18
+ }) {
19
+ const rowStyle = {
20
+ alignItems: 'center',
21
+ display: 'flex',
22
+ gap: 12,
23
+ justifyContent: 'space-between',
24
+ minHeight: 32,
25
+ };
26
+ const leftColumnStyle = {
27
+ flex: 1,
28
+ minWidth: 0,
29
+ };
30
+ const rightColumnStyle = {
31
+ alignItems: 'center',
32
+ display: 'flex',
33
+ flexShrink: 0,
34
+ gap: 8,
35
+ justifyContent: 'flex-end',
36
+ minWidth: 116,
37
+ };
38
+ const infoIconStyle = {
39
+ color: 'rgba(0, 0, 0, 0.35)',
40
+ cursor: 'pointer',
41
+ fontSize: 13,
42
+ };
43
+ const renderInfoIcon = (description) => (
44
+ <Popover
45
+ content={<Text style={{ fontSize: 12 }} type="secondary">{description}</Text>}
46
+ overlayStyle={{ maxWidth: 280 }}
47
+ placement="topLeft"
48
+ trigger={['hover', 'click']}
49
+ >
50
+ <InfoCircleOutlined style={infoIconStyle} />
51
+ </Popover>
52
+ );
53
+
54
+ return (
55
+ <div style={{ maxWidth: 320, minWidth: 320, width: 320 }}>
56
+ <Space direction="vertical" size={6} style={{ width: '100%' }}>
57
+ <Text strong>触发阈值与文案风格</Text>
58
+ <div style={rowStyle}>
59
+ <div style={leftColumnStyle}>
60
+ <Text>证据命中严格模式</Text>
61
+ </div>
62
+ <div style={rightColumnStyle}>
63
+ {renderInfoIcon('开启后:仅在具体语义线索命中时展示证据,不再只靠泛关键词。')}
64
+ <Switch checked={evidenceStrictMode} onChange={setEvidenceStrictMode} />
65
+ </div>
66
+ </div>
67
+ <div style={rowStyle}>
68
+ <div style={leftColumnStyle}>
69
+ <Text>证据分数阈值</Text>
70
+ </div>
71
+ <div style={rightColumnStyle}>
72
+ {renderInfoIcon('开启严格模式后,分数低于阈值的证据不会展示。')}
73
+ </div>
74
+ </div>
75
+ <Text type="secondary" style={{ fontSize: 12, marginTop: -2 }}>
76
+ {`当前值:${Number(evidenceHitScoreThreshold || 0).toFixed(2)}`}
77
+ </Text>
78
+ <div style={{ marginTop: -2 }}>
79
+ <Slider
80
+ disabled={!evidenceStrictMode}
81
+ max={1}
82
+ min={0}
83
+ onChange={setEvidenceHitScoreThreshold}
84
+ step={0.01}
85
+ style={{ margin: 0, width: '100%' }}
86
+ value={evidenceHitScoreThreshold}
87
+ />
88
+ </div>
89
+ <Divider style={{ margin: '4px 0' }} />
90
+ <div style={rowStyle}>
91
+ <div style={leftColumnStyle}>
92
+ <Text>文案去系统痕迹</Text>
93
+ </div>
94
+ <div style={rightColumnStyle}>
95
+ {renderInfoIcon('开启后:自动隐藏 mode= / goal= / provider_status 等技术痕迹文案。')}
96
+ <Switch checked={languageGuardEnabled} onChange={setLanguageGuardEnabled} />
97
+ </div>
98
+ </div>
99
+ <Divider style={{ margin: '4px 0' }} />
100
+ <div style={rowStyle}>
101
+ <div style={leftColumnStyle}>
102
+ <Text>友好回复风格</Text>
103
+ </div>
104
+ <div style={rightColumnStyle}>
105
+ {renderInfoIcon('开启后:自动补充更自然的开头与结尾。')}
106
+ <Switch checked={friendlyToneEnabled} onChange={setFriendlyToneEnabled} />
107
+ </div>
108
+ </div>
109
+ <Divider style={{ margin: '4px 0' }} />
110
+ <div style={rowStyle}>
111
+ <div style={leftColumnStyle}>
112
+ <Text>问题回复建议</Text>
113
+ </div>
114
+ <div style={rightColumnStyle}>
115
+ {renderInfoIcon('开启后:当助手提出问题时,统一建议打开梳理模式。')}
116
+ <Switch checked={questionModeSuggestionEnabled} onChange={setQuestionModeSuggestionEnabled} />
117
+ </div>
118
+ </div>
119
+ </Space>
120
+ </div>
121
+ );
122
+ }
@@ -0,0 +1,109 @@
1
+ import React from 'react';
2
+ import {
3
+ ChatWorkspaceSourcesPanel,
4
+ ChatWorkspaceConversationPane,
5
+ ChatWorkspaceProjectInitView,
6
+ } from 'flare-chat-ui';
7
+
8
+ export default function WorkspaceBodySection({
9
+ hasProject,
10
+ activeWorkspaceTab,
11
+ themeTokens,
12
+ projectItems,
13
+ resolvedUILabels,
14
+ handleCreateProject,
15
+ composerNode,
16
+ contentMaxWidth,
17
+ handleOpenSourcePicker,
18
+ handleRemoveSource,
19
+ handleRetrySource,
20
+ handleViewSourceDetail,
21
+ sourceActionError,
22
+ sourceItems,
23
+ sourceRemovingId,
24
+ sourceSyncLoading,
25
+ sourceUploadLoading,
26
+ showCanvasPanel,
27
+ canvasPanelNode,
28
+ streamError,
29
+ streamLoading,
30
+ onStreamRetry,
31
+ renderedTimelineItems,
32
+ onUICardAction,
33
+ registry,
34
+ currentInputValue,
35
+ showAllScenarios,
36
+ onToggleShowAllScenarios,
37
+ visibleScenarios,
38
+ onScenarioCardClick,
39
+ }) {
40
+ if (!hasProject) {
41
+ return (
42
+ <ChatWorkspaceProjectInitView
43
+ isCompactLayout={false}
44
+ themeTokens={themeTokens}
45
+ resolvedProductName="F.L.A.R.E"
46
+ resolvedProductTag="项目协同工作台"
47
+ hasProjectItems={projectItems.length > 0}
48
+ resolvedUILabels={resolvedUILabels}
49
+ handleCreateProject={handleCreateProject}
50
+ />
51
+ );
52
+ }
53
+
54
+ if (activeWorkspaceTab === 'sources') {
55
+ return (
56
+ <ChatWorkspaceSourcesPanel
57
+ composerNode={composerNode}
58
+ contentMaxWidth={contentMaxWidth}
59
+ handleOpenSourcePicker={handleOpenSourcePicker}
60
+ handleRemoveSource={handleRemoveSource}
61
+ handleRetrySource={handleRetrySource}
62
+ handleViewSourceDetail={handleViewSourceDetail}
63
+ isCompactLayout={false}
64
+ resolvedUILabels={resolvedUILabels}
65
+ sourceActionError={sourceActionError}
66
+ sourceItems={sourceItems}
67
+ sourceRemovingId={sourceRemovingId}
68
+ sourceSyncLoading={sourceSyncLoading}
69
+ sourceUploadLoading={sourceUploadLoading}
70
+ themeTokens={themeTokens}
71
+ />
72
+ );
73
+ }
74
+
75
+ return (
76
+ <ChatWorkspaceConversationPane
77
+ themeTokens={themeTokens}
78
+ isCompactLayout={false}
79
+ resolvedActiveModeKey="auto"
80
+ canvasWorkspaceFullscreenMode={false}
81
+ showCanvasPanel={showCanvasPanel}
82
+ canvasPanelNode={canvasPanelNode}
83
+ composerNode={composerNode}
84
+ contentMaxWidth={contentMaxWidth}
85
+ streamError={streamError}
86
+ streamLoading={streamLoading}
87
+ onStreamRetry={onStreamRetry}
88
+ renderedTimeline={{
89
+ isEmpty: renderedTimelineItems.length === 0,
90
+ items: renderedTimelineItems,
91
+ }}
92
+ onUICardAction={onUICardAction}
93
+ registry={registry}
94
+ resolvedUILabels={resolvedUILabels}
95
+ showWorkspaceDebugPanel={false}
96
+ resolvedDevMode="full_effects"
97
+ workspaceEventLog={[]}
98
+ workspaceDocSnapshot={{}}
99
+ shouldRenderRecommendationPanel={renderedTimelineItems.length === 0}
100
+ currentInputValue={currentInputValue}
101
+ recommendationPanelTitle="起步入口"
102
+ canExpandScenarios
103
+ showAllScenarios={showAllScenarios}
104
+ onToggleShowAllScenarios={onToggleShowAllScenarios}
105
+ visibleScenarios={visibleScenarios}
106
+ onScenarioCardClick={onScenarioCardClick}
107
+ />
108
+ );
109
+ }
@@ -0,0 +1,113 @@
1
+ import React from 'react';
2
+ import WorkspaceTopBarSection from './WorkspaceTopBarSection.jsx';
3
+ import WorkspaceBodySection from './WorkspaceBodySection.jsx';
4
+
5
+ export default function WorkspaceMainPane({
6
+ themeTokens,
7
+ hasProject,
8
+ viewModel,
9
+ projectSlot,
10
+ projectNameEditing,
11
+ projectNameDraft,
12
+ setProjectNameDraft,
13
+ handleProjectNameSave,
14
+ handleProjectNameCancel,
15
+ projectDisplayName,
16
+ handleProjectNameStartEdit,
17
+ activeWorkspaceTab,
18
+ setActiveWorkspaceTab,
19
+ resolvedUILabels,
20
+ activeSession,
21
+ knowledgeHubPopoverContent,
22
+ knowledgeHubPopoverOpen,
23
+ setKnowledgeHubPopoverOpen,
24
+ handleOpenKnowledgeHub,
25
+ showCanvasPanel,
26
+ handleToggleWorkspacePanel,
27
+ projectItems,
28
+ actionGuards,
29
+ composerNode,
30
+ contentMaxWidth,
31
+ handleOpenSourcePicker,
32
+ handleRemoveSource,
33
+ handleRetrySource,
34
+ handleViewSourceDetail,
35
+ sourceActionError,
36
+ sourceItems,
37
+ sourceRemovingId,
38
+ sourceSyncLoading,
39
+ sourceUploadLoading,
40
+ canvasPanelNode,
41
+ renderedTimelineItems,
42
+ handleUICardAction,
43
+ generativeRegistry,
44
+ showAllScenarios,
45
+ setShowAllScenarios,
46
+ visibleScenarios,
47
+ }) {
48
+ const resolvedSessionDigestText = activeSession
49
+ ? `当前会话:${String(activeSession.preview || activeSession.title || '').trim()}`
50
+ : '';
51
+ return (
52
+ <div style={{ background: themeTokens.panelBg, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
53
+ {hasProject ? (
54
+ <WorkspaceTopBarSection
55
+ hasConversationStarted={viewModel.timeline.items.length > 0}
56
+ projectSlot={projectSlot}
57
+ projectNameEditing={projectNameEditing}
58
+ projectNameDraft={projectNameDraft}
59
+ setProjectNameDraft={setProjectNameDraft}
60
+ handleProjectNameSave={handleProjectNameSave}
61
+ handleProjectNameCancel={handleProjectNameCancel}
62
+ resolvedProjectDisplayName={projectDisplayName}
63
+ handleProjectNameStartEdit={handleProjectNameStartEdit}
64
+ activeWorkspaceTab={activeWorkspaceTab}
65
+ setActiveWorkspaceTab={setActiveWorkspaceTab}
66
+ themeTokens={themeTokens}
67
+ resolvedUILabels={resolvedUILabels}
68
+ hasSessionDigest={Boolean(activeSession)}
69
+ resolvedSessionDigest={resolvedSessionDigestText}
70
+ knowledgeHubPopoverContent={knowledgeHubPopoverContent}
71
+ knowledgeHubPopoverOpen={knowledgeHubPopoverOpen}
72
+ handleKnowledgeHubOpenChange={setKnowledgeHubPopoverOpen}
73
+ handleOpenKnowledgeHub={handleOpenKnowledgeHub}
74
+ showCanvasPanel={showCanvasPanel}
75
+ handleToggleWorkspacePanel={handleToggleWorkspacePanel}
76
+ />
77
+ ) : null}
78
+
79
+ <WorkspaceBodySection
80
+ hasProject={hasProject}
81
+ activeWorkspaceTab={activeWorkspaceTab}
82
+ themeTokens={themeTokens}
83
+ projectItems={projectItems}
84
+ resolvedUILabels={resolvedUILabels}
85
+ handleCreateProject={actionGuards.handleCreateProject}
86
+ composerNode={composerNode}
87
+ contentMaxWidth={contentMaxWidth}
88
+ handleOpenSourcePicker={handleOpenSourcePicker}
89
+ handleRemoveSource={handleRemoveSource}
90
+ handleRetrySource={handleRetrySource}
91
+ handleViewSourceDetail={handleViewSourceDetail}
92
+ sourceActionError={sourceActionError}
93
+ sourceItems={sourceItems}
94
+ sourceRemovingId={sourceRemovingId}
95
+ sourceSyncLoading={sourceSyncLoading}
96
+ sourceUploadLoading={sourceUploadLoading}
97
+ showCanvasPanel={showCanvasPanel}
98
+ canvasPanelNode={canvasPanelNode}
99
+ streamError={actionGuards.streamError}
100
+ streamLoading={viewModel.timeline.loading}
101
+ onStreamRetry={viewModel.toolbar.onRefreshSessions}
102
+ renderedTimelineItems={renderedTimelineItems}
103
+ onUICardAction={handleUICardAction}
104
+ registry={generativeRegistry}
105
+ currentInputValue={viewModel.composer.value}
106
+ showAllScenarios={showAllScenarios}
107
+ onToggleShowAllScenarios={() => setShowAllScenarios((prev) => !prev)}
108
+ visibleScenarios={visibleScenarios}
109
+ onScenarioCardClick={(entry) => viewModel.composer.onChange({ target: { value: entry.prompt } })}
110
+ />
111
+ </div>
112
+ );
113
+ }
@@ -0,0 +1,48 @@
1
+ import React from 'react';
2
+ import { SessionListPane } from 'flare-chat-ui';
3
+
4
+ export default function WorkspaceSessionPane({
5
+ themeTokens,
6
+ viewModel,
7
+ actionGuards,
8
+ apiReadinessGate,
9
+ hasProject,
10
+ projectItems,
11
+ projectSlot,
12
+ sessions,
13
+ resolvedUILabels,
14
+ }) {
15
+ return (
16
+ <div style={{ minWidth: 0 }}>
17
+ <SessionListPane
18
+ activeSessionId={viewModel.sessionList.activeSessionId}
19
+ error={actionGuards.sessionPaneError}
20
+ loading={viewModel.timeline.loading || apiReadinessGate.checking}
21
+ onCreateProject={actionGuards.handleCreateProject}
22
+ onCreateSession={actionGuards.handleCreateSession}
23
+ onNewSession={actionGuards.handleCreateSession}
24
+ onOperationClick={() => {}}
25
+ onProjectSelect={() => {}}
26
+ onSelectSession={viewModel.sessionList.onSelectSession}
27
+ onRenameSession={viewModel.sessionList.onRenameSession}
28
+ onArchiveSession={viewModel.sessionList.onArchiveSession}
29
+ operations={[]}
30
+ operationsTitle={resolvedUILabels.sidebar_operations_title}
31
+ projectItems={hasProject ? projectItems : []}
32
+ projectSlot={hasProject ? projectSlot : null}
33
+ compact={false}
34
+ emptyProjectsLabel={resolvedUILabels.project_list_empty}
35
+ emptySessionsLabel="暂无会话"
36
+ loadErrorMessage={apiReadinessGate.error ? 'API 预检失败' : '加载会话列表失败'}
37
+ createProjectLabel={resolvedUILabels.new_project_button}
38
+ createSessionInProjectLabel={resolvedUILabels.new_session_button}
39
+ newSessionLabel={resolvedUILabels.new_session_button}
40
+ sessions={sessions}
41
+ sortProjectsByNameLabel={resolvedUILabels.project_sort_by_name}
42
+ sortProjectsLabel={resolvedUILabels.project_sort_by_updated}
43
+ themeTokens={themeTokens}
44
+ title={hasProject ? '会话历史' : '项目列表'}
45
+ />
46
+ </div>
47
+ );
48
+ }
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import { Typography } from 'antd';
3
+ import { ChatWorkspaceTopBar } from 'flare-chat-ui';
4
+
5
+ const { Text } = Typography;
6
+
7
+ export default function WorkspaceTopBarSection({
8
+ hasConversationStarted,
9
+ projectSlot,
10
+ projectNameEditing,
11
+ projectNameDraft,
12
+ setProjectNameDraft,
13
+ handleProjectNameSave,
14
+ handleProjectNameCancel,
15
+ resolvedProjectDisplayName,
16
+ handleProjectNameStartEdit,
17
+ activeWorkspaceTab,
18
+ setActiveWorkspaceTab,
19
+ themeTokens,
20
+ resolvedUILabels,
21
+ hasSessionDigest,
22
+ resolvedSessionDigest,
23
+ knowledgeHubPopoverContent,
24
+ knowledgeHubPopoverOpen,
25
+ handleKnowledgeHubOpenChange,
26
+ handleOpenKnowledgeHub,
27
+ showCanvasPanel,
28
+ handleToggleWorkspacePanel,
29
+ }) {
30
+ return (
31
+ <ChatWorkspaceTopBar
32
+ hasConversationStarted={hasConversationStarted}
33
+ projectSlot={projectSlot}
34
+ projectNameEditing={projectNameEditing}
35
+ projectNameDraft={projectNameDraft}
36
+ setProjectNameDraft={setProjectNameDraft}
37
+ handleProjectNameSave={handleProjectNameSave}
38
+ handleProjectNameCancel={handleProjectNameCancel}
39
+ resolvedProjectDisplayName={resolvedProjectDisplayName}
40
+ handleProjectNameStartEdit={handleProjectNameStartEdit}
41
+ isCompactLayout={false}
42
+ activeWorkspaceTab={activeWorkspaceTab}
43
+ setActiveWorkspaceTab={setActiveWorkspaceTab}
44
+ segmentedThemeStyle={{}}
45
+ themeTokens={themeTokens}
46
+ resolvedUILabels={resolvedUILabels}
47
+ resolvedProductName={resolvedProjectDisplayName}
48
+ customerRoleFallback="FLARE"
49
+ hasSessionDigest={hasSessionDigest}
50
+ resolvedSessionDigest={resolvedSessionDigest}
51
+ knowledgeHubPopoverContent={knowledgeHubPopoverContent}
52
+ knowledgeHubPopoverOpen={knowledgeHubPopoverOpen}
53
+ handleKnowledgeHubOpenChange={handleKnowledgeHubOpenChange}
54
+ handleOpenKnowledgeHub={handleOpenKnowledgeHub}
55
+ mcpHubPopoverContent={<Text type="secondary">MCP(演示)</Text>}
56
+ mcpHubPopoverOpen={false}
57
+ handleMcpHubOpenChange={() => {}}
58
+ handleOpenMcpHub={() => {}}
59
+ workspaceOpenButtonVisible
60
+ hasUnseenWorkspaceUpdate={false}
61
+ showCanvasPanel={showCanvasPanel}
62
+ handleToggleWorkspacePanel={handleToggleWorkspacePanel}
63
+ />
64
+ );
65
+ }
@@ -0,0 +1,241 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { ChatWorkspaceComposerSection } from 'flare-chat-ui';
3
+ import useComposerModeSuggestion from './useComposerModeSuggestion.js';
4
+
5
+ const MODE_STATUS_IDLE = {
6
+ requirement_canvas: 'idle',
7
+ intelligent_sourcing: 'idle',
8
+ analysis_mode: 'idle',
9
+ };
10
+
11
+ function buildModeStatusByActiveKey(modeKey) {
12
+ if (!modeKey || modeKey === 'auto') {
13
+ return MODE_STATUS_IDLE;
14
+ }
15
+ return {
16
+ requirement_canvas: modeKey === 'requirement_canvas' ? 'active' : 'idle',
17
+ intelligent_sourcing: modeKey === 'intelligent_sourcing' ? 'active' : 'idle',
18
+ analysis_mode: modeKey === 'analysis_mode' ? 'active' : 'idle',
19
+ };
20
+ }
21
+
22
+ export default function ComposerSectionNode({
23
+ themeTokens,
24
+ contentMaxWidth,
25
+ streamLoading,
26
+ resolvedUILabels,
27
+ modeSwitchOptions,
28
+ inputState,
29
+ onAttachFiles,
30
+ onSend,
31
+ effectiveComposerChooser = null,
32
+ onComposerActionSelect = () => {},
33
+ onComposerChooserDismiss = () => {},
34
+ runtimeComposerActions = [],
35
+ initialModeKey = 'auto',
36
+ onModeChanged,
37
+ userId,
38
+ intentEscalationPolicyPopoverContent,
39
+ contextGaugePopoverContent,
40
+ shouldRenderModeSuggestion = true,
41
+ modeSuggestion = null,
42
+ modeSuggestionAction = null,
43
+ tipRules = [],
44
+ suggestionContextFlags = {},
45
+ }) {
46
+ const [activeModeKey, setActiveModeKey] = useState(initialModeKey);
47
+ const [statusOnlyTipHidden, setStatusOnlyTipHidden] = useState(false);
48
+ const [intentEscalationPolicyPopoverOpen, setIntentEscalationPolicyPopoverOpen] = useState(false);
49
+ React.useEffect(() => {
50
+ setActiveModeKey(String(initialModeKey || 'auto').trim() || 'auto');
51
+ }, [initialModeKey]);
52
+ React.useEffect(() => {
53
+ setStatusOnlyTipHidden(false);
54
+ }, [activeModeKey, inputState?.inputValue]);
55
+ const modeStatusByKey = useMemo(
56
+ () => buildModeStatusByActiveKey(activeModeKey),
57
+ [activeModeKey],
58
+ );
59
+ const {
60
+ modeSuggestion: resolvedModeSuggestion,
61
+ modeSuggestionAction: resolvedModeSuggestionAction,
62
+ fromFallback: modeSuggestionFromFallback,
63
+ } = useComposerModeSuggestion({
64
+ inputValue: String(inputState?.inputValue || ''),
65
+ activeModeKey,
66
+ modeSuggestion,
67
+ modeSuggestionAction,
68
+ tipRules,
69
+ contextFlags: suggestionContextFlags,
70
+ });
71
+ const resolvedModeKey = String(activeModeKey || '').trim();
72
+ const recommendationPanelState = 'hidden';
73
+ const handleModeSuggestionAction = (actionItem) => {
74
+ const actionPayload = actionItem?.action && typeof actionItem.action === 'object'
75
+ ? actionItem.action
76
+ : null;
77
+ const actionType = String(actionPayload?.action_type || actionPayload?.type || '').trim();
78
+ const targetMode = String(
79
+ actionPayload?.target_mode
80
+ || actionPayload?.mode
81
+ || actionItem?.modeKey
82
+ || ''
83
+ ).trim();
84
+ if ((actionType === 'switch_mode' || actionType === 'activate_mode') && targetMode) {
85
+ const normalizedModeKey = targetMode || 'auto';
86
+ setActiveModeKey(normalizedModeKey);
87
+ onModeChanged?.(normalizedModeKey);
88
+ return;
89
+ }
90
+ onComposerActionSelect?.(actionItem);
91
+ };
92
+ const handleCollectingAnalysisAction = (collectingAnalysisAction) => {
93
+ if (!collectingAnalysisAction) {
94
+ return;
95
+ }
96
+ onComposerActionSelect?.({
97
+ source: 'runtime_action',
98
+ text: '开始需求分析',
99
+ action: collectingAnalysisAction,
100
+ });
101
+ };
102
+ const handleComposerChooserSubmit = (payload) => {
103
+ const selectedChoice = payload?.selectedChoice && typeof payload.selectedChoice === 'object'
104
+ ? payload.selectedChoice
105
+ : null;
106
+ if (!selectedChoice) {
107
+ return;
108
+ }
109
+ const composerChooser = payload?.composerChooser && typeof payload.composerChooser === 'object'
110
+ ? payload.composerChooser
111
+ : null;
112
+ const currentQuestion = composerChooser?.currentQuestion && typeof composerChooser.currentQuestion === 'object'
113
+ ? composerChooser.currentQuestion
114
+ : null;
115
+ const isCustomInputChoice = selectedChoice?.allowCustomInput === true || selectedChoice?.requiresCustomInput === true;
116
+ const answerValue = String(payload?.answerValue || '').trim();
117
+ if (!answerValue) {
118
+ return;
119
+ }
120
+ const fieldKey = String(currentQuestion?.fieldKey || '').trim();
121
+ const questionKind = String(currentQuestion?.questionKind || 'field').trim();
122
+ const branchNodeKey = String(currentQuestion?.branchNodeKey || '').trim();
123
+ const questionText = String(currentQuestion?.questionText || composerChooser?.title || '').trim();
124
+ const selectedOptionText = String(selectedChoice?.text || '').trim();
125
+ const selectedOptionKey = String(selectedChoice?.key || '').trim();
126
+ const selectedOptionValue = isCustomInputChoice
127
+ ? answerValue
128
+ : String(selectedChoice?.answerValue || selectedOptionText || '').trim();
129
+ const selectedOptionDisplayText = isCustomInputChoice
130
+ ? answerValue
131
+ : selectedOptionText;
132
+ const answerText = String(answerValue || selectedOptionDisplayText).trim();
133
+ const hasCurrentQuestionContext = Boolean(currentQuestion && (fieldKey || branchNodeKey));
134
+ if (hasCurrentQuestionContext) {
135
+ const action = selectedChoice?.action && typeof selectedChoice.action === 'object'
136
+ ? {
137
+ ...selectedChoice.action,
138
+ payload: {
139
+ ...(selectedChoice.action.payload && typeof selectedChoice.action.payload === 'object'
140
+ ? selectedChoice.action.payload
141
+ : {}),
142
+ question_answer: {
143
+ field_key: fieldKey,
144
+ question_kind: questionKind,
145
+ branch_node_key: branchNodeKey,
146
+ question_text: questionText,
147
+ selected_option_key: selectedOptionKey,
148
+ selected_option_text: selectedOptionDisplayText,
149
+ selected_option_value: selectedOptionValue,
150
+ field_value: answerValue,
151
+ },
152
+ prefill_prompt: answerText,
153
+ },
154
+ }
155
+ : null;
156
+ onComposerActionSelect?.({
157
+ ...selectedChoice,
158
+ source: 'question_answer',
159
+ text: answerText,
160
+ answerValue,
161
+ action,
162
+ });
163
+ return;
164
+ }
165
+ onComposerActionSelect?.({
166
+ ...selectedChoice,
167
+ source: String(selectedChoice?.source || 'action'),
168
+ text: String(selectedChoice?.text || answerValue).trim(),
169
+ answerValue,
170
+ action: selectedChoice?.action && typeof selectedChoice.action === 'object'
171
+ ? selectedChoice.action
172
+ : null,
173
+ });
174
+ };
175
+
176
+ return (
177
+ <ChatWorkspaceComposerSection
178
+ canvasWorkspaceFullscreenMode={false}
179
+ themeTokens={themeTokens}
180
+ isCompactLayout={false}
181
+ contentMaxWidth={contentMaxWidth}
182
+ recommendationPanelState={recommendationPanelState}
183
+ effectiveComposerChooser={effectiveComposerChooser}
184
+ streamLoading={streamLoading}
185
+ shouldRenderModeSuggestion={shouldRenderModeSuggestion}
186
+ modeSuggestion={resolvedModeSuggestion}
187
+ modeSuggestionAction={resolvedModeSuggestionAction}
188
+ modeSuggestionFromFallback={modeSuggestionFromFallback}
189
+ onModeSuggestionAction={handleModeSuggestionAction}
190
+ handleComposerActionSelect={onComposerActionSelect}
191
+ isStatusOnlyModeSuggestion={false}
192
+ statusOnlyTipHidden={statusOnlyTipHidden}
193
+ setStatusOnlyTipHidden={setStatusOnlyTipHidden}
194
+ applyBehaviorEvent={() => {}}
195
+ isRequirementCanvasMode
196
+ chooserAwaitingAuthoritative={false}
197
+ canonicalAwaitingReason=""
198
+ patchEventHealth={{ ok: true }}
199
+ showCollectingAnalysisCta={false}
200
+ collectingAnalysisAction={null}
201
+ onCollectingAnalysisAction={handleCollectingAnalysisAction}
202
+ attachmentActionError={null}
203
+ resolvedUILabels={resolvedUILabels}
204
+ attachmentConstraintsHint="当前附件仅用于本次消息。最多 10 个,单个不超过 20MB。"
205
+ composerInputRef={null}
206
+ modeSwitchOptions={modeSwitchOptions}
207
+ resolvedActiveModeKey={activeModeKey}
208
+ modeStatusByKey={modeStatusByKey}
209
+ handleAttachFiles={onAttachFiles}
210
+ inputState={inputState}
211
+ handleModeSelect={(nextModeKey) => {
212
+ const normalizedModeKey = String(nextModeKey || '').trim() || 'auto';
213
+ setActiveModeKey(normalizedModeKey);
214
+ onModeChanged?.(normalizedModeKey);
215
+ }}
216
+ handleSend={() => onSend({
217
+ streamOptions: {
218
+ modeKey: resolvedModeKey && resolvedModeKey !== 'auto' ? resolvedModeKey : '',
219
+ manualModeKey: resolvedModeKey && resolvedModeKey !== 'auto' ? resolvedModeKey : '',
220
+ payloadExtra: (
221
+ resolvedModeKey === 'intelligent_sourcing'
222
+ ? { sourcing_enabled: true, source_scope: 'hybrid', top_k: 8 }
223
+ : {}
224
+ ),
225
+ },
226
+ })}
227
+ resolvedComposerPlaceholder="请输入你的需求"
228
+ runtimeComposerActions={runtimeComposerActions}
229
+ onComposerChooserSubmit={handleComposerChooserSubmit}
230
+ onComposerChooserDismiss={onComposerChooserDismiss}
231
+ sendButtonLabel="发送"
232
+ resolvedUserDisplayName={userId}
233
+ intentEscalationPolicyPopoverContent={intentEscalationPolicyPopoverContent}
234
+ intentEscalationPolicyPopoverOpen={intentEscalationPolicyPopoverOpen}
235
+ handleIntentEscalationPolicyPopoverOpenChange={setIntentEscalationPolicyPopoverOpen}
236
+ intentEscalationConfigEntryLabel="触发阈值"
237
+ contextGaugePopoverContent={contextGaugePopoverContent}
238
+ contextWindowPercent={8}
239
+ />
240
+ );
241
+ }