HouYunFei commited on
Commit ·
e16cef9
1
Parent(s): 24810d1
feat(accounts): 新增账号级代理功能并修复图像生成错误处理
Browse files- 新增账号级代理配置选项
- 修复503异常信息显示问题
- 解决前端邮箱域名输入换行问题
- 实现图像生成错误消息的安全过滤机制
- 添加图像生成错误的统一响应处理
- 优化注册页面邮箱配置输入验证逻辑
- 更新版本号至1.2.3
- CHANGELOG.md +3 -0
- VERSION +1 -1
- services/log_service.py +11 -1
- services/protocol/conversation.py +9 -1
- web/src/app/register/components/register-card.tsx +2 -2
- web/src/components/ui/textarea.tsx +1 -1
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.
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 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())
|
| 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())
|
| 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
|
| 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}
|