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,28 +521,51 @@ const handleRemoveBackground = async () => {
|
|||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', uploadedImage.value)
|
formData.append('file', uploadedImage.value)
|
||||||
|
|
||||||
// 使用 file/free API,无需认证
|
// 使用 fetch 处理流式响应(NDJSON 格式)
|
||||||
// 注意:不要手动设置 Content-Type,让浏览器自动设置(包含 boundary)
|
const controller = new AbortController()
|
||||||
const response = await axios.post(
|
const timeoutId = setTimeout(() => controller.abort(), 180000)
|
||||||
'/jingrow.tools.remove_background.remove_background.remove_background_from_file_free',
|
|
||||||
formData,
|
const response = await fetch('/tools/rmbg/file/free', {
|
||||||
{
|
method: 'POST',
|
||||||
timeout: 180000
|
body: formData,
|
||||||
|
signal: controller.signal
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
if (response.data?.success && response.data?.data) {
|
// 读取流式响应(NDJSON 格式,每行一个 JSON 对象)
|
||||||
const result = response.data.data
|
const reader = response.body?.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let buffer = ''
|
||||||
|
|
||||||
// 处理返回格式(与 remove_background_from_file 一致)
|
if (!reader) {
|
||||||
// result 是函数返回的字典,包含 success、data 数组等字段
|
throw new Error('Response body is not readable')
|
||||||
if (result.success && result.data && Array.isArray(result.data) && result.data.length > 0) {
|
}
|
||||||
const firstResult = result.data[0]
|
|
||||||
if (firstResult.success && firstResult.image_url) {
|
while (true) {
|
||||||
resultImage.value = firstResult.image_url
|
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 用于下载
|
// 缓存图片 blob URL 用于下载
|
||||||
await cacheResultImage(firstResult.image_url)
|
await cacheResultImage(result.image_url)
|
||||||
|
|
||||||
// 添加到历史记录
|
// 添加到历史记录
|
||||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||||
@ -550,30 +573,66 @@ const handleRemoveBackground = async () => {
|
|||||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||||
originalImageUrl: uploadedImageUrl.value,
|
originalImageUrl: uploadedImageUrl.value,
|
||||||
originalImageFile: uploadedImage.value,
|
originalImageFile: uploadedImage.value,
|
||||||
resultImage: firstResult.image_url,
|
resultImage: result.image_url,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
}
|
}
|
||||||
historyList.value.unshift(historyItem)
|
historyList.value.unshift(historyItem)
|
||||||
currentHistoryIndex.value = 0
|
currentHistoryIndex.value = 0
|
||||||
} else if (currentHistoryIndex.value >= 0) {
|
} else if (currentHistoryIndex.value >= 0) {
|
||||||
historyList.value[currentHistoryIndex.value].resultImage = firstResult.image_url
|
historyList.value[currentHistoryIndex.value].resultImage = result.image_url
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message.error(firstResult.error || t('Failed to remove background'))
|
|
||||||
}
|
}
|
||||||
|
return // 成功处理,退出
|
||||||
} else {
|
} else {
|
||||||
message.error(result.error || t('Failed to remove background'))
|
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)
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
message.error(response.data?.error || response.data?.message || t('Failed to remove background'))
|
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(t('No image data returned'))
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let errorMessage = t('Failed to remove background')
|
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) {
|
} else if (error.message) {
|
||||||
errorMessage = error.message
|
errorMessage = error.message
|
||||||
}
|
}
|
||||||
|
|
||||||
message.error(errorMessage)
|
message.error(errorMessage)
|
||||||
} finally {
|
} finally {
|
||||||
processing.value = false
|
processing.value = false
|
||||||
|
|||||||
@ -612,71 +612,115 @@ const handleRemoveBackground = async () => {
|
|||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', uploadedImage.value)
|
formData.append('file', uploadedImage.value)
|
||||||
|
|
||||||
const response = await axios.post(
|
// 使用 fetch 处理流式响应(NDJSON 格式)
|
||||||
'/jingrow.tools.remove_background.remove_background.remove_background_from_file_free',
|
const controller = new AbortController()
|
||||||
formData,
|
const timeoutId = setTimeout(() => controller.abort(), 180000)
|
||||||
{
|
|
||||||
timeout: 180000
|
const response = await fetch('/tools/rmbg/file/free', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
signal: controller.signal
|
||||||
|
})
|
||||||
|
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
if (response.data?.success && response.data?.data) {
|
// 读取流式响应(NDJSON 格式,每行一个 JSON 对象)
|
||||||
const result = response.data.data
|
const reader = response.body?.getReader()
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
let buffer = ''
|
||||||
|
|
||||||
if (result.success) {
|
if (!reader) {
|
||||||
if (result.data && Array.isArray(result.data) && result.data.length > 0) {
|
throw new Error('Response body is not readable')
|
||||||
const firstResult = result.data[0]
|
}
|
||||||
|
|
||||||
if (firstResult.success && firstResult.image_url) {
|
while (true) {
|
||||||
resultImage.value = firstResult.image_url
|
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 用于下载
|
// 缓存图片 blob URL 用于下载
|
||||||
await cacheResultImage(firstResult.image_url)
|
await cacheResultImage(result.image_url)
|
||||||
|
|
||||||
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
if (currentHistoryIndex.value === -1 && uploadedImage.value && uploadedImageUrl.value) {
|
||||||
const historyItem: HistoryItem = {
|
const historyItem: HistoryItem = {
|
||||||
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
id: `history-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||||
originalImageUrl: uploadedImageUrl.value,
|
originalImageUrl: uploadedImageUrl.value,
|
||||||
originalImageFile: uploadedImage.value,
|
originalImageFile: uploadedImage.value,
|
||||||
resultImage: firstResult.image_url,
|
resultImage: result.image_url,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
}
|
}
|
||||||
historyList.value.unshift(historyItem)
|
historyList.value.unshift(historyItem)
|
||||||
currentHistoryIndex.value = 0
|
currentHistoryIndex.value = 0
|
||||||
} else if (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 {
|
} else {
|
||||||
message.error(firstResult.error || t('Failed to remove background'))
|
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)
|
||||||
|
|
||||||
|
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 {
|
} else {
|
||||||
message.error(t('No image data returned'))
|
message.error(t('No image data returned'))
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
message.error(result.error || t('Failed to remove background'))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
message.error(response.data?.error || response.data?.message || t('Failed to remove background'))
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let errorMessage = t('Failed to remove background')
|
let errorMessage = t('Failed to remove background')
|
||||||
|
|
||||||
if (error.response) {
|
if (error.name === 'AbortError') {
|
||||||
if (error.response.data) {
|
errorMessage = t('Request timeout. Please try again.')
|
||||||
errorMessage = error.response.data.error ||
|
} else if (error.message) {
|
||||||
error.response.data.detail ||
|
errorMessage = error.message
|
||||||
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)
|
message.error(errorMessage)
|
||||||
|
|||||||
@ -1,58 +1,23 @@
|
|||||||
import { defineConfig, loadEnv } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import { resolve } from 'path'
|
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
import Icons from 'unplugin-icons/vite'
|
import Icons from 'unplugin-icons/vite'
|
||||||
import IconsResolver from 'unplugin-icons/resolver'
|
import IconsResolver from 'unplugin-icons/resolver'
|
||||||
import Components from 'unplugin-vue-components/vite'
|
import Components from 'unplugin-vue-components/vite'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import fs from 'node:fs'
|
|
||||||
import { createRequire } from 'node:module'
|
|
||||||
import { vitePluginPrerender } from './scripts/vite-plugin-prerender.js'
|
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)
|
// 计算本工程中的 apps 目录(当前文件位于 apps/jingrow/frontend/vite.config.ts)
|
||||||
const currentDir = fileURLToPath(new URL('.', import.meta.url))
|
const currentDir = fileURLToPath(new URL('.', import.meta.url))
|
||||||
const appsDir = path.resolve(currentDir, '..', '..')
|
const appsDir = path.resolve(currentDir, '..', '..')
|
||||||
const APPS_ORDER = loadAppsOrder(appsDir)
|
|
||||||
|
|
||||||
export default defineConfig(({ mode, command }) => {
|
export default defineConfig(({ mode, command }) => {
|
||||||
const env = loadEnv(mode, process.cwd(), '')
|
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_HOST = env.VITE_FRONTEND_HOST || '0.0.0.0'
|
||||||
const FRONTEND_PORT = Number(env.VITE_FRONTEND_PORT) || 3100
|
const FRONTEND_PORT = Number(env.VITE_FRONTEND_PORT) || 3100
|
||||||
const ALLOWED_HOSTS = (env.VITE_ALLOWED_HOSTS || '').split(',').map((s) => s.trim()).filter(Boolean)
|
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 {
|
return {
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
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({
|
command === 'build' && vitePluginPrerender({
|
||||||
// 路由列表会自动从工具 store 生成
|
// 路由列表会自动从工具 store 生成
|
||||||
@ -108,21 +64,20 @@ export default defineConfig(({ mode, command }) => {
|
|||||||
allow: [appsDir]
|
allow: [appsDir]
|
||||||
},
|
},
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api/data': {
|
'/api': {
|
||||||
target: BACKEND_URL,
|
target: BACKEND_URL,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: true,
|
||||||
cookieDomainRewrite: { '*': '' },
|
|
||||||
cookiePathRewrite: { '*': '/' },
|
|
||||||
configure: cookieRewriteConfigure
|
|
||||||
},
|
},
|
||||||
'/jingrow': {
|
'/jingrow': {
|
||||||
target: BACKEND_URL,
|
target: BACKEND_URL,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: true,
|
||||||
cookieDomainRewrite: { '*': '' },
|
},
|
||||||
cookiePathRewrite: { '*': '/' },
|
'/tools': {
|
||||||
configure: cookieRewriteConfigure
|
target: BACKEND_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -135,8 +90,8 @@ export default defineConfig(({ mode, command }) => {
|
|||||||
define: {
|
define: {
|
||||||
// 确保环境变量在构建时可用
|
// 确保环境变量在构建时可用
|
||||||
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
|
__APP_VERSION__: JSON.stringify(process.env.npm_package_version),
|
||||||
// 注入 apps.txt 的应用顺序到前端
|
// 应用顺序(已移除 apps.txt 支持,使用默认值)
|
||||||
__APPS_ORDER__: JSON.stringify(APPS_ORDER)
|
__APPS_ORDER__: JSON.stringify(['jingrow'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user