Metadata-Version: 2.4
Name: bomx
Version: 0.0.1
Summary: AI-native PLM system for managing parts, BOMs, and documents
Author-email: zhangsan <acboo2020@gmail.com>
License: MIT
Keywords: ai,bom,cli,manufacturing,plm
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.11
Requires-Dist: aiosqlite>=0.20.0
Requires-Dist: alembic>=1.13.0
Requires-Dist: asyncpg>=0.29.0
Requires-Dist: fastapi>=0.115.0
Requires-Dist: httpx>=0.27.0
Requires-Dist: litellm>=1.0.0
Requires-Dist: minio>=7.2.0
Requires-Dist: passlib[bcrypt]>=1.7.4
Requires-Dist: pydantic-settings>=2.0.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: python-jose[cryptography]>=3.3.0
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: rich>=13.0.0
Requires-Dist: sqlalchemy[asyncio]>=2.0.0
Requires-Dist: typer>=0.12.0
Requires-Dist: uvicorn[standard]>=0.30.0
Description-Content-Type: text/markdown

# BOMX — AI-native PLM Agent

> 用自然语言管理零件和 BOM 的工程工具。  
> 本文档面向**学习者**，重点解释"为什么这样设计"和"代码里值得学的地方"。

---

## 目录

1. [项目是什么](#1-项目是什么)
2. [技术栈选型](#2-技术栈选型)
3. [整体架构](#3-整体架构)
4. [目录结构详解](#4-目录结构详解)
5. [数据模型](#5-数据模型)
6. [后端详解（FastAPI）](#6-后端详解fastapi)
7. [AI Agent 详解](#7-ai-agent-详解)
8. [前端详解（Vue 3）](#8-前端详解vue-3)
9. [值得学习的设计模式](#9-值得学习的设计模式)
10. [快速启动](#10-快速启动)
11. [API 一览](#11-api-一览)

---

## 1. 项目是什么

**BOMX** 是一个 **PLM（产品生命周期管理）** 工具，核心功能：

- 管理**零件**（Part）：编码、名称、类型、多版本
- 管理**BOM**（Bill of Materials，物料清单）：多级树状结构，支持草稿/发布状态
- 管理**图文档**：图纸、工艺、手册等文件，可关联到 BOM 行
- **AI 驱动**：通过自然语言对话完成以上所有操作（CLI + Web 双入口）

### 三种使用方式

```
用户
 ├── CLI（终端）  ──→  plm.py  ──→  LiteLLM + Tools  ──→  FastAPI
 ├── Web UI      ──→  Vue 3   ──→  /api/*            ──→  FastAPI
 └── Web AI 助手 ──→  Vue 3   ──→  /api/chat (SSE)   ──→  LiteLLM + Tools
```

---

## 2. 技术栈选型

### 后端

| 库 | 用途 | 学习重点 |
|----|------|---------|
| **FastAPI** | Web 框架 | 异步路由、依赖注入、Pydantic 自动校验 |
| **SQLAlchemy 2.0** | ORM | async session、mapped_column 新语法、关系加载 |
| **aiosqlite** | SQLite 异步驱动 | 单机开发无需 PostgreSQL |
| **Pydantic v2** | 数据校验/序列化 | BaseModel、字段校验、`from_attributes` |
| **LiteLLM** | LLM 多模型适配层 | 统一调用 OpenAI / Claude / Qwen，Tool Use 模式 |
| **python-multipart** | 文件上传解析 | Form + File 同时接收 |

### 前端

| 库 | 用途 | 学习重点 |
|----|------|---------|
| **Vue 3** | UI 框架 | Composition API、`<script setup>`、ref/computed |
| **Element Plus** | 组件库 | el-table、el-dialog、el-tree、el-tabs |
| **Vite** | 构建工具 | dev proxy、HMR、TypeScript 支持 |
| **Axios** | HTTP 客户端 | 与 fetch 的对比；本项目 AI 聊天改用原生 fetch 读 SSE |
| **Vue Router** | 路由 | createWebHistory、动态路由参数 |

### CLI Agent

| 库 | 用途 |
|----|------|
| **Typer** | 构建命令行工具，自动生成 `--help` |
| **Rich** | 终端彩色输出、表格、树状图、Markdown 渲染 |
| **httpx** | 同步 HTTP 客户端（agent 调用后端 API） |

---

## 3. 整体架构

```
┌─────────────────────────────────────────────────────────┐
│                      用户界面层                          │
│  ┌───────────────┐        ┌────────────────────────┐    │
│  │  CLI (plm.py) │        │   Web UI (Vue 3 :5173) │    │
│  │  Typer + Rich │        │   Element Plus         │    │
│  └──────┬────────┘        └───────────┬────────────┘    │
└─────────┼───────────────────────────────┼────────────────┘
          │ 自然语言                       │ REST / SSE
          ▼                               ▼
┌─────────────────────────────────────────────────────────┐
│                  AI Agent 层                             │
│  ┌──────────────────────────────────────────────────┐   │
│  │  LiteLLM  ←→  Tool Use Loop                      │   │
│  │  (Qwen / Claude / GPT 均可)                      │   │
│  │  工具: search_part / get_bom / create_part / ...  │   │
│  └──────────────────────┬───────────────────────────┘   │
└─────────────────────────┼───────────────────────────────┘
                          │ HTTP (httpx / fetch)
                          ▼
┌─────────────────────────────────────────────────────────┐
│               FastAPI 后端层 (:8001)                     │
│  ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐  │
│  │  /parts  │ │  /bom    │ │/documents │ │  /chat   │  │
│  └────┬─────┘ └────┬─────┘ └─────┬─────┘ └────┬─────┘  │
│       └────────────┴─────────────┴─────────────┘        │
│                    SQLAlchemy ORM (async)                │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
                    SQLite / bomx.db
                 (~/.plm/files/ 存文件)
```

### 关键设计决策

**为什么 Agent 工具调用自己的 HTTP 接口，而不是直接调 DB？**  
保持关注点分离：Agent 层只知道"后端有哪些 API"，不关心数据库细节。这样 Agent 可以脱离后端单独测试，也方便未来指向远程服务器。

**为什么 Web AI 聊天用 SSE 而不是 WebSocket？**  
SSE（Server-Sent Events）是单向流，只需要普通 HTTP，不需要握手协议，浏览器原生支持。工具调用进度推送场景天然适合 SSE。WebSocket 适合双向实时通信（如多人协作），此处过度设计。

---

## 4. 目录结构详解

```
bomx/
│
├── app/                        # FastAPI 后端
│   ├── main.py                 # 应用入口：注册路由、数据库初始化
│   │
│   ├── models/                 # SQLAlchemy ORM 模型（数据库表结构）
│   │   ├── base.py             # 公共 Mixin：UUID主键、创建/更新时间
│   │   ├── part.py             # 零件：Part + PartVersion（主版本模式）
│   │   ├── bom.py              # BOM：BomHeader + BomLine + BomLineDocument
│   │   ├── document.py         # 文档：Document + DocumentVersion + FileObject
│   │   ├── user.py             # 用户（占位，认证暂未实现）
│   │   └── audit.py            # 审计日志（SQLAlchemy event hook 自动记录）
│   │
│   ├── routers/                # FastAPI 路由处理器（Controller 层）
│   │   ├── part.py             # /api/parts — 零件 CRUD
│   │   ├── bom.py              # /api/bom   — BOM 增删改查、发布
│   │   ├── document.py         # /api/bom/lines/{id}/documents — BOM行附件
│   │   ├── documents.py        # /api/documents — 全局文档库（含独立上传）
│   │   └── chat.py             # /api/chat  — AI 对话（SSE 流式）
│   │
│   └── schemas/                # Pydantic 模型（请求体/响应体定义）
│       ├── part.py             # PartCreate、PartResponse 等
│       ├── bom.py              # BomHeaderCreate、BomLineResponse 等
│       └── document.py         # BomLineDocumentResponse 等
│
├── agent/                      # CLI AI Agent
│   ├── loop.py                 # REPL 主循环：读输入 → LLM → 工具 → 输出
│   ├── tools.py                # 工具定义（LiteLLM schema）+ 工具执行函数
│   ├── api.py                  # httpx 封装：调用后端 REST API
│   └── display.py              # Rich 渲染：零件表格、BOM 树状图
│
├── web/                        # Vue 3 前端
│   └── src/
│       ├── App.vue             # 应用外壳：顶栏、侧边栏、主内容区（CSS Grid）
│       ├── style.css           # 全局设计系统（CSS 变量、工具类）
│       ├── main.ts             # Vue 应用入口
│       │
│       ├── api/                # 前端 HTTP 客户端（axios 封装）
│       │   ├── part.ts
│       │   ├── bom.ts
│       │   └── document.ts
│       │
│       ├── components/         # 全局组件
│       │   ├── AIDrawer.vue    # ⌘I AI 聊天抽屉（SSE 流式 + 历史记录）
│       │   └── CommandPalette.vue  # ⌘K 命令面板
│       │
│       ├── views/              # 页面组件
│       │   ├── PartList.vue    # 零件列表（搜索、过滤、新建）
│       │   ├── PartDetail.vue  # 零件详情（版本管理）
│       │   ├── BomList.vue     # BOM 列表（新建向导）
│       │   ├── BomDetail.vue   # BOM 详情（可拖拽分栏、多级树、文档关联）
│       │   └── DocumentList.vue # 文档库（上传、预览、下载）
│       │
│       └── router/index.ts     # 路由配置
│
├── database.py                 # 数据库连接：引擎创建、session 工厂
├── plm.py                      # CLI 入口：Typer app，读取 .env
├── seed_examples.py            # 示例数据：智能手环 + 电动自行车两个产品
├── seed.py                     # 最小种子：系统用户
│
├── start.bat                   # Windows：同时启动后端 + 前端
├── stop.bat                    # Windows：按窗口标题关闭服务
└── restart.bat                 # Windows：stop + start
```

---

## 5. 数据模型

### 核心概念：主记录 + 版本记录

项目采用"**主记录不可变，变更产生版本**"的模式：

```
Part (身份)  ──< PartVersion (历史版本)
  code             version: "A", "B", "1.0"
  name             is_current: true/false
  part_type        current_version_id ──┐
  └──────────────────────────────────── ┘ 快速查当前版
```

这是工业 PLM 系统的标准设计，好处是：
- 零件的"身份"（编码）永远不变
- 每次改设计参数，产生新版本，旧版本完整保留
- BOM 可以固定引用某个版本（`child_ver_rule = "fixed"`）

### 完整 ER 关系

```
Part ──────────────< PartVersion
  │                      │
  │                      └──── BomHeader ──────< BomLine
  │                                                 │
  │                                        child_part_id (→ Part)
  │                                                 │
  │                                           BomLineDocument
  │                                                 │
  └────── PartDocument                         Document
                │                                   │
                └──────────────────────────── DocumentVersion
                                                    │
                                               FileObject
                                          (MD5去重，物理文件)
```

### 多级 BOM 的实现方式

每个**总成零件**有自己独立的 `BomHeader`。多级结构通过递归查询实现：

```
查询 BAND-001 的BOM树：
  1. 找 BAND-001 的 BomHeader → 得到直属子件列表
  2. 对每个子件，检查它是否也有 BomHeader（即它是否也是总成）
  3. 如果是，递归获取其 BomHeader
  4. 组装成树（DFS，Set 防循环）
```

---

## 6. 后端详解（FastAPI）

### 异步数据库 Session 管理

**`database.py`** 是理解异步 SQLAlchemy 的核心：

```python
# 创建异步引擎
engine = create_async_engine(DATABASE_URL, echo=False)

# Session 工厂
AsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)

# 依赖注入函数（每个请求获得独立 session）
async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionLocal() as session:
        async with session.begin():   # 自动提交/回滚
            yield session
```

**FastAPI 依赖注入**用法：
```python
@router.get("/parts")
async def list_parts(db: AsyncSession = Depends(get_db)):
    # db 由框架自动注入，请求结束后自动关闭
```

### Pydantic + SQLAlchemy 分层

项目严格分离了两种模型：

| 层 | 文件位置 | 作用 |
|----|---------|------|
| ORM 模型 | `app/models/` | 定义数据库表结构，SQLAlchemy 使用 |
| Schema 模型 | `app/schemas/` | 定义 API 请求/响应格式，Pydantic 使用 |

ORM 模型转 Pydantic 靠 `model_config = {"from_attributes": True}`，让 Pydantic 可以直接读 SQLAlchemy 对象的属性。

### 文件去重：MD5 内容寻址

**`app/routers/documents.py`** 中的 `_get_or_create_file_object()`：

```python
md5 = hashlib.md5(data).hexdigest()
existing = await db.scalar(select(FileObject).where(FileObject.md5 == md5))
if existing:
    return existing  # 相同内容不重复存储
# 否则写入磁盘 → ~/.plm/files/{md5[:2]}/{md5}/{原文件名}
```

这是内容寻址存储（Content-Addressable Storage）的简化版，Git 也用同样思路。

### 审计日志：SQLAlchemy Event Hook

**`app/models/audit.py`** 注册了 `after_flush` 事件，无需在每个接口里手写日志：

```python
@event.listens_for(Session, "after_flush")
def _after_flush(session, flush_context):
    for obj in session.new:      # 新增的对象
        _log(session, obj, "create")
    for obj in session.dirty:    # 修改的对象
        _log(session, obj, "update", 记录字段变化)
    for obj in session.deleted:  # 删除的对象
        _log(session, obj, "delete")
```

### SSE 流式响应

**`app/routers/chat.py`** 展示了如何用 FastAPI 发送 SSE：

```python
def generate():          # 普通生成器函数
    yield "data: {...}\n\n"   # SSE 格式：data: + JSON + 双换行
    yield "data: {...}\n\n"

return StreamingResponse(generate(), media_type="text/event-stream")
```

`StreamingResponse` 接收生成器，FastAPI 把它放入线程池运行（因为是同步 `def`），边计算边推送。

---

## 7. AI Agent 详解

### Tool Use（工具调用）模式

这是项目的核心 AI 技术，流程：

```
用户输入
    │
    ▼
LLM（带工具定义）
    │
    ├── 直接回答？ ──→ 输出文本，结束
    │
    └── 需要调工具？
            │
            ▼
        解析 tool_calls（LLM 告诉我们调哪个工具、传什么参数）
            │
            ▼
        execute_tool()（Python 代码真正调用 API）
            │
            ▼
        把工具结果塞回 history
            │
            ▼
        再次调用 LLM ──→ 循环，直到 LLM 不再调工具
```

**`agent/tools.py`** 里，每个工具有两部分：

```python
# 1. Schema（告诉 LLM 这个工具能做什么、参数是什么）
{
    "type": "function",
    "function": {
        "name": "search_part",
        "description": "按编码或名称搜索零件",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "搜索关键词"}
            },
            "required": ["query"]
        }
    }
}

# 2. 执行函数（真正干活的 Python 代码）
def _search_part(query: str, base_url: str = None) -> str:
    parts = api.list_parts(query=query, base_url=base_url)
    return f"找到 {len(parts)} 个零件：..."
```

### LiteLLM 的价值

```python
# 调用 Claude
litellm.completion(model="claude-sonnet-4-6", messages=..., tools=...)

# 调用 Qwen（只改 model 和 api_base，代码完全一样）
litellm.completion(model="openai/qwen-plus", api_base="https://...", messages=..., tools=...)
```

LiteLLM 统一了不同 LLM 厂商的 API 差异，项目切换模型只需改 `.env` 里的 `PLM_MODEL`。

### history 的结构

LLM 对话历史是一个消息列表，工具调用时结构较复杂：

```python
history = [
    {"role": "user", "content": "查看手环的BOM"},
    # LLM 决定调工具，返回这样的消息：
    {"role": "assistant", "content": "", "tool_calls": [
        {"id": "call_xxx", "function": {"name": "get_bom", "arguments": '{"part_code":"BAND-001"}'}}
    ]},
    # 工具执行结果，必须用 tool_call_id 对应：
    {"role": "tool", "tool_call_id": "call_xxx", "content": "BOM已渲染，直属子件6个..."},
    # LLM 看到工具结果后，给出最终回答：
    {"role": "assistant", "content": "BAND-001 手环的BOM包含6个直属子件..."}
]
```

---

## 8. 前端详解（Vue 3）

### Composition API + `<script setup>`

项目全部使用 Vue 3 最新写法：

```vue
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'

// ref: 响应式基本值
const loading = ref(false)
const parts = ref<Part[]>([])

// computed: 依赖 parts 自动重算
const activeParts = computed(() => parts.value.filter(p => p.status === 'active'))

// 生命周期
onMounted(() => load())

async function load() { ... }
</script>
```

相比 Options API（`data()`, `methods:`, `computed:`），Composition API 把**相关逻辑组织在一起**，更适合复杂组件。

### CSS Grid 应用外壳

**`App.vue`** 用 CSS Grid 实现固定布局（不出现双滚动条的关键）：

```css
.bx-app {
  display: grid;
  grid-template-columns: 220px 1fr;    /* 侧边栏 | 主内容 */
  grid-template-rows: 52px 1fr 28px;  /* 顶栏 | 内容 | 底栏 */
  height: 100vh;
  overflow: hidden;  /* 整体不滚动 */
}

.bx-main {
  overflow-y: auto;  /* 只有主内容区滚动 */
}
```

### BomDetail 的可拖拽分栏

**`BomDetail.vue`** 实现了鼠标拖拽调整左右面板宽度：

```javascript
function startResize(e: MouseEvent) {
  resizing.value = true
  const startX = e.clientX
  const startW = treeWidth.value

  function onMove(e: MouseEvent) {
    treeWidth.value = Math.min(600, Math.max(220, startW + (e.clientX - startX)))
  }
  function onUp() {
    resizing.value = false
    window.removeEventListener('mousemove', onMove)
    window.removeEventListener('mouseup', onUp)
  }
  window.addEventListener('mousemove', onMove)
  window.addEventListener('mouseup', onUp)
}
```

核心思路：记录起始鼠标位置和起始宽度，在 `mousemove` 里计算差值，注意在 `mouseup` 时清除监听器防止内存泄漏。

### SSE 流式接收（AIDrawer）

`fetch` 原生支持流式读取，比 `EventSource` 更灵活（支持 POST）：

```javascript
const res = await fetch('/api/chat', { method: 'POST', body: ... })
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''

while (true) {
  const { done, value } = await reader.read()
  if (done) break

  buffer += decoder.decode(value, { stream: true })
  const lines = buffer.split('\n')
  buffer = lines.pop() ?? ''  // 保留不完整的行，等下次 read()

  for (const line of lines) {
    if (line.startsWith('data: ')) {
      const event = JSON.parse(line.slice(6))
      // 处理事件...
    }
  }
}
```

`buffer = lines.pop()` 是处理 TCP 分包的关键：一次 `read()` 可能只收到半行，需要缓存等待后续数据。

### API 层封装

**`web/src/api/`** 目录把所有 HTTP 调用集中管理：

```typescript
// part.ts
const http = axios.create({ baseURL: '/api' })

export const partApi = {
  list: (params?) => http.get<Part[]>('/parts', { params }).then(r => r.data),
  get: (code) => http.get<PartDetail>(`/parts/${code}`).then(r => r.data),
  create: (data) => http.post<Part>('/parts', data).then(r => r.data),
}
```

好处：所有接口地址集中在一处，修改时不用找遍所有 Vue 文件。

---

## 9. 值得学习的设计模式

### 1. 主记录 + 版本记录（Master-Version Pattern）
`Part → PartVersion`，`Document → DocumentVersion`。工业软件的经典模式，保留完整历史，`current_version_id` 外键提供快速查当前版本的能力。

### 2. 依赖注入（FastAPI Depends）
每个路由函数通过 `Depends(get_db)` 自动获得数据库 session，框架负责生命周期管理。这是控制反转（IoC）思想的实践。

### 3. 内容寻址存储（Content-Addressable Storage）
文件按 MD5 存储，相同内容只存一份。Git 对象库、Docker 镜像层都用同样思路。

### 4. SSE for Server Push
后端用生成器函数 `yield` 事件，前端用 `ReadableStream` 读取。比轮询高效，比 WebSocket 简单，适合"服务端单向推送进度"场景。

### 5. LLM Tool Use 循环
"LLM → 调工具 → 结果回填 → 再次 LLM"的循环模式，是 AI Agent 的基础架构。理解这个循环，就理解了 ChatGPT Plugins、Claude Tool Use、OpenAI Function Calling 的底层逻辑。

### 6. 输入历史（Arrow Key Navigation）
`history.unshift(content)` 记录，`historyIndex` 跟踪位置，`inputDraft` 保存未发送草稿。这是命令行 shell 历史的 Web 版实现。

---

## 10. 快速启动

### 环境要求

- Python 3.11+
- Node.js 18+

### 安装

```bash
# 克隆项目
git clone https://github.com/zhangyida-lab/bomx.git
cd bomx

# 安装 Python 依赖
pip install -e .

# 安装前端依赖
cd web && npm install && cd ..

# 配置环境变量
cp .env.example .env  # 编辑 .env，填入 LLM API Key
```

### `.env` 配置

```ini
# 使用阿里千问（推荐）
PLM_LLM_API_KEY=sk-你的key
PLM_LLM_API_BASE=https://dashscope.aliyuncs.com/compatible-mode/v1
PLM_MODEL=openai/qwen-plus

# 或使用 Claude
# ANTHROPIC_API_KEY=sk-ant-...
# PLM_MODEL=claude-sonnet-4-6

# 后端地址（通常不用改）
PLM_API_URL=http://localhost:8001
```

### 启动服务

```bash
# Windows（推荐）：同时启动后端 + 前端
start.bat

# 或手动启动：
# 终端1 - 后端
uvicorn app.main:app --reload --host 0.0.0.0 --port 8001

# 终端2 - 前端
cd web && npm run dev
```

### 初始化示例数据

```bash
python seed_examples.py
```

创建两个产品：**智能健康手环（BAND-001）** 和 **城市电动自行车（EB-001）**，各含 4 级 BOM。

### 访问

| 服务 | 地址 |
|------|------|
| Web UI | http://localhost:5173 |
| API 文档 | http://localhost:8001/docs |
| CLI Agent | `python plm.py` |

### 停止 / 重启

```bash
stop.bat      # 停止
restart.bat   # 重启
```

---

## 11. API 一览

### 零件

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/parts` | 列表（支持 q / part_type / status 过滤） |
| POST | `/api/parts` | 创建零件 |
| GET | `/api/parts/{code}` | 详情（含版本列表） |
| PATCH | `/api/parts/{code}` | 更新 |
| POST | `/api/parts/{code}/versions` | 新增版本 |
| PATCH | `/api/parts/{code}/versions/{id}/activate` | 设为当前版本 |

### BOM

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/bom` | BOM 列表 |
| POST | `/api/bom` | 创建 BOM（传 part_version_id） |
| GET | `/api/bom/{id}` | BOM 详情（含明细行） |
| POST | `/api/bom/{id}/release` | 发布（发布后只读） |
| POST | `/api/bom/{id}/lines` | 添加 BOM 行 |
| PATCH | `/api/bom/lines/{id}` | 修改 BOM 行 |
| DELETE | `/api/bom/lines/{id}` | 删除 BOM 行 |

### 文档

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/documents` | 全局文档列表（含未关联文档） |
| POST | `/api/documents` | 上传独立文档 |
| POST | `/api/bom/lines/{id}/documents` | 上传并关联到 BOM 行 |
| POST | `/api/bom/lines/{id}/documents/attach` | 关联已有文档到 BOM 行 |
| GET | `/api/bom/lines/{id}/documents/{link_id}/file` | 下载/预览文件 |
| DELETE | `/api/bom/lines/{id}/documents/{link_id}` | 移除关联 |

### AI 聊天

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/chat/config` | 返回当前模型名 |
| POST | `/api/chat` | 发送消息（SSE 流式响应） |

---

## CLI Agent 工具清单

| 工具 | 说明 |
|------|------|
| `search_part(query)` | 模糊搜索零件 |
| `get_bom(part_code)` | 获取多级 BOM 树（终端渲染 + 返回文字摘要） |
| `create_part(code, name, part_type)` | 创建零件 |
| `update_part(code, ...)` | 更新零件信息 |
| `add_bom_line(parent_code, child_code, quantity)` | 添加 BOM 行（自动建草稿） |
| `remove_bom_line(line_id)` | 删除 BOM 行 |
