# Eternal AI

AI 陪伴平台 — 人设创作者设定并发布角色，自动生成 Hermes Agent 可用的配置文件（SOUL.md + config.yaml），支持跨机器 CLI 拉取部署。

## 架构

```
┌──────────────────────────────────────────────────────────┐
│                     浏览器 (SPA)                          │
│  index.html + app.js + styles.css                        │
│  ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐  │
│  │ 认证视图  │ │ 角色库    │ │ 创作者中心 │ │ 角色编辑器 │  │
│  └──────────┘ └──────────┘ └───────────┘ └───────────┘  │
└──────────────────────┬───────────────────────────────────┘
                       │ HTTP / JSON
┌──────────────────────▼───────────────────────────────────┐
│              Express.js 服务端 (server.js)                │
│  ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌───────────────┐  │
│  │ /api/auth│ │/api/roles│ │/api/apikeys│ │ /api/hermes  │  │
│  └────┬────┘ └────┬────┘ └─────┬────┘ └──────┬────────┘  │
│       │           │            │              │            │
│  ┌────▼───────────▼────────────▼──────────────▼────────┐  │
│  │              认证中间件 (src/lib/auth.js)            │  │
│  │  JWT 认证 (authMiddleware) + API Key 认证            │  │
│  │  (apiKeyMiddleware, eak_ 前缀, SHA-256 哈希)         │  │
│  └─────────────────────┬───────────────────────────────┘  │
└────────────────────────┼─────────────────────────────────┘
                         │ Prisma ORM
┌────────────────────────▼─────────────────────────────────┐
│                  PostgreSQL 数据库                        │
│  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐         │
│  │  User  │  │  Role  │  │ ApiKey │  │ Order  │         │
│  └────────┘  └────────┘  └────────┘  └────────┘         │
└──────────────────────────────────────────────────────────┘
```

### 技术栈

| 层 | 技术 |
|----|------|
| 前端 | 原生 HTML5 SPA（IIFE 模式，无框架）、玻璃拟态 UI |
| 后端 | Node.js + Express 5.x |
| 数据库 | PostgreSQL 15 + Prisma ORM 5.x |
| 认证 | JWT（7 天有效期）+ API Key（`eak_` 前缀，SHA-256 哈希存储） |
| 密码 | bcryptjs（10 轮盐） |
| 测试 | Playwright E2E（35 个测试用例） |
| 部署 | PM2 + Nginx 反向代理 |

### 项目结构

```
EternalAI/
├── server.js                # Express 入口，注册路由 + 静态文件
├── app.js                   # 前端 SPA 逻辑（路由、状态、表单、API 调用）
├── index.html               # 单页应用入口（9 个视图）
├── styles.css               # 玻璃拟态样式
├── prisma/
│   └── schema.prisma        # 数据模型定义（User / Role / ApiKey / Order）
├── src/
│   ├── lib/
│   │   ├── auth.js          # JWT + API Key 认证中间件
│   │   └── prisma.js        # Prisma Client 单例
│   └── routes/
│       ├── auth.js          # 注册 / 登录 / 用户信息 / 设置
│       ├── roles.js         # 角色 CRUD + 角色库
│       ├── apikeys.js       # API Key 生成 / 列表 / 删除
│       └── hermes.js        # Hermes 配置拉取端点（SOUL.md / config.yaml）
├── deploy/
│   ├── setup-server.sh      # 服务器一键初始化脚本
│   ├── deploy.sh            # CI/CD 部署脚本
│   └── nginx.conf           # Nginx 反向代理配置
├── e2e/                     # Playwright E2E 测试
├── docs/                    # 需求文档与规划
└── package.json
```

## 快速开始

### 本地开发

```bash
# 1. 安装依赖
npm install

# 2. 配置环境变量
cp .env.example .env
# 编辑 .env，设置 DATABASE_URL 和 JWT_SECRET
# JWT_SECRET 可用 openssl rand -hex 32 生成

# 3. 初始化数据库
npx prisma db push
npx prisma generate

# 4. 启动开发服务器
npm run dev
# → http://localhost:3001
```

