feat: 简化vite配置并修复去背景接口
- 简化vite.config.ts,删除冗余的apps.txt读取和文件服务插件 - 更新去背景接口路径为 /tools/rmbg/file/free - 修复流式响应处理:使用fetch API正确处理NDJSON格式 - 添加/tools代理配置以支持新的接口路径 - 优化错误处理,适配fetch API的错误格式
This commit is contained in:
parent
328a9f4408
commit
6b638ac143
@ -521,59 +521,118 @@ const handleRemoveBackground = async () => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', uploadedImage.value)
|
||||
|
||||
// 使用 file/free API,无需认证
|
||||
// 注意:不要手动设置 Content-Type,让浏览器自动设置(包含 boundary)
|
||||
const response = await axios.post(
|
||||
'/jingrow.tools.remove_background.remove_background.remove_background_from_file_free',
|
||||
formData,
|
||||
{
|
||||
timeout: 180000
|
||||
}
|
||||
)
|
||||
// 使用 fetch 处理流式响应(NDJSON 格式)
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 180000)
|
||||
|
||||
const response = await fetch('/tools/rmbg/file/free', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (response.data?.success && response.data?.data) {
|
||||
const result = response.data.data
|
||||
|
||||
// 处理返回格式(与 remove_background_from_file 一致)
|
||||
// result 是函数返回的字典,包含 success、data 数组等字段
|
||||
if (result.success && result.data && Array.isArray(result.data) && result.data.length > 0) {
|
||||
const firstResult = result.data[0]
|
||||
if (firstResult.success && firstResult.image_url) {
|
||||
resultImage.value = firstResult.image_url
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
// 读取流式响应(NDJSON 格式,每行一个 JSON 对象)
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
|
||||
if (!reader) {
|
||||
throw new Error('Response body is not readable')
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split('\n')
|
||||
buffer = lines.pop() || '' // 保留最后一个不完整的行
|
||||
|
||||
// 处理完整的行
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
try {
|
||||
const result = JSON.parse(line.trim())
|
||||
|
||||
// 后端返回格式:{"status": "success", "image_url": "..."}
|
||||
if (result.status === 'success' && result.image_url) {
|
||||
resultImage.value = result.image_url
|
||||
|
||||
// 缓存图片 blob URL 用于下载
|
||||
await cacheResultImage(result.image_url)
|
||||
|
||||
// 添加到历史记录
|
||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||
const historyItem: HistoryItem = {
|
||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
originalImageUrl: uploadedImageUrl.value,
|
||||
originalImageFile: uploadedImage.value,
|
||||
resultImage: result.image_url,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
historyList.value.unshift(historyItem)
|
||||
currentHistoryIndex.value = 0
|
||||
} else if (currentHistoryIndex.value >= 0) {
|
||||
historyList.value[currentHistoryIndex.value].resultImage = result.image_url
|
||||
}
|
||||
return // 成功处理,退出
|
||||
} else {
|
||||
message.error(result.error || t('Failed to remove background'))
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('Failed to parse JSON:', parseError, 'Line:', line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理最后一行
|
||||
if (buffer.trim()) {
|
||||
try {
|
||||
const result = JSON.parse(buffer.trim())
|
||||
if (result.status === 'success' && result.image_url) {
|
||||
resultImage.value = result.image_url
|
||||
await cacheResultImage(result.image_url)
|
||||
|
||||
// 缓存图片 blob URL 用于下载
|
||||
await cacheResultImage(firstResult.image_url)
|
||||
|
||||
// 添加到历史记录
|
||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||
const historyItem: HistoryItem = {
|
||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
originalImageUrl: uploadedImageUrl.value,
|
||||
originalImageFile: uploadedImage.value,
|
||||
resultImage: firstResult.image_url,
|
||||
resultImage: result.image_url,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
historyList.value.unshift(historyItem)
|
||||
currentHistoryIndex.value = 0
|
||||
} else if (currentHistoryIndex.value >= 0) {
|
||||
historyList.value[currentHistoryIndex.value].resultImage = firstResult.image_url
|
||||
historyList.value[currentHistoryIndex.value].resultImage = result.image_url
|
||||
}
|
||||
return
|
||||
} else {
|
||||
message.error(firstResult.error || t('Failed to remove background'))
|
||||
message.error(result.error || t('Failed to remove background'))
|
||||
}
|
||||
} else {
|
||||
message.error(result.error || t('Failed to remove background'))
|
||||
} catch (parseError) {
|
||||
console.error('Failed to parse final JSON:', parseError)
|
||||
message.error(t('Failed to parse response'))
|
||||
}
|
||||
} else {
|
||||
message.error(response.data?.error || response.data?.message || t('Failed to remove background'))
|
||||
message.error(t('No image data returned'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
let errorMessage = t('Failed to remove background')
|
||||
if (error.response?.data?.error) {
|
||||
errorMessage = error.response.data.error
|
||||
|
||||
if (error.name === 'AbortError') {
|
||||
errorMessage = t('Request timeout. Please try again.')
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
message.error(errorMessage)
|
||||
} finally {
|
||||
processing.value = false
|
||||
|
||||
@ -612,71 +612,115 @@ const handleRemoveBackground = async () => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', uploadedImage.value)
|
||||
|
||||
const response = await axios.post(
|
||||
'/jingrow.tools.remove_background.remove_background.remove_background_from_file_free',
|
||||
formData,
|
||||
{
|
||||
timeout: 180000
|
||||
}
|
||||
)
|
||||
// 使用 fetch 处理流式响应(NDJSON 格式)
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 180000)
|
||||
|
||||
const response = await fetch('/tools/rmbg/file/free', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
if (response.data?.success && response.data?.data) {
|
||||
const result = response.data.data
|
||||
|
||||
if (result.success) {
|
||||
if (result.data && Array.isArray(result.data) && result.data.length > 0) {
|
||||
const firstResult = result.data[0]
|
||||
|
||||
if (firstResult.success && firstResult.image_url) {
|
||||
resultImage.value = firstResult.image_url
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
// 读取流式响应(NDJSON 格式,每行一个 JSON 对象)
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
let buffer = ''
|
||||
|
||||
if (!reader) {
|
||||
throw new Error('Response body is not readable')
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
|
||||
buffer += decoder.decode(value, { stream: true })
|
||||
const lines = buffer.split('\n')
|
||||
buffer = lines.pop() || '' // 保留最后一个不完整的行
|
||||
|
||||
// 处理完整的行
|
||||
for (const line of lines) {
|
||||
if (line.trim()) {
|
||||
try {
|
||||
const result = JSON.parse(line.trim())
|
||||
|
||||
// 缓存图片 blob URL 用于下载
|
||||
await cacheResultImage(firstResult.image_url)
|
||||
|
||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||
const historyItem: HistoryItem = {
|
||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
originalImageUrl: uploadedImageUrl.value,
|
||||
originalImageFile: uploadedImage.value,
|
||||
resultImage: firstResult.image_url,
|
||||
timestamp: Date.now()
|
||||
// 后端返回格式:{"status": "success", "image_url": "..."}
|
||||
if (result.status === 'success' && result.image_url) {
|
||||
resultImage.value = result.image_url
|
||||
|
||||
// 缓存图片 blob URL 用于下载
|
||||
await cacheResultImage(result.image_url)
|
||||
|
||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||
const historyItem: HistoryItem = {
|
||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
originalImageUrl: uploadedImageUrl.value,
|
||||
originalImageFile: uploadedImage.value,
|
||||
resultImage: result.image_url,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
historyList.value.unshift(historyItem)
|
||||
currentHistoryIndex.value = 0
|
||||
} else if (currentHistoryIndex.value >= 0) {
|
||||
historyList.value[currentHistoryIndex.value].resultImage = result.image_url
|
||||
}
|
||||
historyList.value.unshift(historyItem)
|
||||
currentHistoryIndex.value = 0
|
||||
} else if (currentHistoryIndex.value >= 0) {
|
||||
historyList.value[currentHistoryIndex.value].resultImage = firstResult.image_url
|
||||
return // 成功处理,退出
|
||||
} else {
|
||||
message.error(result.error || t('Failed to remove background'))
|
||||
}
|
||||
} else {
|
||||
message.error(firstResult.error || t('Failed to remove background'))
|
||||
} catch (parseError) {
|
||||
console.error('Failed to parse JSON:', parseError, 'Line:', line)
|
||||
}
|
||||
} else {
|
||||
message.error(t('No image data returned'))
|
||||
}
|
||||
} else {
|
||||
message.error(result.error || t('Failed to remove background'))
|
||||
}
|
||||
}
|
||||
|
||||
// 处理最后一行
|
||||
if (buffer.trim()) {
|
||||
try {
|
||||
const result = JSON.parse(buffer.trim())
|
||||
if (result.status === 'success' && result.image_url) {
|
||||
resultImage.value = result.image_url
|
||||
await cacheResultImage(result.image_url)
|
||||
|
||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||
const historyItem: HistoryItem = {
|
||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
originalImageUrl: uploadedImageUrl.value,
|
||||
originalImageFile: uploadedImage.value,
|
||||
resultImage: result.image_url,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
historyList.value.unshift(historyItem)
|
||||
currentHistoryIndex.value = 0
|
||||
} else if (currentHistoryIndex.value >= 0) {
|
||||
historyList.value[currentHistoryIndex.value].resultImage = result.image_url
|
||||
}
|
||||
return
|
||||
} else {
|
||||
message.error(result.error || t('Failed to remove background'))
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.error('Failed to parse final JSON:', parseError)
|
||||
message.error(t('Failed to parse response'))
|
||||
}
|
||||
} else {
|
||||
message.error(response.data?.error || response.data?.message || t('Failed to remove background'))
|
||||
message.error(t('No image data returned'))
|
||||
}
|
||||
} catch (error: any) {
|
||||
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
|
||||
if (error.name === 'AbortError') {
|
||||
errorMessage = t('Request timeout. Please try again.')
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
message.error(errorMessage)
|
||||
|
||||
@ -1,58 +1,23 @@
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { resolve } from 'path'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import Icons from 'unplugin-icons/vite'
|
||||
import IconsResolver from 'unplugin-icons/resolver'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { vitePluginPrerender } from './scripts/vite-plugin-prerender.js'
|
||||
|
||||
// 统一处理后端 Set-Cookie,移除 Secure 标志,便于在 HTTP 开发环境保存 Cookie
|
||||
const cookieRewriteConfigure = (proxy: any) => {
|
||||
proxy.on('proxyRes', (proxyRes: any) => {
|
||||
const setCookieHeaders = proxyRes.headers['set-cookie']
|
||||
if (setCookieHeaders) {
|
||||
proxyRes.headers['set-cookie'] = setCookieHeaders.map((cookie: string) => {
|
||||
return cookie
|
||||
.replace(/;\s*[Ss]ecure/gi, '')
|
||||
.replace(/,\s*[Ss]ecure/gi, '')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 读取 apps.txt 确定应用优先级(靠后优先)
|
||||
function loadAppsOrder(appsDir: string) {
|
||||
const appsTxt = path.join(appsDir, 'apps.txt')
|
||||
try {
|
||||
const content = fs.readFileSync(appsTxt, 'utf-8')
|
||||
return content
|
||||
.split(/\r?\n/)
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
} catch {
|
||||
return ['jingrow']
|
||||
}
|
||||
}
|
||||
|
||||
// 计算本工程中的 apps 目录(当前文件位于 apps/jingrow/frontend/vite.config.ts)
|
||||
const currentDir = fileURLToPath(new URL('.', import.meta.url))
|
||||
const appsDir = path.resolve(currentDir, '..', '..')
|
||||
const APPS_ORDER = loadAppsOrder(appsDir)
|
||||
|
||||
export default defineConfig(({ mode, command }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const BACKEND_URL = env.VITE_BACKEND_SERVER_URL || 'http://localhost:9001'
|
||||
const BACKEND_URL = env.VITE_BACKEND_SERVER_URL || 'https://api.jingrow.com'
|
||||
const FRONTEND_HOST = env.VITE_FRONTEND_HOST || '0.0.0.0'
|
||||
const FRONTEND_PORT = Number(env.VITE_FRONTEND_PORT) || 3100
|
||||
const ALLOWED_HOSTS = (env.VITE_ALLOWED_HOSTS || '').split(',').map((s) => s.trim()).filter(Boolean)
|
||||
|
||||
// 开发环境:文件目录路径
|
||||
const filesDir = command === 'serve' ? path.resolve(currentDir, '..', 'jingrow', 'public', 'files') : null
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
@ -68,15 +33,6 @@ export default defineConfig(({ mode, command }) => {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
// 开发环境:直接服务文件系统
|
||||
command === 'serve' && filesDir && {
|
||||
name: 'serve-files',
|
||||
configureServer(server) {
|
||||
const require = createRequire(import.meta.url)
|
||||
const serve = require('serve-static')(filesDir, { index: false })
|
||||
server.middlewares.use('/files', serve)
|
||||
}
|
||||
},
|
||||
// 预渲染插件(仅在构建时启用)
|
||||
command === 'build' && vitePluginPrerender({
|
||||
// 路由列表会自动从工具 store 生成
|
||||
@ -108,21 +64,20 @@ export default defineConfig(({ mode, command }) => {
|
||||
allow: [appsDir]
|
||||
},
|
||||
proxy: {
|
||||
'/api/data': {
|
||||
'/api': {
|
||||
target: BACKEND_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
cookieDomainRewrite: { '*': '' },
|
||||
cookiePathRewrite: { '*': '/' },
|
||||
configure: cookieRewriteConfigure
|
||||
secure: true,
|
||||
},
|
||||
'/jingrow': {
|
||||
target: BACKEND_URL,
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
cookieDomainRewrite: { '*': '' },
|
||||
cookiePathRewrite: { '*': '/' },
|
||||
configure: cookieRewriteConfigure
|
||||
secure: true,
|
||||
},
|
||||
'/tools': {
|
||||
target: BACKEND_URL,
|
||||
changeOrigin: true,
|
||||
secure: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -135,8 +90,8 @@ export default defineConfig(({ mode, command }) => {
|
||||
define: {
|
||||
// 确保环境变量在构建时可用
|
||||
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
|
||||
// 注入 apps.txt 的应用顺序到前端
|
||||
__APPS_ORDER__: JSON.stringify(APPS_ORDER)
|
||||
// 应用顺序(已移除 apps.txt 支持,使用默认值)
|
||||
__APPS_ORDER__: JSON.stringify(['jingrow'])
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user