forked from fogsightai/fogsight
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
203 lines (174 loc) · 7.09 KB
/
app.py
File metadata and controls
203 lines (174 loc) · 7.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import asyncio
import json
import os
from datetime import datetime
from typing import AsyncGenerator, List, Optional
import pytz
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from openai import AsyncOpenAI, OpenAIError
from pydantic import BaseModel
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
try:
import google.generativeai as genai
except ModuleNotFoundError:
from google import genai
# -----------------------------------------------------------------------
# 0. 配置
# -----------------------------------------------------------------------
shanghai_tz = pytz.timezone("Asia/Shanghai")
credentials = json.load(open("credentials.json"))
API_KEY = credentials["API_KEY"]
BASE_URL = credentials.get("BASE_URL", "")
MODEL = credentials.get("MODEL", "gemini-2.5-pro")
if API_KEY.startswith("sk-"):
# 为 OpenRouter 添加应用标识
extra_headers = {}
if "openrouter.ai" in BASE_URL.lower():
extra_headers = {
"HTTP-Referer": "https://github.com/fogsightai/fogsight",
"X-Title": "Fogsight - AI Animation Generator"
}
client = AsyncOpenAI(
api_key=API_KEY,
base_url=BASE_URL,
default_headers=extra_headers
)
USE_GEMINI = False
else:
os.environ["GEMINI_API_KEY"] = API_KEY
gemini_client = genai.Client()
USE_GEMINI = True
if API_KEY.startswith("sk-REPLACE_ME"):
raise RuntimeError("请在环境变量里配置 API_KEY")
templates = Jinja2Templates(directory="templates")
# -----------------------------------------------------------------------
# 1. FastAPI 初始化
# -----------------------------------------------------------------------
app = FastAPI(title="AI Animation Backend", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["GET", "POST"],
allow_headers=["Content-Type", "Authorization"],
)
app.mount("/static", StaticFiles(directory="static"), name="static")
class ChatRequest(BaseModel):
topic: str
history: Optional[List[dict]] = None
# -----------------------------------------------------------------------
# 2. 核心:流式生成器 (现在会使用 history)
# -----------------------------------------------------------------------
async def llm_event_stream(
topic: str,
history: Optional[List[dict]] = None,
model: str = None, # Will use MODEL from config if not specified
) -> AsyncGenerator[str, None]:
history = history or []
# Use configured model if not specified
if model is None:
model = MODEL
# The system prompt is now more focused
system_prompt = f"""请你生成一个非常精美的动态动画,讲讲 {topic}
要动态的,要像一个完整的,正在播放的视频。包含一个完整的过程,能把知识点讲清楚。
页面极为精美,好看,有设计感,同时能够很好的传达知识。知识和图像要准确
附带一些旁白式的文字解说,从头到尾讲清楚一个小的知识点
不需要任何互动按钮,直接开始播放
使用和谐好看,广泛采用的浅色配色方案,使用很多的,丰富的视觉元素。双语字幕
**请保证任何一个元素都在一个2k分辨率的容器中被摆在了正确的位置,避免穿模,字幕遮挡,图形位置错误等等问题影响正确的视觉传达**
html+css+js+svg,放进一个html里"""
if USE_GEMINI:
try:
full_prompt = system_prompt + "\n\n" + topic
if history:
history_text = "\n".join([f"{msg['role']}: {msg['content']}" for msg in history])
full_prompt = history_text + "\n\n" + full_prompt
response = await asyncio.get_event_loop().run_in_executor(
None,
lambda: gemini_client.models.generate_content(
model=model,
contents=full_prompt
)
)
text = response.text
chunk_size = 50
for i in range(0, len(text), chunk_size):
chunk = text[i:i+chunk_size]
payload = json.dumps({"token": chunk}, ensure_ascii=False)
yield f"data: {payload}\n\n"
await asyncio.sleep(0.05)
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
return
else:
messages = [
{"role": "system", "content": system_prompt},
*history,
{"role": "user", "content": topic},
]
try:
response = await client.chat.completions.create(
model=model,
messages=messages,
stream=True,
temperature=0.8,
)
except OpenAIError as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
return
async for chunk in response:
token = chunk.choices[0].delta.content or ""
if token:
payload = json.dumps({"token": token}, ensure_ascii=False)
yield f"data: {payload}\n\n"
await asyncio.sleep(0.001)
yield 'data: {"event":"[DONE]"}\n\n'
# -----------------------------------------------------------------------
# 3. 路由 (CHANGED: Now a POST request)
# -----------------------------------------------------------------------
@app.post("/generate")
async def generate(
chat_request: ChatRequest, # CHANGED: Use the Pydantic model
request: Request,
):
"""
Main endpoint: POST /generate
Accepts a JSON body with "topic" and optional "history".
Returns an SSE stream.
"""
accumulated_response = "" # for caching flow results
async def event_generator():
nonlocal accumulated_response
try:
async for chunk in llm_event_stream(chat_request.topic, chat_request.history):
accumulated_response += chunk
if await request.is_disconnected():
break
yield chunk
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
async def wrapped_stream():
async for chunk in event_generator():
yield chunk
headers = {
"Cache-Control": "no-store",
"Content-Type": "text/event-stream; charset=utf-8",
"X-Accel-Buffering": "no",
}
return StreamingResponse(wrapped_stream(), headers=headers)
@app.get("/", response_class=HTMLResponse)
async def read_index(request: Request):
return templates.TemplateResponse(
"index.html", {
"request": request,
"time": datetime.now(shanghai_tz).strftime("%Y%m%d%H%M%S")})
# -----------------------------------------------------------------------
# 4. 本地启动命令
# -----------------------------------------------------------------------
# uvicorn app:app --reload --host 0.0.0.0 --port 8000
if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000, reload=False)