架构与实现

这一页写给要读代码或改代码的人:分层、请求流、数据模型、分页与 SQLite 限制的处理方式。

桌面外壳(Tauri 2) 窗口 · 托盘 · 自动更新 · sidecar 管理 浏览器(开发模式) http://localhost:5174 React 19 前端 页面 / 组件 / Zustand / TanStack Query SSE 事件流渲染 · 消息队列 · 工具面板 i18n(zh / en) · 深浅色主题 ASP.NET Core API REST(ApiResponse / PagedResult) + SSE /scalar/v1 接口文档 · /api/health 健康检查 领域服务(Hetu.Core) 笔记 / 对话 / 工作台 / 知识库 / 图谱 / Wiki / 记忆 仓储接口 · 后台任务协调器 · Agent 循环 模型供应商 OpenAI 兼容 · Anthropic(Key 加密存储) MCP 服务 stdio · JSON-RPC 2.0 · tools/list + tools/call Git 与 CLI 工作树 · gh / glab · 真实 PTY 外部检索 联网搜索(可开关) SQLite + sqlite-vec(默认) PostgreSQL + pgvector
整体形状:单后端进程 + 单数据库;前端只与后端 API/SSE 通信,模型与 MCP 都是出站依赖。

分层与依赖方向

Hetu.Api           控制器 / SSE 流 / 后台 Worker / DI 装配
   │  (依赖 Core 与 Infrastructure 的接口)
Hetu.Core          实体 · 领域服务 · 仓储接口        ← 不引用 EF Core / ASP.NET 实现
   ▲
Hetu.Infrastructure  EF Core 实现 · AI Provider · MCP · sqlite-vec · 文件与进程
Hetu.Shared         DTO / 枚举 / 常量(前后端契约)

依赖方向是 Api → Core ← Infrastructure:Core 只定义规则与接口,具体实现(数据库、模型调用、进程、文件系统)都在 Infrastructure。

Hetu.Api 控制器 · SSE · 后台 Worker Hetu.Core 实体 · 领域服务 · 仓储接口 Hetu.Infrastructure EF Core · AI Provider · MCP · sqlite-vec Hetu.Shared DTO / 枚举 / 常量(前后端契约) 1 控制器:校验参数 → 调用服务 2 服务:业务规则 / 编排 3 仓储:SQL 查询(分页、聚合、投影) 4 长任务:交给后台任务协调器
分层与一次典型请求的落点:控制器只做校验与转发,业务规则在服务层,SQL 在仓储层,长耗时操作交给后台任务。

