add image background removal tool
This commit is contained in:
parent
bf9d52a2db
commit
77982b66ff
@ -1117,5 +1117,12 @@
|
||||
"Tool shown successfully": "工具已显示",
|
||||
"Default tools cannot be edited": "默认工具无法编辑",
|
||||
"Hidden Tools": "已隐藏的工具",
|
||||
"Click to show hidden tools": "点击显示隐藏的工具"
|
||||
"Click to show hidden tools": "点击显示隐藏的工具",
|
||||
"API endpoint not found. Please check if the backend service is running.": "API端点未找到,请检查后端服务是否运行",
|
||||
"Authentication failed. Please login again.": "认证失败,请重新登录",
|
||||
"Network error. Please check your connection.": "网络错误,请检查您的连接",
|
||||
"No image data returned": "未返回图片数据",
|
||||
"Please login first to use this feature": "请先登录以使用此功能",
|
||||
"Session expired. Please login again.": "会话已过期,请重新登录",
|
||||
"Session expired. Please login again to use this feature.": "会话已过期,请重新登录以使用此功能"
|
||||
}
|
||||
|
||||
@ -140,13 +140,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { NButton, useMessage } from 'naive-ui'
|
||||
import axios from 'axios'
|
||||
import { t } from '@/shared/i18n'
|
||||
import { get_session_api_headers } from '@/shared/api/auth'
|
||||
import { useAuthStore } from '@/shared/stores/auth'
|
||||
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// 文件相关
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null)
|
||||
@ -237,54 +238,17 @@ const resetUpload = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 上传图片并获取URL
|
||||
const uploadImageToServer = async (file: File): Promise<string | null> => {
|
||||
try {
|
||||
// 将文件转换为base64,然后使用download_image_to_local API
|
||||
const reader = new FileReader()
|
||||
const base64Promise = new Promise<string>((resolve, reject) => {
|
||||
reader.onload = (e) => {
|
||||
const result = e.target?.result as string
|
||||
resolve(result)
|
||||
}
|
||||
reader.onerror = reject
|
||||
})
|
||||
reader.readAsDataURL(file)
|
||||
|
||||
const dataUrl = await base64Promise
|
||||
|
||||
// 使用download_image_to_local API上传图片
|
||||
const response = await axios.post(
|
||||
'/jingrow.utils.fs.download_image_to_local',
|
||||
{
|
||||
image_url: dataUrl,
|
||||
filename: file.name
|
||||
},
|
||||
{
|
||||
headers: get_session_api_headers(),
|
||||
withCredentials: true,
|
||||
timeout: 30000
|
||||
}
|
||||
)
|
||||
|
||||
if (response.data?.data?.success && response.data?.data?.local_path) {
|
||||
// 如果是相对路径,需要转换为完整URL
|
||||
const fileUrl = response.data.data.local_path
|
||||
if (fileUrl.startsWith('/')) {
|
||||
return `${window.location.origin}${fileUrl}`
|
||||
}
|
||||
return fileUrl
|
||||
}
|
||||
return null
|
||||
} catch (error: any) {
|
||||
console.error('Upload error:', error)
|
||||
message.error(t('Failed to upload image: ') + (error.response?.data?.detail || error.message))
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 处理去背景
|
||||
const handleRemoveBackground = async () => {
|
||||
// 检查用户是否已登录
|
||||
if (!authStore.isLoggedIn) {
|
||||
message.error(t('Please login first to use this feature'))
|
||||
return
|
||||
}
|
||||
|
||||
// 与应用市场保持一致,不检查 cookie,直接调用 API
|
||||
// 后端 whitelist 会处理认证,如果没有 cookie 会返回 401
|
||||
|
||||
if (!uploadedImage.value) {
|
||||
message.warning(t('Please upload an image first'))
|
||||
return
|
||||
@ -294,43 +258,82 @@ const handleRemoveBackground = async () => {
|
||||
resultImage.value = ''
|
||||
|
||||
try {
|
||||
// 先上传图片获取URL
|
||||
const imageUrl = await uploadImageToServer(uploadedImage.value)
|
||||
if (!imageUrl) {
|
||||
processing.value = false
|
||||
return
|
||||
}
|
||||
// 将文件转换为base64
|
||||
const reader = new FileReader()
|
||||
const base64Promise = new Promise<string>((resolve, reject) => {
|
||||
reader.onload = (e) => {
|
||||
const result = e.target?.result as string
|
||||
resolve(result)
|
||||
}
|
||||
reader.onerror = reject
|
||||
})
|
||||
reader.readAsDataURL(uploadedImage.value)
|
||||
const base64Data = await base64Promise
|
||||
|
||||
// 调用去背景API
|
||||
// 调用去背景API,需要认证时使用 withCredentials 发送 session cookie
|
||||
const response = await axios.post(
|
||||
'/jingrow.tools.tools.remove_background',
|
||||
{
|
||||
image_urls: [imageUrl]
|
||||
image_data: base64Data
|
||||
},
|
||||
{
|
||||
headers: get_session_api_headers(),
|
||||
withCredentials: true,
|
||||
withCredentials: true, // 发送 session cookie 以进行认证
|
||||
timeout: 180000 // 3分钟超时
|
||||
}
|
||||
)
|
||||
|
||||
if (response.data?.success && response.data?.data?.length > 0) {
|
||||
const result = response.data.data[0]
|
||||
if (result.success && result.image_content) {
|
||||
resultImage.value = result.image_content
|
||||
message.success(t('Background removed successfully!'))
|
||||
// whitelist 返回格式: {success: true, data: function_result}
|
||||
// function_result 格式: {success: true/false, data: [...], ...}
|
||||
if (response.data?.success && response.data?.data) {
|
||||
const result = response.data.data
|
||||
|
||||
// 检查函数返回的 success 字段
|
||||
if (result.success) {
|
||||
// 检查 data 字段(数组格式)
|
||||
if (result.data && Array.isArray(result.data) && result.data.length > 0) {
|
||||
const firstResult = result.data[0]
|
||||
if (firstResult.success && firstResult.image_content) {
|
||||
resultImage.value = firstResult.image_content
|
||||
message.success(t('Background removed successfully!'))
|
||||
} else {
|
||||
message.error(firstResult.error || t('Failed to remove background'))
|
||||
}
|
||||
} else {
|
||||
message.error(t('No image data returned'))
|
||||
}
|
||||
} else {
|
||||
// 函数返回失败
|
||||
message.error(result.error || t('Failed to remove background'))
|
||||
}
|
||||
} else {
|
||||
message.error(response.data?.error || t('Failed to remove background'))
|
||||
// whitelist 包装失败
|
||||
message.error(response.data?.error || response.data?.message || t('Failed to remove background'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Remove background error:', error)
|
||||
const errorMessage = error.response?.data?.error ||
|
||||
error.response?.data?.detail ||
|
||||
error.message ||
|
||||
t('Failed to remove background')
|
||||
let errorMessage = t('Failed to remove background')
|
||||
|
||||
if (error.response) {
|
||||
// 服务器返回了错误响应
|
||||
if (error.response.data) {
|
||||
errorMessage = error.response.data.error ||
|
||||
error.response.data.detail ||
|
||||
error.response.data.message ||
|
||||
errorMessage
|
||||
}
|
||||
if (error.response.status === 404) {
|
||||
errorMessage = t('API endpoint not found. Please check if the backend service is running.')
|
||||
} else if (error.response.status === 401 || error.response.status === 403) {
|
||||
errorMessage = t('Authentication failed. Please login again.')
|
||||
}
|
||||
} else if (error.request) {
|
||||
// 请求已发出但没有收到响应
|
||||
errorMessage = t('Network error. Please check your connection.')
|
||||
} else {
|
||||
// 其他错误
|
||||
errorMessage = error.message || errorMessage
|
||||
}
|
||||
|
||||
message.error(errorMessage)
|
||||
} finally {
|
||||
processing.value = false
|
||||
@ -342,11 +345,12 @@ const handleDownload = () => {
|
||||
if (!resultImage.value) return
|
||||
|
||||
try {
|
||||
// 将base64转换为blob
|
||||
// 提取 base64 数据
|
||||
const base64Data = resultImage.value.includes(',')
|
||||
? resultImage.value.split(',')[1]
|
||||
: resultImage.value
|
||||
|
||||
// 将 base64 转换为二进制数据
|
||||
const byteCharacters = atob(base64Data)
|
||||
const byteNumbers = new Array(byteCharacters.length)
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
@ -355,19 +359,25 @@ const handleDownload = () => {
|
||||
const byteArray = new Uint8Array(byteNumbers)
|
||||
const blob = new Blob([byteArray], { type: 'image/png' })
|
||||
|
||||
// 创建下载链接
|
||||
// 使用 blob URL 创建下载链接
|
||||
// 注意:在 HTTP 环境下,浏览器可能会显示安全警告,这是正常的,不影响下载功能
|
||||
// 要消除警告,需要将网站升级到 HTTPS
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `removed-background-${Date.now()}.png`
|
||||
link.style.display = 'none'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
// 延迟清理,确保下载开始后再清理
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}, 100)
|
||||
|
||||
message.success(t('Download started'))
|
||||
} catch (error: any) {
|
||||
console.error('Download error:', error)
|
||||
message.error(t('Failed to download image'))
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,9 +7,8 @@ Jingrow Tools API
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import requests
|
||||
from typing import Dict, Any, List, Union
|
||||
from typing import Dict, Any, Union
|
||||
import logging
|
||||
|
||||
import jingrow
|
||||
@ -19,139 +18,117 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@jingrow.whitelist()
|
||||
def remove_background(image_urls: Union[str, List[str]]) -> Dict[str, Any]:
|
||||
def remove_background(image_data: Union[str, list]) -> Dict[str, Any]:
|
||||
"""
|
||||
图片去背景工具
|
||||
调用 Jingrow Cloud API 实现图片背景移除
|
||||
|
||||
Args:
|
||||
image_urls (str | List[str]): 图片URL(单个URL字符串或URL列表)
|
||||
image_data: Base64编码的图片数据(data:image/开头)
|
||||
|
||||
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
|
||||
"error": "API密钥未设置,请在设置中配置 Jingrow Cloud Api Key 和 Jingrow Cloud Api Secret"
|
||||
}
|
||||
|
||||
# 处理单个URL或URL列表
|
||||
if isinstance(image_urls, str):
|
||||
image_urls = [image_urls]
|
||||
# 处理单个数据
|
||||
if isinstance(image_data, list):
|
||||
image_data = image_data[0] if image_data else None
|
||||
|
||||
if not image_urls:
|
||||
if not image_data:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "未提供图片URL"
|
||||
"error": "未提供图片数据"
|
||||
}
|
||||
|
||||
# 准备请求数据
|
||||
request_data = {"urls": image_urls}
|
||||
# 提取base64部分
|
||||
if ',' in image_data:
|
||||
header, base64_content = image_data.split(',', 1)
|
||||
if 'png' in header:
|
||||
ext = 'png'
|
||||
content_type = 'image/png'
|
||||
elif 'jpeg' in header or 'jpg' in header:
|
||||
ext = 'jpg'
|
||||
content_type = 'image/jpeg'
|
||||
elif 'webp' in header:
|
||||
ext = 'webp'
|
||||
content_type = 'image/webp'
|
||||
else:
|
||||
ext = 'png'
|
||||
content_type = 'image/png'
|
||||
else:
|
||||
base64_content = image_data
|
||||
ext = 'png'
|
||||
content_type = 'image/png'
|
||||
|
||||
# 调用API并获取流式响应
|
||||
# 解码base64数据
|
||||
image_bytes = base64.b64decode(base64_content)
|
||||
|
||||
# 调用文件上传端点
|
||||
api_url = f"{get_jingrow_cloud_api_url()}/rmbg/file"
|
||||
|
||||
# 移除 Content-Type,让 requests 自动设置 multipart/form-data
|
||||
upload_headers = {k: v for k, v in headers.items() if k.lower() != 'content-type'}
|
||||
files = {
|
||||
'file': (f'image.{ext}', image_bytes, content_type)
|
||||
}
|
||||
|
||||
# 转发请求
|
||||
response = requests.post(
|
||||
api_url,
|
||||
json=request_data,
|
||||
headers=headers,
|
||||
stream=True,
|
||||
files=files,
|
||||
headers=upload_headers,
|
||||
timeout=180
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
if response.status_code == 200:
|
||||
result_data = response.json()
|
||||
# 处理返回的图片内容
|
||||
if 'image_content' in result_data:
|
||||
image_content = result_data.get('image_content', '')
|
||||
# 如果包含 data:image 前缀,提取base64部分
|
||||
if ',' in image_content:
|
||||
image_content = image_content.split(',')[1]
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": [{
|
||||
"success": True,
|
||||
"image_content": image_content,
|
||||
"index": 1,
|
||||
"total": 1
|
||||
}],
|
||||
"total_processed": 1,
|
||||
"total_success": 1
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"success": False,
|
||||
"error": result_data.get('error', '未知错误')
|
||||
}
|
||||
else:
|
||||
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)
|
||||
logger.error(f"调用图片去背景API异常:{str(e)}", exc_info=True)
|
||||
return {
|
||||
"success": False,
|
||||
"error": error_message
|
||||
"error": f"调用图片去背景API异常:{str(e)}"
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user