From 8c96eaaab3e3ef72541eb310212ead1f284bb810 Mon Sep 17 00:00:00 2001 From: Yumiue <229866007@qq.com> Date: Wed, 25 Mar 2026 08:44:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E6=9B=B4=E6=94=B9=E6=9C=AA=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E6=96=87=E6=A1=A3=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/AGENTS.md | 4 +- docs/TUI_REFINED_ARCHITECTURE.md | 183 +++++++++++----------------- docs/detailed_architecture_guide.md | 24 ++-- docs/structure.md | 18 +-- 4 files changed, 95 insertions(+), 134 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index ac2befd2..c1f1692b 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -3,8 +3,10 @@ ## Project Structure & Module Organization - `cmd/tui/main.go` 是TUI模式的主要入口,负责初始化配置和启动终端用户界面。 - `cmd/server/main.go` 是服务器模式的入口,用于验证服务组装,目前仅作为占位符存在。 +- `internal/tui/bootstrap/` 负责TUI启动前准备,包括工作区解析、配置初始化和程序装配。 - `internal/tui/core/` 包含TUI的核心逻辑,实现了Bubble Tea ELM架构(Model-Update-View)。 -- `internal/tui/infra/` 包含TUI的基础设施层,负责与后端服务交互。 +- `internal/tui/state/` 保存TUI纯状态结构,如聊天历史、窗口尺寸和运行时标记。 +- `internal/tui/services/` 提供TUI的本地服务适配层,负责组装后端 service/provider/repository/tools 并暴露统一接口。 - `internal/server/domain/` 定义了核心领域模型和接口,遵循依赖倒置原则。 - `internal/server/service/` 实现了应用服务层,负责编排业务流程。 - `internal/server/infra/provider/` 包含具体的外部服务提供方实现,如ModelScopeProvider。 diff --git a/docs/TUI_REFINED_ARCHITECTURE.md b/docs/TUI_REFINED_ARCHITECTURE.md index a94d1db6..7363b3be 100644 --- a/docs/TUI_REFINED_ARCHITECTURE.md +++ b/docs/TUI_REFINED_ARCHITECTURE.md @@ -1,149 +1,104 @@ -# NeoCode TUI 增强版架构设计指南 (细化版) +# NeoCode TUI 架构说明 ## 核心设计 -本架构基于 **TEA (The Elm Architecture)** 模式,遵循“**单向数据流**”与“**逻辑/视图/通信彻底解耦**”的原则。 -- **状态驱动**:界面是状态(State)的函数。 -- **异步解耦**:所有耗时操作(网络、I/O)必须通过 `tea.Cmd` 异步执行。 -- **物理隔离**:TUI 层严禁引用 `internal/server` 目录下的任何非 API 结构。 +当前 TUI 仍然基于 Bubble Tea 的 TEA 模式,但重构后职责边界已经从旧版的 `app + infra` 结构调整为 `bootstrap + core + state + components + services`: + +- 状态驱动:界面输出由 `core.Model` 持有的状态决定。 +- 异步更新:模型响应、工具执行、记忆刷新都通过 `tea.Cmd` 回流到 `Update`。 +- 依赖收口:TUI 对后端实现的依赖统一集中在 `internal/tui/services/`,`core` 和 `components` 不直接触碰 `internal/server/...`。 +- 本地组装:当前 TUI 默认通过本地 `service + provider + repository + tools` 组装聊天能力,而不是通过独立的 gRPC/HTTP 客户端。 --- -## 细化后的六层架构模型 +## 五层结构 -我们将整个 TUI 客户端细化为六个逻辑层,每个层级有严格的边界限制: +### 入口层 - `cmd/tui/` -### 入口层 (Entry) - `cmd/tui/` +- 解析命令行参数,目前支持 `--workspace`。 +- 负责启动前准备:设置终端 UTF-8、准备工作区、交互式检查 API Key、加载配置与人设。 +- 调用 `bootstrap.NewProgram(...)` 构建 Bubble Tea Program 并运行。 -* **职责**:程序的物理起点。 -* **具体任务**: - * 解析命令行启动参数(如 `--debug`, `--config`)。 - * 调用 `bootstrap` 层获取初始化好的 `Program` 实例。 - * 启动 `tea.NewProgram` 并处理最终的退出错误。 -* **禁止**:严禁编写任何业务逻辑或具体的 UI 布局代码。 +### 启动层 - `internal/tui/bootstrap/` -### 启动与注入层 (Bootstrap) - `internal/tui/bootstrap/` +- `setup.go` 负责工作区解析、配置文件初始化、API Key 校验前的交互式引导。 +- `runtime.go` 负责创建 `services.ChatClient`,再注入 `core.NewModel(...)`。 +- 这一层是依赖装配点,后续如果要切换成远端 API 客户端,也应该优先在这里替换实现。 -* **职责**:系统的“总装车间”,负责**依赖注入 (DI)**。 -* **具体任务**: - * 读取 `config.yaml` 配置文件。 - * 实例化 `services` 层(如 `APIClient`, `Logger`)。 - * 将这些服务注入到 `app` 层中。 - * **关键作用**:通过在这一层注入不同的实现,可以轻松实现“离线测试模式”或“Mock 测试”。 +### 状态机层 - `internal/tui/core/` -### 应用逻辑层 (App/Core) - `internal/tui/app/` +- `model.go` 定义顶层 `Model`,聚合 UI 状态、聊天状态、Bubble 组件实例和流式通道。 +- `update.go` 负责按键处理、命令解析、消息流更新、工具调用闭环、记忆清理与模型切换。 +- `view.go` 负责顶层布局,组合状态栏、聊天区、帮助区、输入区。 +- `msg.go` 定义流式输出、工具结果、帮助切换等内部消息。 -* **职责**:状态机中心,负责调度 `Update` 和 `View`。 -* **具体文件**: - * `model.go`: 定义顶层 `Model`,聚合各子模块状态。 - * `update.go`: 核心业务逻辑路由器。根据收到的 `tea.Msg` 决定调用哪个 Service 或更新哪个 State。 - * `view.go`: 顶层布局管理器。决定 `Header`, `Content`, `Footer` 的排版位置(使用 Lipgloss)。 - * `msg.go`: 定义所有自定义消息类型(如 `AIGeneratingMsg`, `SocketErrorMsg`)。 +### 纯状态层 - `internal/tui/state/` -### 纯状态层 (State) - `internal/tui/state/` +- `ui_state.go` 保存窗口尺寸、当前模式、自动滚动等纯 UI 状态。 +- `chat_state.go` 保存消息历史、当前模型、记忆统计、命令历史、工作区和配置状态。 +- 状态层只保留结构体定义,不承载业务流程。 -* **职责**:**数据容器**。仅存放纯粹的 Go 结构体。 -* **具体任务**: - * `ui_state.go`: 记录 UI 细节(如:窗口宽高、当前焦点在哪个输入框、滚动条位置)。 - * `chat_state.go`: 存放当前的聊天历史、AI 思考中的临时文本。 -* **准则**:这一层**不含任何方法**,只存放数据,确保状态的可序列化和易测试性。 +### 组件与适配层 -### 视图组件层 (Components) - `internal/tui/components/` +#### 视图组件 - `internal/tui/components/` -* **职责**:**原子级 UI 渲染器**(“傻瓜组件”)。 -* **具体任务**: - * `code_block.go`: 负责代码高亮渲染。 - * `status_bar.go`: 负责底部状态栏的样式。 -* **原则**: - * **输入**:仅接收基础数据或 State 结构体。 - * **输出**:返回渲染好的字符串(`string`)。 - * **禁止**:组件内严禁发起任何网络请求或修改全局状态。 +- 负责状态栏、输入框、帮助面板、消息列表、代码块高亮等纯渲染逻辑。 +- 输入是基础数据或轻量结构,输出是渲染后的字符串。 +- 不发起请求,不修改全局状态。 -### 服务对接层 (Services) - `internal/tui/services/` +#### 服务适配 - `internal/tui/services/` -* **职责**:**外交部**。负责与后端 Server 或系统环境通信。 -* **具体任务**: - * `api_client.go`: 封装对 `internal/server/transport` 的调用。 - * `file_service.go`: 处理本地文件的临时读取。 -* **原则**:所有方法必须返回 `tea.Cmd` 或在回调中触发 `tea.Msg`。 +- `api_client.go` 当前不是网络客户端,而是本地聊天适配器:直接组装 `internal/server/service`、`internal/server/infra/provider`、`internal/server/infra/repository` 和 `internal/server/infra/tools`。 +- 同时负责工作区根目录、提供商/模型规范化、工具调用封装、记忆统计等 TUI 依赖的运行时能力。 +- `core` 只依赖这里暴露的接口和数据结构,不关心底层是本地实现还是远程实现。 --- -## 标准数据流向 (Lifecycle) - -以“用户发送消息”为例: -1. **用户按下回车**:`app/update.go` 捕获到按键事件。 -2. **更新本地状态**:`update.go` 将用户输入追加到 `state/chat_state.go`,并返回一个 `tea.Cmd` 触发发送请求。 -3. **服务调用**:`services/api_client.go` 执行异步 API 调用。 -4. **结果反馈**:API 返回结果后,封装成 `APIResponseMsg` 发回给 `app/update.go`。 -5. **界面重绘**:`app/view.go` 根据更新后的 `state` 重新生成字符串,渲染到屏幕。 - - - -#### 用户视角:一个“发送消息”动作的全层级演变 - - - 假设用户在终端输入了 "你好" 并按下 回车键。以下是各层级像齿轮一样咬合转动的过程: - 第一阶段:输入捕获 (Entry -> App) - * 用户看到:手指按下回车。 - * 层级变化:入口层 (Entry) 的底层的 tea.Program 捕获到操作系统发来的按键信号,并将其包装成一个 KeyMsg - 发送给 应用逻辑层 (App) 的 Update 函数。 - - - 第二阶段:本地状态更新 (App -> State -> View) - * 层级变化: - 1. App (Update):识别出这是回车键。 - 2. State:Update 把 state.ChatState.InputBuffer 里的 "你好" 提取出来,清空输入框,并把这条消息塞进 - state.ChatState.History 数组。 - 3. View:Runtime 立即调用 View。View 发现 History 里多了一条消息,于是命令 组件层 (Components) 渲染一个新的气泡。 - * 用户看到:屏幕上自己发送的 "你好" 瞬间出现在了聊天记录区域,且输入框变空了。(此时 AI - 还没说话,但用户感觉响应非常快) - - - 第三阶段:发起异步请求 (App -> Services) - * 层级变化: - 1. App (Update):在更新完本地状态的同时,返回一个 tea.Cmd。这个命令指向 服务层 (Services) 的 SendToAI 函数。 - 2. Services:在后台偷偷发起网络请求,把 "你好" 发给后端的 Go Server。 +## 当前数据流 +以“用户输入一条消息并触发工具调用”为例: - 第四阶段:AI 响应回流 (Services -> App -> State -> View) - * 层级变化: - 1. Services:收到后端返回的 AI 回复(如 "你好!我是 NeoCode"),将其包装成一个 AIResponseMsg 投递回App。 - 2. App (Update):收到这个消息,再次修改 State,把 AI 的话加入 History。 - 3. View:Runtime 再次触发 View 重绘。 - * 用户看到:屏幕上刷新出了 AI 的回复。 +1. 用户在输入框编辑内容,`core/update.go` 处理按键并更新 `textarea` 状态。 +2. 按下 `F5` 或 `F8` 后,`handleSubmit()` 将用户消息写入 `chat_state`,然后触发 `streamResponse(...)`。 +3. `services.ChatClient.Chat(...)` 启动本地聊天服务,流式返回模型输出。 +4. `StreamChunkMsg` 持续追加 assistant 内容;`StreamDoneMsg` 在流结束时检查最后一条 assistant 消息是否是工具调用 JSON。 +5. 若检测到 `{"tool":"...","params":{...}}`,TUI 会通过 `services.ExecuteToolCall(...)` 执行工具,并把工具结果重新注入为 system 上下文。 +6. 模型基于新的上下文继续生成,直到得到最终自然语言回复。 --- -## 细化后的目录结构 +## 当前目录结构 ```text internal/tui/ -├── bootstrap/ # 依赖装配 (Runtime 构造器) -│ └── runtime.go -├── app/ # 状态机核心 (TEA 循环) -│ ├── model.go # 顶层模型定义 -│ ├── update.go # 消息分发逻辑 -│ ├── view.go # 顶层布局 (Layout) -│ ├── msg.go # 消息类型定义 -│ └── keymap.go # 快捷键配置 -├── state/ # 纯状态定义 (数据结构) -│ ├── ui_state.go # 窗口、焦点等状态 -│ └── chat_state.go # 聊天记录等数据 -├── components/ # 纯 UI 组件 (Lipgloss 渲染) -│ ├── code_block.go # 代码块组件 -│ ├── input_box.go # 输入框增强 -│ └── status_bar.go # 状态栏组件 -└── services/ # 外部适配器 (API/I/O) - ├── api_client.go # 后端通信 - └── config_svc.go # 配置读取 +├── bootstrap/ # 启动准备与依赖装配 +│ ├── runtime.go +│ └── setup.go +├── components/ # 纯渲染组件 +│ ├── code_block.go +│ ├── help.go +│ ├── input_box.go +│ ├── message_list.go +│ └── statusbar.go +├── core/ # Bubble Tea 状态机 +│ ├── model.go +│ ├── msg.go +│ ├── update.go +│ └── view.go +├── services/ # 本地服务适配与运行时能力 +│ ├── api_client.go +│ └── runtime_services.go +└── state/ # 纯状态定义 + ├── chat_state.go + └── ui_state.go ``` --- -## 开发守则 (Constraints) +## 约束建议 -1. **禁止跨层修改**:`components` 里的代码绝对不能修改 `state` 里的数据,必须通过 `Update` 函数统一处理。 -2. **样式与逻辑分离**:所有的颜色、边距(Lipgloss 样式)应在 `components` 中定义,`app/view.go` 仅负责大框架的拼装。 -3. **零后端业务依赖**:TUI 只能依赖 `api/proto` 中定义的结构。如果后端修改了业务逻辑,只要 API 不变,TUI 代码应保持一行不改。 -4. **异步命令化**:任何可能超过 10ms 的操作(读取大文件、调用 AI)必须封装在 `services` 层返回 `tea.Cmd`。 +1. `core` 不直接引用 `internal/server/...`,所有后端能力统一经 `services` 暴露。 +2. `components` 只做渲染,不做状态修改和副作用。 +3. `state` 只放结构体,避免把流程控制重新塞回状态层。 +4. 与模型、工具、工作区、记忆相关的新能力,优先落在 `services` 或 `bootstrap`,不要堆进 `cmd/tui/main.go`。 +5. 如果未来切换到远端 API,优先替换 `services.ChatClient` 实现,尽量不改 `core` 的 Update/View 逻辑。 diff --git a/docs/detailed_architecture_guide.md b/docs/detailed_architecture_guide.md index 47fbf966..b3af9b96 100644 --- a/docs/detailed_architecture_guide.md +++ b/docs/detailed_architecture_guide.md @@ -52,21 +52,21 @@ ## 3. 💡 深度解析:为什么需要 API 和 Transport?(举个例子) -很多同学会问:我直接在 TUI 里调 Service 不行吗?为什么要搞这么复杂? +很多同学会问:我直接在 TUI 里调 Service 不行吗?为什么还要分层? **我们举个“点快餐”的例子:** 1. **api/proto (菜单标准)**: - 菜单上规定:点“巨无霸”必须说明“是否加生菜”和“份数”。 - - 这就是 `api/proto` 的作用。它规定了外界(TUI)和内部(Service)说话的**统一格式**。没有它,外界乱点餐(发了一堆乱七八糟的数据),厨师(Service)就没法干活。 + - 在 NeoCode 里,`api/proto` 更像未来远程化时会用到的菜单标准;当前本地 TUI 默认并不通过它发请求,但它仍然适合作为未来 HTTP/gRPC 接口的契约来源。 -2. **Transport (传菜员)**: - - 假设今天你在店里吃(TUI 和 Server 在同一个程序里),传菜员直接把单子递给厨师; +2. **Transport / Adapter (传菜员)**: + - 假设今天你在店里吃(TUI 和 Server 在同一个程序里),传菜员可以就在后厨门口,把单子直接递给厨师; - 假设明天你用手机点外卖(TUI 在你手机上,Server 在云端),传菜员就要通过网络把单子传过去。 - - **Transport 层就是这个传菜员**。它屏蔽了“怎么传”的细节。Service 永远只需要安心做菜,不需要管单子是从柜台递进来的,还是从外卖平台发过来的。 + - 当前 TUI 的这个“传菜员”主要落在 `internal/tui/services/`:它把本地 `service/provider/repository/tools` 组装成统一的聊天客户端接口,屏蔽底层到底是本地调用还是未来远程调用。 **现阶段的意义**: -虽然目前项目是“本地直接跑”,但我们把这两个层级架设好,是为了以后如果我们想做一个**网页版**、**手机 App 版**或者**VSCode 插件版**,我们只需要增加一个新的 `Transport` 实现(比如 HTTP 或 gRPC),而核心的 `Service`(大脑逻辑)一行代码都不用改! +虽然目前项目是“本地直接跑”,但我们依然保留了 `transport` 和 `api/proto` 的演进空间。以后如果想做**网页版**、**手机 App 版**或者**VSCode 插件版**,可以新增远端 Adapter/Transport,而尽量保持现有 `service` 逻辑不变。 --- @@ -74,9 +74,11 @@ - **位置**:`internal/tui/` - **模式**:基于 Bubble Tea 的 Model-Update-View (MUV) 模式。 - - **Core (`core/`)**: 状态机。记录“当前 AI 是在思考还是在输入”。 - - **Infra (`infra/`)**: 客户端的“通讯员”。负责通过网络打电话给 Server 层。 - - **Components (`components/`)**: UI 零件。如彩色的进度条、代码高亮块。 + - **Bootstrap (`bootstrap/`)**:启动前准备,负责工作区、配置文件和 API Key 引导。 + - **Core (`core/`)**:状态机。处理按键、命令、流式响应、工具调用闭环。 + - **State (`state/`)**:纯状态结构,如消息历史、窗口尺寸、当前模型、记忆统计。 + - **Services (`services/`)**:客户端的适配层。当前以本地组装方式调用后端 service/provider/repository/tools。 + - **Components (`components/`)**:UI 零件,如状态栏、帮助面板、消息列表、代码高亮块。 --- @@ -95,7 +97,7 @@ │ │ ├── infra/ # 【重要】具体工具的实现 (调AI、写文件、跑命令) │ │ ├── service/ # 【核心】编排逻辑 (AI如何思考、如何连贯动作) │ │ └── transport/ # 【柜台】处理外界请求的入口 -│ └── tui/ # 界面展示逻辑 +│ └── tui/ # 终端界面与本地适配层 └── data/ # 记忆数据库 ``` @@ -113,6 +115,6 @@ --- ## 7. 核心开发守则 -1. **禁止跨层调用**:TUI 绝对不能绕过 Service 直接去读文件。 +1. **禁止跨层调用**:`core` 和 `components` 不能绕过 `tui/services` 直接依赖后端实现或工具实现。 2. **依赖倒置**:Service 只依赖 Domain 里的接口,不依赖 Infra 里的具体实现。 3. **安全第一**:所有 `infra/tools` 里的命令执行,必须经过安全过滤。 diff --git a/docs/structure.md b/docs/structure.md index ef044914..67b6add3 100644 --- a/docs/structure.md +++ b/docs/structure.md @@ -1,14 +1,14 @@ ### 一、 整体架构设计说明 本架构采用 “契约驱动 + 领域解耦” 的设计思路: -1. 物理隔离解耦 :后端服务(Server)与终端客户端(TUI)在 internal 目录下物理分离。TUI 严禁调用后端业务逻辑,仅通过 api/proto 定义的契约进行通信。 +1. 物理隔离解耦 :后端服务(Server)与终端客户端(TUI)在 internal 目录下物理分离。当前 TUI 通过 `internal/tui/services` 统一适配后端能力;`core`/`components` 不直接依赖 `internal/server` 的具体实现。 2. 四层分层模型 :后端遵循 Transport -> Service -> Domain <- Infra 。利用 依赖倒置(DIP) ,核心业务逻辑(Domain)定义接口,基础设施(Infra)实现接口,彻底规避新手常见的“代码一锅端”和循环依赖。 3. 约束重于灵活 :通过 internal 目录特性保护核心代码,确保所有组件必须显式依赖接口。这种结构天然支持单元测试(Mocking),且代码流向单一(自顶向下),极大地降低了心智负担。 ### 二、 完整项目目录树结构 - - api/ # API 契约:定义前后端通信协议 (.proto, .yaml) - - proto/ # gRPC/Protobuf 原始定义文件,暂时不使用,保留等待迭代 + - api/ # API 契约:保留未来远程通信所需的协议定义 + - proto/ # Protobuf 契约与生成结果 - cmd/ # 程序入口:仅负责依赖注入与启动,不含业务逻辑 - server/ # 后端服务入口 (main.go) - tui/ # TUI 客户端入口 (main.go) @@ -21,9 +21,11 @@ - transport/ # 接入层:gRPC/HTTP/LSP 路由与参数解析,暂时不使用,保留等待迭代 - infra/ # 基础设施:LLM 适配器、数据库、代码仓库实现 - tui/ # TUI 客户端核心 + - bootstrap/ # 启动准备、工作区与配置装配 - core/ # 状态管理:Bubble Tea Model 与消息循环 + - state/ # 纯状态定义 - components/ # UI 组件:可复用的终端视图组件 - - infra/ # 通信实现:后端 API 的 gRPC 客户端封装 + - services/ # 本地服务适配、工具桥接与工作区能力 - pkg/ # 内部公共库:仅限本项目内部使用的通用工具 - pkg/ # 外部公共库:可被其他项目引用的通用工具 (如 Logger) - scripts/ # 脚本:编译、Proto 生成、代码质量检查 @@ -47,7 +49,7 @@ - 【禁止规则】 :禁止出现具体的数据库 SQL 或 HTTP 请求代码。 - 【依赖规则】 :依赖 domain 。 4. internal/tui/core (TUI 状态机) -- 【目录职责】 :基于 Bubble Tea 的 ELM 架构,管理终端交互的全局状态。 -- 【准入规则】 :存放 Update 、 View 、 Model 函数。 -- 【禁止规则】 :禁止直接编写复杂的 UI 渲染代码(应拆分到 components)。 -- 【依赖规则】 :依赖 tui/components 和 tui/infra (API 客户端)。 \ No newline at end of file +- 【目录职责】 :基于 Bubble Tea 的 ELM 架构,管理终端交互、流式响应、工具调用和命令解析。 +- 【准入规则】 :存放 Update、View、Model 以及内部消息定义。 +- 【禁止规则】 :禁止直接依赖 `internal/server/...`;复杂 UI 渲染应拆分到 `components`。 +- 【依赖规则】 :依赖 `tui/components`、`tui/state` 和 `tui/services`。