diff --git a/routes/ai.js b/routes/ai.js index c1d8f88..392216a 100644 --- a/routes/ai.js +++ b/routes/ai.js @@ -138,13 +138,18 @@ router.post('/recognize-product', async (ctx) => { ctx.body = { code: 500, message: 'AI 功能未配置(缺少 DASHSCOPE_API_KEY)' } return } - const { imageUrl } = ctx.request.body; + const { imageBase64, imageUrl } = ctx.request.body; - if (!imageUrl) { + let inputImageUrl = imageUrl; + if (imageBase64) { + inputImageUrl = `data:image/jpeg;base64,${imageBase64}`; + } + + if (!inputImageUrl) { ctx.status = 400; ctx.body = { code: 400, - message: '请提供商品图片地址' + message: '请提供商品图片' }; return; } @@ -160,7 +165,7 @@ router.post('/recognize-product', async (ctx) => { "confidence": 0到1之间的数字(识别置信度) }`; - console.log('Calling Qwen Omni API with image:', imageUrl); + console.log('Calling Qwen Omni API with image...'); const response = await fetch(AI_API_URL, { method: 'POST', headers: { @@ -168,7 +173,7 @@ router.post('/recognize-product', async (ctx) => { 'Content-Type': 'application/json' }, body: JSON.stringify({ - model: 'qwen3.5-omni-flash', + model: 'qwen3.5-omni', messages: [ { role: 'user', @@ -176,7 +181,7 @@ router.post('/recognize-product', async (ctx) => { { type: 'image_url', image_url: { - url: imageUrl + url: inputImageUrl } }, { @@ -186,11 +191,8 @@ router.post('/recognize-product', async (ctx) => { ] } ], - modalities: ['text'], temperature: 0.3, - max_tokens: 500, - stream: true, - stream_options: { include_usage: true } + max_tokens: 500 }), timeout: 60000 }); @@ -210,8 +212,6 @@ router.post('/recognize-product', async (ctx) => { errorMsg = 'API 调用次数超限,请稍后重试'; } else if (response.status === 503) { errorMsg = 'AI 服务暂时不可用,请稍后重试'; - } else if (response.status === 404) { - errorMsg = '模型不支持图片输入,正在尝试兼容模式...'; } ctx.status = response.status; @@ -222,26 +222,8 @@ router.post('/recognize-product', async (ctx) => { return; } - // 解析 SSE 流式响应 - const text = await response.text(); - const lines = text.split('\n'); - let aiResponse = ''; - - for (const line of lines) { - if (line.startsWith('data: ')) { - const dataStr = line.slice(6).trim(); - if (dataStr === '[DONE]') break; - try { - const parsed = JSON.parse(dataStr); - const content = parsed.choices?.[0]?.delta?.content; - if (content) { - aiResponse += content; - } - } catch (e) { - // 跳过解析失败的行 - } - } - } + const data = await response.json(); + const aiResponse = data.choices?.[0]?.message?.content; if (!aiResponse) { ctx.status = 500; @@ -281,7 +263,7 @@ router.post('/recognize-product', async (ctx) => { let matchedGoods = []; if (keyword) { const dbResult = await query( - 'SELECT id, name, price, unit, category_id, images, stock, pricing_type, is_hot, is_new, description, goods_no, barcode FROM goods WHERE name LIKE ? LIMIT 20', + 'SELECT id, name, price, unit, category_id, images, stock, pricing_type, is_hot, is_new, description FROM goods WHERE name LIKE ? LIMIT 20', [`%${keyword}%`] ); matchedGoods = dbResult; diff --git a/utils/pagination.js b/utils/pagination.js index deb56f7..9185880 100644 --- a/utils/pagination.js +++ b/utils/pagination.js @@ -9,9 +9,10 @@ async function paginate(queryFn, sql, params, page = 1, pageSize = 20) { const total = countResult[0].total const totalPages = Math.ceil(total / ps) + const offset = (p - 1) * ps const data = await queryFn( - `${sql} LIMIT ? OFFSET ?`, - [...params, ps, (p - 1) * ps] + `${sql} LIMIT ${ps} OFFSET ${offset}`, + params ) return { data, total, page: p, pageSize: ps, totalPages }