flare-chat-core 0.2.0 → 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.
- package/README.md +28 -0
- package/docs/CAPABILITY-INVENTORY.md +42 -0
- package/docs/CHAT-CORE-BOUNDARY.md +47 -0
- package/docs/CORE-APP-REALIGNMENT-WORKLOAD-2026-04-18.md +86 -0
- package/docs/SSOT-CHAT-CORE-BOUNDARY.md +73 -0
- package/docs/SSOT-CHAT-CORE-DATAFLOW.md +97 -0
- package/index.html +12 -0
- package/package.json +24 -2
- package/src/adapters/index.js +6 -0
- package/src/adapters/message-api.adapter.js +59 -0
- package/src/adapters/session-api.adapter.js +133 -0
- package/src/adapters/session-message-api.http.js +161 -0
- package/src/adapters/session-message-api.js +34 -0
- package/src/adapters/session-message-api.normalize-source-record.test.mjs +180 -0
- package/src/adapters/session-message-api.normalizers.js +153 -0
- package/src/adapters/source-api.adapter.js +135 -0
- package/src/adapters/sse-client.js +244 -0
- package/src/adapters/sse-event-dispatcher.js +121 -0
- package/src/app/App.jsx +11 -0
- package/src/app/AppProviders.jsx +12 -0
- package/src/app/ChatWorkspaceScreen.jsx +33 -0
- package/src/app/WorkspaceLayout.jsx +125 -0
- package/src/app/components/AppCanvasPanel.jsx +64 -0
- package/src/app/components/TriggerThresholdPopoverContent.jsx +122 -0
- package/src/app/components/WorkspaceBodySection.jsx +109 -0
- package/src/app/components/WorkspaceMainPane.jsx +113 -0
- package/src/app/components/WorkspaceSessionPane.jsx +48 -0
- package/src/app/components/WorkspaceTopBarSection.jsx +65 -0
- package/src/app/core-chat-entry/ComposerSectionNode.jsx +241 -0
- package/src/app/core-chat-entry/attachmentSendRefs.js +154 -0
- package/src/app/core-chat-entry/attachmentSendRefs.test.mjs +101 -0
- package/src/app/core-chat-entry/composerActionRouter.js +26 -0
- package/src/app/core-chat-entry/constants.js +108 -0
- package/src/app/core-chat-entry/selectors.js +28 -0
- package/src/app/core-chat-entry/useAppActionErrorGuards.js +68 -0
- package/src/app/core-chat-entry/useChatCorePipelines.js +110 -0
- package/src/app/core-chat-entry/useComposerModeSuggestion.js +89 -0
- package/src/app/core-chat-entry/useDevCapabilityStatusNote.js +22 -0
- package/src/app/core-chat-entry/useProjectNameEditing.js +41 -0
- package/src/app/core-chat-entry/useProjectSourceUpload.js +341 -0
- package/src/app/core-chat-entry/useRealApiReadinessGate.js +103 -0
- package/src/app/core-chat-entry/useUnavailableActionError.js +29 -0
- package/src/app/core-chat-entry/useWorkspaceCanvasController.jsx +177 -0
- package/src/app/core-chat-entry/useWorkspaceCanvasProjection.jsx +171 -0
- package/src/app/core-chat-entry/useWorkspaceComposerController.jsx +199 -0
- package/src/app/core-chat-entry/useWorkspaceController.jsx +226 -0
- package/src/app/core-chat-entry/useWorkspacePanels.js +55 -0
- package/src/app/hooks/useComposerAttachmentSync.js +223 -0
- package/src/app/hooks/useComposerChooserHandlers.js +52 -0
- package/src/app/hooks/useSendWithContextRefs.js +140 -0
- package/src/app/hooks/useSendWithContextRefs.test.mjs +29 -0
- package/src/app/hooks/useUserThresholdProfile.js +121 -0
- package/src/app/index.js +1 -0
- package/src/app/selectors/assistantTextSelector.js +73 -0
- package/src/app/selectors/canvasEvidenceSummarySelector.js +28 -0
- package/src/app/selectors/canvasReportTemplateSelector.js +28 -0
- package/src/app/selectors/canvasTabsSelector.js +58 -0
- package/src/app/selectors/evidenceProjectionSelector.js +175 -0
- package/src/app/selectors/evidenceProjectionSelector.test.mjs +107 -0
- package/src/app/selectors/modeSuggestionSelector.js +50 -0
- package/src/chat-core/app/mockRuntime.js +291 -0
- package/src/chat-core/app/useAppStream.js +187 -0
- package/src/chat-core/app/useAppStream.refs.test.mjs +44 -0
- package/src/chat-core/app/useAppStream.request-body.test.mjs +116 -0
- package/src/chat-core/app/useCoreChatApp.js +115 -0
- package/src/chat-core/facade/useBasicConversationFacade.js +280 -0
- package/src/chat-core/index.js +9 -1
- package/src/chat-core/messages/buildTimelineItems.analysis-route.test.mjs +36 -0
- package/src/chat-core/messages/buildTimelineItems.js +172 -11
- package/src/chat-core/messages/buildTimelineItems.knowledge-citation.test.mjs +183 -0
- package/src/chat-core/messages/contextUsageDefaults.js +3 -0
- package/src/chat-core/messages/contextUsageViewModel.js +147 -0
- package/src/chat-core/messages/contextUsageViewModel.test.mjs +74 -0
- package/src/chat-core/messages/useContextUsageViewModel.js +41 -0
- package/src/chat-core/orchestration/useBasicSendHandler.js +55 -0
- package/src/chat-core/pipelines/build-action-request.js +46 -0
- package/src/chat-core/pipelines/build-stream-request.js +74 -0
- package/src/chat-core/pipelines/entity-extraction.js +159 -0
- package/src/chat-core/pipelines/preprocess-message.js +16 -0
- package/src/chat-core/pipelines/stream-persist-utils.js +32 -0
- package/src/chat-core/pipelines/transport/send-mock-stream.js +86 -0
- package/src/chat-core/pipelines/transport/send-real-stream.js +330 -0
- package/src/chat-core/pipelines/transport/send-real-stream.test.mjs +27 -0
- package/src/chat-core/pipelines/transport/send-sourcing-search.js +86 -0
- package/src/chat-core/pipelines/transport/send-sourcing-search.test.mjs +14 -0
- package/src/chat-core/pipelines/transport/sourcing-response-templates.js +55 -0
- package/src/chat-core/pipelines/transport/sourcing-search-api.js +155 -0
- package/src/chat-core/runtime/runtimeMode.js +69 -0
- package/src/chat-core/session/chatSessionActionTypes.js +24 -0
- package/src/chat-core/session/chatSessionReducer.js +352 -0
- package/src/chat-core/session/chatSessionReducer.streaming-done.test.mjs +39 -0
- package/src/chat-core/session/index.js +2 -0
- package/src/chat-core/session/sessionActionsMessages.js +44 -0
- package/src/chat-core/session/sessionActionsSessionCrud.js +131 -0
- package/src/chat-core/session/sessionActionsStreaming.js +80 -0
- package/src/chat-core/session/sessionActionsUiState.js +51 -0
- package/src/chat-core/session/useChatSessionReducer.js +67 -390
- package/src/chat-core/session/useSessionListController.js +67 -0
- package/src/chat-core/stream/sse-client.js +1 -142
- package/src/chat-core/stream/sse-event-dispatcher.js +1 -0
- package/src/chat-core/stream/sse-events.js +1 -598
- package/src/chat-core/stream/useSSEStream.js +1 -273
- package/src/chat-core/stream/useStreamSendController.js +46 -0
- package/src/contracts/context-ssot.js +47 -0
- package/src/contracts/index.js +1 -0
- package/src/contracts/sse-events/base-parsers.js +79 -0
- package/src/contracts/sse-events/domain-parsers.js +3 -0
- package/src/contracts/sse-events/internal-normalizers.js +143 -0
- package/src/contracts/sse-events/parsers-intake.js +235 -0
- package/src/contracts/sse-events/parsers-runtime.js +37 -0
- package/src/contracts/sse-events/parsers-sourcing.js +179 -0
- package/src/contracts/sse-events/patch-event-parser.js +121 -0
- package/src/contracts/sse-events/runtime-parsers.js +79 -0
- package/src/contracts/sse-events.js +4 -0
- package/src/index.js +5 -0
- package/src/main.jsx +28 -0
- package/src/orchestration/index.js +6 -0
- package/src/orchestration/useSSEStream.js +221 -0
- package/src/state/index.js +4 -0
- package/vite.config.js +36 -0
package/README.md
CHANGED
|
@@ -13,3 +13,31 @@ Chat 核心能力包。
|
|
|
13
13
|
|
|
14
14
|
1. Chat GUI 宿主层。
|
|
15
15
|
2. 生成式 UI 渲染层。
|
|
16
|
+
|
|
17
|
+
## 架构边界文档
|
|
18
|
+
|
|
19
|
+
1. `docs/SSOT-CHAT-CORE-BOUNDARY.md`(SSOT)
|
|
20
|
+
2. `docs/SSOT-CHAT-CORE-DATAFLOW.md`(引用型)
|
|
21
|
+
3. `docs/CHAT-CORE-BOUNDARY.md`(引用型)
|
|
22
|
+
4. `docs/CAPABILITY-INVENTORY.md`
|
|
23
|
+
|
|
24
|
+
## 开发运行
|
|
25
|
+
|
|
26
|
+
已移除 `playground/`,统一使用根入口:
|
|
27
|
+
|
|
28
|
+
1. `npm run dev`
|
|
29
|
+
2. `npm run dev:real`
|
|
30
|
+
3. `npm run build`
|
|
31
|
+
4. `npm run preview`
|
|
32
|
+
|
|
33
|
+
## 运行模式与调试开关(Core)
|
|
34
|
+
|
|
35
|
+
Core 内置运行模式机制(`src/chat-core/runtime/runtimeMode.js`):
|
|
36
|
+
|
|
37
|
+
1. `VITE_FLARE_CHAT_RUNTIME_MODE`:可选,`development` / `production`
|
|
38
|
+
2. `VITE_FLARE_CHAT_DEBUG`:可选,`1` 或 `true` 开启 debug 日志
|
|
39
|
+
|
|
40
|
+
规则:
|
|
41
|
+
|
|
42
|
+
1. **仅在非 production 模式下允许 debug 日志输出**
|
|
43
|
+
2. 即使设置了 `VITE_FLARE_CHAT_DEBUG=1`,若模式为 production 也不会输出调试日志
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Chat Core Capability Inventory
|
|
2
|
+
|
|
3
|
+
> Scope: `packages/flare-chat-core`
|
|
4
|
+
> SSOT: `docs/SSOT-CHAT-CORE-BOUNDARY.md`
|
|
5
|
+
|
|
6
|
+
| Capability ID | What | Public API | Owner Module | Status | SSOT Ref |
|
|
7
|
+
|---|---|---|---|---|---|
|
|
8
|
+
| CCORE-SES-001 | Session/round state orchestration | `useChatSessionReducer`, `chatSessionInitialState` | `src/chat-core/session/useChatSessionReducer.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
9
|
+
| CCORE-INP-001 | Chat input state/actions | `useChatInput` | `src/chat-core/input/useChatInput.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
10
|
+
| CCORE-STR-001 | SSE stream runtime orchestration | `useSSEStream` | `src/chat-core/stream/useSSEStream.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
11
|
+
| CCORE-STR-002 | SSE transport client | `SSEClient` | `src/chat-core/stream/sse-client.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
12
|
+
| CCORE-STR-003 | SSE event normalization helpers | `* from stream/sse-events` | `src/chat-core/stream/sse-events.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
13
|
+
| CCORE-MSG-001 | Timeline view-model assembly | `buildTimelineItems` | `src/chat-core/messages/buildTimelineItems.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
14
|
+
| CCORE-SES-002 | Session list lifecycle orchestration | `useSessionListController` | `src/chat-core/session/useSessionListController.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
15
|
+
| CCORE-STR-004 | Stream send lifecycle controller | `useStreamSendController` | `src/chat-core/stream/useStreamSendController.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
16
|
+
| CCORE-ORC-001 | Basic send handler orchestration | `useBasicSendHandler` | `src/chat-core/orchestration/useBasicSendHandler.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
17
|
+
| CCORE-FCD-001 | Basic conversation facade orchestration | `useBasicConversationFacade` | `src/chat-core/facade/useBasicConversationFacade.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
18
|
+
| CCORE-APP-001 | Core app assembly orchestration | `useCoreChatApp` | `src/chat-core/app/useCoreChatApp.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
19
|
+
| CCORE-APP-002 | App stream runtime hook | `useAppStream` | `src/chat-core/app/useAppStream.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
20
|
+
| CCORE-APP-003 | App mock runtime | `createMockRuntime` | `src/chat-core/app/mockRuntime.js` | active | `docs/SSOT-CHAT-CORE-BOUNDARY.md` |
|
|
21
|
+
|
|
22
|
+
## Notes
|
|
23
|
+
|
|
24
|
+
1. Inventory entries track exports from `src/chat-core/index.js`.
|
|
25
|
+
2. New public API must be added here before cross-package usage.
|
|
26
|
+
|
|
27
|
+
## Baseline Metrics(2026-04-18)
|
|
28
|
+
|
|
29
|
+
### Code volume baseline
|
|
30
|
+
|
|
31
|
+
1. `src/chat-core` 子模块文件数:**13**
|
|
32
|
+
2. 超过 350 行文件:**3**
|
|
33
|
+
- `stream/sse-events.js`:**969**
|
|
34
|
+
- `session/useChatSessionReducer.js`:**553**
|
|
35
|
+
- `stream/useSSEStream.js`:**356**
|
|
36
|
+
3. 200~350 行文件:**2**
|
|
37
|
+
- `app/useAppStream.js`:**292**
|
|
38
|
+
- `stream/sse-client.js`:**244**
|
|
39
|
+
|
|
40
|
+
### Realignment workload baseline
|
|
41
|
+
|
|
42
|
+
见:`docs/CORE-APP-REALIGNMENT-WORKLOAD-2026-04-18.md`
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Chat Core Boundary(冻结版)
|
|
2
|
+
|
|
3
|
+
> 引用型文档:以 `SSOT-CHAT-CORE-BOUNDARY.md` 为准;本文仅保留结构示例。
|
|
4
|
+
|
|
5
|
+
## 1. 定位
|
|
6
|
+
|
|
7
|
+
`flare-chat-core` 是 **可独立运行的前端应用壳**:
|
|
8
|
+
负责路由/页面装配、会话流程编排、状态聚合、API/SSE 接入。
|
|
9
|
+
|
|
10
|
+
## 2. 责任边界(做什么)
|
|
11
|
+
|
|
12
|
+
1. 页面内组装 `flare-chat-ui` 组件并注入状态/动作
|
|
13
|
+
2. 会话与轮次状态机(session/round lifecycle)
|
|
14
|
+
3. send/stream 执行编排
|
|
15
|
+
4. API/SSE 事件消费与归一
|
|
16
|
+
5. 路由入口管理(`/chat`、`/chat/new`、`/chat/:sessionId`)
|
|
17
|
+
|
|
18
|
+
## 3. 非职责(不做什么)
|
|
19
|
+
|
|
20
|
+
1. 不实现 `chat-ui` 组件库内部视觉细节
|
|
21
|
+
2. 不在组件层散落复杂业务裁决
|
|
22
|
+
3. 不在前端推断 authoritative backend state
|
|
23
|
+
4. 不承载后端服务职责(非 SSR/非 Node API 服务)
|
|
24
|
+
|
|
25
|
+
## 4. 推荐内部结构(目标)
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
src/
|
|
29
|
+
app/ # 路由 + 页面装配(直接调用 flare-chat-ui)
|
|
30
|
+
state/ # reducer/store/selectors
|
|
31
|
+
orchestration/ # send/stream/round 编排
|
|
32
|
+
adapters/ # API/SSE client + normalize
|
|
33
|
+
contracts/ # 前端契约与守卫
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 5. 设计模式(约束)
|
|
37
|
+
|
|
38
|
+
1. Orchestrator(唯一编排入口)
|
|
39
|
+
2. Strategy Registry(规则集中,不散落 if/else)
|
|
40
|
+
3. Adapter/Port(实现可替换)
|
|
41
|
+
4. Assembler(仅装配,不拍板)
|
|
42
|
+
|
|
43
|
+
## 6. 与 Chat UI 协作协议
|
|
44
|
+
|
|
45
|
+
1. Core App 输出:页面级 `state + actions + route-context`
|
|
46
|
+
2. UI 输入:用户事件(answer/select/confirm/cancel)
|
|
47
|
+
3. authoritative 字段仅来自 API/SSE contract,不在 UI/Core 本地“猜状态”
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Chat Core App Realignment Workload(2026-04-18)
|
|
2
|
+
|
|
3
|
+
- Status: Draft-Confirmed
|
|
4
|
+
- Scope: `packages/flare-chat-core`
|
|
5
|
+
- Baseline Date: 2026-04-18
|
|
6
|
+
|
|
7
|
+
## 1) 目标确认(本次结论)
|
|
8
|
+
|
|
9
|
+
1. `chat-core` 定位为可独立运行的前端应用壳(非 SSR、非 Node API 服务)。
|
|
10
|
+
2. `chat-core` 在 `src/app` 直接组装 `flare-chat-ui` + API/SSE + 状态编排。
|
|
11
|
+
3. 不引入 `src/presentation` 额外层级。
|
|
12
|
+
4. `chat-ui` 保持独立组件库定位。
|
|
13
|
+
|
|
14
|
+
## 2) 当前工作量基线(实测)
|
|
15
|
+
|
|
16
|
+
### 2.1 关键风险文件
|
|
17
|
+
|
|
18
|
+
| 文件 | 行数 | 风险 |
|
|
19
|
+
|---|---:|---|
|
|
20
|
+
| `src/chat-core/stream/sse-events.js` | 969 | 单文件职责混合(协议/normalize/兼容) |
|
|
21
|
+
| `src/chat-core/session/useChatSessionReducer.js` | 553 | 状态与行为集中度过高 |
|
|
22
|
+
| `src/chat-core/stream/useSSEStream.js` | 356 | 事件分发分支过多 |
|
|
23
|
+
|
|
24
|
+
### 2.2 结构化改造工作包
|
|
25
|
+
|
|
26
|
+
| Workstream | 范围 | 预计文件变更数 | 预计人天 |
|
|
27
|
+
|---|---|---:|---:|
|
|
28
|
+
| WS1: 目录重整 | `src/app,state,orchestration,adapters,contracts` 建立并迁移入口 | 8-12 | 1.5-2.0 |
|
|
29
|
+
| WS2: SSE 事件拆分 | 拆 `sse-events.js` 为多文件并保持 API 兼容 | 6-8 | 2.0-2.5 |
|
|
30
|
+
| WS3: Stream 编排降复杂度 | `useSSEStream` 分发拆表驱动/handler map | 3-5 | 1.0-1.5 |
|
|
31
|
+
| WS4: Session 状态收敛 | `useChatSessionReducer` 拆分 action/reducer/selectors | 4-6 | 1.5-2.0 |
|
|
32
|
+
| WS5: App 路由落地 | `/chat` `/chat/new` `/chat/:sessionId` 页面装配 | 3-4 | 0.8-1.2 |
|
|
33
|
+
| WS6: 回归与文档 | smoke/build/关键流测试 + 文档收敛 | 4-6 | 1.0-1.5 |
|
|
34
|
+
|
|
35
|
+
**总计估算:7.8 ~ 10.7 人天(单人)**
|
|
36
|
+
|
|
37
|
+
## 3) 迭代建议(最小风险顺序)
|
|
38
|
+
|
|
39
|
+
1. Iteration-1(2~3 人天)
|
|
40
|
+
- 完成 WS1 + WS2(先拆结构,0 语义变更)
|
|
41
|
+
2. Iteration-2(2~3 人天)
|
|
42
|
+
- 完成 WS3 + WS4(降低复杂度)
|
|
43
|
+
3. Iteration-3(2~4 人天)
|
|
44
|
+
- 完成 WS5 + WS6(路由收口与验收)
|
|
45
|
+
|
|
46
|
+
## 4) 验收量化标准
|
|
47
|
+
|
|
48
|
+
1. 超过 350 行文件数量:**从 3 降到 ≤1**
|
|
49
|
+
2. `sse-events.js`:**从 969 行降到 ≤120 行(薄入口)**
|
|
50
|
+
3. 路由入口覆盖:`/chat`、`/chat/new`、`/chat/:sessionId` **100% 可运行**
|
|
51
|
+
4. UI 装配路径:统一经过 `src/app`,非 `app` 路径直连率降到 **0**
|
|
52
|
+
5. 构建与基础回归:`npm run build` + 关键聊天发送链路 **通过**
|
|
53
|
+
|
|
54
|
+
## 5) 风险与回滚点
|
|
55
|
+
|
|
56
|
+
1. 风险:SSE 拆分后事件字段兼容性回归。
|
|
57
|
+
- 回滚点:保留原 `sse-events.js` 导出签名不变。
|
|
58
|
+
2. 风险:路由引入后会话切换状态错位。
|
|
59
|
+
- 回滚点:先保留单页入口,再逐步切换到参数路由。
|
|
60
|
+
3. 风险:reducer 拆分导致 action 名称漂移。
|
|
61
|
+
- 回滚点:冻结 action type 常量,不改对外命名。
|
|
62
|
+
|
|
63
|
+
## 6) 已完成进度(2026-04-18)
|
|
64
|
+
|
|
65
|
+
### 6.1 已完成
|
|
66
|
+
|
|
67
|
+
1. `src/app/App.jsx` 已建立并接入根入口。
|
|
68
|
+
2. 新架构占位目录已落位:`src/app`、`src/state`、`src/orchestration`、`src/adapters`、`src/contracts`。
|
|
69
|
+
3. `src/index.js` 已增加新层导出,保持旧 `chat-core/*` 导出兼容。
|
|
70
|
+
|
|
71
|
+
### 6.2 占位文件
|
|
72
|
+
|
|
73
|
+
1. `src/app/index.js`
|
|
74
|
+
2. `src/state/index.js`
|
|
75
|
+
3. `src/orchestration/index.js`
|
|
76
|
+
4. `src/adapters/index.js`
|
|
77
|
+
5. `src/contracts/index.js`
|
|
78
|
+
|
|
79
|
+
### 6.3 当前完成率(按 WS)
|
|
80
|
+
|
|
81
|
+
1. WS1(目录重整):**40%**(目录与入口占位完成,逻辑迁移未开始)
|
|
82
|
+
2. WS2(SSE 拆分):**0%**
|
|
83
|
+
3. WS3(Stream 降复杂度):**0%**
|
|
84
|
+
4. WS4(Session 收敛):**0%**
|
|
85
|
+
5. WS5(路由落地):**10%**(入口集中已完成,路由未开始)
|
|
86
|
+
6. WS6(回归与文档):**20%**(SSOT/工作量文档已更新,专项回归未开始)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# SSOT: Chat Core Boundary
|
|
2
|
+
|
|
3
|
+
> Chat Core 规范以本文为唯一准绳(SSOT);其他文档仅可引用,不可平行定义。
|
|
4
|
+
|
|
5
|
+
- Status: Active
|
|
6
|
+
- Owner: FLARE Frontend/Core
|
|
7
|
+
- Scope: `packages/flare-chat-core`
|
|
8
|
+
- Last Updated: 2026-04-18
|
|
9
|
+
|
|
10
|
+
## 1. 定位(唯一事实来源)
|
|
11
|
+
|
|
12
|
+
`flare-chat-core` 是 **可独立运行的前端应用层中枢**(App-Orchestration Shell)。
|
|
13
|
+
它负责:页面装配、路由组织、会话流程编排、API/SSE 接入与状态推进。
|
|
14
|
+
它不是 SSR 应用,也不是 Node 后端服务。
|
|
15
|
+
|
|
16
|
+
## 2. 职责(必须做)
|
|
17
|
+
|
|
18
|
+
1. 作为宿主应用装配 `flare-chat-ui` 组件库(页面内实际调用)
|
|
19
|
+
2. 管理路由与页面入口(如 `/chat`、`/chat/new`、`/chat/:sessionId`)
|
|
20
|
+
3. 会话与轮次状态管理(session/round lifecycle)
|
|
21
|
+
4. send / stream / completion 执行编排
|
|
22
|
+
5. API/SSE 适配与 contract normalize
|
|
23
|
+
6. 输出稳定 UI 所需状态(state/selectors)并驱动渲染
|
|
24
|
+
|
|
25
|
+
## 3. 非职责(禁止做)
|
|
26
|
+
|
|
27
|
+
1. 不实现 `flare-chat-ui` 组件库内部视觉细节
|
|
28
|
+
2. 不把 UI 组件库业务逻辑反向塞回 `chat-ui`
|
|
29
|
+
3. 不承载后端基础设施(非 Node 服务)
|
|
30
|
+
4. 不以 SSR 形态承接当前链路
|
|
31
|
+
|
|
32
|
+
## 4. 依赖方向(硬约束)
|
|
33
|
+
|
|
34
|
+
允许:
|
|
35
|
+
|
|
36
|
+
`contracts -> adapters -> orchestration -> state -> app`
|
|
37
|
+
|
|
38
|
+
禁止:
|
|
39
|
+
|
|
40
|
+
1. `chat-ui` 反向依赖 `chat-core` 内部实现细节
|
|
41
|
+
2. `state/*` 直接触网
|
|
42
|
+
3. 在页面组件中散落协议解析/复杂编排
|
|
43
|
+
|
|
44
|
+
## 5. 目录落地(确定版)
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
src/
|
|
48
|
+
app/ # 路由 + 页面装配(直接调用 flare-chat-ui)
|
|
49
|
+
state/ # reducer/store/selectors
|
|
50
|
+
orchestration/ # send/stream/round 编排
|
|
51
|
+
adapters/ # API/SSE client + normalize
|
|
52
|
+
contracts/ # 前端契约与守卫
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
说明:不新增 `src/presentation` 分层,避免过度设计。
|
|
56
|
+
|
|
57
|
+
## 6. 与 Chat UI 协作边界
|
|
58
|
+
|
|
59
|
+
1. `chat-ui` 是独立 UI 库,只接 props/callback
|
|
60
|
+
2. `chat-core` 在 `src/app` 中直接组装并调用 `chat-ui`
|
|
61
|
+
3. `chat-ui` 不读取 `chat-core` 内部模块路径
|
|
62
|
+
|
|
63
|
+
## 7. 与 Kernel 协作边界
|
|
64
|
+
|
|
65
|
+
1. authoritative workflow state 以 kernel canonical 为准
|
|
66
|
+
2. core 可做体验态管理,但不得重定义 authoritative stage/status
|
|
67
|
+
3. core 不得依赖“猜测”补齐 authoritative 字段
|
|
68
|
+
|
|
69
|
+
## 8. 变更治理
|
|
70
|
+
|
|
71
|
+
1. 新增业务流程逻辑必须先落在 core(`orchestration/state/adapters`)
|
|
72
|
+
2. 页面呈现逻辑优先在 `src/app` 装配,避免抽象过早
|
|
73
|
+
3. 若职责冲突,以本 SSOT 为准,先改边界再改代码
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# SSOT: Chat Core Data Flow
|
|
2
|
+
|
|
3
|
+
> 引用型文档:本文件遵循 `SSOT-CHAT-CORE-BOUNDARY.md`,仅维护数据流说明。
|
|
4
|
+
|
|
5
|
+
- Status: Active
|
|
6
|
+
- Owner: FLARE Core
|
|
7
|
+
- Scope: `packages/flare-chat-core`
|
|
8
|
+
- Last Updated: 2026-04-18
|
|
9
|
+
|
|
10
|
+
## 1) 目标
|
|
11
|
+
|
|
12
|
+
定义 chat-core 作为“可运行前端应用壳”的数据流与控制点。
|
|
13
|
+
chat-core 负责装配 UI + API + 状态编排,不采用 SSR。
|
|
14
|
+
|
|
15
|
+
## 2) 核心分层
|
|
16
|
+
|
|
17
|
+
1. App Layer(路由与页面装配)
|
|
18
|
+
2. State Layer(authoritative/ui_local 状态)
|
|
19
|
+
3. Orchestration Layer(round 生命周期与执行)
|
|
20
|
+
4. Adapter Layer(API/SSE 连接与 normalize)
|
|
21
|
+
5. Contract Layer(event/patch/schema 守卫)
|
|
22
|
+
|
|
23
|
+
## 3) 端到端数据流(单轮)
|
|
24
|
+
|
|
25
|
+
1. 路由进入页面(`/chat` / `/chat/new` / `/chat/:sessionId`)
|
|
26
|
+
2. 页面装配 `flare-chat-ui` 并绑定 state + actions
|
|
27
|
+
3. 用户触发意图事件(text submit / chooser select / confirm)
|
|
28
|
+
4. orchestration 生成并发送请求(API + SSE)
|
|
29
|
+
5. adapter 接收流事件并 normalize
|
|
30
|
+
6. state/reducer 应用合法事件并更新快照
|
|
31
|
+
7. app 将最新 state 映射到 UI props
|
|
32
|
+
8. UI 重渲染并等待下一次用户动作
|
|
33
|
+
|
|
34
|
+
## 4) 状态分层(必须)
|
|
35
|
+
|
|
36
|
+
### 4.1 authoritative_state(来自 API/SSE contract)
|
|
37
|
+
|
|
38
|
+
包含但不限于:
|
|
39
|
+
|
|
40
|
+
1. `primary_flow`(stage/status/chooser_state)
|
|
41
|
+
2. `checkpoint`
|
|
42
|
+
3. `question`
|
|
43
|
+
4. `confirmed_fields`
|
|
44
|
+
5. `missing_fields`
|
|
45
|
+
6. `next_actions`
|
|
46
|
+
7. `analysis`
|
|
47
|
+
8. `capabilities.*`
|
|
48
|
+
|
|
49
|
+
### 4.2 ui_local_state(仅体验态)
|
|
50
|
+
|
|
51
|
+
包含但不限于:
|
|
52
|
+
|
|
53
|
+
1. 输入框草稿
|
|
54
|
+
2. 面板开关
|
|
55
|
+
3. 折叠/展开
|
|
56
|
+
4. loading 标记
|
|
57
|
+
5. 本地 hover/焦点
|
|
58
|
+
|
|
59
|
+
禁止将 authoritative 字段写入 ui_local 作为“替代真相”。
|
|
60
|
+
|
|
61
|
+
## 5) 事件字典(Core 视角)
|
|
62
|
+
|
|
63
|
+
### 5.1 输入事件(UI -> Core App)
|
|
64
|
+
|
|
65
|
+
1. `USER_MESSAGE_SUBMITTED`
|
|
66
|
+
2. `CHOOSER_ANSWER_SUBMITTED`
|
|
67
|
+
3. `CONFIRM_PLAN_CLICKED`
|
|
68
|
+
4. `CONTINUE_COLLECTION_CLICKED`
|
|
69
|
+
5. `INTERRUPT_ROUND_REQUESTED`
|
|
70
|
+
6. `RESUME_ROUND_REQUESTED`
|
|
71
|
+
|
|
72
|
+
### 5.2 流事件(Adapter -> Core State)
|
|
73
|
+
|
|
74
|
+
1. `STREAM_ACK`
|
|
75
|
+
2. `STREAM_PATCH_EVENT`
|
|
76
|
+
3. `STREAM_DONE`
|
|
77
|
+
4. `STREAM_ERROR`
|
|
78
|
+
|
|
79
|
+
## 6) 控制点(硬约束)
|
|
80
|
+
|
|
81
|
+
1. Round Gate:同一时刻仅 1 个 active round
|
|
82
|
+
2. Authority Gate:只有合法 patch/event 才可改 authoritative_state
|
|
83
|
+
3. Idempotency Gate:相同 `request_id + sequence` 不重复应用
|
|
84
|
+
4. Route Gate:`sessionId` 与状态不一致时先对齐,再渲染
|
|
85
|
+
5. Interrupt Gate:中断后保留已积累字段,不清空历史
|
|
86
|
+
|
|
87
|
+
## 7) 非法输入与降级
|
|
88
|
+
|
|
89
|
+
1. 非 contract 事件:忽略,不污染 authoritative
|
|
90
|
+
2. patch_scope 与 payload 不匹配:拒绝该 patch,保留当前 authoritative
|
|
91
|
+
3. `final` 文本只作为内容完成信号,不作为 authoritative patch
|
|
92
|
+
|
|
93
|
+
## 8) 与 UI 协作契约
|
|
94
|
+
|
|
95
|
+
1. UI 只能通过 app 绑定的 action API 触发流程
|
|
96
|
+
2. UI 不得直接解析原始 SSE 事件作为主状态来源
|
|
97
|
+
3. UI 只消费 app 注入的 props/view-state
|
package/index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>flare-chat-core</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flare-chat-core",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite --mode development",
|
|
8
|
+
"dev:real": "VITE_FLARE_CHAT_DEV_DEFAULT_BACKEND=real npm run dev",
|
|
9
|
+
"build": "vite build --mode production",
|
|
10
|
+
"preview": "vite preview --mode production --port 9001"
|
|
11
|
+
},
|
|
6
12
|
"main": "./src/index.js",
|
|
7
13
|
"module": "./src/index.js",
|
|
8
14
|
"exports": {
|
|
@@ -14,9 +20,25 @@
|
|
|
14
20
|
"./chat-core/session/useChatSessionReducer.js": "./src/chat-core/session/useChatSessionReducer.js",
|
|
15
21
|
"./chat-core/stream/sse-client.js": "./src/chat-core/stream/sse-client.js",
|
|
16
22
|
"./chat-core/stream/sse-events.js": "./src/chat-core/stream/sse-events.js",
|
|
17
|
-
"./chat-core/stream/useSSEStream.js": "./src/chat-core/stream/useSSEStream.js"
|
|
23
|
+
"./chat-core/stream/useSSEStream.js": "./src/chat-core/stream/useSSEStream.js",
|
|
24
|
+
"./chat-core/orchestration/useBasicSendHandler.js": "./src/chat-core/orchestration/useBasicSendHandler.js",
|
|
25
|
+
"./chat-core/session/useSessionListController.js": "./src/chat-core/session/useSessionListController.js",
|
|
26
|
+
"./chat-core/stream/useStreamSendController.js": "./src/chat-core/stream/useStreamSendController.js",
|
|
27
|
+
"./chat-core/facade/useBasicConversationFacade.js": "./src/chat-core/facade/useBasicConversationFacade.js",
|
|
28
|
+
"./chat-core/app/useCoreChatApp.js": "./src/chat-core/app/useCoreChatApp.js",
|
|
29
|
+
"./chat-core/app/useAppStream.js": "./src/chat-core/app/useAppStream.js",
|
|
30
|
+
"./chat-core/app/mockRuntime.js": "./src/chat-core/app/mockRuntime.js"
|
|
18
31
|
},
|
|
19
32
|
"peerDependencies": {
|
|
20
33
|
"react": "^18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@ant-design/icons": "^6.0.0",
|
|
37
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
38
|
+
"antd": "^6.2.2",
|
|
39
|
+
"react": "^18.3.1",
|
|
40
|
+
"react-dom": "^18.3.1",
|
|
41
|
+
"vite": "^7.2.4",
|
|
42
|
+
"flare-chat-ui": "file:../flare-chat-ui"
|
|
21
43
|
}
|
|
22
44
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { SSEClient } from './sse-client.js';
|
|
2
|
+
export { createSessionMessageAPI } from './session-message-api.js';
|
|
3
|
+
export { useSSEStream } from '../orchestration/useSSEStream.js';
|
|
4
|
+
export { useAppStream } from '../chat-core/app/useAppStream.js';
|
|
5
|
+
export { createMockRuntime } from '../chat-core/app/mockRuntime.js';
|
|
6
|
+
export { dispatchSSEEvent } from './sse-event-dispatcher.js';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { normalizeMessageRecord } from './session-message-api.normalizers.js';
|
|
2
|
+
import { requestJsonWithRouteFallback } from './session-message-api.http.js';
|
|
3
|
+
|
|
4
|
+
export function createMessageAPI(requestOptions, routeState) {
|
|
5
|
+
return {
|
|
6
|
+
async list(sessionId, params = {}) {
|
|
7
|
+
const resolvedSessionId = String(sessionId || '').trim();
|
|
8
|
+
if (!resolvedSessionId) {
|
|
9
|
+
throw new Error('sessionId is required');
|
|
10
|
+
}
|
|
11
|
+
const query = new URLSearchParams();
|
|
12
|
+
if (params.page !== undefined) query.set('page', String(params.page));
|
|
13
|
+
if (params.page_size !== undefined) query.set('page_size', String(params.page_size));
|
|
14
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : '';
|
|
15
|
+
const encodedSessionId = encodeURIComponent(resolvedSessionId);
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await requestJsonWithRouteFallback({
|
|
19
|
+
buildPath: (mode) => (mode === 'chat'
|
|
20
|
+
? `/chat/sessions/${encodedSessionId}/messages${suffix}`
|
|
21
|
+
: `/sessions/${encodedSessionId}/messages${suffix}`),
|
|
22
|
+
method: 'GET',
|
|
23
|
+
options: requestOptions,
|
|
24
|
+
routeMode: routeState.sessionMessageRouteMode,
|
|
25
|
+
setRouteMode: (nextMode) => {
|
|
26
|
+
routeState.sessionMessageRouteMode = nextMode;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (Number(error?.status) !== 404) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
const fallbackQuery = new URLSearchParams();
|
|
34
|
+
fallbackQuery.set('session_id', resolvedSessionId);
|
|
35
|
+
if (params.page !== undefined) fallbackQuery.set('page', String(params.page));
|
|
36
|
+
if (params.page_size !== undefined) fallbackQuery.set('page_size', String(params.page_size));
|
|
37
|
+
response = await requestJsonWithRouteFallback({
|
|
38
|
+
buildPath: (mode) => (mode === 'chat'
|
|
39
|
+
? `/chat/messages?${fallbackQuery.toString()}`
|
|
40
|
+
: `/messages?${fallbackQuery.toString()}`),
|
|
41
|
+
method: 'GET',
|
|
42
|
+
options: requestOptions,
|
|
43
|
+
routeMode: routeState.messageCollectionRouteMode,
|
|
44
|
+
setRouteMode: (nextMode) => {
|
|
45
|
+
routeState.messageCollectionRouteMode = nextMode;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const source = Array.isArray(response?.messages)
|
|
50
|
+
? response.messages
|
|
51
|
+
: Array.isArray(response?.data?.messages)
|
|
52
|
+
? response.data.messages
|
|
53
|
+
: [];
|
|
54
|
+
return {
|
|
55
|
+
messages: source.map(normalizeMessageRecord),
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { normalizeSessionRecord } from './session-message-api.normalizers.js';
|
|
2
|
+
import { requestJsonWithRouteFallback } from './session-message-api.http.js';
|
|
3
|
+
|
|
4
|
+
export function createSessionAPI(requestOptions, routeState) {
|
|
5
|
+
return {
|
|
6
|
+
async list(params = {}) {
|
|
7
|
+
const query = new URLSearchParams();
|
|
8
|
+
if (params.page !== undefined) query.set('page', String(params.page));
|
|
9
|
+
if (params.page_size !== undefined) query.set('page_size', String(params.page_size));
|
|
10
|
+
if (params.status) query.set('status', String(params.status));
|
|
11
|
+
if (params.function_type) query.set('function_type', String(params.function_type));
|
|
12
|
+
if (params.project_id) query.set('project_id', String(params.project_id));
|
|
13
|
+
const suffix = query.size > 0 ? `?${query.toString()}` : '';
|
|
14
|
+
const response = await requestJsonWithRouteFallback({
|
|
15
|
+
buildPath: (mode) => (mode === 'chat' ? `/chat/sessions${suffix}` : `/sessions${suffix}`),
|
|
16
|
+
method: 'GET',
|
|
17
|
+
options: requestOptions,
|
|
18
|
+
routeMode: routeState.sessionCollectionRouteMode,
|
|
19
|
+
setRouteMode: (nextMode) => {
|
|
20
|
+
routeState.sessionCollectionRouteMode = nextMode;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const source = Array.isArray(response?.sessions)
|
|
24
|
+
? response.sessions
|
|
25
|
+
: Array.isArray(response?.data?.sessions)
|
|
26
|
+
? response.data.sessions
|
|
27
|
+
: [];
|
|
28
|
+
return {
|
|
29
|
+
sessions: source.map(normalizeSessionRecord),
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async get(sessionId) {
|
|
34
|
+
const resolvedSessionId = String(sessionId || '').trim();
|
|
35
|
+
if (!resolvedSessionId) {
|
|
36
|
+
throw new Error('sessionId is required');
|
|
37
|
+
}
|
|
38
|
+
const encodedSessionId = encodeURIComponent(resolvedSessionId);
|
|
39
|
+
try {
|
|
40
|
+
const response = await requestJsonWithRouteFallback({
|
|
41
|
+
buildPath: (mode) => (mode === 'chat'
|
|
42
|
+
? `/chat/sessions/${encodedSessionId}`
|
|
43
|
+
: `/sessions/${encodedSessionId}`),
|
|
44
|
+
method: 'GET',
|
|
45
|
+
options: requestOptions,
|
|
46
|
+
routeMode: routeState.sessionDetailRouteMode,
|
|
47
|
+
setRouteMode: (nextMode) => {
|
|
48
|
+
routeState.sessionDetailRouteMode = nextMode;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
return normalizeSessionRecord(response?.data || response);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (Number(error?.status) !== 404) {
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
const listResponse = await requestJsonWithRouteFallback({
|
|
57
|
+
buildPath: (mode) => (mode === 'chat'
|
|
58
|
+
? '/chat/sessions?page=1&page_size=200'
|
|
59
|
+
: '/sessions?page=1&page_size=200'),
|
|
60
|
+
method: 'GET',
|
|
61
|
+
options: requestOptions,
|
|
62
|
+
routeMode: routeState.sessionCollectionRouteMode,
|
|
63
|
+
setRouteMode: (nextMode) => {
|
|
64
|
+
routeState.sessionCollectionRouteMode = nextMode;
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const source = Array.isArray(listResponse?.sessions)
|
|
68
|
+
? listResponse.sessions
|
|
69
|
+
: Array.isArray(listResponse?.data?.sessions)
|
|
70
|
+
? listResponse.data.sessions
|
|
71
|
+
: [];
|
|
72
|
+
const matched = source
|
|
73
|
+
.map(normalizeSessionRecord)
|
|
74
|
+
.find((item) => String(item?.sessionId || '').trim() === resolvedSessionId);
|
|
75
|
+
if (!matched) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
return matched;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async create(payload = {}) {
|
|
83
|
+
const body = {
|
|
84
|
+
function_type: payload.function_type ?? 'default',
|
|
85
|
+
title: payload.title ?? '新会话',
|
|
86
|
+
project_id: payload.project_id ?? null,
|
|
87
|
+
user_id: payload.user_id ?? null,
|
|
88
|
+
};
|
|
89
|
+
const response = await requestJsonWithRouteFallback({
|
|
90
|
+
buildPath: (mode) => (mode === 'chat' ? '/chat/sessions' : '/sessions'),
|
|
91
|
+
method: 'POST',
|
|
92
|
+
body,
|
|
93
|
+
options: requestOptions,
|
|
94
|
+
routeMode: routeState.sessionCollectionRouteMode,
|
|
95
|
+
setRouteMode: (nextMode) => {
|
|
96
|
+
routeState.sessionCollectionRouteMode = nextMode;
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
return normalizeSessionRecord(response?.data || response);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async update(sessionId, payload = {}) {
|
|
103
|
+
const resolvedSessionId = String(sessionId || '').trim();
|
|
104
|
+
if (!resolvedSessionId) {
|
|
105
|
+
throw new Error('sessionId is required');
|
|
106
|
+
}
|
|
107
|
+
const body = {};
|
|
108
|
+
if (payload.title !== undefined) {
|
|
109
|
+
body.title = payload.title;
|
|
110
|
+
}
|
|
111
|
+
if (payload.status !== undefined) {
|
|
112
|
+
body.status = payload.status;
|
|
113
|
+
}
|
|
114
|
+
if (payload.function_type !== undefined) {
|
|
115
|
+
body.function_type = payload.function_type;
|
|
116
|
+
}
|
|
117
|
+
const encodedSessionId = encodeURIComponent(resolvedSessionId);
|
|
118
|
+
const response = await requestJsonWithRouteFallback({
|
|
119
|
+
buildPath: (mode) => (mode === 'chat'
|
|
120
|
+
? `/chat/sessions/${encodedSessionId}`
|
|
121
|
+
: `/sessions/${encodedSessionId}`),
|
|
122
|
+
method: 'PATCH',
|
|
123
|
+
body,
|
|
124
|
+
options: requestOptions,
|
|
125
|
+
routeMode: routeState.sessionDetailRouteMode,
|
|
126
|
+
setRouteMode: (nextMode) => {
|
|
127
|
+
routeState.sessionDetailRouteMode = nextMode;
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
return normalizeSessionRecord(response?.data || response);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|