### 服务器部署

```bash
# 在服务器上执行一键初始化脚本
bash deploy/setup-server.sh
# 自动安装 Node.js / PostgreSQL / Nginx / PM2，创建 .env，推送 Schema
```

## 操作流程

### 1. 用户注册与登录

```
用户 → 首页 → 登录/注册视图
  ├─ 注册：填写账号 + 密码（≥6位）→ 自动登录，返回 JWT
  └─ 登录：账号 + 密码 → 验证成功，返回 JWT
       └─ JWT 存入 localStorage，后续请求携带 Authorization: Bearer <token>
```

### 2. 创作者入驻与角色设定

```
登录用户 → 创作者入驻页 → 开通创作者身份
  → 创作者管理中心（三个 Tab）
    ├─ 角色 Tab：新建 / 编辑角色
    ├─ 收入 Tab：余额 + 付费流水
    └─ 设置 Tab：昵称 / 角色库名 / API Key 管理

新建角色（4 步表单）：
  Step 1 → 基础信息（名称、性别、年龄、关系）
  Step 2 → 性格设定（性格、背景、说话风格、喜好）
  Step 3 → 模型配置（模型、温度、最大 Token、记忆/工具开关）
  Step 4 → 发布设置（问候语、价格、描述）
  → 提交后自动生成 SOUL.md + config.yaml 存入数据库
  → 角色上架到角色库
```

### 3. 角色库浏览与购买

```
任意用户 → 角色库 → 浏览已上架角色
  → 点击角色卡片 → 查看详情
  → 付款 → 创建订单 → 解锁角色
```

## 与 Hermes Agent 交互流程

EternalAI 生成的角色配置（SOUL.md + config.yaml）可通过 CLI 拉取命令部署到任意 Hermes Agent 机器上，支持跨机器部署。

### 架构图

```
服务器 A (EternalAI)                      服务器 B (Hermes Agent)
┌───────────────────────┐                ┌───────────────────────┐
│ 1. 设置页生成 API Key  │                │                       │
│    (eak_xxxxxxxxxx)   │                │ 5. hermes profile     │
│                       │                │    create role-mio    │
│ 2. 创建角色 → 发布     │                │                       │
│    生成 SOUL.md +     │                │ 6. curl 拉取文件      │
│    config.yaml        │                │    SOUL.md            │
│                       │                │    config.yaml        │
│ 3. 角色管理页显示      │  4. 用户复制   │                       │
│    curl 命令模板      │──── curl ────▶│ 7. 配置 .env          │
│    (含 API Key)       │    命令执行    │    (API 密钥)         │
│                       │                │                       │
│                       │                │ 8. role-mio chat      │
└───────────────────────┘                └───────────────────────┘
```

### 步骤详解

#### Step 1-3：在 EternalAI Web UI 操作

1. **生成 API Key**：创作者管理中心 → 设置 Tab → 点击「生成 API Key」→ 明文 Key 仅显示一次，请妥善保存
2. **创建角色**：创作者管理中心 → 角色 Tab → 新建角色 → 填写 4 步表单 → 发布
3. **获取部署命令**：角色卡片点击「Hermes 部署」→ 弹窗显示完整的 curl 命令模板（含服务器地址、角色 ID、API Key）→ 点击复制

#### Step 4-8：在 Hermes Agent 机器上操作

```bash
# 4. 复制 curl 命令到 Hermes 机器执行

# 5. 创建 Hermes profile
hermes profile create role-mio

# 6. 拉取 SOUL.md
curl -H "Authorization: Bearer eak_xxx" \
     https://your-eternalai-domain.com/api/hermes/roles/<role-id>/SOUL.md \
     -o ~/.hermes/profiles/role-mio/SOUL.md

# 7. 拉取 config.yaml
curl -H "Authorization: Bearer eak_xxx" \
     https://your-eternalai-domain.com/api/hermes/roles/<role-id>/config.yaml \
     -o ~/.hermes/profiles/role-mio/config.yaml

# 8. 配置 API 密钥并启动
echo "OPENROUTER_API_KEY=sk-or-xxx" > ~/.hermes/profiles/role-mio/.env
role-mio chat
```

