# AGENTS.md — 项目开发规范

## 项目概述

本项目是**沙鲁智能体工作流平台**前端应用，基于 Vue 3 + TypeScript + Vite 构建的企业级 SPA。

### 技术栈

| 类别 | 技术选型 |
|------|----------|
| 框架 | Vue 3.5 (Composition API, `<script setup>`) |
| 语言 | TypeScript 5.9 (strict mode) |
| 构建 | Vite 7 (rolldown-vite) |
| 路由 | Vue Router 4 (Hash 模式) |
| 状态管理 | Pinia 3 (Setup Store 语法) |
| UI 组件库 | Element Plus 2.x |
| CSS 方案 | UnoCSS + Less |
| 图标 | unplugin-icons (Iconify 生态) |
| 国际化 | 自研 i18n (zh-cn / en-us) |
| 代码编辑器 | Monaco Editor |
| 富文本 | TinyMCE + ByteMD (Markdown) |
| 图表 | ECharts 6 |
| 工作流编排 | @repo/workflow (内部 Monorepo 包) |

### 依赖约束

- 包管理器: pnpm
- Monorepo 架构，通过 `@repo/*` workspace 引用共享包
- `@repo/api-service` — 由 OpenAPI schema 自动生成的 API 调用层
- `@repo/api-client` — 统一 HTTP 请求客户端 (基于 Axios)
- `@repo/ui` — 共享 UI 组件库
- `@repo/workflow` — 工作流编排引擎

---

## 目录结构

```
apps/web/
├── public/                  # 静态资源
├── src/
│   ├── api/                 # 通用 API 调用 (非 repo 生成的接口)
│   │   ├── index.ts         #   文件上传等通用接口
│   │   └── interceptor.ts   #   全局响应拦截器 (错误提示)
│   ├── assets/              # 静态资源 (图片、SVG 图标等)
│   │   └── icons/           # SVG 图标 (由 vite-plugin-svg-icons 收集)
│   ├── components/          # 全局共享组件
│   │   ├── Chart/           #   图表组件
│   │   ├── Chat/            #   聊天组件 (MessageList, ChatInput)
│   │   ├── CodeEditor/      #   Monaco 代码编辑器封装
│   │   ├── MarkdownEditor/  #   ByteMD Markdown 编辑器
│   │   ├── RichEditor/      #   TinyMCE 富文本编辑器
│   │   ├── SearchDialog/    #   全局搜索弹窗
│   │   ├── Sidebar/         #   侧边栏
│   │   ├── SvgIcon/         #   SVG 图标组件
│   │   └── ...
│   ├── composables/         # 组合式函数 (Hooks)
│   │   └── useI18n.ts       #   国际化 Hook
│   ├── config/              # 应用配置
│   │   └── menu.ts          #   侧边栏菜单配置
│   ├── constant/            # 常量定义
│   ├── features/            # 功能模块 (业务组件 + 逻辑)
│   │   ├── fileUpload/      #   文件上传
│   │   ├── RunWorkflow/     #   工作流运行
│   │   ├── editorFooter/    #   编辑器底部栏
│   │   ├── setter/          #   属性配置面板
│   │   └── ...
│   ├── i18n/                # 国际化
│   │   ├── index.ts         #   i18n 实例
│   │   └── locales/         #   语言包 (zh-cn.ts, en-us.ts)
│   ├── layouts/             # 布局组件
│   │   └── MainLayout.vue   #   主布局 (侧边栏 + 内容区)
│   ├── nodes/               # 工作流节点定义
│   │   └── custom/          #   自定义节点注册
│   ├── router/              # 路由配置
│   │   └── index.ts         #   路由表 (懒加载)
│   ├── store/               # Pinia 状态管理
│   │   ├── index.ts         #   Pinia 实例创建
│   │   └── modules/         #   Store 模块 (按功能拆分)
│   ├── theme/               # 主题系统 (CSS 变量)
│   │   ├── light.ts         #   亮色主题变量
│   │   └── dark.ts          #   暗色主题变量
│   ├── types/               # 全局 TypeScript 类型定义
│   ├── utils/               # 工具函数
│   ├── views/               # 页面视图 (按功能模块分目录)
│   │   ├── agent/           #   智能体管理
│   │   ├── chat/            #   对话页面
│   │   ├── editor/          #   工作流编辑器
│   │   ├── flow/            #   工作流管理
│   │   ├── knowledge/       #   知识库管理
│   │   ├── model/           #   模型管理
│   │   ├── mcp/             #   MCP 管理
│   │   ├── prompt/          #   Prompt 管理
│   │   ├── skills/          #   技能管理
│   │   ├── storage/         #   存储管理
│   │   ├── workspace/       #   工作空间
│   │   └── ...
│   ├── App.vue              # 根组件
│   ├── main.ts              # 应用入口
│   └── style.css            # 全局样式
├── AGENTS.md                # 本文件
├── package.json
├── tsconfig.json
├── tsconfig.app.json
├── vite.config.ts
└── uno.config.ts
```

