|
from flask import Flask, request, Response, stream_with_context |
|
import requests |
|
import os |
|
import json |
|
import urllib3 |
|
|
|
|
|
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') |
|
|
|
|
|
@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') |
|
|
|
|
|
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 |
|
) |
|
|
|
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') |
|
|
|
|
|
@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') |
|
|
|
|
|
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 |
|
) |
|
|
|
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'] |
|
|
|
|
|
while True: |
|
|
|
json_start = buffer.find('{') |
|
if json_start == -1: |
|
break |
|
|
|
|
|
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: |
|
|
|
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: |
|
|
|
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) |