feat(edit): AI套图生成、图片切七牛
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'node:test';
|
||||
import { deepSeekJson, makeModel, validateCopy, validatePackage } from '../src/product-ai.ts';
|
||||
import { deepSeekJson, makeModel, validateCopy, validatePackage, runProductAiTask } from '../src/product-ai.ts';
|
||||
|
||||
test('同一商品模型只由类目和稳定商品 ID 确定', () => {
|
||||
const category = { name: '手办', path: '玩具 / 手办' };
|
||||
@@ -24,7 +24,7 @@ test('文案只保留成对且含俄文字符的字段', () => {
|
||||
}),
|
||||
{
|
||||
title: { ru: 'Фигурка героя', zh: '角色手办' },
|
||||
tags: [{ ru: 'Коллекционная', zh: '收藏' }],
|
||||
tags: [{ ru: '#Коллекционная', zh: '收藏' }],
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -42,7 +42,7 @@ test('俄文标题、标签和描述中的汉字不会进入回填结果', () =>
|
||||
zh: '中性棒球帽。尺码:均码。',
|
||||
},
|
||||
}),
|
||||
{ tags: [{ ru: 'Спортивная кепка', zh: '运动帽' }] },
|
||||
{ tags: [{ ru: '#Спортивная_кепка', zh: '运动帽' }] },
|
||||
);
|
||||
});
|
||||
|
||||
@@ -180,3 +180,70 @@ test('DeepSeek JSON 模式返回空白内容时调整提示词重试', async ()
|
||||
else process.env.DEEPSEEK_API_KEY = originalKey;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('AI 标签补齐井号、整理词间空格和连字符,过滤非法及超长项并保留中文配对', () => {
|
||||
const copy = validateCopy({ tags: [
|
||||
{ ru: 'Спортивный стиль', zh: '运动风格' },
|
||||
{ ru: '#Чёрно-белый', zh: '黑白' },
|
||||
{ ru: '#Спортивный_стиль', zh: '重复主题' },
|
||||
{ ru: '#стиль/мода', zh: '非法标点' },
|
||||
{ ru: '#стиль#мода', zh: '缺少分隔' },
|
||||
{ ru: '#стиль😀', zh: '表情' },
|
||||
{ ru: '#' + 'я'.repeat(30), zh: '超长' },
|
||||
{ ru: '#' + 'я'.repeat(29), zh: '长度边界' },
|
||||
] });
|
||||
assert.deepEqual(copy.tags, [
|
||||
{ ru: '#Спортивный_стиль', zh: '运动风格' },
|
||||
{ ru: '#Чёрно_белый', zh: '黑白' },
|
||||
{ ru: '#' + 'я'.repeat(29), zh: '长度边界' },
|
||||
]);
|
||||
const many = validateCopy({tags:Array.from({length:35},(_,i)=>({ru:`#стиль${i}`,zh:`风格${i}`}))});
|
||||
assert.equal(many.tags.length,30);
|
||||
});
|
||||
|
||||
test('全局 AI 填报和 SKU 文案都要求标签格式并在返回前整理输出', async () => {
|
||||
const previousFetch = globalThis.fetch;
|
||||
const previousKey = process.env.DEEPSEEK_API_KEY;
|
||||
process.env.DEEPSEEK_API_KEY = 'test-key';
|
||||
const prompts = [];
|
||||
globalThis.fetch = async (_url, options) => {
|
||||
const request = JSON.parse(options.body);
|
||||
prompts.push(request.messages[0].content);
|
||||
return new Response(JSON.stringify({ choices: [{ finish_reason: 'stop', message: {
|
||||
content: JSON.stringify({ tags: [{ru:'Спортивный стиль',zh:'运动风格'}] }),
|
||||
}}] }), {status:200});
|
||||
};
|
||||
try {
|
||||
for (const kind of ['autofill','sku_copy']) {
|
||||
let saved;
|
||||
const input = {
|
||||
platform_id:'platform',product_id:'product',profile_id:'profile',variant_id:'sku',
|
||||
source_material:{title:'帽子',description:null,attributes:[],categories:[]},
|
||||
common:{model:'model'},profile:{category:{id:'category'},tags:[],target_store_id:'store'},
|
||||
variants:[{id:'sku',listing:{status:'imported',offer_id:'offer'},specification_values:[]}],
|
||||
};
|
||||
const tx = { query: async (sql, values) => {
|
||||
if(sql.includes('SELECT kind, status')) return {rows:[{kind,status:'queued',input_snapshot:input}]};
|
||||
if(sql.includes('FROM atlas.platform_product_categories')) return {rows:[{id:'category',name:'帽子',path:'帽子',is_active:true,external_type_id:'1'}]};
|
||||
if(sql.includes('FROM atlas.product_variant_platform_listings')) return {rows:[]};
|
||||
if(sql.includes('result = $3::jsonb')) saved=JSON.parse(values[2]);
|
||||
return {rows:[]};
|
||||
}};
|
||||
await runProductAiTask({workspaceTransaction:async (_workspace,work)=>work(tx)}, {
|
||||
workspaceId:'workspace',payload:{task_id:'task'},
|
||||
});
|
||||
assert.deepEqual((kind==='autofill'?saved.spu_copy:saved.copy).tags,[{ru:'#Спортивный_стиль',zh:'运动风格'}]);
|
||||
}
|
||||
assert.equal(prompts.length,2);
|
||||
for(const prompt of prompts) {
|
||||
assert.match(prompt,/每个 tags\[\]\.ru 仅包含一个标签/);
|
||||
assert.match(prompt,/最多 30 个字符/);
|
||||
assert.match(prompt,/#спортивный_стиль/);
|
||||
}
|
||||
} finally {
|
||||
globalThis.fetch=previousFetch;
|
||||
if(previousKey===undefined) delete process.env.DEEPSEEK_API_KEY;
|
||||
else process.env.DEEPSEEK_API_KEY=previousKey;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user