Quellcode durchsuchen

test: 测试自动部署触发

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
MyFramework User vor 4 Monaten
Ursprung
Commit
b4c13ed4ab

+ 0 - 327
CLAUDE.MD

@@ -1,327 +0,0 @@
-# 长时运行代理规则
-
-## 核心原则
-
-1. **三 Agent 架构**:
-   - **Initializer Agent**:仅首次运行,初始化项目
-   - **Coding Agent**:开发功能
-   - **Reviewer Agent**:质量检查(对照功能表 + 浏览器验证)
-2. **外部持久化**:所有进度必须写入文件,不依赖 AI 记忆
-3. **增量开发**:每次会话只完成 1 个功能
-4. **状态流转**:功能状态按流程逐步推进
-5. **自动执行**:AI 必须自动执行所有命令,禁止让用户手动执行
-6. **失败处理**:验证失败 → 修复 → 重新执行该环节
-7. **完整实现**:**功能完成 = 后端 API + 前端页面**,缺一不可
-8. **质量审查**:Coding Agent 完成后 → Reviewer Agent 检查
-
-### 状态流转(必须完整执行)
-
-```
-init → start → compiling → running → db-checking → backend-testing → frontend-testing → done
-                                                                                    ↑
-                                                                            【必须测试前端⚠️】
-                                                                            禁止跳过!
-```
-
-| 状态 | 含义 | 工具 |
-|------|------|------|
-| `backend-testing` | 后端接口测试 | curl |
-| `frontend-testing` | 前端页面测试 | Playwright ⚠️ |
-
-**⚠️⚠️⚠️ 强制要求 ⚠️⚠️⚠️**
-- **必须测试前端页面**
-- **禁止跳过 frontend-testing 步骤**
-- **禁止用 curl 代替 Playwright 测试前端**
-- **功能完成 = 后端测试通过 + 前端测试通过**
-
----
-
-## Initializer Agent 规则
-
-### 职责(仅第一次运行)
-
-1. **生成 `init.sh`**:安装依赖、初始化数据库、启动服务
-2. **生成 `agent-progress.txt`**:进度日志
-3. **生成 `feature_list.json`**:细粒度功能清单(JSON 格式)
-4. **初始化 Git**:创建仓库并提交
-5. **自动执行初始化**:启动服务并验证
-
-### 禁止行为
-- ❌ 编写功能代码
-- ❌ 跳过任何工件生成
-- ❌ 使用占位符
-- ❌ 输出命令让用户手动执行
-
----
-
-## Coding Agent 规则
-
-### 标准流程(按顺序执行)
-
-#### 步骤 1:读取状态
-```bash
-pwd
-cat agent-progress.txt
-cat feature_list.json
-git log --oneline -20
-```
-
-#### 步骤 2:选择功能
-- 从 `feature_list.json` 选择最小 id 的未完成功能
-- **只做这一条**
-
-#### 步骤 3:更新状态为 `start`
-```bash
-# 修改 feature_list.json: status: "start"
-# 记录日志
-```
-
-#### 步骤 4:开发功能
-- 编写代码
-
-#### 步骤 5:编译验证(状态 → compiling)
-```bash
-# 根据项目类型执行编译
-python -m py_compile backend/*.py  # Python
-npm run build  # Node.js/TS
-mvn compile  # Java
-```
-- 成功 → 更新状态为 `running`
-- 失败 → 修复 → 重新编译
-
-#### 步骤 6:运行验证(状态 → running)
-```bash
-# 重启服务
-./init.sh
-sleep 10
-
-# 验证服务可用
-curl -f http://localhost:{backend_port}/health
-curl -f http://localhost:{frontend_port}
-```
-- 成功 → 更新状态为 `db-checking`
-- 失败 → 修复 → 重新运行
-
-#### 步骤 7:数据库验证(状态 → db-checking)
-```bash
-python backend/db_check.py
-```
-- 成功 → 更新状态为 `backend-testing`
-- 失败 → 修复 → 重新验证
-
-#### 步骤 8:后端接口测试(状态 → backend-testing)
-```bash
-# 按照 backend_test_steps 逐项测试
-# 必须包含增删改查(CRUD)
-```
-- 成功 → 更新状态为 `frontend-testing`
-- 失败 → 修复 → 重新测试
-
-**后端测试要求**:
-- 必须包含增删改查(Create/Read/Update/Delete)
-- 使用 curl 测试 API 接口
-- 开发阶段 `auth_enabled: false`,跳过 token 验证
-
----
-
-### ⚠️⚠️⚠️ 重要:必须继续执行步骤 9 ⚠️⚠️⚠️
-
-**后端测试通过后,必须继续执行步骤 9(前端测试)**
-**禁止在步骤 8 完成后直接标记 done**
-**功能完成 = 后端测试通过 + 前端测试通过**
-
-#### ⚠️ 步骤 9:前端页面测试(状态 → frontend-testing)⚠️
-**【必须测试前端,禁止跳过此步骤】**
-```bash
-# 使用 Playwright 进行浏览器自动化测试
-npx playwright test
-```
-- 成功 → 更新状态为 `done`
-- 失败 → 修复 → 重新测试
-
-**前端测试要求**:
-- 必须使用 Playwright 或 Playwright cli
-- ❌ 禁止使用 curl 测试前端(curl 只能测后端)
-
-#### 步骤 10:功能完成(状态 → done)
-```bash
-# 修改 feature_list.json: status: "done", passes: true
-# 更新日志
-# Git 提交
-```
-
-**⚠️⚠️⚠️ 重要:功能完成必须满足以下条件 ⚠️⚠️⚠️**
-- ✅ 后端 API 已实现(通过 backend_test_steps 验证)
-- ✅ 前端页面已实现(通过 frontend_test_steps 验证)
-- ✅ 功能完整可用(不是半成品)
-- ❌ 不能只实现后端就标记 done
-- ❌ 不能只标记状态不写代码
-
-### ⚠️ 禁止行为(违反则任务失败)⚠️
-
-- ❌ **只实现后端 API,不实现前端页面**⚠️⚠️⚠️
-- ❌ **只标记状态为 done,不实际编写代码**⚠️⚠️⚠️
-- ❌ **跳过前端开发步骤**(只做 backend_test_steps,不做 frontend_test_steps)⚠️
-- ❌ **跳过前端测试步骤**(只测后端就标记 done)⚠️
-- ❌ **用 curl 测试前端页面**(curl 只能测后端)
-- ❌ 每次做多个功能
-- ❌ 跳过状态直接 done
-- ❌ 不编译就测试
-- ❌ 不运行就测试
-- ❌ 不验证数据库连接
-- ❌ 删除/修改 backend_test_steps
-- ❌ 删除/修改 frontend_test_steps
-- ❌ backend_test_steps 不包含增删改查
-- ❌ 输出命令让用户手动执行
-
-**⚠️⚠️⚠️ 核心违规 ⚠️⚠️⚠️**
-- **只实现 API 不算完成 = 必须同时有后端 + 前端代码**
-- **跳过前端开发/测试 = 任务失败**
-- **只标记状态不写代码 = 任务失败**
-
----
-
-## 工件规范
-
-### feature_list.json
-```json
-{
-  "project_name": "项目名称",
-  "base_config": {
-    "backend_port": "{根据实际项目配置}",
-    "frontend_port": "{根据实际项目配置}",
-    "db_host": "localhost",
-    "db_port": 3306,
-    "auth_enabled": false
-  },
-  "features": [
-    {
-      "id": 1,
-      "description": "功能描述",
-      "backend_test_steps": [
-        "1. curl POST /api/chats - 验证创建(增)",
-        "2. curl GET /api/chats - 验证查询(查)",
-        "3. curl PUT /api/chats/{id} - 验证更新(改)",
-        "4. curl DELETE /api/chats/{id} - 验证删除(删)"
-      ],
-      "frontend_test_steps": [
-        "1. 在前端页面创建分类输入框",
-        "2. 点击新增按钮",
-        "3. 验证分类显示在列表中",
-        "4. 点击分类筛选按钮",
-        "5. 验证列表按分类筛选"
-      ],
-      "status": "init",
-      "passes": false
-    }
-  ]
-}
-```
-
-**⚠️ 重要:功能完成条件 ⚠️**
-- 功能完成 = 后端 API 实现 + 前端页面实现
-- 缺一不可:只有后端不算完成,只有前端也不算完成
-- frontend_test_steps 必须包含具体的前端操作(如:点击按钮、输入文本、验证显示)
-
-### agent-progress.txt
-- 格式:纯文本
-- 要求:每次会话追加记录,保留所有历史
-
-### init.sh
-- 格式:Bash 脚本
-- 要求:一键启动项目、安装依赖、启动服务、运行测试
-
-### Git
-- 提交频率:每次功能完成后
-
----
-
-## 权限验证配置
-
-- `auth_enabled: false`:开发阶段,跳过登录验证 token
-- `auth_enabled: true`:生产阶段,开启登录验证 token
-
-**建议**:开发阶段关闭 `auth_enabled`,等核心功能完成后再开启
-
----
-
-## Reviewer Agent 规则
-
-### 职责
-在 Coding Agent 完成后进行质量检查:
-1. 对照 `feature_list.json` 验证功能是否实现
-2. 检查后端代码逻辑是否正确
-3. **用 Playwright 浏览器验证前端效果**
-4. 发现问题反馈给 Coding Agent 修复
-
-### 工作流程
-
-#### 步骤 1:读取功能清单
-```bash
-cat feature_list.json
-```
-
-#### 步骤 2:检查后端代码
-```bash
-# 检查 API 是否实现
-ls backend/
-cat backend/api/*.py
-```
-
-#### 步骤 3:检查前端代码
-```bash
-# 检查页面是否存在
-ls frontend/src/views/
-cat frontend/src/views/*.vue
-```
-
-#### 步骤 4:浏览器验证(必须执行)
-```bash
-# 用 Playwright 验证前端效果
-npx playwright test --reporter=line
-```
-
-验证:
-- ✅ 页面能正常打开
-- ✅ 按钮能点击
-- ✅ 功能能正常使用
-- ✅ 数据显示正确
-
-#### 步骤 5:生成审查报告
-```bash
-cat > feature-review-report.md << 'EOF'
-# 功能审查报告
-
-## 功能 X 审查结果
-
-| 检查项 | 状态 | 说明 |
-|--------|------|------|
-| 后端代码 | ✅/❌ | |
-| 前端代码 | ✅/❌ | |
-| 浏览器验证 | ✅/❌ | |
-
-### 问题列表
-1. [问题描述]
-
-### 结论
-- ✅ 通过 → 功能可以标记为完成
-- ❌ 不通过 → 返回 Coding Agent 修复
-EOF
-```
-
-### 审查标准
-
-| 问题类型 | 严重程度 | 处理方式 |
-|---------|---------|---------|
-| 功能完全没实现 | 🔴 严重 | 返回重做 |
-| 部分功能缺失 | 🟡 中等 | 返回补充 |
-| UI 样式问题 | 🟢 轻微 | 可忽略 |
-
-### 核心原则
-
-> **Reviewer Agent 是质量把关者**
-> 
-> **发现问题必须反馈,不能视而不见**
-> 
-> **浏览器验证是必须的**
-

