HouYunFei commited on
Commit
e16cef9
·
1 Parent(s): 24810d1

feat(accounts): 新增账号级代理功能并修复图像生成错误处理

Browse files

- 新增账号级代理配置选项
- 修复503异常信息显示问题
- 解决前端邮箱域名输入换行问题
- 实现图像生成错误消息的安全过滤机制
- 添加图像生成错误的统一响应处理
- 优化注册页面邮箱配置输入验证逻辑
- 更新版本号至1.2.3

CHANGELOG.md CHANGED
@@ -2,7 +2,10 @@
2
 
3
  ## Unreleased
4
 
 
 
5
  + [新增] 新增账号级代理。
 
6
 
7
  ## 1.2.2 - 2026-05-29
8
 
 
2
 
3
  ## Unreleased
4
 
5
+ ## 1.2.3 - 2026-05-29
6
+
7
  + [新增] 新增账号级代理。
8
+ + [修复] 修复503异常信息、前端邮箱换行问题。
9
 
10
  ## 1.2.2 - 2026-05-29
11
 
VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.2
 
1
+ 1.2.3
services/log_service.py CHANGED
@@ -166,7 +166,9 @@ def _request_excerpt(text: object, limit: int = 1000) -> str:
166
 
167
 
168
  def _image_error_response(exc: Exception) -> JSONResponse:
169
- message = str(exc)
 
 
170
  if "no available image quota" in message.lower():
