yaoqifa commited on
Commit
a37eb5b
·
verified ·
1 Parent(s): 346648f

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -131
app.py CHANGED
@@ -61,63 +61,40 @@ def check_ip_rate_limit(ip: str, limit: int = 5, window: int = 60):
61
  return True
62
  valid_history.append(now)
63
  IP_REQUEST_HISTORY[ip] = valid_history
64
- if len(IP_REQUEST_HISTORY) > 5000: IP_REQUEST_HISTORY.clear()
65
  return False
66
 
67
  def contains_local_sensitive_words(text: str):
68
- if text in DYNAMIC_BLOCK_CACHE: return True, text
69
  text_lower = text.lower()
70
  for word in SENSITIVE_WORDS:
71
- if word and word.lower() in text_lower: return True, word
72
  return False, None
73
 
74
- # ==========================================
75
- # 3. 审核与图片处理逻辑
76
- # ==========================================
77
-
78
- _BAIDU_TOKEN_CACHE = {"token": None, "expires_at": 0}
79
-
80
  async def get_baidu_access_token():
81
  ak = os.getenv("BAIDU_API_KEY", "").strip()
82
  sk = os.getenv("BAIDU_SECRET_KEY", "").strip()
83
  if not ak or not sk: return None
84
- now = time.time()
85
- if _BAIDU_TOKEN_CACHE["token"] and now < _BAIDU_TOKEN_CACHE["expires_at"]:
86
- return _BAIDU_TOKEN_CACHE["token"]
87
-
88
  url = "https://aip.baidubce.com/oauth/2.0/token"
89
- params = {"grant_type": "client_credentials", "client_id": ak, "client_secret": sk}
90
  try:
91
  async with httpx.AsyncClient(timeout=10) as client:
92
- resp = await client.post(url, params=params)
93
- token = resp.json().get("access_token")
94
- if token:
95
- _BAIDU_TOKEN_CACHE["token"] = token
96
- _BAIDU_TOKEN_CACHE["expires_at"] = now + 1728000
97
- return token
98
  except: return None
99
 
100
  async def check_baidu_text_censor(text):
101
  token = await get_baidu_access_token()
102
  if not token: return False, None
103
-
104
  url = f"https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token={token}"
105
- headers = {'Content-Type': 'application/x-www-form-urlencoded'}
106
- data = {'text': text}
107
-
108
  try:
109
- async with httpx.AsyncClient(timeout=20) as client:
110
- resp = await client.post(url, data=data, headers=headers)
111
- result = resp.json()
112
- if result.get('conclusionType') == 2:
113
- msg = [i.get('msg') for i in result.get('data', [])]
114
- reason = ",".join(msg) if msg else "违规"
115
- if text not in DYNAMIC_BLOCK_CACHE:
116
- DYNAMIC_BLOCK_CACHE.append(text)
117
- return True, reason
118
- return False, None
119
  except: return False, None
120
 
 
 
 
 
121
  class GenerateRequest(BaseModel):
122
  api_key: str
123
  model: str
@@ -125,135 +102,87 @@ class GenerateRequest(BaseModel):
125
  ratio: str
126
  scale: str
127
  format: str
128
- init_images: Optional[List[str]] = [] # 支持多图
129
-
130
- def process_image_in_memory(image_bytes: bytes, target_format: str, target_size: tuple = None) -> str:
131
- try:
132
- with Image.open(BytesIO(image_bytes)) as img:
133
- out = BytesIO()
134
- final_img = img
135
- if target_size:
136
- w_diff = abs(img.size[0] - target_size[0])
137
- h_diff = abs(img.size[1] - target_size[1])
138
- if w_diff > 50 or h_diff > 50:
139
- final_img = img.resize(target_size, Image.Resampling.LANCZOS)
140
-
141
- fmt = "JPEG" if target_format == "jpg" else "PNG"
142
- final_img = final_img.convert("RGB") if target_format == "jpg" else final_img
143
- final_img.save(out, format=fmt, quality=95)
144
- return base64.b64encode(out.getvalue()).decode('utf-8')
145
- except Exception as e:
146
- raise Exception(f"Image Error: {e}")
147
- finally:
148
- gc.collect()
149
 
