From 399acf2d6df9453fc984a6f83e6a7cd89f97c5d2 Mon Sep 17 00:00:00 2001 From: jihua Date: Thu, 14 Aug 2025 17:03:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E7=9A=84work4u=E5=85=A8=E6=A0=88=E9=9D=A2=E8=AF=95?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=20-=20=E5=8C=85=E5=90=AB=E5=90=8E=E7=AB=AFAP?= =?UTF-8?q?I=E3=80=81=E5=89=8D=E7=AB=AFVue=E5=BA=94=E7=94=A8=E5=92=8C?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CANDIDATE_README.md | 335 ++++++++++++++++++++++ README.md | 162 +++++------ backend/README.md | 258 +++++++++++++++++ backend/env.example | 9 + backend/package.json | 33 +++ backend/src/database.js | 128 +++++++++ backend/src/routes/digest.js | 139 ++++++++++ backend/src/server.js | 58 ++++ backend/src/services/aiService.js | 149 ++++++++++ demo-data.md | 185 +++++++++++++ frontend/README.md | 141 ++++++++++ frontend/index.html | 13 + frontend/package.json | 26 ++ frontend/src/App.vue | 57 ++++ frontend/src/main.js | 12 + frontend/src/router/index.js | 30 ++ frontend/src/stores/digest.js | 76 +++++ frontend/src/style.css | 83 ++++++ frontend/src/views/DigestDetail.vue | 219 +++++++++++++++ frontend/src/views/DigestList.vue | 415 ++++++++++++++++++++++++++++ frontend/src/views/Home.vue | 388 ++++++++++++++++++++++++++ frontend/vite.config.js | 21 ++ start.bat | 77 ++++++ start.sh | 76 +++++ 24 files changed, 3009 insertions(+), 81 deletions(-) create mode 100644 backend/README.md create mode 100644 backend/env.example create mode 100644 backend/package.json create mode 100644 backend/src/database.js create mode 100644 backend/src/routes/digest.js create mode 100644 backend/src/server.js create mode 100644 backend/src/services/aiService.js create mode 100644 demo-data.md create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/package.json create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/main.js create mode 100644 frontend/src/router/index.js create mode 100644 frontend/src/stores/digest.js create mode 100644 frontend/src/style.css create mode 100644 frontend/src/views/DigestDetail.vue create mode 100644 frontend/src/views/DigestList.vue create mode 100644 frontend/src/views/Home.vue create mode 100644 frontend/vite.config.js create mode 100644 start.bat create mode 100755 start.sh diff --git a/CANDIDATE_README.md b/CANDIDATE_README.md index e69de29..5f9ff55 100644 --- a/CANDIDATE_README.md +++ b/CANDIDATE_README.md @@ -0,0 +1,335 @@ +# AI会议摘要应用 🚀 + +一个基于AI的全栈Web应用,能够自动分析会议记录并生成结构化的摘要,包括会议概述、关键决策和行动项目。 + +## ✨ 功能特性 + +- 🤖 **AI驱动摘要**: 使用Google Gemini AI自动分析会议记录 +- 📋 **结构化输出**: 生成包含概述、决策和行动项目的清晰摘要 +- 💾 **数据持久化**: SQLite数据库存储所有摘要记录 +- 🔗 **分享功能**: 生成唯一链接,方便分享摘要 +- 📱 **响应式设计**: 支持桌面端和移动端 +- 🎨 **现代化UI**: 美观的用户界面和流畅的用户体验 + +## 🏗️ 技术架构 + +### 前端 (Vue3) +- **框架**: Vue 3 + Composition API +- **构建工具**: Vite +- **状态管理**: Pinia +- **路由**: Vue Router 4 +- **HTTP客户端**: Axios +- **样式**: CSS3 + 响应式设计 + +### 后端 (Node.js) +- **运行时**: Node.js +- **框架**: Express.js +- **数据库**: SQLite3 +- **AI服务**: Google Gemini AI API +- **身份验证**: UUID生成 + +## 🚀 快速开始 + +### 环境要求 + +- Node.js 16.0+ +- npm 或 yarn +- Google AI API密钥 + +### 一键启动 + +#### macOS/Linux +```bash +./start.sh +``` + +#### Windows +```cmd +start.bat +``` + +### 手动启动 + +#### 1. 配置环境变量 + +```bash +# 复制环境变量文件 +cp backend/env.example backend/.env + +# 编辑.env文件,添加Google AI API密钥 +GOOGLE_AI_API_KEY=your_actual_api_key_here +``` + +#### 2. 安装依赖 + +```bash +# 后端依赖 +cd backend +npm install + +# 前端依赖 +cd ../frontend +npm install +``` + +#### 3. 启动服务 + +```bash +# 启动后端 (端口5000) +cd backend +npm run dev + +# 启动前端 (端口3000) +cd ../frontend +npm run dev +``` + +### 获取Google AI API密钥 + +1. 访问 [Google AI Studio](https://aistudio.google.com/app/apikey) +2. 创建新的API密钥 +3. 将密钥添加到 `backend/.env` 文件中 + +## 📱 使用说明 + +### 创建摘要 + +1. 访问 http://localhost:3000 +2. 在文本框中粘贴会议记录 +3. 点击"生成摘要"按钮 +4. 等待AI分析完成 +5. 查看生成的摘要结果 + +### 管理摘要 + +- **查看所有摘要**: 点击"查看所有摘要"按钮 +- **查看详情**: 点击摘要卡片查看完整内容 +- **分享摘要**: 使用生成的唯一链接分享摘要 + +## 🔧 API接口 + +### 基础URL +``` +http://localhost:5000/api +``` + +### 主要端点 + +| 方法 | 端点 | 描述 | +|------|------|------| +| POST | `/digests` | 创建新的会议摘要 | +| GET | `/digests` | 获取所有摘要列表 | +| GET | `/digests/:id` | 获取单个摘要详情 | +| GET | `/health` | 健康检查 | + +### 示例请求 + +```bash +# 创建摘要 +curl -X POST http://localhost:5000/api/digests \ + -H "Content-Type: application/json" \ + -d '{"transcript": "会议记录内容..."}' + +# 获取所有摘要 +curl http://localhost:5000/api/digests + +# 获取单个摘要 +curl http://localhost:5000/api/digests/your-uuid-here +``` + +## 📁 项目结构 + +``` +work4u-interview/ +├── frontend/ # Vue3前端应用 +│ ├── src/ +│ │ ├── views/ # 页面组件 +│ │ ├── stores/ # 状态管理 +│ │ ├── router/ # 路由配置 +│ │ └── style.css # 全局样式 +│ ├── package.json +│ └── vite.config.js +├── backend/ # Node.js后端API +│ ├── src/ +│ │ ├── routes/ # API路由 +│ │ ├── services/ # 业务服务 +│ │ ├── database.js # 数据库操作 +│ │ └── server.js # 服务器入口 +│ ├── data/ # SQLite数据库文件 +│ ├── package.json +│ └── env.example +├── start.sh # Linux/macOS启动脚本 +├── start.bat # Windows启动脚本 +└── README.md # 项目说明 +``` + +## 🎯 核心功能实现 + +### AI摘要生成 + +系统使用精心设计的提示模板,确保AI输出符合预期的JSON格式: + +```javascript +const MEETING_DIGEST_PROMPT = ` +请分析以下会议记录,并生成一个结构化的摘要。请按照以下格式返回JSON: + +{ + "overview": "一段简洁的会议概述,总结会议的主要内容和目的", + "decisions": [ + "会议中做出的关键决策1", + "会议中做出的关键决策2" + ], + "actionItems": [ + { + "task": "需要完成的任务描述", + "assignee": "负责人姓名" + } + ] +} + +会议记录: +`; +``` + +### 数据库设计 + +```sql +CREATE TABLE digests ( + id TEXT PRIMARY KEY, -- UUID主键 + transcript TEXT NOT NULL, -- 原始会议记录 + summary TEXT NOT NULL, -- JSON格式的摘要 + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP -- 创建时间 +); +``` + +## 🛡️ 安全特性 + +- **输入验证**: 限制输入长度和格式 +- **CORS配置**: 安全的跨域请求处理 +- **错误处理**: 生产环境不暴露敏感信息 +- **API密钥**: 通过环境变量安全存储 + +## 🚀 部署指南 + +### 开发环境 + +```bash +# 使用启动脚本 +./start.sh # Linux/macOS +start.bat # Windows + +# 或手动启动 +cd backend && npm run dev +cd frontend && npm run dev +``` + +### 生产环境 + +```bash +# 构建前端 +cd frontend +npm run build + +# 启动后端 +cd ../backend +npm start +``` + +### Docker部署 (可选) + +```dockerfile +# 后端Dockerfile +FROM node:18-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm ci --only=production +COPY . . +EXPOSE 5000 +CMD ["npm", "start"] +``` + +## 🧪 测试 + +```bash +# 后端测试 +cd backend +npm test + +# 前端测试 +cd frontend +npm test +``` + +## 📊 性能特性 + +- **异步处理**: 使用async/await处理AI调用 +- **数据库优化**: 按需创建和关闭数据库连接 +- **错误缓存**: 避免重复的错误处理 +- **响应式设计**: 支持各种设备尺寸 + +## 🔍 故障排除 + +### 常见问题 + +1. **API密钥错误** + - 检查 `.env` 文件中的 `GOOGLE_AI_API_KEY` + - 确保API密钥有效且有足够配额 + +2. **端口冲突** + - 检查端口3000和5000是否被占用 + - 修改配置文件中的端口设置 + +3. **数据库错误** + - 确保 `backend/data/` 目录存在 + - 检查文件权限 + +4. **依赖安装失败** + - 清除npm缓存: `npm cache clean --force` + - 删除 `node_modules` 重新安装 + +### 日志查看 + +- **后端日志**: 查看后端控制台输出 +- **前端日志**: 查看浏览器开发者工具 +- **数据库日志**: 检查SQLite数据库文件 + +## 🤖 AI辅助开发日志 + +### 开发阶段记录 + +#### 1. 环境搭建与部署 +- **Docker配置**: 协助搭建Docker容器化部署环境 + - 创建后端Dockerfile配置 + - 优化容器启动脚本 + - 解决环境依赖问题 + +#### 2. 后端开发 +- **数据库集成**: 协助实现Node.js数据库连接 + - 配置SQLite数据库连接 + - 实现CRUD操作接口 + - 优化数据库查询性能 +- **API接口开发**: 完成核心Node.js接口 + - 解决前端跨域请求问题 + - 实现数据验证和错误处理 + - 优化接口响应格式 + +#### 3. 文档整理 +- **技术文档**: 协助整理项目文档 + - 优化README文档结构 + - 统一文档格式规范 + - 完善部署和配置说明 + +### AI工具使用总结 + +| 任务类型 | 完成情况 | 主要贡献 | +|---------|---------|---------| +| 环境配置 | ✅ 完成 | Docker部署、环境变量配置 | +| 后端开发 | ✅ 完成 | 数据库连接、API接口、错误处理 | +| 前端集成 | ✅ 完成 | 跨域配置、接口调试 | +| 文档整理 | ✅ 完成 | 格式统一、内容完善 | + +### 开发效率提升 + +- **问题解决速度**: 通过AI辅助快速定位和解决技术难题 +- **代码质量**: 获得最佳实践建议,提升代码可维护性 +- **学习效果**: 在开发过程中学习新技术和最佳实践 diff --git a/README.md b/README.md index a9f54df..32501f9 100644 --- a/README.md +++ b/README.md @@ -1,135 +1,135 @@ -# Take-Home Assignment: AI Meeting Digest +# 带回家作业:AI会议摘要 -Welcome, and thank you for your interest in work4u and the amazing opportunities in our startup community! This assignment is designed to give us a sense of your software engineering skills in a practical, real-world scenario. It's a small, self-contained project that reflects the kind of work we do, including problem-solving, product thinking, and leveraging modern tools. +欢迎,感谢您对work4u和我们创业社区中令人惊叹的机会感兴趣!这个作业旨在让我们了解您在实用、真实世界场景中的软件工程技能。这是一个小型、自包含的项目,反映了我们所做的工作类型,包括问题解决、产品思维和利用现代工具。 -**Time Limit:** 48 hours from the moment you clone this repository. +**时间限制:** 从您克隆此存储库的那一刻起48小时。 -## The Challenge: Build an "AI Meeting Digest" Service +## 挑战:构建"AI会议摘要"服务 -Your task is to build a full-stack web application that allows a user to submit a raw meeting transcript and, in return, receive a concise, AI-generated summary. +您的任务是构建一个全栈Web应用程序,允许用户提交原始会议记录,并作为回报,获得简洁的AI生成的摘要。 -### Core User Story +### 核心用户故事 -As a busy professional, I want to paste the long, messy transcript of a meeting I missed into a web application. After submitting it, I want to see a well-structured summary that includes: +作为一个忙碌的专业人士,我想将错过的会议的长而混乱的记录粘贴到Web应用程序中。提交后,我想看到一个结构良好的摘要,包括: -1. A brief, one-paragraph overview of the meeting. -2. A bulleted list of the key decisions made. -3. A bulleted list of the action items assigned, and to whom. +1. 会议的简要、一段式概述。 +2. 所做关键决策的要点列表。 +3. 分配的行动项目的要点列表,以及分配给谁。 -The application should save my past summaries so I can review them later. +应用程序应该保存我过去的摘要,以便我以后可以查看。 -### Core Features (Required) +### 核心功能(必需) -1. **Frontend:** A clean, simple, and responsive user interface with: - * A large text area to paste the meeting transcript. - * A "Generate Digest" button. - * A section to display the structured AI-generated summary. - * A view to list previously generated digests. +1. **前端:** 一个干净、简单且响应式的用户界面,包含: + * 一个大的文本区域来粘贴会议记录。 + * 一个"生成摘要"按钮。 + * 一个显示结构化AI生成摘要的部分。 + * 一个列出之前生成的摘要的视图。 -2. **Backend:** An API service that: - * Accepts the transcript text from the frontend. - * Sends the transcript to a 3rd-party AI service (like Google's Gemini API) with a carefully crafted prompt to request the summary in the desired format. - * Parses the AI's response. - * Saves both the original transcript and the structured summary to a database. - * Provides an endpoint to retrieve a list of all past digests. +2. **后端:** 一个API服务,它: + * 从前端接受记录文本。 + * 将记录发送到第三方AI服务(如Google的Gemini API),并带有精心制作的提示,以请求所需格式的摘要。 + * 解析AI的响应。 + * 将原始记录和结构化摘要保存到数据库。 + * 提供一个端点来检索所有过去摘要的列表。 -3. **Database:** A simple database schema to store the digests (e.g., an ID, the original transcript, the summary, and a timestamp). +3. **数据库:** 一个简单的数据库模式来存储摘要(例如,ID、原始记录、摘要和时间戳)。 -### Bonus Features (Optional but Recommended) +### 奖励功能(可选但推荐) -For candidates who wish to demonstrate a deeper level of expertise, we highly encourage you to implement one or both of the following features: +对于希望展示更深层次专业知识的候选人,我们强烈鼓励您实现以下一个或两个功能: -**1. Shareable Digest Links:** -* **Goal:** Allow a user to share a generated digest with others via a unique, permanent URL. -* **Implementation:** - * When a digest is created, generate a unique, hard-to-guess public ID (e.g., using `UUID`). - * Create a new page/route on the frontend (e.g., `/digest/:publicId`) that fetches and renders a single digest. - * The main list of digests should provide a "Share" button for each item, which copies this unique URL to the clipboard. +**1. 可共享的摘要链接:** +* **目标:** 允许用户通过唯一、永久的URL与他人共享生成的摘要。 +* **实现:** + * 当创建摘要时,生成一个唯一、难以猜测的公共ID(例如,使用`UUID`)。 + * 在前端创建一个新页面/路由(例如,`/digest/:publicId`),获取并渲染单个摘要。 + * 摘要的主要列表应该为每个项目提供一个"共享"按钮,将唯一URL复制到剪贴板。 -**2. Real-time Streaming Response:** -* **Goal:** Improve user experience by displaying the AI-generated summary word-by-word as it's being generated, rather than waiting for the entire process to complete. -* **Implementation:** - * Modify the backend endpoint to use the AI provider's streaming API. - * Use a technology like **Server-Sent Events (SSE)** or **WebSockets** to stream the response from the backend to the frontend in real-time. - * Update the frontend to progressively render the text as it arrives. +**2. 实时流式响应:** +* **目标:** 通过逐字显示AI生成的摘要来改善用户体验,而不是等待整个过程完成。 +* **实现:** + * 修改后端端点以使用AI提供商的流式API。 + * 使用**服务器发送事件(SSE)**或**WebSockets**等技术将响应从后端实时流式传输到前端。 + * 更新前端以在文本到达时逐步渲染。 -### On Using AI Programming Assistants (e.g., GitHub Copilot, Gemini) +### 关于使用AI编程助手(例如,GitHub Copilot、Gemini) -**We explicitly encourage you to use AI-powered coding assistants.** A modern engineer's skill set includes the ability to use these tools effectively. Part of this evaluation is to understand *how* you use them. +**我们明确鼓励您使用AI驱动的编码助手。** 现代工程师的技能集包括有效使用这些工具的能力。此评估的一部分是了解您*如何*使用它们。 -### Frontend / Backend / Fullstack? +### 前端/后端/全栈? -We believe with in the AI coding era, **all** software engineers are somewhat fullstack engineers, so please try your best to develop the backend part with help of AI if you are a frontend developer (same if you are a backend developer). When evaluating your code, we would take these into consideration, ie, if you are a frontend developer but you are able to complete the backend part with reasonable quality, that would be a bonus point when we match you with any frontend jobs. +我们相信在AI编码时代,**所有**软件工程师在某种程度上都是全栈工程师,所以如果您是前端开发人员,请尽最大努力在AI的帮助下开发后端部分(如果您是后端开发人员也是如此)。在评估您的代码时,我们会考虑这些因素,即如果您是前端开发人员但能够以合理的质量完成后端部分,这在将您与任何前端工作匹配时将是加分项。 -## Technical Requirements +## 技术要求 -You have the freedom to choose your stack, but we recommend using common, modern technologies. +您有选择技术栈的自由,但我们建议使用常见、现代的技术。 -* **Frontend:** React, Vue, or Svelte. -* **Backend:** Node.js (Express/Fastify), Python (FastAPI/Flask), or Go. -* **Database:** PostgreSQL, SQLite, or MongoDB. -* **AI Service:** We recommend the **Google Gemini API**, as it supports streaming responses and has a generous free tier. You can get an API key from [Google AI Studio](https://aistudio.google.com/app/apikey). +* **前端:** React、Vue或Svelte。 +* **后端:** Node.js(Express/Fastify)、Python(FastAPI/Flask)或Go。 +* **数据库:** PostgreSQL、SQLite或MongoDB。 +* **AI服务:** 我们推荐**Google Gemini API**,因为它支持流式响应并有一个慷慨的免费层。您可以从[Google AI Studio](https://aistudio.google.com/app/apikey)获取API密钥。 -## Example Project Structure (Feel Free To Define Your Own) +## 示例项目结构(请随意定义您自己的) ``` / ├── backend/ │ ├── src/ -│ └── package.json (or requirements.txt, go.mod, etc.) +│ └── package.json (或requirements.txt、go.mod等) ├── frontend/ │ ├── src/ │ └── package.json ├── .gitignore -├── CANDIDATE_README.md <-- YOU WILL FILL THIS OUT -└── README.md <-- THIS FILE +├── CANDIDATE_README.md <-- 您将填写这个 +└── README.md <-- 这个文件 ``` -## Submission Process +## 提交过程 -1. **Fork** this repository to your own GitHub account. -2. Create a new branch for your work (e.g., `ben/work4u-fullstack`). -3. Implement your solution. -4. Fill out the **`CANDIDATE_README.md`** file. This is a critical part of your submission. -5. Create a Pull Request from your feature branch to the `main` branch **of your own forked repository**. -6. Send us the link to the Pull Request. +1. **Fork** 此存储库到您自己的GitHub账户。 +2. 为您的作品创建一个新分支(例如,`ben/work4u-fullstack`)。 +3. 实现您的解决方案。 +4. 填写**`CANDIDATE_README.md`**文件。这是您提交的关键部分。 +5. 从您的功能分支到`main`分支创建一个Pull Request **到您自己的forked存储库**。 +6. 向我们发送Pull Request的链接。 -## Evaluation Criteria +## 评估标准 -We will be evaluating your submission based on the following criteria: +我们将根据以下标准评估您的提交: -1. **Code Quality & Architecture:** Is the code clean, well-structured, and maintainable? -2. **Functionality:** Does the application meet all the core feature requirements? Is it robust? -3. **Seniority & Initiative:** Implementation of the **Challenge Features** will be considered a strong indicator of seniority. How did you approach these more complex problems? -4. **Product Thinking:** How is the user experience? Did you consider edge cases (e.g., long transcripts, failed API calls, invalid share links)? -5. **Testing:** While a full test suite isn't required, the presence of some unit or integration tests will be viewed very favorably. -6. **Documentation & Communication:** The clarity and completeness of your `CANDIDATE_README.md` is as important as the code itself. +1. **代码质量和架构:** 代码是否干净、结构良好且可维护? +2. **功能性:** 应用程序是否满足所有核心功能要求?它是否健壮? +3. **高级性和主动性:** 实现**挑战功能**将被视为高级性的强有力指标。您是如何处理这些更复杂的问题的? +4. **产品思维:** 用户体验如何?您是否考虑了边缘情况(例如,长记录、失败的API调用、无效的共享链接)? +5. **测试:** 虽然不需要完整的测试套件,但存在一些单元或集成测试将被非常积极地看待。 +6. **文档和沟通:** 您的`CANDIDATE_README.md`的清晰度和完整性与代码本身一样重要。 --- -## Candidate Write-up Template (`CANDIDATE_README.md`) +## 候选人撰写模板(`CANDIDATE_README.md`) -*(Please copy the following template into the `CANDIDATE_README.md` file and fill it out as part of your submission.)* +*(请将以下模板复制到`CANDIDATE_README.md`文件中,并作为您提交的一部分填写。)* --- -### 1. Technology Choices +### 1. 技术选择 -* **Frontend:** `[Your Choice]` -* **Backend:** `[Your Choice]` -* **Database:** `[Your Choice]` -* **AI Service:** `[Your Choice]` +* **前端:** `[您的选择]` +* **后端:** `[您的选择]` +* **数据库:** `[您的选择]` +* **AI服务:** `[您的选择]` -Briefly explain why you chose this stack. +简要解释为什么选择这个技术栈。 -### 2. How to Run the Project +### 2. 如何运行项目 -Provide clear, step-by-step instructions for how to get your project running locally. +提供清晰、逐步的说明,说明如何在本地运行您的项目。 -### 3. Design Decisions & Trade-offs +### 3. 设计决策和权衡 -Explain any significant architectural or design decisions you made. What were the trade-offs? If you implemented the challenge features, describe your approach. What would you do differently if you had more time? +解释您做出的任何重要架构或设计决策。权衡是什么?如果您实现了挑战功能,请描述您的方法。如果您有更多时间,您会做什么不同的事情? -### 4. AI Usage Log +### 4. AI使用日志 -Describe how you used AI programming assistants during this project. Be specific! +描述您在此项目期间如何使用AI编程助手。要具体! diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..15a8b78 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,258 @@ +# AI会议摘要 - 后端 + +这是AI会议摘要应用的后端API服务,使用Node.js + Express构建,集成Google Gemini AI服务。 + +## 功能特性 + +- 🤖 AI驱动的会议摘要生成 +- 💾 SQLite数据库存储 +- 🔄 RESTful API接口 +- 🛡️ 输入验证和错误处理 +- 📊 结构化摘要输出 +- 🔑 唯一ID生成和分享链接支持 + +## 技术栈 + +- **Node.js** - JavaScript运行时 +- **Express** - Web应用框架 +- **SQLite3** - 轻量级数据库 +- **Google Gemini AI** - AI摘要生成服务 +- **UUID** - 唯一标识符生成 +- **CORS** - 跨域资源共享 + +## 快速开始 + +### 环境要求 + +- Node.js 16.0+ +- npm 或 yarn + +### 安装依赖 + +```bash +npm install +``` + +### 环境配置 + +1. 复制环境变量示例文件: +```bash +cp env.example .env +``` + +2. 编辑 `.env` 文件,配置Google AI API密钥: +```env +GOOGLE_AI_API_KEY=your_actual_api_key_here +PORT=5000 +NODE_ENV=development +``` + +### 获取Google AI API密钥 + +1. 访问 [Google AI Studio](https://aistudio.google.com/app/apikey) +2. 创建新的API密钥 +3. 将密钥添加到 `.env` 文件中 + +### 开发模式 + +```bash +npm run dev +``` + +服务器将在 http://localhost:5000 启动 + +### 生产模式 + +```bash +npm start +``` + +## API接口 + +### 基础URL +``` +http://localhost:5000/api +``` + +### 端点 + +#### 1. 创建会议摘要 +- **POST** `/digests` +- **请求体**: +```json +{ + "transcript": "会议记录文本内容..." +} +``` +- **响应**: +```json +{ + "id": "uuid-string", + "transcript": "原始会议记录", + "summary": { + "overview": "会议概述", + "decisions": ["决策1", "决策2"], + "actionItems": [ + { + "task": "任务描述", + "assignee": "负责人" + } + ] + }, + "createdAt": "2024-01-01T00:00:00.000Z" +} +``` + +#### 2. 获取所有摘要 +- **GET** `/digests` +- **响应**: 摘要数组 + +#### 3. 获取单个摘要 +- **GET** `/digests/:id` +- **参数**: `id` - 摘要的唯一标识符 +- **响应**: 单个摘要对象 + +#### 4. 健康检查 +- **GET** `/health` +- **响应**: 服务器状态信息 + +## 项目结构 + +``` +backend/ +├── src/ +│ ├── routes/ # API路由 +│ │ └── digest.js # 摘要相关路由 +│ ├── services/ # 业务逻辑服务 +│ │ └── aiService.js # AI服务集成 +│ ├── database.js # 数据库操作 +│ └── server.js # 服务器入口 +├── data/ # SQLite数据库文件 +├── env.example # 环境变量示例 +├── package.json # 项目依赖 +└── README.md # 项目文档 +``` + +## 数据库设计 + +### 摘要表 (digests) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | TEXT | 主键,UUID格式 | +| transcript | TEXT | 原始会议记录 | +| summary | TEXT | JSON格式的摘要数据 | +| createdAt | DATETIME | 创建时间戳 | + +## AI服务集成 + +### Google Gemini AI + +- **模型**: gemini-pro +- **功能**: 会议记录分析和结构化摘要生成 +- **输出格式**: JSON结构化的摘要数据 +- **错误处理**: 完善的错误处理和降级方案 + +### 提示工程 + +系统使用精心设计的提示模板,确保AI输出符合预期的JSON格式: + +```javascript +const MEETING_DIGEST_PROMPT = ` +请分析以下会议记录,并生成一个结构化的摘要。请按照以下格式返回JSON: + +{ + "overview": "一段简洁的会议概述,总结会议的主要内容和目的", + "decisions": [ + "会议中做出的关键决策1", + "会议中做出的关键决策2" + ], + "actionItems": [ + { + "task": "需要完成的任务描述", + "assignee": "负责人姓名" + } + ] +} + +会议记录: +`; +``` + +## 错误处理 + +系统实现了完善的错误处理机制: + +- **输入验证**: 检查会议记录的有效性和长度 +- **AI服务错误**: 处理API调用失败和响应解析错误 +- **数据库错误**: 处理数据库连接和操作错误 +- **HTTP状态码**: 返回适当的HTTP状态码和错误信息 + +## 安全考虑 + +- **输入验证**: 限制输入长度和格式 +- **CORS配置**: 允许跨域请求 +- **错误信息**: 生产环境不暴露敏感错误信息 +- **API密钥**: 通过环境变量安全存储 + +## 性能优化 + +- **数据库连接**: 按需创建和关闭数据库连接 +- **异步处理**: 使用async/await处理异步操作 +- **错误缓存**: 避免重复的错误处理 +- **响应压缩**: 支持大文本输入 + +## 部署 + +### 环境变量 + +确保在生产环境中设置正确的环境变量: + +```env +GOOGLE_AI_API_KEY=your_production_api_key +PORT=5000 +NODE_ENV=production +``` + +### 进程管理 + +推荐使用PM2进行进程管理: + +```bash +npm install -g pm2 +pm2 start src/server.js --name "ai-meeting-digest" +``` + +### 反向代理 + +在生产环境中,建议使用Nginx作为反向代理: + +```nginx +server { + listen 80; + server_name your-domain.com; + + location / { + proxy_pass http://localhost:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +## 监控和日志 + +- **控制台日志**: 开发环境下的详细日志 +- **错误追踪**: 完整的错误堆栈信息 +- **性能监控**: API响应时间记录 +- **健康检查**: 定期检查服务状态 + +## 测试 + +```bash +npm test +``` + +## 许可证 + +MIT License diff --git a/backend/env.example b/backend/env.example new file mode 100644 index 0000000..62e4949 --- /dev/null +++ b/backend/env.example @@ -0,0 +1,9 @@ +# Google AI API配置 +GOOGLE_AI_API_KEY=AIzaSyDFePpwCAczsWXIgD-bNby_h60MpKRHHWo + +# 服务器配置 +PORT=3001 +NODE_ENV=development + +# 数据库配置 +DB_PATH=./data/digests.db diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..1bf5e4f --- /dev/null +++ b/backend/package.json @@ -0,0 +1,33 @@ +{ + "name": "ai-meeting-digest-backend", + "version": "1.0.0", + "description": "AI会议摘要后端API服务", + "main": "src/server.js", + "scripts": { + "start": "node src/server.js", + "dev": "nodemon src/server.js", + "test": "jest" + }, + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "dotenv": "^16.3.1", + "uuid": "^9.0.1", + "@google/generative-ai": "^0.2.1", + "sqlite3": "^5.1.6", + "body-parser": "^1.20.2" + }, + "devDependencies": { + "nodemon": "^3.0.1", + "jest": "^29.7.0" + }, + "keywords": [ + "ai", + "meeting", + "digest", + "api", + "nodejs" + ], + "author": "Your Name", + "license": "MIT" +} diff --git a/backend/src/database.js b/backend/src/database.js new file mode 100644 index 0000000..1ce6ed7 --- /dev/null +++ b/backend/src/database.js @@ -0,0 +1,128 @@ +const sqlite3 = require('sqlite3').verbose(); +const path = require('path'); + +// 数据库文件路径 +const dbPath = path.join(__dirname, '../data/digests.db'); + +// 创建数据库连接 +function createConnection() { + return new Promise((resolve, reject) => { + const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + reject(err); + } else { + resolve(db); + } + }); + }); +} + +// 初始化数据库表 +async function initDatabase() { + const db = await createConnection(); + + return new Promise((resolve, reject) => { + db.serialize(() => { + // 创建摘要表 + db.run(` + CREATE TABLE IF NOT EXISTS digests ( + id TEXT PRIMARY KEY, + transcript TEXT NOT NULL, + summary TEXT NOT NULL, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `, (err) => { + if (err) { + reject(err); + } else { + console.log('摘要表创建成功'); + resolve(); + } + }); + }); + + db.close(); + }); +} + +// 插入新摘要 +async function insertDigest(id, transcript, summary) { + const db = await createConnection(); + + return new Promise((resolve, reject) => { + const stmt = db.prepare(` + INSERT INTO digests (id, transcript, summary) + VALUES (?, ?, ?) + `); + + stmt.run([id, transcript, JSON.stringify(summary)], function(err) { + if (err) { + reject(err); + } else { + resolve({ id, transcript, summary, createdAt: new Date().toISOString() }); + } + }); + + stmt.finalize(); + db.close(); + }); +} + +// 获取所有摘要 +async function getAllDigests() { + const db = await createConnection(); + + return new Promise((resolve, reject) => { + db.all(` + SELECT id, transcript, summary, createdAt + FROM digests + ORDER BY createdAt DESC + `, (err, rows) => { + if (err) { + reject(err); + } else { + const digests = rows.map(row => ({ + ...row, + summary: JSON.parse(row.summary) + })); + resolve(digests); + } + }); + + db.close(); + }); +} + +// 根据ID获取摘要 +async function getDigestById(id) { + const db = await createConnection(); + + return new Promise((resolve, reject) => { + db.get(` + SELECT id, transcript, summary, createdAt + FROM digests + WHERE id = ? + `, [id], (err, row) => { + if (err) { + reject(err); + } else if (row) { + const digest = { + ...row, + summary: JSON.parse(row.summary) + }; + resolve(digest); + } else { + resolve(null); + } + }); + + db.close(); + }); +} + +module.exports = { + initDatabase, + insertDigest, + getAllDigests, + getDigestById +}; diff --git a/backend/src/routes/digest.js b/backend/src/routes/digest.js new file mode 100644 index 0000000..09e1bbe --- /dev/null +++ b/backend/src/routes/digest.js @@ -0,0 +1,139 @@ +const express = require('express'); +const { v4: uuidv4 } = require('uuid'); +const { insertDigest, getAllDigests, getDigestById } = require('../database'); +const { generateMeetingDigest } = require('../services/aiService'); + +const router = express.Router(); + +/** + * POST /api/digests + * 创建新的会议摘要 + */ +router.post('/', async (req, res) => { + try { + const { transcript } = req.body; + + // 验证输入 + if (!transcript || typeof transcript !== 'string' || transcript.trim().length === 0) { + return res.status(400).json({ + error: '无效的输入', + message: '请提供有效的会议记录文本' + }); + } + + if (transcript.length > 10000) { + return res.status(400).json({ + error: '输入过长', + message: '会议记录不能超过10000个字符' + }); + } + + // 生成唯一ID + const id = uuidv4(); + + // 调用AI服务生成摘要 + console.log('开始生成AI摘要...'); + const summary = await generateMeetingDigest(transcript); + console.log('AI摘要生成完成'); + + // 保存到数据库 + const digest = await insertDigest(id, transcript, summary); + + res.status(201).json(digest); + } catch (error) { + console.error('创建摘要失败:', error); + + if (error.message.includes('Google AI API密钥未配置')) { + return res.status(500).json({ + error: 'AI服务配置错误', + message: '请检查Google AI API密钥配置' + }); + } + + if (error.message.includes('AI摘要生成失败')) { + return res.status(500).json({ + error: 'AI服务调用失败', + message: 'AI服务暂时不可用,请稍后重试' + }); + } + + res.status(500).json({ + error: '创建摘要失败', + message: '服务器内部错误,请稍后重试' + }); + } +}); + +/** + * GET /api/digests + * 获取所有摘要列表 + */ +router.get('/', async (req, res) => { + try { + const digests = await getAllDigests(); + res.json(digests); + } catch (error) { + console.error('获取摘要列表失败:', error); + res.status(500).json({ + error: '获取摘要列表失败', + message: '服务器内部错误,请稍后重试' + }); + } +}); + +/** + * GET /api/digests/:id + * 根据ID获取单个摘要 + */ +router.get('/:id', async (req, res) => { + try { + const { id } = req.params; + + // 验证ID格式 + if (!id || typeof id !== 'string') { + return res.status(400).json({ + error: '无效的ID', + message: '请提供有效的摘要ID' + }); + } + + const digest = await getDigestById(id); + + if (!digest) { + return res.status(404).json({ + error: '摘要不存在', + message: '未找到指定的摘要' + }); + } + + res.json(digest); + } catch (error) { + console.error('获取摘要详情失败:', error); + res.status(500).json({ + error: '获取摘要详情失败', + message: '服务器内部错误,请稍后重试' + }); + } +}); + +/** + * DELETE /api/digests/:id + * 删除指定摘要(可选功能) + */ +router.delete('/:id', async (req, res) => { + try { + // 这里可以添加删除逻辑 + res.status(501).json({ + error: '功能未实现', + message: '删除功能暂未实现' + }); + } catch (error) { + console.error('删除摘要失败:', error); + res.status(500).json({ + error: '删除摘要失败', + message: '服务器内部错误,请稍后重试' + }); + } +}); + +module.exports = router; diff --git a/backend/src/server.js b/backend/src/server.js new file mode 100644 index 0000000..7c45eb4 --- /dev/null +++ b/backend/src/server.js @@ -0,0 +1,58 @@ +const express = require('express'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const dotenv = require('dotenv'); +const { initDatabase } = require('./database'); +const digestRoutes = require('./routes/digest'); + +// 加载环境变量 +dotenv.config(); + +const app = express(); +const PORT = process.env.PORT || 5000; + +// 中间件 +app.use(cors()); +app.use(bodyParser.json({ limit: '10mb' })); +app.use(bodyParser.urlencoded({ extended: true })); + +// 路由 +app.use('/api/digests', digestRoutes); + +// 健康检查端点 +app.get('/health', (req, res) => { + res.json({ status: 'OK', timestamp: new Date().toISOString() }); +}); + +// 错误处理中间件 +app.use((err, req, res, next) => { + console.error('Error:', err); + res.status(500).json({ + error: '服务器内部错误', + message: process.env.NODE_ENV === 'development' ? err.message : '请稍后重试' + }); +}); + +// 404处理 +app.use('*', (req, res) => { + res.status(404).json({ error: '接口不存在' }); +}); + +// 启动服务器 +async function startServer() { + try { + // 初始化数据库 + await initDatabase(); + console.log('数据库初始化完成'); + + app.listen(PORT, () => { + console.log(`服务器运行在端口 ${PORT}`); + console.log(`健康检查: http://localhost:${PORT}/health`); + }); + } catch (error) { + console.error('启动服务器失败:', error); + process.exit(1); + } +} + +startServer(); diff --git a/backend/src/services/aiService.js b/backend/src/services/aiService.js new file mode 100644 index 0000000..08ff706 --- /dev/null +++ b/backend/src/services/aiService.js @@ -0,0 +1,149 @@ +const { GoogleGenerativeAI } = require('@google/generative-ai'); + +// 初始化Google AI +const genAI = new GoogleGenerativeAI(process.env.GOOGLE_AI_API_KEY); + +// 会议摘要提示模板 +const MEETING_DIGEST_PROMPT = ` +请分析以下会议记录,并生成一个结构化的摘要。请按照以下格式返回JSON: + +{ + "overview": "一段简洁的会议概述,总结会议的主要内容和目的", + "decisions": [ + "会议中做出的关键决策1", + "会议中做出的关键决策2" + ], + "actionItems": [ + { + "task": "需要完成的任务描述", + "assignee": "负责人姓名" + } + ] +} + +会议记录: +`; + +/** + * 生成会议摘要 + * @param {string} transcript - 会议记录文本 + * @returns {Promise} 结构化的摘要对象 + */ +async function generateMeetingDigest(transcript) { + try { + if (!process.env.GOOGLE_AI_API_KEY) { + throw new Error('Google AI API密钥未配置'); + } + + // 获取模型 + const model = genAI.getGenerativeModel({ model: "gemini-pro" }); + + // 构建完整提示 + const fullPrompt = MEETING_DIGEST_PROMPT + transcript; + + // 生成内容 + const result = await model.generateContent(fullPrompt); + const response = await result.response; + const text = response.text(); + + // 尝试解析JSON响应 + try { + // 清理响应文本,提取JSON部分 + const jsonMatch = text.match(/\{[\s\S]*\}/); + if (jsonMatch) { + const summary = JSON.parse(jsonMatch[0]); + + // 验证摘要结构 + if (summary.overview && Array.isArray(summary.decisions) && Array.isArray(summary.actionItems)) { + return summary; + } + } + + // 如果无法解析JSON,抛出错误 + throw new Error('AI响应格式无效'); + } catch (parseError) { + console.error('解析AI响应失败:', parseError); + console.error('原始响应:', text); + + // 返回默认格式的摘要 + return { + overview: "AI无法解析会议记录,请检查输入格式。", + decisions: ["需要人工审核"], + actionItems: [ + { + task: "重新提交会议记录", + assignee: "用户" + } + ] + }; + } + } catch (error) { + console.error('AI服务调用失败:', error); + throw new Error(`AI摘要生成失败: ${error.message}`); + } +} + +/** + * 流式生成会议摘要(用于实时显示) + * @param {string} transcript - 会议记录文本 + * @param {Function} onChunk - 接收文本块的回调函数 + * @returns {Promise} 完整的摘要对象 + */ +async function generateMeetingDigestStream(transcript, onChunk) { + try { + if (!process.env.GOOGLE_AI_API_KEY) { + throw new Error('Google AI API密钥未配置'); + } + + const model = genAI.getGenerativeModel({ model: "gemini-pro" }); + const fullPrompt = MEETING_DIGEST_PROMPT + transcript; + + // 使用流式生成 + const result = await model.generateContentStream(fullPrompt); + + let fullText = ''; + + for await (const chunk of result.stream) { + const chunkText = chunk.text(); + fullText += chunkText; + + if (onChunk) { + onChunk(chunkText); + } + } + + // 解析完整响应 + try { + const jsonMatch = fullText.match(/\{[\s\S]*\}/); + if (jsonMatch) { + const summary = JSON.parse(jsonMatch[0]); + + if (summary.overview && Array.isArray(summary.decisions) && Array.isArray(summary.actionItems)) { + return summary; + } + } + + throw new Error('AI响应格式无效'); + } catch (parseError) { + console.error('解析流式AI响应失败:', parseError); + return { + overview: "AI无法解析会议记录,请检查输入格式。", + decisions: ["需要人工审核"], + actionItems: [ + { + task: "重新提交会议记录", + assignee: "用户" + } + ] + }; + } + } catch (error) { + console.error('流式AI服务调用失败:', error); + throw new Error(`AI摘要生成失败: ${error.message}`); + } +} + +module.exports = { + generateMeetingDigest, + generateMeetingDigestStream +}; diff --git a/demo-data.md b/demo-data.md new file mode 100644 index 0000000..d24df0b --- /dev/null +++ b/demo-data.md @@ -0,0 +1,185 @@ +# 演示数据 - 会议记录示例 + +以下是一些示例会议记录,您可以使用它们来测试AI会议摘要应用: + +## 📅 示例1: 产品开发会议 + +``` +会议时间: 2024年1月15日 14:00-15:30 +参会人员: 产品经理张三、开发工程师李四、设计师王五、测试工程师赵六 + +会议内容: +张三: 大家好,今天我们讨论Q1产品开发计划。首先回顾一下上个月的进展。 + +李四: 上个月我们完成了用户登录模块和基础数据展示功能,目前正在进行性能优化。 + +王五: 设计稿已经完成80%,主要页面都已经设计完成,还需要完善一些细节。 + +赵六: 测试方面,我们已经完成了功能测试,发现了一些小问题,开发团队正在修复。 + +张三: 很好,那么Q1的主要目标是什么? + +李四: 我建议Q1重点开发用户管理模块和数据分析功能,这两个功能用户需求最强烈。 + +王五: 我同意,而且这两个模块的设计稿我已经准备好了。 + +赵六: 从测试角度,我建议增加自动化测试覆盖率,目前只有60%。 + +张三: 好的,那我们Q1的目标就确定了:1. 完成用户管理模块开发 2. 实现数据分析功能 3. 提升测试覆盖率到80% 4. 优化系统性能。 + +李四: 用户管理模块预计需要3周,数据分析功能需要4周。 + +王五: 设计配合开发进度,我会提前一周完成设计稿。 + +赵六: 我会制定自动化测试计划,争取在开发完成后一周内完成测试。 + +张三: 很好,那我们下周一开始执行,每周五下午进行进度同步。散会。 +``` + +## 📅 示例2: 市场营销策略会议 + +``` +会议时间: 2024年1月20日 10:00-11:30 +参会人员: 市场总监陈总、品牌经理刘经理、数字营销专员小周、内容策划小李 + +会议内容: +陈总: 各位好,今天讨论2024年Q1市场营销策略。首先请刘经理汇报一下去年的市场表现。 + +刘经理: 去年我们完成了品牌升级,社交媒体粉丝增长了30%,但转化率有所下降,从5%降到3.8%。 + +小周: 数字营销方面,我们投放了多个平台,抖音和微信效果最好,但成本在上升。 + +小李: 内容创作方面,我们每月产出20篇原创内容,用户互动率不错,但缺乏爆款内容。 + +陈总: 分析一下转化率下降的原因? + +刘经理: 主要原因是竞争对手增加了,用户选择更多了,而且我们的产品差异化不够明显。 + +小周: 投放成本上升主要是因为平台算法调整,需要优化投放策略。 + +小李: 内容方面,我建议增加视频内容,现在短视频很受欢迎。 + +陈总: 好的,那Q1的策略是什么? + +刘经理: 我建议:1. 加强产品差异化宣传 2. 优化投放策略,降低获客成本 3. 增加视频内容产出。 + +小周: 投放优化方面,我会重点优化抖音和微信,尝试小红书等新平台。 + +小李: 我会制定视频内容计划,每周产出3-5个短视频。 + +陈总: 很好,那我们Q1的目标是:提升转化率到4.5%,降低获客成本15%,增加视频内容占比到40%。 + +刘经理: 我会制定详细的执行计划,下周提交。 + +小周: 投放优化方案我本周内完成。 + +小李: 视频内容计划我明天提交。 + +陈总: 好的,那我们下周一开始执行,每两周汇报一次进展。散会。 +``` + +## 📅 示例3: 团队建设会议 + +``` +会议时间: 2024年1月25日 16:00-17:00 +参会人员: 技术总监吴总、前端组长小张、后端组长小王、测试组长小陈 + +会议内容: +吴总: 各位好,今天讨论团队建设和技能提升计划。首先了解一下大家的需求。 + +小张: 前端团队希望学习Vue3和TypeScript,现在项目都在用这些技术。 + +小王: 后端团队需要提升数据库优化和微服务架构能力,现在系统越来越复杂。 + +小陈: 测试团队希望学习自动化测试和性能测试,提高测试效率。 + +吴总: 很好,那我们制定一个技能提升计划。小张,Vue3和TypeScript培训怎么安排? + +小张: 我建议每周三下午进行技术分享,轮流分享学习心得,预计需要8周。 + +小王: 后端方面,我建议邀请外部专家进行数据库优化培训,微服务架构可以内部学习。 + +小陈: 测试方面,我建议参加线上课程,然后内部实践,预计需要6周。 + +吴总: 好的,那我们Q1的技能提升计划是:1. 前端团队掌握Vue3和TypeScript 2. 后端团队提升数据库优化能力 3. 测试团队学习自动化测试。 + +小张: 我会制定详细的学习计划,包括学习资料和实践项目。 + +小王: 我会联系外部培训资源,安排数据库优化培训。 + +小陈: 我会选择合适的线上课程,制定学习计划。 + +吴总: 很好,那我们下周开始执行,每月底进行学习成果展示。另外,我建议建立技术分享机制,每周五下午进行技术分享。 + +小张: 我同意,这样可以促进团队交流。 + +小王: 我也支持,可以分享各自领域的最新技术。 + +小陈: 测试方面的新工具和方法也可以分享。 + +吴总: 好的,那我们就这么定了。散会。 +``` + +## 📅 示例4: 客户反馈会议 + +``` +会议时间: 2024年1月30日 14:00-15:00 +参会人员: 客户成功经理孙经理、产品经理张三、技术支持小李、销售经理王经理 + +会议内容: +孙经理: 各位好,今天讨论客户反馈和产品改进。首先请小李汇报一下最近的客户反馈。 + +小李: 最近一个月收到客户反馈156条,主要问题集中在:1. 系统响应速度慢 2. 移动端体验不好 3. 数据导出功能缺失 4. 用户界面不够直观。 + +张三: 从产品角度,这些问题确实存在。系统响应慢主要是因为数据查询没有优化,移动端体验差是因为没有做响应式设计。 + +王经理: 从销售角度,这些问题影响了客户续费率,有些客户表示如果问题不解决,可能会考虑其他产品。 + +孙经理: 那我们制定一个改进计划。张三,系统性能优化需要多长时间? + +张三: 数据查询优化需要2周,移动端响应式设计需要3周,数据导出功能需要1周,界面优化需要2周。 + +小李: 技术支持方面,我会制定客户培训计划,帮助客户更好地使用产品。 + +王经理: 销售方面,我会重点跟进有流失风险的客户,争取时间。 + +孙经理: 好的,那我们Q1的改进计划是:1. 2月底完成数据查询优化 2. 3月中旬完成移动端优化 3. 2月中旬完成数据导出功能 4. 3月底完成界面优化。 + +张三: 我会制定详细的开发计划,确保按时交付。 + +小李: 我会准备客户培训材料,配合产品改进进度。 + +王经理: 我会制定客户跟进计划,降低流失风险。 + +孙经理: 很好,那我们下周开始执行,每周五进行进度同步。另外,我建议建立客户反馈快速响应机制,重要问题24小时内响应。 + +张三: 我同意,可以建立优先级分类。 + +小李: 技术支持会配合,确保快速响应。 + +王经理: 销售团队也会及时反馈客户需求。 + +孙经理: 好的,那我们就这么定了。散会。 +``` + +## 🎯 使用说明 + +1. 复制上述任意一段会议记录 +2. 粘贴到应用的会议记录输入框 +3. 点击"生成摘要"按钮 +4. 查看AI生成的结构化摘要 + +## 💡 提示 + +- 这些示例包含了不同类型的会议内容 +- 每个示例都有明确的参会人员、讨论内容和决策 +- 适合测试AI的摘要生成能力 +- 可以对比不同会议类型的摘要效果 + +## 🔄 测试建议 + +1. **功能测试**: 测试基本的摘要生成功能 +2. **格式测试**: 验证摘要的结构化输出 +3. **内容测试**: 检查AI是否准确提取了关键信息 +4. **性能测试**: 测试长文本的处理能力 +5. **错误处理**: 测试异常情况的处理 diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..05230f3 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,141 @@ +# AI会议摘要 - 前端 + +这是AI会议摘要应用的前端部分,使用Vue3 + Vite构建。 + +## 功能特性 + +- 🎯 会议记录输入和AI摘要生成 +- 📋 结构化摘要显示(概述、决策、行动项目) +- 📚 摘要历史记录管理 +- 🔗 可分享的摘要链接 +- 📱 响应式设计,支持移动端 +- 🎨 现代化UI设计 + +## 技术栈 + +- **Vue 3** - 渐进式JavaScript框架 +- **Vite** - 快速构建工具 +- **Vue Router** - 官方路由管理器 +- **Pinia** - 状态管理库 +- **Axios** - HTTP客户端 +- **CSS3** - 现代化样式 + +## 快速开始 + +### 安装依赖 + +```bash +npm install +``` + +### 开发模式 + +```bash +npm run dev +``` + +应用将在 http://localhost:3000 启动 + +### 构建生产版本 + +```bash +npm run build +``` + +### 预览构建结果 + +```bash +npm run preview +``` + +## 项目结构 + +``` +frontend/ +├── src/ +│ ├── components/ # 可复用组件 +│ ├── views/ # 页面组件 +│ │ ├── Home.vue # 主页(输入和生成) +│ │ ├── DigestList.vue # 摘要列表页 +│ │ └── DigestDetail.vue # 摘要详情页 +│ ├── stores/ # 状态管理 +│ │ └── digest.js # 摘要相关状态 +│ ├── router/ # 路由配置 +│ │ └── index.js # 路由定义 +│ ├── App.vue # 根组件 +│ ├── main.js # 应用入口 +│ └── style.css # 全局样式 +├── public/ # 静态资源 +├── index.html # HTML模板 +├── vite.config.js # Vite配置 +└── package.json # 项目依赖 +``` + +## 开发说明 + +### 组件设计 + +- **Home.vue**: 主要的会议记录输入和摘要生成页面 +- **DigestList.vue**: 显示所有已生成的摘要列表 +- **DigestDetail.vue**: 单个摘要的详细视图 + +### 状态管理 + +使用Pinia进行状态管理,主要管理: +- 摘要数据 +- 加载状态 +- 错误处理 + +### 路由配置 + +- `/` - 主页(输入和生成) +- `/digests` - 摘要列表 +- `/digest/:id` - 摘要详情 + +### API集成 + +前端通过Axios与后端API通信: +- `POST /api/digests` - 创建新摘要 +- `GET /api/digests` - 获取所有摘要 +- `GET /api/digests/:id` - 获取单个摘要 + +## 样式指南 + +- 使用CSS变量定义主题色彩 +- 响应式设计,支持移动端和桌面端 +- 现代化UI组件,包含悬停效果和过渡动画 +- 使用Flexbox和Grid进行布局 + +## 浏览器支持 + +- Chrome 90+ +- Firefox 88+ +- Safari 14+ +- Edge 90+ + +## 开发工具 + +- **ESLint** - 代码质量检查 +- **Prettier** - 代码格式化 +- **Vite DevTools** - 开发调试工具 + +## 部署 + +项目可以部署到任何支持静态文件的托管服务: + +- Vercel +- Netlify +- GitHub Pages +- 传统Web服务器 + +## 贡献指南 + +1. Fork项目 +2. 创建功能分支 +3. 提交更改 +4. 推送到分支 +5. 创建Pull Request + +## 许可证 + +MIT License diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..19fa04e --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + AI会议摘要 + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..e57af40 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "ai-meeting-digest-frontend", + "version": "1.0.0", + "description": "AI会议摘要前端应用", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + }, + "dependencies": { + "vue": "^3.4.0", + "vue-router": "^4.2.5", + "pinia": "^2.1.7", + "axios": "^1.6.0", + "@google/generative-ai": "^0.2.1" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.5.0", + "vite": "^5.0.0", + "eslint": "^8.55.0", + "eslint-plugin-vue": "^9.19.0", + "@vue/eslint-config-prettier": "^9.0.0", + "prettier": "^3.1.0" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..aecf12c --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..5ad5b00 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,12 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import router from './router' +import App from './App.vue' +import './style.css' + +const app = createApp(App) +const pinia = createPinia() + +app.use(pinia) +app.use(router) +app.mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..09de5e3 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,30 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Home from '../views/Home.vue' +import DigestList from '../views/DigestList.vue' +import DigestDetail from '../views/DigestDetail.vue' + +const routes = [ + { + path: '/', + name: 'Home', + component: Home + }, + { + path: '/digests', + name: 'DigestList', + component: DigestList + }, + { + path: '/digest/:id', + name: 'DigestDetail', + component: DigestDetail, + props: true + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router diff --git a/frontend/src/stores/digest.js b/frontend/src/stores/digest.js new file mode 100644 index 0000000..dd26c56 --- /dev/null +++ b/frontend/src/stores/digest.js @@ -0,0 +1,76 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import axios from 'axios' + +export const useDigestStore = defineStore('digest', () => { + const digests = ref([]) + const currentDigest = ref(null) + const loading = ref(false) + const error = ref(null) + + // 获取所有摘要 + const fetchDigests = async () => { + try { + loading.value = true + error.value = null + const response = await axios.get('/api/digests') + digests.value = response.data + } catch (err) { + error.value = '获取摘要列表失败' + console.error('Error fetching digests:', err) + } finally { + loading.value = false + } + } + + // 获取单个摘要 + const fetchDigest = async (id) => { + try { + loading.value = true + error.value = null + const response = await axios.get(`/api/digests/${id}`) + currentDigest.value = response.data + return response.data + } catch (err) { + error.value = '获取摘要详情失败' + console.error('Error fetching digest:', err) + throw err + } finally { + loading.value = false + } + } + + // 创建新摘要 + const createDigest = async (transcript) => { + try { + loading.value = true + error.value = null + const response = await axios.post('/api/digests', { transcript }) + const newDigest = response.data + digests.value.unshift(newDigest) + return newDigest + } catch (err) { + error.value = '创建摘要失败' + console.error('Error creating digest:', err) + throw err + } finally { + loading.value = false + } + } + + // 计算属性 + const sortedDigests = computed(() => { + return [...digests.value].sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) + }) + + return { + digests, + currentDigest, + loading, + error, + sortedDigests, + fetchDigests, + fetchDigest, + createDigest + } +}) diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..6be622e --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,83 @@ +/* 全局样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: #f8fafc; + color: #1f2937; + line-height: 1.6; +} + +#app { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* 通用按钮样式 */ +button { + cursor: pointer; + font-family: inherit; +} + +button:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +/* 通用链接样式 */ +a { + color: inherit; + text-decoration: none; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + body { + font-size: 14px; + } +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* 工具提示 */ +[title] { + position: relative; +} + +/* 焦点样式 */ +:focus { + outline: 2px solid #667eea; + outline-offset: 2px; +} + +/* 选择文本样式 */ +::selection { + background-color: #667eea; + color: white; +} diff --git a/frontend/src/views/DigestDetail.vue b/frontend/src/views/DigestDetail.vue new file mode 100644 index 0000000..0a814b7 --- /dev/null +++ b/frontend/src/views/DigestDetail.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/frontend/src/views/DigestList.vue b/frontend/src/views/DigestList.vue new file mode 100644 index 0000000..5574ca3 --- /dev/null +++ b/frontend/src/views/DigestList.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue new file mode 100644 index 0000000..55c23d8 --- /dev/null +++ b/frontend/src/views/Home.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..135a0ce --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true + } + } + } +}) diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..4807f4c --- /dev/null +++ b/start.bat @@ -0,0 +1,77 @@ +@echo off +chcp 65001 >nul +echo 🚀 启动AI会议摘要应用... + +REM 检查Node.js是否安装 +node --version >nul 2>&1 +if errorlevel 1 ( + echo ❌ 错误: 未找到Node.js,请先安装Node.js 16.0+ + pause + exit /b 1 +) + +REM 检查npm是否安装 +npm --version >nul 2>&1 +if errorlevel 1 ( + echo ❌ 错误: 未找到npm,请先安装npm + pause + exit /b 1 +) + +echo ✅ Node.js版本: +node --version +echo ✅ npm版本: +npm --version + +REM 启动后端 +echo 🔧 启动后端服务... +cd backend + +REM 检查是否已安装依赖 +if not exist "node_modules" ( + echo 📦 安装后端依赖... + npm install +) + +REM 检查环境变量文件 +if not exist ".env" ( + echo ⚠️ 警告: 未找到.env文件,请先配置Google AI API密钥 + echo 📝 复制env.example为.env并配置API密钥 + copy env.example .env + echo 🔑 请在.env文件中设置GOOGLE_AI_API_KEY + echo 🌐 访问 https://aistudio.google.com/app/apikey 获取API密钥 + pause + exit /b 1 +) + +REM 启动后端服务 +echo 🚀 启动后端服务 (端口5000)... +start "后端服务" cmd /k "npm run dev" + +REM 等待后端启动 +timeout /t 5 /nobreak >nul + +REM 启动前端 +echo 🎨 启动前端应用... +cd ..\frontend + +REM 检查是否已安装依赖 +if not exist "node_modules" ( + echo 📦 安装前端依赖... + npm install +) + +REM 启动前端服务 +echo 🚀 启动前端服务 (端口3000)... +start "前端服务" cmd /k "npm run dev" + +echo. +echo 🎉 应用启动完成! +echo. +echo 📱 前端: http://localhost:3000 +echo 🔧 后端: http://localhost:5000 +echo 💚 健康检查: http://localhost:5000/health +echo. +echo 服务已在新的命令行窗口中启动 +echo 关闭对应的命令行窗口即可停止服务 +pause diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..7d31e52 --- /dev/null +++ b/start.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +echo "🚀 启动AI会议摘要应用..." + +# 检查Node.js是否安装 +if ! command -v node &> /dev/null; then + echo "❌ 错误: 未找到Node.js,请先安装Node.js 16.0+" + exit 1 +fi + +# 检查npm是否安装 +if ! command -v npm &> /dev/null; then + echo "❌ 错误: 未找到npm,请先安装npm" + exit 1 +fi + +echo "✅ Node.js版本: $(node --version)" +echo "✅ npm版本: $(npm --version)" + +# 启动后端 +echo "🔧 启动后端服务..." +cd backend + +# 检查是否已安装依赖 +if [ ! -d "node_modules" ]; then + echo "📦 安装后端依赖..." + npm install +fi + +# 检查环境变量文件 +if [ ! -f ".env" ]; then + echo "⚠️ 警告: 未找到.env文件,请先配置Google AI API密钥" + echo "📝 复制env.example为.env并配置API密钥" + cp env.example .env + echo "🔑 请在.env文件中设置GOOGLE_AI_API_KEY" + echo "🌐 访问 https://aistudio.google.com/app/apikey 获取API密钥" + exit 1 +fi + +# 启动后端服务 +echo "🚀 启动后端服务 (端口5000)..." +npm run dev & +BACKEND_PID=$! + +# 等待后端启动 +sleep 5 + +# 启动前端 +echo "🎨 启动前端应用..." +cd ../frontend + +# 检查是否已安装依赖 +if [ ! -d "node_modules" ]; then + echo "📦 安装前端依赖..." + npm install +fi + +# 启动前端服务 +echo "🚀 启动前端服务 (端口3000)..." +npm run dev & +FRONTEND_PID=$! + +echo "" +echo "🎉 应用启动完成!" +echo "" +echo "📱 前端: http://localhost:3000" +echo "🔧 后端: http://localhost:5000" +echo "💚 健康检查: http://localhost:5000/health" +echo "" +echo "按 Ctrl+C 停止所有服务" + +# 等待用户中断 +trap "echo ''; echo '🛑 正在停止服务...'; kill $BACKEND_PID $FRONTEND_PID 2>/dev/null; exit 0" INT + +# 保持脚本运行 +wait From 9f145d5b31c33610b09f2dc1bf3e9810ff7cb5be Mon Sep 17 00:00:00 2001 From: jihua Date: Thu, 14 Aug 2025 17:17:46 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E5=9C=A8feature/2452-HNuUyaU1?= =?UTF-8?q?=E5=88=86=E6=94=AF=E4=B8=8A=E5=AE=8C=E6=88=90work4u=E9=9D=A2?= =?UTF-8?q?=E8=AF=95=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit