Skip to content

Commit

Permalink
Merge pull request #21 from DjangoPeng/v0.7
Browse files Browse the repository at this point in the history
[v0.7]: Add unit tests, Docker integration, and update documentation  @DjangoPeng
  • Loading branch information
DjangoPeng authored Nov 3, 2024
2 parents 5429b3e + dd92a95 commit cf0882b
Show file tree
Hide file tree
Showing 13 changed files with 627 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,4 @@ jupyter/*.pptx
.gradio/*
nohup.out
images/*
test_results.txt
25 changes: 25 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 使用 Python 3.10 slim 作为基础镜像
FROM python:3.10-slim

# 设置工作目录
WORKDIR /app

# 复制并安装项目依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制项目文件到容器
COPY . .

# 赋予验证脚本执行权限
RUN chmod +x validate_tests.sh

# 设置环境变量,以便在运行时可以传入实际的 API Key
ENV LANGCHAIN_API_KEY=${LANGCHAIN_API_KEY}
ENV OPENAI_API_KEY=${OPENAI_API_KEY}

# 在构建过程中运行单元测试
RUN ./validate_tests.sh

# 设置容器的入口点,默认运行 ChatPPT Gradio Server
CMD ["python", "src/gradio_server.py"]
117 changes: 112 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# ChatPPT

## 目录

- [主要功能](#主要功能)
- [产品演示](#产品演示)
- [快速开始](#快速开始)
- [1. 安装依赖](#1-安装依赖)
- [2. 配置应用](#2-配置应用)
- [3. 如何运行](#3-如何运行)
- [A. 作为 Gradio 服务运行](#a-作为-gradio-服务运行)
- [B. 命令行方式运行](#b-命令行方式运行)
- [使用 Docker 部署服务](#使用-docker-部署服务)
- [1. 运行 Docker 容器](#1-运行-docker-容器)
- [2. 配置环境变量](#2-配置环境变量)
- [PowerPoint 母版布局命名规范](#powerpoint-母版布局命名规范)
- [单元测试](#单元测试)
- [单元测试和验证脚本 `validate_tests.sh`](#单元测试和验证脚本-validate_testssh)
- [用途](#用途)
- [功能](#功能)
- [使用 Docker 构建与验证](#使用-docker-构建与验证)
- [1. `Dockerfile`](#1-dockerfile)
- [用途](#用途)
- [关键步骤](#关键步骤)
- [2. `build_image.sh`](#2-build_imagesh)
- [用途](#用途)
- [功能](#功能)
- [贡献](#贡献)
- [许可证](#许可证)
- [联系](#联系)

ChatPPT 是一个基于多模态 AI 技术的智能助手,旨在提升企业办公自动化流程的效率。它能够处理语音、图像和文本等多种输入形式,通过精确的提示工程和强大的自然语言处理能力,为用户生成高质量的 PowerPoint 演示文稿。ChatPPT 不仅简化了信息收集和内容创作过程,还通过自动化的报告生成和分析功能,帮助企业快速、准确地完成各类汇报和展示任务,从而显著提升工作效率和业务价值。

### 主要功能
Expand Down Expand Up @@ -58,7 +87,7 @@ pip install -r requirements.txt
python src/gradio_server.py
```

#### 命令行方式运行
#### B. 命令行方式运行

您可以通过命令行模式运行 ChatPPT:

Expand All @@ -68,6 +97,34 @@ python src/main.py test_input.md

通过此模式,您可以手动提供 PowerPoint 文件内容(格式请参考:[ChatPPT 输入文本格式说明](docs/ppt_input_format.md)),并按照配置的 [PowerPoint 模板](templates/MasterTemplate.pptx),生成演示文稿。

## 使用 Docker 部署服务

ChatPPT 提供了 Docker 支持,以便在隔离环境中运行。以下是使用 Docker 运行的步骤。

### 1. 运行 Docker 容器

使用以下命令运行 ChatPPT 指定版本(如:v0.7)Docker 容器服务。关于如何 [使用 Docker 构建与验证](#使用-docker-构建与验证)

```sh
docker run -it -p 7860:7860 -e LANGCHAIN_API_KEY=$LANGCHAIN_API_KEY -e OPENAI_API_KEY=$OPENAI_API_KEY -v $(pwd)/outputs:/app/outputs chatppt:v0.7

```

### 2. 参数说明

在运行容器时,可以通过环境变量传入`LANGCHAIN_API_KEY``OPENAI_API_KEY`,例如:

```sh
-e LANGCHAIN_API_KEY=$LANGCHAIN_API_KEY -e OPENAI_API_KEY=$OPENAI_API_KEY
```

将本地的 `outputs` 文件夹挂载到容器内的 `/app/outputs`,便于访问生成的文件。

```sh
-v $(pwd)/outputs:/app/outputs`
```


## PowerPoint 母版布局命名规范

为确保 ChatPPT 能正确匹配布局,PowerPoint 母版文件 ([PowerPoint 模板](templates/MasterTemplate.pptx)) 中的布局名称应遵循以下命名规范:
Expand All @@ -83,16 +140,66 @@ python src/main.py test_input.md

该规范确保布局匹配的灵活性,同时支持多种不同内容的组合和扩展。

### 4. 贡献
## 单元测试

为了确保 ChatPPT 项目的代码质量和可靠性,我们使用了 `unittest` 模块进行单元测试。关于 `unittest` 及其相关工具(如 `@patch``MagicMock`)的详细说明,请参考 [单元测试详细说明](docs/unit_test.md)。

### 单元测试和验证脚本 `validate_tests.sh`

#### 用途
`validate_tests.sh` 是一个用于运行单元测试并验证结果的 Shell 脚本。它会在 Docker 镜像构建过程中执行,以确保代码的正确性和稳定性。

#### 功能
- 脚本运行所有单元测试,并将结果输出到 `test_results.txt` 文件中。
- 如果测试失败,脚本会输出测试结果,并导致 Docker 构建失败,确保未通过测试的代码不会进入生产环境。
- 如果所有测试通过,脚本会继续进行 Docker 镜像的构建。

## 使用 Docker 构建与验证

为了便于在各种环境中构建和部署 ChatPPT 项目,我们提供了 Docker 支持。该支持包括以下文件和功能:

### 1. `Dockerfile`

#### 用途
`Dockerfile` 是用于定义 ChatPPT 项目 Docker 镜像构建过程的配置文件。它描述了构建步骤,包括安装依赖、复制项目文件、运行单元测试等。

#### 关键步骤
- 使用 `python:3.10-slim` 作为基础镜像,并设置工作目录为 `/app`
- 复制项目的 `requirements.txt` 文件,并安装所有 Python 依赖。
- 复制项目的所有文件到容器中,并赋予 `validate_tests.sh` 脚本执行权限。
- 在构建过程中执行 `validate_tests.sh` 脚本,以确保所有单元测试通过。如果测试失败,构建过程将中止。
- 构建成功后,将默认运行 `src/main.py` 作为容器的入口

点,以启动 ChatPPT 服务。

### 2. `build_image.sh`

#### 用途
`build_image.sh` 是一个自动构建 Docker 镜像的 Shell 脚本。它从当前的 Git 分支中获取分支名称,并将其用作 Docker 镜像的标签,便于在不同开发分支上生成不同的 Docker 镜像。

#### 功能
- 获取当前 Git 分支名称,并将其用作 Docker 镜像的标签,以便追踪不同开发分支的版本。
- 使用 `docker build` 命令构建 Docker 镜像,并使用当前 Git 分支名称作为标签。

#### 使用示例
```bash
./build_image.sh
```

![build_docker_image](images/build_docker_image.png)

通过这些脚本和配置文件,ChatPPT 项目可以在不同的开发分支中确保构建的 Docker 镜像基于通过单元测试的代码,从而提高了代码质量和部署的可靠性。

### 贡献

我们欢迎所有的贡献!如果你有任何建议或功能请求,请先开启一个议题讨论。你的帮助将使 ChatPPT 变得更加完善。

### 5. 许可证
### 许可证

该项目根据 **Apache 2.0** 许可证进行许可。详情请参见 [LICENSE](LICENSE) 文件。

### 6. 联系
### 联系

项目作者: Django Peng

项目链接: https://github.com/DjangoPeng/ChatPPT
项目链接: https://github.com/DjangoPeng/ChatPPT
16 changes: 16 additions & 0 deletions build_image.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

# 获取当前的 Git 分支名称
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)

# 如果需要,可以处理分支名称,例如替换无效字符
BRANCH_NAME=${BRANCH_NAME//\//-}

# 使用 Git 分支名称作为 Docker 镜像的标签
IMAGE_TAG="chatppt:${BRANCH_NAME}"

# 构建 Docker 镜像
docker build -t $IMAGE_TAG .

# 输出构建结果
echo "Docker 镜像已构建并打上标签: $IMAGE_TAG"
4 changes: 2 additions & 2 deletions src/docx_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ def generate_markdown_from_docx(docx_filename):
elif text:
markdown_content += f'{text}\n\n' # 普通段落直接添加文本

# 记录调试信息
LOG.debug(f"从 docx 文件解析的 markdown 内容:\n{markdown_content}")
# 记录调试信息
LOG.debug(f"从 docx 文件解析的 markdown 内容:\n{markdown_content}")

return markdown_content

Expand Down
14 changes: 7 additions & 7 deletions src/gradio_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from layout_manager import LayoutManager
from logger import LOG
from openai_whisper import asr, transcribe
from minicpm_v_model import chat_with_image
# from minicpm_v_model import chat_with_image
from docx_parser import generate_markdown_from_docx


Expand Down Expand Up @@ -56,12 +56,12 @@ def generate_contents(message, history):
audio_text = asr(uploaded_file)
texts.append(audio_text)
# 解释说明图像文件
elif file_ext in ('.jpg', '.png', '.jpeg'):
if text_input:
image_desc = chat_with_image(uploaded_file, text_input)
else:
image_desc = chat_with_image(uploaded_file)
return image_desc
# elif file_ext in ('.jpg', '.png', '.jpeg'):
# if text_input:
# image_desc = chat_with_image(uploaded_file, text_input)
# else:
# image_desc = chat_with_image(uploaded_file)
# return image_desc
# 使用 Docx 文件作为素材创建 PowerPoint
elif file_ext in ('.docx', '.doc'):
# 调用 generate_markdown_from_docx 函数,获取 markdown 内容
Expand Down
41 changes: 41 additions & 0 deletions tests/test_data_structures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import unittest
import os
import sys

# 添加 src 目录到模块搜索路径,以便可以导入 src 目录中的模块
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))

from data_structures import PowerPoint, Slide, SlideContent

class TestDataStructures(unittest.TestCase):
"""
测试 PowerPoint、Slide、SlideContent 数据类,验证数据结构的正确性。
"""

def test_slide_content(self):
slide_content = SlideContent(title="Test Slide", bullet_points=[{'text': "Bullet 1", 'level': 0}], image_path="images/test.png")
self.assertEqual(slide_content.title, "Test Slide")
self.assertEqual(slide_content.bullet_points, [{'text': "Bullet 1", 'level': 0}])
self.assertEqual(slide_content.image_path, "images/test.png")

def test_slide(self):
slide_content = SlideContent(title="Slide with Layout")
slide = Slide(layout_id=2, layout_name="Title, Content 0", content=slide_content)
self.assertEqual(slide.layout_id, 2)
self.assertEqual(slide.layout_name, "Title, Content 0")
self.assertEqual(slide.content.title, "Slide with Layout")

def test_powerpoint(self):
slide_content1 = SlideContent(title="Slide 1")
slide_content2 = SlideContent(title="Slide 2")
slide1 = Slide(layout_id=1, layout_name="Title 1", content=slide_content1)
slide2 = Slide(layout_id=2, layout_name="Title, Content 0", content=slide_content2)
ppt = PowerPoint(title="Test Presentation", slides=[slide1, slide2])

self.assertEqual(ppt.title, "Test Presentation")
self.assertEqual(len(ppt.slides), 2)
self.assertEqual(ppt.slides[0].content.title, "Slide 1")
self.assertEqual(ppt.slides[1].content.title, "Slide 2")

if __name__ == "__main__":
unittest.main()
88 changes: 88 additions & 0 deletions tests/test_doc_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import unittest
import os
import sys

# 添加 src 目录到模块搜索路径,以便可以导入 src 目录中的模块
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))

from docx_parser import generate_markdown_from_docx

class TestGenerateMarkdownFromDocx(unittest.TestCase):
"""
测试从 docx 文件生成 Markdown 格式内容的功能。
"""

def setUp(self):
"""
在每个测试方法执行前运行。用于准备测试所需的文件和目录。
"""
# 定义测试 docx 文件的路径
self.test_docx_filename = 'inputs/docx/multimodal_llm_overview.docx'

# 生成 Markdown 内容
self.generated_markdown = generate_markdown_from_docx(self.test_docx_filename)

def test_generated_markdown_content(self):
"""
测试生成的 Markdown 内容是否符合预期。
"""
# 期望的 Markdown 输出内容
expected_markdown = """
# 多模态大模型概述
多模态大模型是指能够处理多种数据模态(如文本、图像、音频等)的人工智能模型。它们在自然语言处理、计算机视觉等领域有广泛的应用。
## 1. 多模态大模型的特点
- 支持多种数据类型:
- 跨模态学习能力:
- 广泛的应用场景:
### 1.1 支持多种数据类型
多模态大模型能够同时处理文本、图像、音频等多种类型的数据,实现数据的融合。
## 2. 多模态模型架构
以下是多模态模型的典型架构示意图:
![图片1](images/multimodal_llm_overview/1.png)
TransFormer 架构图:
![图片2](images/multimodal_llm_overview/2.png)
### 2.1 模态融合技术
通过模态融合,可以提升模型对复杂数据的理解能力。
关键技术:注意力机制、Transformer架构等。
- 应用领域:
- 自然语言处理:
- 机器翻译、文本生成等。
- 计算机视觉:
- 图像识别、目标检测等。
## 3. 未来展望
多模态大模型将在人工智能领域持续发挥重要作用,推动技术创新。
"""

# 比较生成的 Markdown 内容与预期内容
self.assertEqual(self.generated_markdown.strip(), expected_markdown.strip(), "生成的 Markdown 内容与预期不匹配")

def tearDown(self):
"""
在每个测试方法执行后运行。用于清理测试产生的文件和目录。
"""
# 获取图像目录路径
images_dir = 'images/multimodal_llm_overview'
# 删除生成的图像文件和目录
if os.path.exists(images_dir):
for filename in os.listdir(images_dir):
file_path = os.path.join(images_dir, filename)
if os.path.isfile(file_path):
os.unlink(file_path) # 删除文件
os.rmdir(images_dir) # 删除目录

if __name__ == '__main__':
unittest.main()
Loading

0 comments on commit cf0882b

Please sign in to comment.