# vibe-next-template

Nova Agent 示例模板工程。[Nova](https://nova.betteryeah.com) 是一个创建 Agent 的 SaaS 产品，可以通过 Nova 创建能够操作沙箱、浏览器、文件系统的专业 Agent。本项目基于 Next.js 提供完整的 Agent 聊天前端，包含消息流、事件列表、会话管理与文件上传能力。

项目中 `.nova/config.json` 内置了一个示例 Agent，如需基于本模板开发自己的应用，请在 Nova 平台创建 Agent 并替换配置。

## 技术栈

- Next.js 16（App Router） / React 19 / TypeScript 5
- Tailwind CSS 4 / Radix UI / Lucide React Icons
- Zustand（状态管理）
- 自定义 WebSocket 客户端（实时通信）
- 自定义 HTTPClient（Fetch 封装，支持拦截器）
- Drizzle ORM + PostgreSQL
- Winston（日志，生产环境按天轮转）
- Shiki（代码高亮）/ React Markdown + Remark GFM

## 快速开始

1. 安装依赖：`pnpm install`
2. `.env`环境变量文件已经初始化完成了，所以设计环境变量的需要在这里修改
3. （可选）修改 `.nova/config.json`，替换为你自己的 Agent
4. 启动开发服务器：`pnpm dev`（端口 13000）

## Agent 配置

Agent 配置存放在 `.nova/config.json`：

```json
{
  "agents": [
    {
      "agent_id": "70da765f2d42490ca574d72dce4d24fe",
      "agent_name": "Nova示例Agent",
      "agent_description": "一个Nova的Agent示例，包含Agent具备的通用功能，文件操作、沙箱环境执行、浏览器操作等等。"
    }
  ]
}
```

- `agents` 数组中的第一个 Agent 作为默认使用的 Agent
- 在 Nova 平台创建新 Agent 后，将 `agent_id`、`agent_name`、`agent_description` 替换为新 Agent 的信息
- 配置通过 `app/api/nova-config.ts` 的 `getDefaultAgentId()` 在服务端读取，启动后缓存

## 目录结构

```
.nova/
  config.json             # Agent 配置（agent_id, agent_name, agent_description）

app/
  layout.tsx              # 根布局（Geist Sans/Mono 字体）
  page.tsx                # 主入口，初始化聊天连接
  globals.css             # 全局样式
  api/                    # Next.js API 路由（BFF 层，代理 Nova 平台 API）
    oapi-client.ts        # 共享的 Nova OpenAPI HTTPClient 实例
    nova-config.ts        # 读取 .nova/config.json，提供 getDefaultAgentId()
    info/route.ts         # 返回客户端配置：agentId, conversationId, wssUrl, token
    chat/event/route.ts   # 代理获取聊天事件历史
    chat/stop/route.ts    # 停止当前任务
    conversation/route.ts # 会话列表
    file/upload/route.ts  # 文件上传
    file/sign/route.ts    # 文件签名 URL
    health/route.ts       # 健康检查

components/
  nova-sdk/               # 核心聊天 SDK
    types.ts              # 核心类型定义（ApiEvent, PlatformConfig, TaskArtifact 等）
    websocket.ts          # WebSocket 客户端（自动重连、心跳、离线队列）
    store/
      useNovaStore.ts     # Zustand 全局状态（events, artifacts, ws 状态, loading）
    context/
      context.ts          # NovaContext 定义（HTTPClient + getArtifactUrl）
      Provider.tsx         # Context Provider
      useNova.ts          # useContext hook
    hooks/
      useNovaChatLogic.ts # 主编排 hook，组合所有子 hook
      useNovaEvents.ts    # WebSocket + 历史事件获取
      useNovaService.ts   # 从 platformConfig 创建 HTTPClient
      useMessageSender.ts # 通过 WebSocket 发送消息
      useEventProcessor.ts # ApiEvent → UI 消息转换
      useBuildConversationConnect.ts # 初始化：获取 agentId、conversationId
      useFileUploader.ts  # 文件上传逻辑
      useAttachmentHandlers.ts # 附件点击处理
      useMessageScroll.ts # 消息列表自动滚动
      usePanelState.ts    # 产物面板开关
      useSize.ts          # 响应式尺寸
    nova-chat/            # 聊天主组件
      index.tsx           # NovaChat（包裹 Provider、Header、MessageList、Input、TaskPanel）
      ChatHeader.tsx      # 聊天头部
      ChatInputArea.tsx   # 输入区域
    message-list/         # 消息列表
      index.tsx           # MessageList
      MessageItem.tsx     # 单条消息
      AttachmentItem.tsx  # 文件附件
      ImageAttachmentItem.tsx # 图片附件
      ToolCallAction.tsx  # 工具调用展示
    message-input/        # 消息输入
      index.tsx           # 输入组件
      FilePreviewList.tsx # 文件预览列表
    task-panel/           # 产物预览面板
      index.tsx           # TaskPanel（侧边栏，50% 宽度）
      ArtifactList.tsx    # 产物列表
      ArtifactPreview.tsx # 产物预览
      Preview/            # 各类预览组件（Markdown、代码、PPT、工具调用）
  ui/                     # 基础 UI 组件（Shadcn 风格）
    button.tsx, dialog.tsx, scroll-area.tsx, image.tsx, image-preview.tsx

http/
  index.ts                # HTTPClient 类（Fetch 封装）
  http.ts                 # 底层 fetch 包装，支持 onRequest/onResponse/onError 钩子
  type.ts                 # HTTP 类型定义

db/
  index.ts                # Drizzle ORM 连接（server-only，全局单例）

utils/
  logger.ts               # Winston 日志（开发→终端，生产→按天文件轮转）
  cn.ts                   # clsx + tailwind-merge 工具函数
```

## 数据流

```
app/page.tsx
  │  useBuildConversationConnect() → GET /api/info
  │  获取 agentId（来自 .nova/config.json）, conversationId, platformConfig
  ▼
NovaChat 组件
  │  useNovaChatLogic() 编排所有子 hook
  ├── useNovaEvents()    → WebSocket 连接 + GET /api/chat/event 加载历史
  ├── useMessageSender() → WebSocket 发送消息
  └── useEventProcessor()→ 事件转 UI 消息
  ▼
Zustand Store (useNovaStore)
  │  events[], artifacts[]（从 events 自动提取）, loading, ws 状态
  ▼
MessageList / TaskPanel 渲染
```

## API 路由

| 路由 | 方法 | 说明 |
|------|------|------|
| `/api/info` | GET | 返回客户端配置：agentId, conversationId, wssUrl, token |
| `/api/chat/event` | GET | 代理 Nova `/chat/event_list`，获取事件历史 |
| `/api/chat/stop` | POST | 停止当前任务 |
| `/api/conversation` | GET | 会话列表 |
| `/api/file/upload` | POST | 上传文件 |
| `/api/file/sign` | POST | 获取文件签名 URL |
| `/api/health` | GET | 健康检查 |

所有 API 路由通过 `app/api/oapi-client.ts` 中的共享 HTTPClient 代理到 Nova 平台，请求头自动注入 `Tenant-Id` 和 `Authorization`。Agent ID 从 `.nova/config.json` 读取，不再依赖环境变量。

## WebSocket 消息格式

- 心跳：`{ message_type: 'ping' }`
- 聊天消息：`{ message_type: 'chat', conversation_id, content, ... }`
- 切换会话：`{ message_type: 'switch_conversation', conversation_id }`

WebSocket 客户端内置自动重连（可配置次数/间隔）、心跳检测、网络状态监听、页面可见性恢复。

## 常用命令

```bash
pnpm install          # 安装依赖
pnpm dev              # 启动开发服务器（端口 13000）
pnpm build            # 生产构建
pnpm start            # 生产运行（端口 13000）
pnpm lint             # ESLint 检查并自动修复
pnpm db:generate      # 生成 Drizzle 迁移
pnpm db:migrate       # 执行数据库迁移
pnpm db:studio        # 打开 Drizzle Studio GUI
```

## 环境变量

参考 `.env.example`，在项目根目录创建 `.env.local`：

| 变量名 | 说明 |
|--------|------|
| `DATABASE_URL` | PostgreSQL 连接地址（Drizzle 使用） |
| `LOG_DIR` | 生产环境日志目录（默认 `./logs`，按天切分） |
| `NOVA_BASE_URL` | Nova OpenAPI 服务地址 |
| `NOVA_TENANT_ID` | 租户 ID（请求头 `Tenant-Id`） |
| `NOVA_ACCESS_KEY` | 鉴权密钥（请求头 `Authorization`） |

> Agent ID 已从环境变量迁移至 `.nova/config.json`，无需在 `.env.local` 中配置。

## 代码约定

- **路径别名**：`@/*` → `./*`（tsconfig paths）
- **组件**：PascalCase 文件名，使用 `React.memo()` 优化
- **Hooks**：camelCase，`use*` 前缀
- **工具函数**：camelCase
- **常量枚举**：UPPER_SNAKE_CASE（如 `EventType`, `TaskStatus`）
- **样式**：Tailwind CSS 工具类，通过 `cn()` 函数（`utils/cn.ts`）合并类名
- **回调稳定性**：使用 ahooks 的 `useMemoizedFn()` 或 `useCallback()`
- **异步状态**：`queueMicrotask()` 延迟更新，避免 React 批量更新冲突
- **Store 去重**：Zustand store 按 `event_id` 去重事件
- **日志**：开发环境输出到终端，生产环境按天写入 `${LOG_DIR}/app-YYYY-MM-DD.log`
- **数据库**：所有操作通过 Drizzle ORM，`import { db } from '@/db'`，仅限 server 端
- **UI 错误提示**：使用中文（如 "Chat 不可用，请检查项目 .env 配置"）
- **请求头约定**：`X-Locale=zh`, `X-Region=CN`

## 注意事项

- `next.config.ts` 中 `reactStrictMode: false`
- `.next/` 目录为构建产物，不要手动修改
- 数据库连接使用全局单例模式，`db/index.ts` 标记了 `server-only`
- 聊天初始化流程：先请求 `/api/info` 获取配置 → 再建立 WebSocket → 加载历史事件
- 产物提取来源：`attachments`, `attachment_files`, `files`, `generated_files`，按文件 key 去重
- 文件签名通过 `getArtifactUrl()` 懒加载，POST `/file/sign`，失败时 fallback 到原始路径