+ 18 - 3
server/src/modules/book-generator/book-generator.store.ts

@@ -30,21 +30,29 @@ function cleanContent(content: string): string {
  */
 function buildSubsectionContentMessages(
   topic: string,
+  bookDescription: string,
   chapterTitle: string,
   chapterSummary: string,
   sectionTitle: string,
   sectionSummary: string,
-  subsection: any
+  subsection: any,
+  writingStyle?: string
 ): ChatMessage[] {
   const keyPoints = typeof subsection.keyPoints === 'string'
     ? JSON.parse(subsection.keyPoints)
     : (subsection.keyPoints || []);
 
+  // 从 bookDescription 中提取写作风格
+  const styleMatch = bookDescription.match(/写作风格:([^\\n]+)/);
+  const style = writingStyle || styleMatch?.[1] || '';
+
   return [
     { role: 'system', content: SUBSECTION_CONTENT_SYSTEM_PROMPT },
     {
       role: 'user',
       content: `书名:《${topic}》
+${bookDescription || ''}
+${style ? `写作风格:${style}` : ''}
 章标题:${chapterTitle}
 章概述:${chapterSummary || ''}
 节标题:${sectionTitle}
@@ -818,13 +826,19 @@ export class BookStore {
     // 判断是否是短文
     const isShortArticle = !parentSection && chapter.level === 1;
 
+    // 从 description 中提取写作风格要求
+    const styleMatch = book.description?.match(/写作风格:([^\\n]+)/);
+    const writingStyle = styleMatch ? styleMatch[1] : (book.style || '');
+
     let messages: ChatMessage[];
     if (isShortArticle) {
       messages = [
         { role: 'system', content: SUBSECTION_CONTENT_SYSTEM_PROMPT },
         {
           role: 'user',
-          content: `书名:《${book.title || book.description}》
+          content: `书名:《${book.title}》
+${book.description || ''}
+${writingStyle ? `写作风格:${writingStyle}` : ''}
 章标题:${chapter.title}
 章概述:${chapter.summary || ''}
 预估字数:${chapter.estimatedWords || 500}字
@@ -834,7 +848,8 @@ export class BookStore {
       ];
     } else {
       messages = buildSubsectionContentMessages(
-        book.title || book.description,
+        book.title,
+        book.description || '',
         chapterTitle,
         chapterSummary,
         sectionTitle,

+ 339 - 0
server/src/modules/book-generator/langgraph-controller.ts

@@ -914,6 +914,345 @@ router.post('/books/:id/retry-chapter', async (ctx: Context) => {
   }
 });
 
+/**
+ * GET /api/book-generator/langgraph/books/:id/full-content
+ * 获取完整书籍内容(所有章节内容合并)
+ */
+router.get('/books/:id/full-content', async (ctx: Context) => {
+  try {
+    const bookId = ctx.params.id as string;
+    const book = await bookStore.getById(bookId);
+
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 获取章节树
+    const chapters = await bookStore.getChapterTree(bookId);
+
+    // 按层级和顺序构建内容
+    let fullContent = `# ${book.title}\n\n`;
+    if (book.description) {
+      fullContent += `## 简介\n${book.description}\n\n`;
+    }
+
+    // 递归添加章节内容
+    const addChapterContent = (chapter: any, indent: string = '') => {
+      if (chapter.content && chapter.contentStatus === 'completed') {
+        fullContent += `${indent}${chapter.number}. ${chapter.title}\n\n`;
+        fullContent += chapter.content + '\n\n';
+      }
+      // 处理子章节
+      const children = chapters.filter((c: any) => c.parentId === chapter.id);
+      children.forEach(child => {
+        addChapterContent(child, indent + '  ');
+      });
+    };
+
+    // 添加一级章节
+    const level1Chapters = chapters.filter((c: any) => c.level === 1);
+    level1Chapters.forEach(chapter => addChapterContent(chapter));
+
+    ctx.body = {
+      code: 0,
+      message: 'success',
+      data: {
+        bookId,
+        title: book.title,
+        totalChapters: level1Chapters.length,
+        content: fullContent,
+        wordCount: book.estimatedWords,
+      },
+    };
+  } catch (error) {
+    console.error('获取完整内容失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取失败' };
+  }
+});
+
+/**
+ * POST /api/book-generator/langgraph/books/:id/outline
+ * 生成书籍大纲
+ */
+router.post('/books/:id/outline', async (ctx: Context) => {
+  try {
+    const bookId = ctx.params.id as string;
+    const book = await bookStore.getById(bookId);
+
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 检查是否已经有大纲
+    if (book.outline && book.outline.chapters && book.outline.chapters.length > 0) {
+      ctx.body = {
+        code: 0,
+        message: '大纲已存在,无需重复生成',
+        data: { outline: book.outline },
+      };
+      return;
+    }
+
+    // 获取书籍类型配置
+    const scaleConfig = getScaleConfig(book.bookScale || '标准教程');
+
+    // 调用AI生成大纲
+    const { generateOutline } = await import('./index');
+    const outline = await generateOutline(bookId, book.description, book.bookScale || '标准教程');
+
+    // 更新书籍的大纲
+    await bookStore.update(bookId, { outline });
+
+    ctx.body = {
+      code: 0,
+      message: '大纲生成成功',
+      data: { outline },
+    };
+  } catch (error) {
+    console.error('生成大纲失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成大纲失败' };
+  }
+});
+
+/**
+ * POST /api/book-generator/langgraph/books/:id/chapters
+ * 生成单个章节(或全部章节,取决于参数)
+ * body: { chapterNumber?: number } - 如果不传chapterNumber,则生成全部
+ */
+router.post('/books/:id/chapters', async (ctx: Context) => {
+  try {
+    const bookId = ctx.params.id as string;
+    const body = ctx.request.body as { chapterNumber?: number };
+    const book = await bookStore.getById(bookId);
+
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 如果指定了章节号,只生成单个章节
+    if (body.chapterNumber) {
+      // 检查章节是否存在
+      const chapters = await bookStore.getChapterTree(bookId);
+      const targetChapter = chapters.find((c: any) => c.number === body.chapterNumber);
+
+      if (!targetChapter) {
+        ctx.status = 404;
+        ctx.body = { code: 1, message: `章节 ${body.chapterNumber} 不存在` };
+        return;
+      }
+
+      // 异步生成章节内容
+      bookStore.generateSingleChapterContent(bookId, targetChapter.id).catch(err => {
+        console.error(`[Generate Chapter] 章节${body.chapterNumber}生成失败:`, err);
+      });
+
+      ctx.body = {
+        code: 0,
+        message: `章节 ${body.chapterNumber} 生成任务已启动`,
+        data: { chapterId: targetChapter.id, chapterNumber: body.chapterNumber },
+      };
+    } else {
+      // 没有指定章节号,触发整本书生成
+      ctx.body = {
+        code: 0,
+        message: '请使用 /generate 接口生成整本书',
+        data: { hint: '使用 POST /books/:id/generate' },
+      };
+    }
+  } catch (error) {
+    console.error('生成章节失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成章节失败' };
+  }
+});
+
+/**
+ * POST /api/book-generator/langgraph/books/:id/foreword
+ * 生成前言
+ */
+router.post('/books/:id/foreword', async (ctx: Context) => {
+  try {
+    const bookId = ctx.params.id as string;
+    const book = await bookStore.getById(bookId);
+
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 调用LLM生成前言
+    const { callLLMWithMessages, ChatMessage } = await import('../../services/llm');
+
+    const messages: ChatMessage[] = [
+      {
+        role: 'system',
+        content: `你是一位专业的图书作者。请根据以下信息为书籍生成前言(前言通常介绍写作背景、目标读者、内容概要)。
+要求:
+1. 字数控制在300-500字
+2. 语言正式但亲切
+3. 不要使用 markdown 格式,直接输出纯文本
+
+输出格式:
+直接输出前言内容,不要包含任何标记。`,
+      },
+      {
+        role: 'user',
+        content: `书籍信息:
+- 书名:${book.title}
+- 描述:${book.description}
+- 目标读者:${book.targetAudience || '普通读者'}
+- 风格:${book.style || '专业'}
+
+请生成这本书的前言。`,
+      },
+    ];
+
+    const foreword = await callLLMWithMessages(messages);
+
+    // 更新书籍元数据
+    const metadata = book.metadata || {};
+    (metadata as any).foreword = foreword;
+    await bookStore.update(bookId, { metadata });
+
+    ctx.body = {
+      code: 0,
+      message: '前言生成成功',
+      data: { foreword },
+    };
+  } catch (error) {
+    console.error('生成前言失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成前言失败' };
+  }
+});
+
+/**
+ * POST /api/book-generator/langgraph/books/:id/afterword
+ * 生成后记
+ */
+router.post('/books/:id/afterword', async (ctx: Context) => {
+  try {
+    const bookId = ctx.params.id as string;
+    const book = await bookStore.getById(bookId);
+
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 调用LLM生成后记
+    const { callLLMWithMessages, ChatMessage } = await import('../../services/llm');
+
+    const messages: ChatMessage[] = [
+      {
+        role: 'system',
+        content: `你是一位专业的图书作者。请为书籍生成后记(后记通常总结全书核心观点、分享写作心得、感谢读者)。
+要求:
+1. 字数控制在300-500字
+2. 语言真挚、诚恳
+3. 不要使用 markdown 格式,直接输出纯文本
+
+输出格式:
+直接输出后记内容,不要包含任何标记。`,
+      },
+      {
+        role: 'user',
+        content: `书籍信息:
+- 书名:${book.title}
+- 描述:${book.description}
+- 章节数:${book.totalChapters}
+- 总字数:约${book.estimatedWords}字
+
+请生成这本书的后记。`,
+      },
+    ];
+
+    const afterword = await callLLMWithMessages(messages);
+
+    // 更新书籍元数据
+    const metadata = book.metadata || {};
+    (metadata as any).afterword = afterword;
+    await bookStore.update(bookId, { metadata });
+
+    ctx.body = {
+      code: 0,
+      message: '后记生成成功',
+      data: { afterword },
+    };
+  } catch (error) {
+    console.error('生成后记失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '生成后记失败' };
+  }
+});
+
+/**
+ * GET /api/book-generator/langgraph/workflow/:bookId
+ * 获取书籍生成工作流状态
+ */
+router.get('/workflow/:bookId', async (ctx: Context) => {
+  try {
+    const bookId = ctx.params.bookId as string;
+    const book = await bookStore.getById(bookId);
+
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 获取章节树
+    const chapters = await bookStore.getChapterTree(bookId);
+
+    // 分析工作流状态
+    const completedChapters = chapters.filter((c: any) => c.status === 'completed').length;
+    const failedChapters = chapters.filter((c: any) => c.status === 'failed').length;
+    const pendingChapters = chapters.filter((c: any) => c.status === 'pending').length;
+    const generatingChapters = chapters.filter((c: any) => c.status === 'generating').length;
+
+    // 确定当前阶段
+    let phase: 'planning' | 'writing' | 'supplement' | 'done' = 'planning';
+    if (book.status === 'generating') {
+      phase = 'writing';
+    } else if (completedChapters > 0 && failedChapters === 0) {
+      phase = 'done';
+    } else if (failedChapters > 0) {
+      phase = 'supplement';
+    }
+
+    ctx.body = {
+      code: 0,
+      message: 'success',
+      data: {
+        bookId,
+        status: book.status,
+        phase,
+        progress: book.progress || 0,
+        completedChapters,
+        failedChapters,
+        pendingChapters,
+        generatingChapters,
+        totalChapters: chapters.length,
+        failedChapterIds: chapters.filter((c: any) => c.status === 'failed').map((c: any) => c.id),
+      },
+    };
+  } catch (error) {
+    console.error('获取工作流状态失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取失败' };
+  }
+});
+
 /**
  * GET /api/book-generator/langgraph/books/:id/chapters/:chapterId/content
  * 获取指定章节的内容