---

## 命名规范

### 文件命名

| 类型 | 规范 | 示例 |
|------|------|------|
| Vue 组件 | PascalCase | `MessageList.vue`, `ChatInput.vue` |
| 页面视图 | PascalCase (按业务模块分目录) | `views/chat/index.vue`, `views/knowledge/WikiManage.vue` |
| 组合式函数 | camelCase, `use` 前缀 | `useI18n.ts`, `useChatStream.ts` |
| 工具函数 | camelCase | `uuid.ts`, `formatDate.ts` |
| Store 模块 | camelCase, `store` 后缀 | `dashboard.ts`, `runner.store.ts` |
| 类型定义 | camelCase 或 PascalCase | `types.ts` |
| 常量文件 | camelCase | `constant/index.ts` |

### 变量/函数命名

| 类型 | 规范 | 示例 |
|------|------|------|
| 组合式函数 | `use` 前缀 | `useI18n()`, `useChatStream()` |
| 响应式变量 | camelCase | `isLoading`, `activeTab` |
| 常量 | UPPER_SNAKE 或 camelCase | `STORAGE_KEY`, `pageSize` |
| 事件处理 | `handle` / `on` 前缀 | `handleSend`, `onMounted` |
| 异步函数 | 描述性动词 | `loadConversations()`, `fetchKnowledgeOptions()` |
| 布尔变量 | `is` / `has` / `should` / `can` 前缀 | `isLoading`, `hasMore` |
| Ref 引用 | `Ref` 后缀 | `messageListRef`, `chatInputRef` |

### 组件 Props / Emits

```typescript
// Props: 使用 defineProps 泛型 + withDefaults
const props = withDefaults(defineProps<{
  messages: BubbleMessage[]
  loading: boolean
  copy?: boolean
}>(), {
  copy: true
})

// Emits: 使用 defineEmits 泛型
const emit = defineEmits<{
  retry: [message: BubbleMessage]
  addToKb: [text: string]
}>()
```

---

## 组件开发规范

### 组件目录结构

每个功能模块视图目录下可包含：

```
views/chat/
├── index.vue          # 主页面组件
├── types.ts           # 模块类型定义
├── api/               # 模块 API 调用
│   └── chat.api.ts
├── components/        # 模块私有组件
│   ├── ChatHeader.vue
│   ├── ChatSidebar.vue
│   └── AddKbModal.vue
└── composables/       # 模块私有 Hooks
    └── useChatStream.ts
```

### `<script setup>` 结构顺序

```vue
<script setup lang="ts">
// 1. 类型导入
import type { BubbleMessage } from './types'

// 2. 组合式函数
const { t } = useI18n()
const { isLoading, streamChat } = useChatStream()

// 3. Props / Emits
const props = withDefaults(defineProps<Props>(), { ... })
const emit = defineEmits<{ ... }>()

// 4. 响应式状态
const messages = ref<BubbleMessage[]>([])
const settingsDraft = reactive<ChatTargetConfig>(...)

// 5. 计算属性
const activeConversationTitle = computed(() => ...)

// 6. 工具函数
const createUserMessage = (content: string): BubbleMessage => { ... }

// 7. 生命周期
onMounted(async () => { ... })
onBeforeUnmount(() => { ... })

// 8. Watch
watch(() => props.messages.length, ...)

// 9. 暴露方法
defineExpose({ scrollToBottom })
</script>
```

### 单向数据流

- 父 → 子: Props 传递，只读
- 子 → 父: Emits 事件
- 跨层级: Pinia Store
- 禁止直接修改 Props

### 组件拆分原则

- 单个组件不超过 300 行 (模板 + 逻辑)
- 可复用的 UI 片段抽取为独立组件
- 业务逻辑复杂时抽取到 `composables/`
- API 调用抽取到 `api/` 目录

---

## TypeScript 使用规范

### 编译配置 (tsconfig.app.json)

```json
{
  "strict": true,
  "noUnusedLocals": true,
  "noUnusedParameters": true,
  "erasableSyntaxOnly": true
}
```

### 类型定义

- 优先使用 `interface` 定义对象类型，`type` 用于联合/交叉类型
- 模块级别类型定义放在 `types.ts`
- 全局通用类型放在 `src/types/`
- 避免 `any`，使用 `unknown` + 类型守卫，或具体类型
- Props 使用泛型 `defineProps<T>()`，不推荐运行时声明

### 路径别名

```typescript
import { useI18n } from '@/composables/useI18n'
import MessageList from '@/components/Chat/MessageList.vue'
// @ 映射到 src/
```

---

## 状态管理 (Pinia)

### Store 定义模式

使用 **Setup Store** 语法 (组合式):

```typescript
// store/modules/dashboard.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useDashboardStore = defineStore('dashboard', () => {
  const activeTab = ref<string>('flows')

  const setActiveTab = (tab: string) => {
    activeTab.value = tab
  }

  return { activeTab, setActiveTab }
})
```

### Store 使用规范

- Store 文件放在 `store/modules/` 下
- 按功能域拆分，避免单一巨型 store
- Store 命名: `use[Xxx]Store`
- 在 `store/index.ts` 中统一导出
- 优先使用 `ref` / `reactive` 而非 `state` 选项
- 暴露方法而非直接暴露状态修改

---

## API 调用规范

### 架构分层

```
UI 组件 / Composable
    ↓
@repo/api-service (自动生成的类型安全 API)
    ↓
@repo/api-client (统一 HTTP 客户端)
    ↓
Axios → 后端服务
```

### 自动生成的 API

`@repo/api-service` 由 `packages/api-service` 的 `openapi2ts` 从 OpenAPI schema 自动生成。各模块的 API 函数按服务拆分：

```typescript
import { knowledge, agentApplication, aiChat } from '@repo/api-service'

// 类型安全的 API 调用
const res = await knowledge.postAiFaqCreate({ ... })
const agents = await agentApplication.getAiAgentOptions()
```

### 通用 API 调用

`src/api/` 下放置非自动生成的手写接口：

```typescript
// api/index.ts
import request from '@repo/api-client'

export const uploadFile = (data: FormData) =>
  request.post<UploadResponse>('/File/Upload', data)
```

### 全局拦截器

`src/api/interceptor.ts` 注册全局响应拦截器：统一的 `isSuccess === false` 错误提示。

### API 调用原则

- 页面级 API 调用放在模块 `api/` 目录下
- 不要在组件中直接写 `fetch` 或 `axios`
- 异步调用统一使用 `async/await` + `try/catch`
- 返回值始终检查 `isSuccess`

---

## 路由规范

### 路由配置

```typescript
// router/index.ts
const routes = [
  {
    path: '/',
    component: MainLayout,       // 布局组件
    children: [                   // 嵌套子路由
      { path: '', name: 'Dashboard', component: Dashboard },
      { path: 'chat', name: 'Chat', component: Chat },
    ]
  },
  {
    path: '/workflow/:id',       // 独立路由 (无布局)
    name: 'Editor',
    component: Editor
  }
]
```