150
  def sanitize_input_image(base64_str: str) -> str:
151
  if not base64_str: return None
152
- base64_data = base64_str.split("base64,")[1] if "base64," in base64_str else base64_str
153
  try:
 
154
  img_data = base64.b64decode(base64_data)
155
  with Image.open(BytesIO(img_data)) as img:
156
  if img.mode != "RGB": img = img.convert("RGB")
157
- max_size = 1024
158
- if max(img.size) > max_size:
159
- img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
160
  out = BytesIO()
161
  img.save(out, format="JPEG", quality=85)
162
  return base64.b64encode(out.getvalue()).decode('utf-8')
163
- except: return base64_data
164
 
165
- async def download_image_as_bytes(url):
166
- async with httpx.AsyncClient(timeout=360) as client:
167
  r = await client.get(url)
168
- if r.status_code == 200: return r.content
169
- raise Exception(f"HTTP {r.status_code}")
170
 
171
  async def core_generate(req: GenerateRequest):
172
  ratio_key = req.ratio.split(' ')[0]
173
- target_w, target_h = 1024, 1024
174
- if ratio_key in RESOLUTION_MAP:
175
- target_w, target_h = RESOLUTION_MAP[ratio_key].get(req.scale, RESOLUTION_MAP[ratio_key]["1k"])
176
- api_size_str = f"{target_w}x{target_h}"
177
 
178
- is_chat_model = "gemini" in req.model.lower() or "grok" in req.model.lower()
179
- final_prompt = req.prompt
180
-
181
- if is_chat_model:
182
- quality_prompt = "standard"
183
- if req.scale == "4k": quality_prompt = "Extreme High Quality, 4K"
184
- elif req.scale == "2k": quality_prompt = "High Quality, 2K"
185
- final_prompt = f"{req.prompt}. (Settings: Ratio {ratio_key}, {quality_prompt})"
186
-
187
- # 预处理多张图片
188
  clean_images = []
189
- if req.init_images:
190
- print(f"🧹 正在预处理 {len(req.init_images)} 张图片...")
191
- for img_b64 in req.init_images:
192
- cleaned = sanitize_input_image(img_b64)
193
- if cleaned: clean_images.append(cleaned)
194
-
195
- client = AsyncOpenAI(api_key=req.api_key, base_url=f"{app_base_url}/v1", max_retries=0, timeout=360.0)
196
- img_b = None
197
-
198
- if is_chat_model:
199
- content_parts = [{"type": "text", "text": final_prompt}]
200
- for b64 in clean_images: # 循环添加多图
201
- content_parts.append({
202
- "type": "image_url",
203
- "image_url": {"url": f"data:image/jpeg;base64,{b64}"}
204
- })
205
 
206
  res = await client.chat.completions.create(
207
- model=req.model,
208
- messages=[{"role":"user","content": content_parts}],
209
- extra_body={"modalities": ["image", "text"], "render": True if "grok" in req.model.lower() else None},
210
- timeout=360.0
211
  )
212
-
213
- c = res.choices[0].message.content if hasattr(res, 'choices') else str(res)
214
- u = re.search(r"!\[.*?\]\((https?://[^\)]+)\)", c) or re.search(r"(https?://\S+\.(?:png|jpg|jpeg|webp))", c)
215
- if u: img_b = await download_image_as_bytes(u.group(1))
216
- elif "base64," in c: img_b = base64.b64decode(c.split("base64,")[1].split(")")[0].strip())
217
- else: raise Exception(f"未找到图片响应: {c[:100]}")
218
-
219
  else:
220
- # 非对话模型默认取首图
221
- first_img = clean_images[0] if clean_images else None
222
- if first_img:
223
- image_file = BytesIO(base64.b64decode(first_img))
224
  image_file.name = "init.jpg"
225
- res = await client.images.edit(
226
- model=req.model, image=image_file, prompt=final_prompt,
227
- size=api_size_str, response_format="b64_json"
228
- )
229
  else:
230
- res = await client.images.generate(
231
- model=req.model, prompt=final_prompt, size=api_size_str, response_format="b64_json"
232
- )
233
- img_b = base64.b64decode(res.data[0].b64_json)
234
 
235
- return process_image_in_memory(img_b, req.format, target_size=(target_w, target_h))
 
 
 
 
 
236
 
237
  app = FastAPI()
238
  app.mount("/static", StaticFiles(directory="static"), name="static")
239
 
240
  @app.get("/")
241
- async def read_index(): return FileResponse('static/index.html')
242
 
243
  @app.post("/api/generate")
244
- async def generate_api(req: GenerateRequest, request: Request):
245
  ip = get_real_ip(request)
246
- if check_ip_rate_limit(ip, 3, 60): return JSONResponse({"status": "error", "message": "请求过快"}, 429)
247
-
248
- is_loc, word = contains_local_sensitive_words(req.prompt)
249
- if is_loc: return JSONResponse({"status": "error", "message": f"违规: {word}"}, 200)
250
-
251
- async with CONCURRENCY_LIMITER:
252
- try:
253
- b64 = await core_generate(req)
254
- return {"status": "success", "image_base64": b64}
255
- except Exception as e:
256
- return JSONResponse({"status": "error", "message": str(e)}, 200)
257
 
258
  if __name__ == "__main__":
259
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
61
  return True
62
  valid_history.append(now)
63
  IP_REQUEST_HISTORY[ip] = valid_history
 
64
  return False
65
 
66
  def contains_local_sensitive_words(text: str):
 
67
  text_lower = text.lower()
68
  for word in SENSITIVE_WORDS:
69
+ if word and word in text_lower: return True, word
70
  return False, None
71
 
72
+ # --- 百度审核略 (保持你原有的即可) ---
 
 
 
 
 
73
  async def get_baidu_access_token():
74
  ak = os.getenv("BAIDU_API_KEY", "").strip()
75
  sk = os.getenv("BAIDU_SECRET_KEY", "").strip()
76
  if not ak or not sk: return None
 
 
 
 
77
  url = "https://aip.baidubce.com/oauth/2.0/token"
 
78
  try:
79
  async with httpx.AsyncClient(timeout=10) as client:
80
+ resp = await client.post(url, params={"grant_type": "client_credentials", "client_id": ak, "client_secret": sk})
81
+ return resp.json().get("access_token")
 
 
 
 
82
  except: return None
83
 
84
  async def check_baidu_text_censor(text):
85
  token = await get_baidu_access_token()
86
  if not token: return False, None
 
87
  url = f"https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined?access_token={token}"
 
 
 
88
  try:
89
+ async with httpx.AsyncClient(timeout=10) as client:
90
+ resp = await client.post(url, data={'text': text})
91
+ return resp.json().get('conclusionType') == 2, "违规"
 
 
 
 
 
 
 
92
  except: return False, None
93
 
94
+ # ==========================================
95
+ # 3. 核心修改:支持多图
96
+ # ==========================================
97
+
98
  class GenerateRequest(BaseModel):
99
  api_key: str
100
  model: str
 
102
  ratio: str
103
  scale: str
104
  format: str
105
+ init_images: Optional[List[str]] = [] # 修改为列表
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
  def sanitize_input_image(base64_str: str) -> str:
108
  if not base64_str: return None
 
109
  try:
110
+ base64_data = base64_str.split("base64,")[1] if "base64," in base64_str else base64_str
111
  img_data = base64.b64decode(base64_data)
112
  with Image.open(BytesIO(img_data)) as img:
113
  if img.mode != "RGB": img = img.convert("RGB")
114
+ img.thumbnail((1024, 1024), Image.Resampling.LANCZOS)
 
 
115
  out = BytesIO()
116
  img.save(out, format="JPEG", quality=85)
117
  return base64.b64encode(out.getvalue()).decode('utf-8')
118
+ except: return None
119
 
120
+ async def download_image(url):
121
+ async with httpx.AsyncClient(timeout=60) as client:
122
  r = await client.get(url)
123
+ return r.content
 
124
 
125
  async def core_generate(req: GenerateRequest):
126
  ratio_key = req.ratio.split(' ')[0]
127
+ target_w, target_h = RESOLUTION_MAP.get(ratio_key, {}).get(req.scale, (1024, 1024))
 
 
 
128
 
129
+ # 预处理多图
 
 
 
 
 
 
 
 
 
130
  clean_images = []
131
+ for img_b64 in req.init_images:
132
+ cleaned = sanitize_input_image(img_b64)
133
+ if cleaned: clean_images.append(cleaned)
134
+
135
+ client = AsyncOpenAI(api_key=req.api_key, base_url=f"{app_base_url}/v1")
136
+ is_chat = "gemini" in req.model.lower() or "grok" in req.model.lower()
137
+
138
+ if is_chat:
139
+ # 重点:构建多图 Content
140
+ content = [{"type": "text", "text": f"{req.prompt} (Ratio: {ratio_key})"}]
141
+ for b64 in clean_images:
142
+ content.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}})
 
 
 
 
143
 
144
  res = await client.chat.completions.create(
145
+ model=req.model,
146
+ messages=[{"role": "user", "content": content}],
147
+ extra_body={"modalities": ["image", "text"]}
 
148
  )
149
+ c = res.choices[0].message.content
150
+ u = re.search(r"https?://\S+\.(?:png|jpg|jpeg|webp)", c)
151
+ if not u: raise Exception("未生成图片链接")
152
+ img_raw = await download_image(u.group(0))
 
 
 
153
  else:
154
+ # 非对话模型取第一张
155
+ first = clean_images[0] if clean_images else None
156
+ if first:
157
+ image_file = BytesIO(base64.b64decode(first))
158
  image_file.name = "init.jpg"
159
+ res = await client.images.edit(model=req.model, image=image_file, prompt=req.prompt, size=f"{target_w}x{target_h}", response_format="b64_json")
 
 
 
160
  else:
161
+ res = await client.images.generate(model=req.model, prompt=req.prompt, size=f"{target_w}x{target_h}", response_format="b64_json")
162
+ img_raw = base64.b64decode(res.data[0].b64_json)
 
 
163
 
164
+ # 统一输出处理
165
+ with Image.open(BytesIO(img_raw)) as final_img:
166
+ final_img = final_img.resize((target_w, target_h), Image.Resampling.LANCZOS)
167
+ out = BytesIO()
168
+ final_img.save(out, format="PNG")
169
+ return base64.b64encode(out.getvalue()).decode('utf-8')
170
 
171
  app = FastAPI()
172
  app.mount("/static", StaticFiles(directory="static"), name="static")
173
 
174
  @app.get("/")
175
+ async def index(): return FileResponse('static/index.html')
176
 
177
  @app.post("/api/generate")
178
+ async def api_gen(req: GenerateRequest, request: Request):
179
  ip = get_real_ip(request)
180
+ if check_ip_rate_limit(ip): return JSONResponse({"status": "error", "message": "Too many requests"}, 429)
181
+ try:
182
+ b64 = await core_generate(req)
183
+ return {"status": "success", "image_base64": b64}
184
+ except Exception as e:
185
+ return {"status": "error", "message": str(e)}
 
 
 
 
 
186
 
187
  if __name__ == "__main__":
188
  uvicorn.run(app, host="0.0.0.0", port=7860)