From bf9d52a2db6aba25df99691cb06710a622b91feb Mon Sep 17 00:00:00 2001 From: jingrow Date: Wed, 19 Nov 2025 03:14:36 +0800 Subject: [PATCH] Add tools management system with image background removal --- apps/jingrow/frontend/src/app/router/index.ts | 5 + apps/jingrow/frontend/src/locales/zh-CN.json | 37 +- .../frontend/src/shared/stores/tools.ts | 191 +++++ .../src/views/tools/RemoveBackground.vue | 804 ++++++++++++++++++ .../frontend/src/views/tools/Tools.vue | 321 +++++-- apps/jingrow/jingrow/config.py | 2 +- apps/jingrow/jingrow/tools/__init__.py | 0 apps/jingrow/jingrow/tools/tools.py | 157 ++++ 8 files changed, 1431 insertions(+), 86 deletions(-) create mode 100644 apps/jingrow/frontend/src/shared/stores/tools.ts create mode 100644 apps/jingrow/frontend/src/views/tools/RemoveBackground.vue create mode 100644 apps/jingrow/jingrow/tools/__init__.py create mode 100644 apps/jingrow/jingrow/tools/tools.py diff --git a/apps/jingrow/frontend/src/app/router/index.ts b/apps/jingrow/frontend/src/app/router/index.ts index 1850dc3..21479fc 100644 --- a/apps/jingrow/frontend/src/app/router/index.ts +++ b/apps/jingrow/frontend/src/app/router/index.ts @@ -89,6 +89,11 @@ const router = createRouter({ name: 'Tools', component: () => import('../../views/tools/Tools.vue') }, + { + path: 'tools/remove-background', + name: 'RemoveBackground', + component: () => import('../../views/tools/RemoveBackground.vue') + }, { path: 'search', name: 'SearchResults', diff --git a/apps/jingrow/frontend/src/locales/zh-CN.json b/apps/jingrow/frontend/src/locales/zh-CN.json index a3acb00..90af833 100644 --- a/apps/jingrow/frontend/src/locales/zh-CN.json +++ b/apps/jingrow/frontend/src/locales/zh-CN.json @@ -1082,5 +1082,40 @@ "Tool added successfully": "工具添加成功", "Are you sure you want to delete tool": "确定要删除工具", "Tool deleted successfully": "工具删除成功", - "Route not found: ": "路由未找到:" + "Route not found: ": "路由未找到:", + "Remove Background": "图片去背景", + "Remove background from images using AI technology": "使用AI技术去除图片背景", + "Upload Image": "上传图片", + "Drag and drop your image here, or click to browse": "拖拽图片到此处,或点击浏览", + "Supports JPG, PNG, WebP formats": "支持 JPG、PNG、WebP 格式", + "Image Preview": "图片预览", + "Change Image": "更换图片", + "Original": "原图", + "Background Removed": "去背景后", + "Result will appear here": "处理结果将显示在这里", + "Processing...": "处理中...", + "Download": "下载", + "How it works": "使用说明", + "Upload an image with a clear subject": "上传一张主体清晰的图片", + "Click \"Remove Background\" to process": "点击\"去背景\"按钮进行处理", + "Download the result with transparent background": "下载透明背景的处理结果", + "Please upload an image file": "请上传图片文件", + "Unsupported image format. Please use JPG, PNG, or WebP": "不支持的图片格式,请使用 JPG、PNG 或 WebP 格式", + "Image size exceeds 10MB limit": "图片大小超过 10MB 限制", + "Please upload an image first": "请先上传图片", + "Background removed successfully!": "背景移除成功!", + "Failed to remove background": "背景移除失败", + "Failed to upload image: ": "图片上传失败:", + "Download started": "下载已开始", + "Failed to download image": "下载失败", + "Hide Default Tool": "隐藏默认工具", + "Are you sure you want to hide default tool": "确定要隐藏默认工具", + "You can show it again later": "您可以稍后再次显示", + "Hide": "隐藏", + "Show": "显示", + "Tool hidden successfully": "工具已隐藏", + "Tool shown successfully": "工具已显示", + "Default tools cannot be edited": "默认工具无法编辑", + "Hidden Tools": "已隐藏的工具", + "Click to show hidden tools": "点击显示隐藏的工具" } diff --git a/apps/jingrow/frontend/src/shared/stores/tools.ts b/apps/jingrow/frontend/src/shared/stores/tools.ts new file mode 100644 index 0000000..b7ad5fe --- /dev/null +++ b/apps/jingrow/frontend/src/shared/stores/tools.ts @@ -0,0 +1,191 @@ +import { defineStore } from 'pinia' +import { computed, ref } from 'vue' +import { t } from '../i18n' + +export interface Tool { + id: string + name: string + description?: string + category?: string + icon?: string + color?: string + type?: 'route' | 'url' + routeName?: string + url?: string + order?: number + isDefault?: boolean + hidden?: boolean +} + +const STORAGE_KEY = 'tools.userItems' +const HIDDEN_DEFAULT_TOOLS_KEY = 'tools.hiddenDefaultTools' + +// 默认工具列表(硬编码,一行一个,方便添加) +function getDefaultTools(): Tool[] { + return [ + { + id: 'remove-background', + name: t('Remove Background'), + description: t('Remove background from images using AI technology'), + category: 'Image Processing', + icon: 'photo-edit', + color: '#1fc76f', + type: 'route', + routeName: 'RemoveBackground', + order: 1, + isDefault: true + }, + // 在这里添加更多默认工具,每行一个: + // { + // id: 'tool-id-2', + // name: t('Tool Name'), + // description: t('Tool description'), + // category: 'Category', + // icon: 'icon-name', + // color: '#1fc76f', + // type: 'route', + // routeName: 'RouteName', + // order: 2, + // isDefault: true + // }, + ] +} + +function loadUserTools(): Tool[] { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return [] + const parsed = JSON.parse(raw) + if (Array.isArray(parsed)) return parsed + return [] + } catch { + return [] + } +} + +function saveUserTools(tools: Tool[]) { + const userTools = tools.filter(t => !t.isDefault) + localStorage.setItem(STORAGE_KEY, JSON.stringify(userTools)) +} + +function loadHiddenDefaultTools(): string[] { + try { + const raw = localStorage.getItem(HIDDEN_DEFAULT_TOOLS_KEY) + if (!raw) return [] + return JSON.parse(raw) + } catch { + return [] + } +} + +function saveHiddenDefaultTools(hiddenIds: string[]) { + localStorage.setItem(HIDDEN_DEFAULT_TOOLS_KEY, JSON.stringify(hiddenIds)) +} + +export const useToolsStore = defineStore('tools', () => { + const userTools = ref(loadUserTools()) + const hiddenDefaultToolIds = ref(loadHiddenDefaultTools()) + + // 合并显示所有工具:默认工具 + 用户工具 + const allTools = computed(() => { + // 获取默认工具(排除隐藏的) + const defaultTools = getDefaultTools() + .filter(tool => !hiddenDefaultToolIds.value.includes(tool.id)) + .map(tool => ({ ...tool, isDefault: true })) + + // 用户工具 + const userToolsList = [...userTools.value] + .map(tool => ({ ...tool, isDefault: false })) + + // 合并并排序:默认工具在前,用户工具在后,各自按order排序 + return [ + ...defaultTools.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)), + ...userToolsList.sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) + ] + }) + + // 获取隐藏的默认工具列表 + const hiddenTools = computed(() => { + return getDefaultTools() + .filter(tool => hiddenDefaultToolIds.value.includes(tool.id)) + .map(tool => ({ ...tool, isDefault: true })) + .sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) + }) + + // 添加用户工具 + function addUserTool(tool: Tool) { + const defaultToolsCount = getDefaultTools().filter( + t => !hiddenDefaultToolIds.value.includes(t.id) + ).length + + tool.order = defaultToolsCount + userTools.value.length + 1 + tool.isDefault = false + userTools.value.push(tool) + saveUserTools(userTools.value) + } + + // 更新用户工具 + function updateUserTool(toolId: string, updates: Partial) { + const index = userTools.value.findIndex(t => t.id === toolId) + if (index >= 0) { + userTools.value[index] = { ...userTools.value[index], ...updates, isDefault: false } + saveUserTools(userTools.value) + } + } + + // 删除用户工具 + function deleteUserTool(toolId: string) { + userTools.value = userTools.value.filter(t => t.id !== toolId) + // 重新分配 order + const defaultToolsCount = getDefaultTools().filter( + t => !hiddenDefaultToolIds.value.includes(t.id) + ).length + userTools.value.forEach((t, index) => { + t.order = defaultToolsCount + index + 1 + }) + saveUserTools(userTools.value) + } + + // 隐藏默认工具 + function hideDefaultTool(toolId: string) { + if (!hiddenDefaultToolIds.value.includes(toolId)) { + hiddenDefaultToolIds.value.push(toolId) + saveHiddenDefaultTools(hiddenDefaultToolIds.value) + } + } + + // 显示隐藏的默认工具 + function showDefaultTool(toolId: string) { + hiddenDefaultToolIds.value = hiddenDefaultToolIds.value.filter(id => id !== toolId) + saveHiddenDefaultTools(hiddenDefaultToolIds.value) + } + + // 更新用户工具顺序 + function updateUserToolsOrder(newOrder: Tool[]) { + const defaultToolsCount = getDefaultTools().filter( + t => !hiddenDefaultToolIds.value.includes(t.id) + ).length + + newOrder.forEach((tool, index) => { + tool.order = defaultToolsCount + index + 1 + }) + + userTools.value = newOrder + saveUserTools(userTools.value) + } + + return { + userTools, + hiddenDefaultToolIds, + allTools, + hiddenTools, + addUserTool, + updateUserTool, + deleteUserTool, + hideDefaultTool, + showDefaultTool, + updateUserToolsOrder, + getDefaultTools + } +}) + diff --git a/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue b/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue new file mode 100644 index 0000000..0500a7f --- /dev/null +++ b/apps/jingrow/frontend/src/views/tools/RemoveBackground.vue @@ -0,0 +1,804 @@ + + + + + diff --git a/apps/jingrow/frontend/src/views/tools/Tools.vue b/apps/jingrow/frontend/src/views/tools/Tools.vue index e7cc019..721d546 100644 --- a/apps/jingrow/frontend/src/views/tools/Tools.vue +++ b/apps/jingrow/frontend/src/views/tools/Tools.vue @@ -10,7 +10,7 @@
-
+
@@ -59,6 +59,31 @@ {{ t('Loading tools...') }}
+ + +
+
+

{{ t('Hidden Tools') }}

+

{{ t('Click to show hidden tools') }}

+
+
+
+
+ +
+
{{ tool.name }}
+ +
+
+
@@ -138,21 +163,7 @@ import { NModal, NForm, NFormItem, NInput, NSelect, NAutoComplete, NColorPicker, import { t } from '../../shared/i18n' import DynamicIcon from '../../core/components/DynamicIcon.vue' import IconPicker from '../../core/components/IconPicker.vue' - -interface Tool { - id: string - name: string - description?: string - category?: string - icon?: string - color?: string - type?: 'route' | 'url' // 工具类型:内部路由或外部URL - routeName?: string // 路由名(当type为route时使用) - url?: string // URL路径(当type为url时使用) - order?: number -} - -const STORAGE_KEY = 'tools.items' +import { useToolsStore, type Tool } from '../../shared/stores/tools' // 生成 UUID(兼容所有环境) function generateUUID(): string { @@ -169,27 +180,9 @@ function generateUUID(): string { }) } -// 加载工具列表 -function loadTools(): Tool[] { - try { - const raw = localStorage.getItem(STORAGE_KEY) - if (!raw) return [] - const parsed = JSON.parse(raw) - if (Array.isArray(parsed)) return parsed - return [] - } catch { - return [] - } -} - -// 保存工具列表 -function saveTools(tools: Tool[]) { - localStorage.setItem(STORAGE_KEY, JSON.stringify(tools)) -} - const router = useRouter() +const toolsStore = useToolsStore() const loading = ref(false) -const tools = ref(loadTools()) const showToolModal = ref(false) const editingTool = ref(null) const toolFormRef = ref(null) @@ -271,13 +264,13 @@ function onToolTypeChange() { toolForm.value.url = '' } -// 按 order 排序显示 -const displayTools = computed(() => { - return [...tools.value].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) -}) +// 使用 store 中的工具列表 +const displayTools = computed(() => toolsStore.allTools) + +// 隐藏的工具列表 +const hiddenTools = computed(() => toolsStore.hiddenTools) onMounted(() => { - // 可以在这里加载远程数据 loading.value = false }) @@ -309,17 +302,34 @@ function handleDrop(event: DragEvent, dropIndexValue: number) { event.preventDefault() if (!draggedTool.value || draggedIndex.value === -1) return - const newTools = [...tools.value] - const dragged = newTools.splice(draggedIndex.value, 1)[0] - newTools.splice(dropIndexValue, 0, dragged) + const dragged = draggedTool.value + + // 默认工具不能拖拽排序 + if (dragged.isDefault) { + resetDragState() + return + } - // 重新分配 order - newTools.forEach((tool, index) => { - tool.order = index + 1 - }) + // 只对用户工具进行排序 + const newUserTools = [...toolsStore.userTools] + const draggedIndexInUser = newUserTools.findIndex(t => t.id === dragged.id) + + if (draggedIndexInUser < 0) { + resetDragState() + return + } - tools.value = newTools - saveTools(tools.value) + // 计算在用户工具中的新位置 + const defaultToolsCount = toolsStore.getDefaultTools().filter( + t => !toolsStore.hiddenDefaultToolIds.includes(t.id) + ).length + const newIndexInUser = Math.max(0, dropIndexValue - defaultToolsCount) + + const draggedToolItem = newUserTools.splice(draggedIndexInUser, 1)[0] + newUserTools.splice(newIndexInUser, 0, draggedToolItem) + + // 更新顺序 + toolsStore.updateUserToolsOrder(newUserTools) resetDragState() } @@ -386,22 +396,23 @@ function handleSaveTool() { } if (editingTool.value) { - // 更新 - const index = tools.value.findIndex(t => t.id === editingTool.value!.id) - if (index >= 0) { - const updatedTool: Tool = { - ...editingTool.value, - ...toolForm.value, - // 清理不需要的字段 - routeName: toolForm.value.type === 'route' ? toolForm.value.routeName : undefined, - url: toolForm.value.type === 'url' ? toolForm.value.url : undefined - } - tools.value[index] = updatedTool - saveTools(tools.value) - message.success(t('Tool updated successfully')) + // 更新(只能更新用户工具,默认工具不能编辑) + if (editingTool.value.isDefault) { + message.warning(t('Default tools cannot be edited')) + return } + + const updates: Partial = { + ...toolForm.value, + // 清理不需要的字段 + routeName: toolForm.value.type === 'route' ? toolForm.value.routeName : undefined, + url: toolForm.value.type === 'url' ? toolForm.value.url : undefined + } + + toolsStore.updateUserTool(editingTool.value.id, updates) + message.success(t('Tool updated successfully')) } else { - // 新增 + // 新增用户工具 const newTool: Tool = { id: generateUUID(), name: toolForm.value.name!, @@ -412,10 +423,10 @@ function handleSaveTool() { type: toolForm.value.type || 'route', routeName: toolForm.value.type === 'route' ? toolForm.value.routeName : undefined, url: toolForm.value.type === 'url' ? toolForm.value.url : undefined, - order: tools.value.length + 1 + isDefault: false } - tools.value.push(newTool) - saveTools(tools.value) + + toolsStore.addUserTool(newTool) message.success(t('Tool added successfully')) } @@ -424,23 +435,40 @@ function handleSaveTool() { } function handleDeleteTool(tool: Tool) { + // 默认工具不能删除,只能隐藏 + if (tool.isDefault) { + dialog.warning({ + title: t('Hide Default Tool'), + content: `${t('Are you sure you want to hide default tool')} "${tool.name}"? ${t('You can show it again later')}.`, + positiveText: t('Hide'), + negativeText: t('Cancel'), + onPositiveClick: () => { + toolsStore.hideDefaultTool(tool.id) + message.success(t('Tool hidden successfully')) + } + }) + return + } + + // 删除用户工具 dialog.warning({ title: t('Confirm Delete'), content: `${t('Are you sure you want to delete tool')} "${tool.name}"?`, positiveText: t('Delete'), negativeText: t('Cancel'), onPositiveClick: () => { - tools.value = tools.value.filter(t => t.id !== tool.id) - // 重新分配 order - tools.value.forEach((t, index) => { - t.order = index + 1 - }) - saveTools(tools.value) + toolsStore.deleteUserTool(tool.id) message.success(t('Tool deleted successfully')) } }) } +// 显示隐藏的默认工具 +function handleShowDefaultTool(toolId: string) { + toolsStore.showDefaultTool(toolId) + message.success(t('Tool shown successfully')) +} + function handleOpenTool(tool: Tool) { if (tool.type === 'route' && tool.routeName) { // 使用路由名导航(最佳实践) @@ -469,19 +497,43 @@ function handleOpenTool(tool: Tool) { } // 获取工具菜单选项 -function getToolMenuOptions(_tool: Tool): DropdownOption[] { - return [ - { - label: t('Edit'), - key: 'edit', - icon: () => h('i', { class: 'fa fa-edit' }) - }, - { - label: t('Delete'), - key: 'delete', - icon: () => h('i', { class: 'fa fa-trash' }) +function getToolMenuOptions(tool: Tool): DropdownOption[] { + const options: DropdownOption[] = [] + + // 默认工具:显示"隐藏"选项 + if (tool.isDefault) { + if (toolsStore.hiddenDefaultToolIds.includes(tool.id)) { + // 已隐藏,显示"显示"选项 + options.push({ + label: t('Show'), + key: 'show', + icon: () => h('i', { class: 'fa fa-eye' }) + }) + } else { + // 未隐藏,显示"隐藏"选项 + options.push({ + label: t('Hide'), + key: 'hide', + icon: () => h('i', { class: 'fa fa-eye-slash' }) + }) } - ] + } else { + // 用户工具:显示"编辑"和"删除"选项 + options.push( + { + label: t('Edit'), + key: 'edit', + icon: () => h('i', { class: 'fa fa-edit' }) + }, + { + label: t('Delete'), + key: 'delete', + icon: () => h('i', { class: 'fa fa-trash' }) + } + ) + } + + return options } // 处理菜单选择 @@ -490,6 +542,13 @@ function handleMenuSelect(key: string, tool: Tool) { handleEditTool(tool) } else if (key === 'delete') { handleDeleteTool(tool) + } else if (key === 'hide') { + // 隐藏默认工具 + toolsStore.hideDefaultTool(tool.id) + message.success(t('Tool hidden successfully')) + } else if (key === 'show') { + // 显示隐藏的默认工具 + handleShowDefaultTool(tool.id) } } @@ -766,6 +825,100 @@ function handleMenuSelect(key: string, tool: Tool) { margin-right: 12px; } +/* 隐藏的工具区域 */ +.hidden-tools-section { + margin-top: 32px; + padding-top: 32px; + border-top: 1px solid #e5e7eb; +} + +.hidden-tools-header { + margin-bottom: 16px; +} + +.hidden-tools-header h3 { + font-size: 18px; + font-weight: 600; + color: #64748b; + margin: 0 0 4px 0; +} + +.hidden-tools-hint { + font-size: 14px; + color: #94a3b8; + margin: 0; +} + +.hidden-tools-list { + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.hidden-tool-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: #f8fafc; + border: 1px solid #e5e7eb; + border-radius: 8px; + cursor: pointer; + transition: all 0.2s; + opacity: 0.7; +} + +.hidden-tool-item:hover { + background: #f1f5f9; + border-color: #cbd5e1; + opacity: 1; + transform: translateY(-1px); +} + +.hidden-tool-icon { + width: 40px; + height: 40px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.hidden-tool-name { + font-size: 14px; + font-weight: 500; + color: #64748b; + flex: 1; +} + +.show-tool-btn { + height: 32px; + padding: 0 12px; + border: 1px solid #1fc76f; + border-radius: 6px; + background: white; + color: #1fc76f; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 500; + transition: all 0.2s; + flex-shrink: 0; +} + +.show-tool-btn:hover { + background: #f0fdf4; + border-color: #1dd87f; + color: #1dd87f; +} + +.show-tool-btn i { + font-size: 12px; +} + /* 响应式设计 */ @media (max-width: 1920px) { .tools-grid { diff --git a/apps/jingrow/jingrow/config.py b/apps/jingrow/jingrow/config.py index bc62da8..37086d8 100644 --- a/apps/jingrow/jingrow/config.py +++ b/apps/jingrow/jingrow/config.py @@ -7,7 +7,7 @@ class Settings(BaseSettings): """应用配置""" # Jingrow API配置 - jingrow_server_url: str = '' + jingrow_server_url: str = 'https://cloud.jingrow.com' jingrow_api_key: str = '' jingrow_api_secret: str = '' diff --git a/apps/jingrow/jingrow/tools/__init__.py b/apps/jingrow/jingrow/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/jingrow/jingrow/tools/tools.py b/apps/jingrow/jingrow/tools/tools.py new file mode 100644 index 0000000..1feabb4 --- /dev/null +++ b/apps/jingrow/jingrow/tools/tools.py @@ -0,0 +1,157 @@ +# Copyright (c) 2025, JINGROW and contributors +# For license information, please see license.txt + +""" +Jingrow Tools API +提供各种工具服务的API端点 +""" + +import base64 +import json +import requests +from typing import Dict, Any, List, Union +import logging + +import jingrow +from jingrow.utils.auth import get_jingrow_cloud_api_headers, get_jingrow_cloud_api_url + +logger = logging.getLogger(__name__) + + +@jingrow.whitelist() +def remove_background(image_urls: Union[str, List[str]]) -> Dict[str, Any]: + """ + 图片去背景工具 + 调用 Jingrow Cloud API 实现图片背景移除 + + Args: + image_urls (str | List[str]): 图片URL(单个URL字符串或URL列表) + + Returns: + dict: 处理结果 + { + 'success': bool, + 'data': List[dict], # 处理结果列表(成功时) + 'error': str, # 错误信息(失败时) + 'total_processed': int, # 处理总数 + 'total_success': int # 成功数量 + } + """ + try: + # 获取API URL和认证头 + api_url = f"{get_jingrow_cloud_api_url()}/rmbg/batch" + headers = get_jingrow_cloud_api_headers() + + if not headers or not headers.get('Authorization'): + error_message = "API密钥未设置,请在设置中配置 Jingrow Cloud Api Key 和 Jingrow Cloud Api Secret" + logger.error(error_message) + return { + "success": False, + "error": error_message + } + + # 处理单个URL或URL列表 + if isinstance(image_urls, str): + image_urls = [image_urls] + + if not image_urls: + return { + "success": False, + "error": "未提供图片URL" + } + + # 准备请求数据 + request_data = {"urls": image_urls} + + # 调用API并获取流式响应 + response = requests.post( + api_url, + json=request_data, + headers=headers, + stream=True, + timeout=180 + ) + + if response.status_code != 200: + try: + error_data = response.json() + error_message = error_data.get("message") or error_data.get("error") or f"API请求失败 (HTTP {response.status_code})" + except: + error_message = f"API请求失败 (HTTP {response.status_code})" + + if response.status_code in [401, 403]: + error_message = "API认证失败,请检查认证信息" + + logger.error(f"图片去背景API调用失败: {error_message}") + return { + "success": False, + "error": error_message + } + + # 处理流式响应 + results = [] + total_processed = 0 + total_success = 0 + + for line in response.iter_lines(): + if line: + try: + result = json.loads(line) + total_processed += 1 + + if result.get('status') == 'success' and 'image_content' in result: + # 获取图片内容(base64编码) + image_content = result.get('image_content', '') + # 如果包含 data:image 前缀,提取base64部分 + if ',' in image_content: + image_content = image_content.split(',')[1] + + results.append({ + "success": True, + "original_url": result.get('original_url', ''), + "image_content": image_content, # Base64编码的图片数据 + "index": result.get('index', 0), + "total": result.get('total', 1) + }) + total_success += 1 + else: + error_message = result.get('message', '未知错误') + results.append({ + "success": False, + "error": error_message, + "original_url": result.get('original_url', ''), + "index": result.get('index', 0), + "total": result.get('total', 1) + }) + except Exception as e: + logger.warning(f"解析流式响应行失败: {e}") + continue + + if total_processed == 0: + return { + "success": False, + "error": "未能获取到任何响应数据" + } + + return { + "success": True, + "data": results, + "total_processed": total_processed, + "total_success": total_success + } + + except requests.exceptions.Timeout: + error_message = "请求超时,请稍后重试" + logger.error(f"图片去背景API调用超时: {error_message}") + return { + "success": False, + "error": error_message + } + except Exception as e: + error_message = f"调用图片去背景API异常:{str(e)}" + logger.error(error_message, exc_info=True) + return { + "success": False, + "error": error_message + } +