from flask import Flask, request, Response, stream_with_context import requests import os import json import urllib3 # 禁用 SSL 警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) app = Flask(__name__) # 从环境变量获取配置 OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY') OPENAI_API_URL = os.environ.get('OPENAI_API_URL', 'https://api.openai.com/v1/chat/completions') # PPT 大纲生成接口 @app.route('/tools/aippt_outline', methods=['POST']) def aippt_outline(): data = request.json content = data.get('content') language = data.get('language', 'zh') model = data.get('model', 'gpt-4o') # 构建发送给 OpenAI 的提示 messages = [ { "role": "system", "content": f"""您是一位专业的PPT大纲生成器。请为主题'{content}'创建一个详细的演示文稿大纲,使用{language}语言。 您的大纲必须严格遵循以下格式,这是非常重要的: # [主题标题] ## [一级章节标题] ### [二级标题] - [要点] - [要点] ... 具体要求: 1. 大纲必须以"# [主题标题]"开始 2. 包含4-6个一级章节,每个章节以"##"开头 3. 每个一级章节下包含2-3个二级标题,每个二级标题以"###"开头 4. 每个二级标题下包含2-4个简短要点,每个要点以"-"开头 5. 要点应简洁明了,每个要点不超过15个字 6. 章节标题和二级标题应该是名词短语,不要使用问句或长句 请确保大纲层次分明,内容全面且有深度。章节安排应当符合逻辑顺序,便于读者理解。""" } ] def generate(): headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {OPENAI_API_KEY}' } payload = { 'model': model, 'messages': messages, 'stream': True } response = requests.post( OPENAI_API_URL, headers=headers, json=payload, stream=True, verify=False # 禁用 SSL 验证 ) for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith('data: ') and line != 'data: [DONE]': try: data = json.loads(line[6:]) if data['choices'][0]['delta'].get('content'): content = data['choices'][0]['delta']['content'] yield content except Exception as e: print(f"Error parsing OpenAI response: {e}") return Response(stream_with_context(generate()), mimetype='text/event-stream') # PPT 内容生成接口 @app.route('/tools/aippt', methods=['POST']) def aippt(): data = request.json content = data.get('content') language = data.get('language', 'zh') model = data.get('model', 'gpt-4.1-mini') # 构建发送给 OpenAI 的提示,包含 PPTist 所需数据结构的详细说明 messages = [ { "role": "system", "content": f"""您是一位专业的PPT内容生成器。根据提供的大纲,您需要生成一系列JSON对象,每个对象代表一张幻灯片。请严格按照以下规则和格式进行生成: 1. 您需要生成这些类型的幻灯片:封面页(cover)、目录页(contents)、过渡页(transition)、内容页(content)和结束页(end) 2. 必须严格遵循以下JSON格式,任何格式错误都会导致幻灯片无法显示: 封面页: {{"type":"cover","data":{{"title":"主标题","text":"副标题或简短描述"}}}} 目录页: {{"type":"contents","data":{{"items":["一级章节1","一级章节2","一级章节3","一级章节4","一级章节5"]}}}} 过渡页: {{"type":"transition","data":{{"title":"章节标题","text":"章节简介"}}}} 内容页: {{"type":"content","data":{{"title":"二级标题","items":[{{"title":"要点1标题","text":"要点1描述"}},{{"title":"要点2标题","text":"要点2描述"}},{{"title":"要点3标题","text":"要点3描述"}},{{"title":"要点4标题","text":"要点4描述"}}]}}}} 结束页: {{"type":"end"}} 3. 转换规则: - 大纲中的主标题("#"开头)转换为封面页 - 所有一级章节("##"开头)组成目录页 - 每个一级章节前生成一个过渡页 - 每个二级标题("###"开头)生成一个内容页 - 内容页中的items数组由该二级标题下的要点("-"开头)组成 - 最后添加一个结束页 4. 输出要求: - 每个JSON对象必须单独成行 - 不要添加任何额外的注释或解释 - 确保每个JSON对象格式正确且完整 - 每个内容页必须包含该节点下所有的要点,通常为2-4个 示例输出: {{"type":"cover","data":{{"title":"5G技术如何改变我们的生活","text":"探索5G技术对现代生活的全方位影响"}}}} {{"type":"contents","data":{{"items":["5G技术概述","5G对通信领域的变革","5G与智能家居的融合","5G推动智能交通发展","5G在医疗领域的应用","5G助力工业互联网升级"]}}}} {{"type":"transition","data":{{"title":"5G技术概述","text":"本章将介绍5G技术的定义、关键特性及发展历程。"}}}}""" }, { "role": "user", "content": f"根据这个大纲生成PPT内容:\n{content}" } ] def generate(): headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {OPENAI_API_KEY}' } payload = { 'model': model, 'messages': messages, 'stream': True } response = requests.post( OPENAI_API_URL, headers=headers, json=payload, stream=True, verify=False # 禁用 SSL 验证 ) buffer = "" for line in response.iter_lines(): if line: line = line.decode('utf-8') if line.startswith('data: ') and line != 'data: [DONE]': try: data = json.loads(line[6:]) if data['choices'][0]['delta'].get('content'): buffer += data['choices'][0]['delta']['content'] # 尝试从缓冲区中提取完整的 JSON 对象 while True: # 寻找可能的 JSON 对象开始和结束 json_start = buffer.find('{') if json_start == -1: break # 寻找匹配的结束括号(处理嵌套JSON) bracket_count = 0 json_end = -1 for i in range(json_start, len(buffer)): if buffer[i] == '{': bracket_count += 1 elif buffer[i] == '}': bracket_count -= 1 if bracket_count == 0: json_end = i break if json_end == -1: # 没有找到匹配的结束括号,等待更多数据 break json_str = buffer[json_start:json_end+1] try: # 验证是否为有效的 JSON slide_data = json.loads(json_str) # 验证是否包含必要的字段 if isinstance(slide_data, dict) and 'type' in slide_data: # 发送到客户端 yield json.dumps(slide_data) + '\n' # 从缓冲区中移除已处理的部分 buffer = buffer[json_end+1:] else: # 格式不符合要求,从缓冲区移除 buffer = buffer[json_end+1:] except Exception as e: # 解析错误,可能不是完整的JSON,等待更多数据 print(f"Error parsing JSON: {e}") break except Exception as e: print(f"Error parsing OpenAI response: {e}") return Response(stream_with_context(generate()), mimetype='text/event-stream') if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=7860)