171
  return openai_error_response(
172
  {
@@ -220,6 +222,8 @@ class LoggedCall:
220
  raise
221
  except Exception as exc:
222
  self.log("调用失败", status="failed", error=str(exc), account_email=getattr(exc, "account_email", ""))
 
 
223
  return _protocol_error_response(exc, 502, sse)
224
 
225
  if isinstance(result, dict):
@@ -239,6 +243,8 @@ class LoggedCall:
239
  raise
240
  except Exception as exc:
241
  self.log("调用失败", status="failed", error=str(exc), account_email=getattr(exc, "account_email", ""))
 
 
242
  return _protocol_error_response(exc, 502, sse)
243
  if not has_first:
244
  self.log("流式调用结束")
@@ -263,6 +269,10 @@ class LoggedCall:
263
  urls=urls,
264
  account_email=(account_emails[0] if account_emails else getattr(exc, "account_email", "")),
265
  )
 
 
 
 
266
  raise
267
  finally:
268
  if not failed:
 
166
 
167
 
168
  def _image_error_response(exc: Exception) -> JSONResponse:
169
+ from services.protocol.conversation import public_image_error_message
170
+
171
+ message = public_image_error_message(str(exc))
172
  if "no available image quota" in message.lower():
173
  return openai_error_response(
174
  {
 
222
  raise
223
  except Exception as exc:
224
  self.log("调用失败", status="failed", error=str(exc), account_email=getattr(exc, "account_email", ""))
225
+ if self.endpoint.startswith("/v1/images"):
226
+ return _image_error_response(exc)
227
  return _protocol_error_response(exc, 502, sse)
228
 
229
  if isinstance(result, dict):
 
243
  raise
244
  except Exception as exc:
245
  self.log("调用失败", status="failed", error=str(exc), account_email=getattr(exc, "account_email", ""))
246
+ if self.endpoint.startswith("/v1/images"):
247
+ return _image_error_response(exc)
248
  return _protocol_error_response(exc, 502, sse)
249
  if not has_first:
250
  self.log("流式调用结束")
 
269
  urls=urls,
270
  account_email=(account_emails[0] if account_emails else getattr(exc, "account_email", "")),
271
  )
272
+ if self.endpoint.startswith("/v1/images") and not hasattr(exc, "to_openai_error"):
273
+ from services.protocol.conversation import ImageGenerationError, public_image_error_message
274
+
275
+ raise ImageGenerationError(public_image_error_message(str(exc))) from exc
276
  raise
277
  finally:
278
  if not failed:
services/protocol/conversation.py CHANGED
@@ -44,7 +44,7 @@ class ImageGenerationError(Exception):
44
  def to_openai_error(self) -> dict[str, Any]:
45
  return {
46
  "error": {
47
- "message": str(self),
48
  "type": self.error_type,
49
  "param": self.param,
50
  "code": self.code,
@@ -52,6 +52,14 @@ class ImageGenerationError(Exception):
52
  }
53
 
54
 
 
 
 
 
 
 
 
 
55
  def is_token_invalid_error(message: str) -> bool:
56
  text = str(message or "").lower()
57
  return (
 
44
  def to_openai_error(self) -> dict[str, Any]:
45
  return {
46
  "error": {
47
+ "message": public_image_error_message(str(self)),
48
  "type": self.error_type,
49
  "param": self.param,
50
  "code": self.code,
 
52
  }
53
 
54
 
55
+ def public_image_error_message(message: str) -> str:
56
+ text = str(message or "").strip()
57
+ lower = text.lower()
58
+ if any(item in lower for item in ("backend-api/", "status=", "body=", "chatgpt.com", "upstreamhttperror")):
59
+ return "The image generation request failed. Please try again later."
60
+ return text or "The image generation request failed. Please try again later."
61
+
62
+
63
  def is_token_invalid_error(message: str) -> bool:
64
  text = str(message or "").lower()
65
  return (
web/src/app/register/components/register-card.tsx CHANGED
@@ -263,13 +263,13 @@ export function RegisterCard() {
263
  {type === "cloudmail_gen" || type === "tempmail_lol" || type === "cloudflare_temp_email" || type === "moemail" || type === "inbucket" || type === "yyds_mail" || type === "ddg_mail" ? (
264
  <div className="space-y-2">
265
  <label className="text-sm text-stone-700">{type === "cloudmail_gen" ? "邮箱域名" : type === "inbucket" ? "基础域名列表" : "Domain"}</label>
266
- <Textarea value={domains} onChange={(event) => updateProvider(index, { domain: event.target.value.split(/[\n,]/).map((item) => item.trim()).filter(Boolean) })} placeholder={type === "cloudmail_gen" ? "每行一个域名,留空则使用服务默认域名" : type === "inbucket" ? "每行一个基础域名,系统会自动生成随机子域名" : type === "moemail" ? "每行一个域名" : "每行一个域名,留空则使用服务默认域名"} className="min-h-20 rounded-xl border-stone-200 bg-white font-mono text-xs" disabled={config.enabled} />
267
  </div>
268
  ) : null}
269
  {type === "cloudmail_gen" ? (
270
  <div className="space-y-2">
271
  <label className="text-sm text-stone-700">子域名(支持多个)</label>
272
- <Textarea value={subdomains} onChange={(event) => updateProvider(index, { subdomain: event.target.value.split(/[\n,]/).map((item) => item.trim()).filter(Boolean) })} placeholder="每行一个子域名前缀,留空则直接使用主域名" className="min-h-20 rounded-xl border-stone-200 bg-white font-mono text-xs" disabled={config.enabled} />
273
  </div>
274
  ) : null}
275
  </div>
 
263
  {type === "cloudmail_gen" || type === "tempmail_lol" || type === "cloudflare_temp_email" || type === "moemail" || type === "inbucket" || type === "yyds_mail" || type === "ddg_mail" ? (
264
  <div className="space-y-2">
265
  <label className="text-sm text-stone-700">{type === "cloudmail_gen" ? "邮箱域名" : type === "inbucket" ? "基础域名列表" : "Domain"}</label>
266
+ <Textarea value={domains} onChange={(event) => updateProvider(index, { domain: event.target.value.split(/[\n,]/).map((item) => item.trim()) })} placeholder={type === "cloudmail_gen" ? "每行一个域名,留空则使用服务默认域名" : type === "inbucket" ? "每行一个基础域名,系统会自动生成随机子域名" : type === "moemail" ? "每行一个域名" : "每行一个域名,留空则使用服务默认域名"} className="min-h-20 rounded-xl border-stone-200 bg-white font-mono text-xs" disabled={config.enabled} />
267
  </div>
268
  ) : null}
269
  {type === "cloudmail_gen" ? (
270
  <div className="space-y-2">
271
  <label className="text-sm text-stone-700">子域名(支持多个)</label>
272
+ <Textarea value={subdomains} onChange={(event) => updateProvider(index, { subdomain: event.target.value.split(/[\n,]/).map((item) => item.trim()) })} placeholder="每行一个子域名前缀,留空则直接使用主域名" className="min-h-20 rounded-xl border-stone-200 bg-white font-mono text-xs" disabled={config.enabled} />
273
  </div>
274
  ) : null}
275
  </div>
web/src/components/ui/textarea.tsx CHANGED
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
7
  <textarea
8
  data-slot="textarea"
9
  className={cn(
10
- "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 flex min-h-32 w-full rounded-[24px] border bg-white/90 px-4 py-3 text-sm shadow-sm outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
11
  className,
12
  )}
13
  {...props}
 
7
  <textarea
8
  data-slot="textarea"
9
  className={cn(
10
+ "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 block min-h-32 w-full rounded-[24px] border bg-white/90 px-4 py-3 text-sm shadow-sm outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
11
  className,
12
  )}
13
  {...props}