一次请求的路径

  1. 前端(React + TanStack Query)调用 /api/*,统一走 axios 实例与响应拦截器。
  2. 控制器只做参数校验与调用服务;业务逻辑在 Hetu.Core/Services。
  3. 服务通过 IUnitOfWork 暴露的仓储访问数据库(EF Core),只读查询用 AsNoTracking 与投影。
  4. 需要长耗时的动作(索引、抽取、Wiki、看板执行)交给 IBackgroundTaskCoordinator,写入 TaskItem 记录后由后台 Worker 执行。
  5. 对话类请求走 SSE:控制器把事件流写回前端,前端按事件类型渲染。

数据模型要点

约定说明
基类实体继承 BaseEntity:Id(Guid)、CreatedAt、UpdatedAt
时间统一 DateTimeOffset 且为 UTC;SQLite 中存为定宽文本(yyyy-MM-dd HH:mm:ss.fffffff+00:00)
软删除IsDeleted(+ DeletedAt)配合全局查询过滤器;需要包含已删数据时显式 IgnoreQueryFilters()
迁移Code First;SQLite 与 PostgreSQL 各有一套迁移(后者在 Hetu.Infrastructure.PostgresMigrations)

数据表一览(按域)

域表关键列 / 说明
笔记Notes · Notebooks · Tags · NoteTags · NoteVersions · ShareLinks笔记含 Title/Content/NotebookId/IsPinned/IsFavorite/IsDeleted/DeletedAt;版本表按 NoteId 关联并靠 CreatedAt 排序保留 20 条;分享表存 ShareCode/ExpiresAt/ViewCount
知识底座KnowledgeItems · NoteChunks · NoteChunkEmbeddings · vec_chunk_embeddings知识项含 Type/Title/Content/SourceUrl/FileName/NoteId/IsDeleted;分块含 ChunkIndex/ChunkMethod/Content;向量元数据含 Model/Dimensions/UpdatedAt,向量本体在 vec0 虚拟表(按 rowid 对应)
图谱 / WikiGraphEntities · GraphRelations · WikiDocuments · WikiGenerationJobs实体含 Name/Type/Description;关系含 SourceId/TargetId/RelationType;Wiki 文档含 SetId/ProjectId/SortOrder/Title/Content,任务含阶段与进度
记忆Memories · MemoryEmbeddings · vec_memory_embeddings记忆含 Content/Category/Importance/Scope/AccessCount/LastAccessedAt,Dream 依据后两个字段判定衰退与遗忘
工作台WorkProjects · WorkSessions · WorkMessages · WorkCodeChunks · WorkCheckpoints会话含 ProjectId/Title/Branch/WorktreePath/PermissionMode;消息含 SessionId/Type/Content(工具结果与正文分开存);代码块向量独立于笔记向量
对话ChatGroups · ChatTopics · ChatMessages话题含 GroupId/ModelId/SystemPrompt;消息按 CreatedAt 升序读取
任务与自动化TaskItems · KanbanTasks · KanbanTaskRuns · KanbanTaskRunSteps · Workflows · WorkflowRuns · ScheduledTasks后台任务含 TaskType/EntityId/Status/StartedAt/CompletedAt/ErrorMessage;看板运行与步骤分开记录,便于回看每次执行
模型与用量AiProviders · AiModels · LlmUsageLogs · AppSettings模型含 ProviderId/ModelId/Purpose/IsDefault;用量含 Source/RefId/ModelId/InputTokens/CompressedTokens/OutputTokens/CachedTokens/LatencyMs;应用设置为键值表
其它InboxNotifications · ManagedProjects · McpServers · Skills · PromptPresetsMCP 服务器含 Type(stdio/sse)/ConnectionConfig(JSON)/IsEnabled;收件箱含分类键与出现次数(相同事件合并计数)

仓储接口

方法用途
GetByIdAsync / GetAllAsync / FindAsync常规读取(只读场景内部用 AsNoTracking)
CountAsyncSQL COUNT,不加载实体
SelectAsync只取部分列(投影),不物化完整实体
CountByAsyncSQL GROUP BY 计数,返回字典
GetPagedByDateAsync按时间字段分页:非 SQLite 走 ORDER BY + OFFSET/FETCH;SQLite 先投影 Id + 排序键 内存定序,再按主键回表取这一页
PruneAsync保留排序后前 N 条,其余用一条 DELETE 清理

关键文件落点

关注点文件
应用装配 / 中间件src/Hetu.Api/Program.cs(DI、响应缓存、健康检查、Scalar)
SSE 流式入口src/Hetu.Api/Controllers/WorkStreamController.cs · ChatMessagesController.cs
Agent 循环src/Hetu.Core/Services/AgentLoopService.cs(含运行中引导、检查点、审批)
仓储实现src/Hetu.Infrastructure/Repositories/(EfRepository 提供分页 / 聚合 / 清理原语)
数据库上下文与迁移src/Hetu.Infrastructure/Data/ 与 src/Hetu.Infrastructure.PostgresMigrations/
Git / 工作树 / PRsrc/Hetu.Api/Services/WorkGitService.cs · src/Hetu.Core/Services/Work/WorkWorktreeService.cs
压缩管道src/Hetu.Core/Services/CompressionPipelineService.cs
桌面外壳shell/hetu-desktop/src/backend.rs(sidecar 生命周期)· src-tauri/nsis-hooks.nsh(安装钩子)
前端路由与服务frontend/src/App.tsx · frontend/src/services/ · frontend/src/stores/

SQLite 的 DateTimeOffset 限制(实际踩过的坑)

  • EF Core 的 SQLite provider 不支持在 SQL 中排序或聚合 DateTimeOffset:ORDER BY 抛 NotSupportedException,MAX() 无法翻译;比较表达式(x.CreatedAt > since)同样不能翻译。
  • 处理方式:分页用 GetPagedByDateAsync;分组计数用 CountByAsync;时间戳聚合只投影两列后在内存取最大值;需要「最近 N 天」这类判断时先投影时间戳再在内存过滤。
  • 不要在 IQueryable 上直接写 OrderBy(x => x.CreatedAt)——运行时才会炸。PostgreSQL 侧不受此限制。

性能实践

场景做法
列表接口分页与排序下推数据库;先 COUNT 取总数,再取当前页
状态统计数据库侧聚合(GROUP BY / DISTINCT / EXISTS),不把整表读进内存
高频轮询接口只读状态接口加 [ResponseCache] 短时缓存;前端只在「有进行中任务」时轮询
批量写入 / 清理用 ExecuteDeleteAsync 等集合操作,避免逐条加载实体
前端渲染派生状态代替 useEffect 里同步 setState;不稳定数组用 useMemo 固定引用

Agent 循环

  1. 请求带上权限模式、工具白名单、联网/知识库/记忆开关与推理强度。
  2. 循环开始时先取「运行中引导」(steering):如果在流式过程中用户注入了新指令,会在这一步并入上下文。
  3. 模型返回工具调用 → 按权限模式决定直接执行还是发审批请求 → 执行结果回填。
  4. 过程事件通过 SSE 推送:正文增量、思考、工具调用与结果、检查点、通知。
  5. 队列中的下一条消息在本轮结束后自动发送;真实落库的会话由首条消息创建。

质量约定

  • 后端 dotnet build 与前端 npx eslint . 都要求零告警,且不接受任何抑制注释(#pragma warning disable / SuppressMessage / eslint-disable / @ts-ignore)。
  • 依赖漏洞告警按升级包处理(例如把带漏洞的传递依赖固定到修复版本),而不是忽略。
  • 文档站与 README 中英双语必须同步;截图放在 docs/screenshots/ 并保持命名稳定。