### 路由规范

- 使用 Hash 模式 (`createWebHashHistory`)
- 页面组件使用**懒加载**: `() => import('@/views/...')`
- 路由 name 使用 PascalCase
- path 使用 kebab-case
- 嵌套路由通过 `children` 配置

---

## 国际化规范

### 使用方式

```typescript
import { useI18n } from '@/composables/useI18n'
const { t } = useI18n()

// 模板中使用
{{ t('pages.chat.send') }}

// 带参数
t('pages.chat.messageCount', { count: 5 })
```

### 语言包结构

`i18n/locales/{locale}.ts` 采用层级嵌套结构：

```typescript
export default {
  common: {
    confirm: '确认',
    cancel: '取消'
  },
  pages: {
    chat: {
      send: '发送',
      emptyTitle: '开始对话'
    }
  }
}
```

### 规范

- Key 命名: `模块.页面.具体文案`，使用 `.` 分隔
- 禁止硬编码中文/英文到模板中
- 新增文案同时更新 `zh-cn.ts` 和 `en-us.ts`

---

## 样式规范

### 技术方案

- **CSS 框架**: UnoCSS (原子化 CSS)
- **预处理**: Less (组件级样式)
- **UI 库**: Element Plus (统一组件风格)
- **主题**: CSS 变量实现亮/暗色切换

### 组件样式规范

```vue
<style lang="less" scoped>
.component-name {
  // 使用 CSS 变量，支持主题切换
  color: var(--text-primary);
  background: var(--bg-container);

  // 穿透 Element Plus 子组件样式
  :deep(.el-button) { ... }
}
</style>
```

### 样式编写原则

- 优先使用 UnoCSS 原子类 (class 属性)
- 组件私有样式使用 `<style scoped>`
- 避免全局样式污染，特殊全局样式放 `style.css`
- 颜色使用 `var(--xxx)` 主题变量，不要硬编码色值
- 布局优先使用 Flexbox / Grid

---

## 开发工作流

### 本地开发

```bash
cd apps/web
pnpm dev                  # 启动开发服务器 (默认 5174 端口)
pnpm build                # 生产构建
pnpm preview              # 预览构建产物
```

### 环境变量

`.env.development`:

```ini
VITE_BASE_URL=localhost:8080
```

- 所有环境变量使用 `VITE_` 前缀
- 通过 `import.meta.env.VITE_XXX` 访问

### Code Review 要点

1. TypeScript strict 编译无错误 (`vue-tsc --noEmit`)
2. 无未使用的变量/参数 (`noUnusedLocals: true`)
3. 组件遵循单向数据流
4. 新增文案同步更新中英文语言包
5. API 调用通过 `@repo/api-service`，不在组件中裸调
6. 样式使用主题变量，支持暗色模式

---

## 性能优化

- 路由懒加载 (已配置)
- 图片使用 `el-image` 懒加载
- 大列表考虑虚拟滚动 (Element Plus 内置)
- Monaco Editor 按需加载语言 worker
- 避免不必要的 `watch` 和 `computed` 嵌套

---

## 已知约定

1. **API 层**: 接口定义在 `packages/api-service/schema/*.openapi.json`，通过 `pnpm genapi` 生成 TS 代码到 `@repo/api-service`
2. **请求客户端**: `@repo/api-client` 基于 `hook-fetch` 封装，支持 SSE 流式响应
3. **Monaco Editor**: 通过 `vite-plugin-monaco-editor` 按需加载，公共补全配置缓存在 `src/components/CodeEditor/codeEditorCompletion.ts`
4. **SVG 图标**: 放在 `src/assets/icons/`，通过 `SvgIcon` 组件使用，ID 格式为 `svg-icon-[dir]-[name]`
5. **工作流节点**: 自定义节点在 `nodes/custom/` 中注册，通过 `initializeCustomNodes()` 统一加载