### config.yaml 格式适配

EternalAI 角色数据自动转换为 Hermes config.yaml 格式：

| EternalAI 字段 | Hermes config.yaml 字段 |
|---|---|
| `role.model` | `model:` |
| `role.temperature` | `temperature:` |
| `role.maxTokens` | `max_tokens:` |
| `role.enableMemory` | `memory.enabled:` |
| `role.enableTools` | `tools.enabled:` |

生成的 config.yaml 示例：

```yaml
# Hermes Agent Config — generated by EternalAI
# Character: Mio
# Agent ID: default
# Generated at: 2026-06-21T12:00:00.000Z

model: "gpt-4o"
temperature: 0.85
max_tokens: 2048

memory:
  enabled: true
  storage: local

tools:
  enabled: true
  toolsets:
    - memory

terminal:
  backend: local
```

### API Key 安全机制

- **格式**：`eak_` + 32 位随机 hex（128 位熵）
- **存储**：SHA-256 哈希存储，明文仅在生成时返回一次
- **脱敏显示**：列表中仅显示前 12 位（如 `eak_a1b2c3d4`）
- **认证**：通过 `Authorization: Bearer eak_xxx` 头传递
- **权限**：API Key 只能拉取所属用户创建的角色配置
- **可吊销**：随时在设置页删除，删除后立即失效

## API 参考

### 认证 API

| 方法 | 路径 | 认证 | 说明 |
|------|------|------|------|
| POST | `/api/auth/register` | 无 | 注册（账号 + 密码） |
| POST | `/api/auth/login` | 无 | 登录 |
| GET | `/api/auth/me` | JWT | 获取当前用户信息 |
| PUT | `/api/auth/settings` | JWT | 更新用户设置 |

### 角色 API

| 方法 | 路径 | 认证 | 说明 |
|------|------|------|------|
| GET | `/api/roles` | 无 | 获取角色库（已上架） |
| GET | `/api/roles/:id` | 无 | 获取角色详情 |
| GET | `/api/roles/my/roles` | JWT | 获取我创建的角色 |
| POST | `/api/roles` | JWT | 发布新角色 |
| PUT | `/api/roles/:id` | JWT | 编辑角色 |
| GET | `/api/roles/:id/full` | JWT | 获取角色完整信息（含 SOUL.md） |

### API Key 管理

| 方法 | 路径 | 认证 | 说明 |
|------|------|------|------|
| POST | `/api/apikeys` | JWT | 生成 API Key（明文仅返回一次） |
| GET | `/api/apikeys` | JWT | 列出所有 API Key（脱敏） |
| DELETE | `/api/apikeys/:id` | JWT | 删除 API Key |

### Hermes 配置拉取

| 方法 | 路径 | 认证 | 说明 |
|------|------|------|------|
| GET | `/api/hermes/roles/:id/SOUL.md` | API Key / JWT | 拉取 SOUL.md（text/plain） |
| GET | `/api/hermes/roles/:id/config.yaml` | API Key / JWT | 拉取 config.yaml（text/plain） |

## 测试

```bash
# 运行全部 E2E 测试（35 个用例）
npm test

# 查看测试报告
npx playwright show-report
```

测试覆盖：认证流程、角色发布/编辑、角色库浏览、导航与可访问性。

## 安全

- **JWT 密钥**：生产环境必须设置 `JWT_SECRET` 环境变量，未设置时 fail-fast 抛错
- **密码存储**：bcrypt 哈希（10 轮盐），不存明文
- **API Key 存储**：SHA-256 哈希存储，明文仅显示一次
- **XSS 防护**：所有用户可控数据通过 `escapeHtml()` 转义后插入 DOM
- **YAML 注入防护**：config.yaml 生成时对用户可控字段做双引号转义和换行符过滤
- **所有权校验**：所有角色操作验证 `creatorId === req.userId`
- **.env 文件权限**：部署脚本自动设置 `chmod 600`

## License

MIT
