diff --git a/felo/create-case-types.js b/felo/create-case-types.js new file mode 100644 index 0000000..a353402 --- /dev/null +++ b/felo/create-case-types.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node + +const https = require('https'); +const url = require('url'); + +const AUTHORIZATION = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyMiJ9.2XLrkX3Fryw4c1Mn3Peqsp8JOMMOu25GP7yxl0gsIG4w9JjfUzlsharGdm8PDusK1fvwMRs5rc1p6LJe0cSg4w'; +const API_URL = 'https://bops.felo.ai/api/home/case/type/add'; + +const data = [ + { + name: 'Logo 设计', + skill_id: 'logo-and-branding', + case_prompt: '请设计一个Logo,品牌名称是:[输入您的品牌名],所属行业是:[如:科技/餐饮/服装]。', + case_type: 'logo_design', + }, + { + name: '品牌周边', + skill_id: 'logo-and-branding', + case_prompt: '基于我们的 Logo 设计一套品牌周边,用在[办公/生活/服饰]上', + case_type: 'brand_merchandise', + }, + { + name: '亚马逊套图', + skill_id: 'ecommerce-product-image', + case_prompt: '用 [$name$] 的视觉风格,生成亚马逊 7 张套图(白底主图 + 信息图 + 生活场景图 + 细节图 + 多角度图),我的产品卖点是:[输入产品卖点]', + case_type: 'amazon_product_images', + }, + { + name: '电商 Banner', + skill_id: 'ecommerce-product-image', + case_prompt: '生成宽幅 Banner 背景图。商品放在[左侧/右侧],卖点是:[输入产品卖点]', + case_type: 'ecommerce_banner', + }, + { + name: '名片', + skill_id: 'marketing-materials', + case_prompt: '设计一套名片,我的要求是:[输入所属行业/职位/风格偏好]', + case_type: 'business_card', + }, + { + name: '海报', + skill_id: 'marketing-materials', + case_prompt: '设计一张海报,我的要求是:[输入活动主题/核心卖点]', + case_type: 'poster', + }, + { + name: '折页', + skill_id: 'marketing-materials', + case_prompt: '设计成一份[三折页宣传册/高级画册],用作活动宣传,活动基础信息为:[输入活动基础信息]', + case_type: 'brochure', + }, +]; + +function post(payload, index) { + return new Promise((resolve, reject) => { + const body = JSON.stringify({ + case_prompt: payload.case_prompt, + case_type: payload.case_type, + name: payload.name, + skill_id: payload.skill_id, + sort: 0, + }); + + const parsed = new url.URL(API_URL); + const options = { + hostname: parsed.hostname, + path: parsed.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(body), + 'Authorization': AUTHORIZATION, + }, + }; + + const req = https.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => (data += chunk)); + res.on('end', () => { + try { + resolve({ status: res.statusCode, body: JSON.parse(data) }); + } catch { + resolve({ status: res.statusCode, body: data }); + } + }); + }); + + req.on('error', reject); + req.write(body); + req.end(); + }); +} + +async function main() { + console.log(`开始提交 ${data.length} 条分类数据...\n`); + + for (let i = 0; i < data.length; i++) { + const item = data[i]; + process.stdout.write(`[${i + 1}/${data.length}] ${item.name} (${item.case_type}) ... `); + try { + const result = await post(item, i + 1); + if (result.status >= 200 && result.status < 300) { + console.log(`✓ ${result.status}`); + } else { + console.log(`✗ ${result.status}: ${JSON.stringify(result.body)}`); + } + } catch (err) { + console.log(`✗ 请求失败: ${err.message}`); + } + } + + console.log('\n完成。'); +} + +main(); diff --git a/felo/create-cases.mjs b/felo/create-cases.mjs new file mode 100755 index 0000000..177752e --- /dev/null +++ b/felo/create-cases.mjs @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { execFileSync } from 'child_process'; +import readline from 'readline'; + +const AUTHORIZATION = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyMiJ9.2XLrkX3Fryw4c1Mn3Peqsp8JOMMOu25GP7yxl0gsIG4w9JjfUzlsharGdm8PDusK1fvwMRs5rc1p6LJe0cSg4w'; +const BASE_URL = 'https://bops.felo.ai'; + +const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.avif']); + +// ── 交互输入 ───────────────────────────────────────────────── + +function prompt(question) { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise((resolve) => { + rl.question(question, (answer) => { + rl.close(); + resolve(answer.trim()); + }); + }); +} + +// ── ffmpeg 转换 ────────────────────────────────────────────── + +function convertToWebp(inputPath) { + const tmpFile = path.join(os.tmpdir(), `case_upload_${Date.now()}_${Math.random().toString(36).slice(2)}.webp`); + execFileSync('cwebp', ['-q', '80', '-resize', '800', '0', inputPath, '-o', tmpFile], { stdio: 'pipe' }); + return tmpFile; +} + +// ── 上传图片 ───────────────────────────────────────────────── + +async function uploadFile(filePath) { + const formData = new FormData(); + const fileData = fs.readFileSync(filePath); + const blob = new Blob([fileData], { type: 'image/webp' }); + formData.append('file', blob, path.basename(filePath)); + + const res = await fetch(`${BASE_URL}/api/home/case/upload`, { + method: 'POST', + headers: { 'Authorization': AUTHORIZATION }, + body: formData, + }); + const body = await res.json(); + return { status: res.status, body }; +} + +// ── 创建案例 ───────────────────────────────────────────────── + +async function createCase(caseTypeId, imageUrl, name, sort) { + const res = await fetch(`${BASE_URL}/api/home/case/create`, { + method: 'POST', + headers: { + 'Authorization': AUTHORIZATION, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + case_type_id: caseTypeId, + image_url: imageUrl, + name, + sort: 0, + }), + }); + const body = await res.json(); + return { status: res.status, body }; +} + +// ── 主流程 ─────────────────────────────────────────────────── + +async function main() { + const caseTypeId = await prompt('请输入分类 ID (case_type_id): '); + const input = await prompt('请输入图片目录路径: '); + const absDir = path.resolve(input); + + if (!fs.existsSync(absDir)) { + console.error(`目录不存在: ${absDir}`); + process.exit(1); + } + + const files = fs.readdirSync(absDir) + .filter((f) => IMAGE_EXTS.has(path.extname(f).toLowerCase())) + .sort(); + + if (files.length === 0) { + console.error('目录中未找到图片文件'); + process.exit(1); + } + + console.log(`找到 ${files.length} 张图片,开始处理...\n`); + + const tmpFiles = []; + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const inputPath = path.join(absDir, file); + console.log(`[${i + 1}/${files.length}] ${file}`); + + // 1. 转换 WebP + let tmpPath; + try { + process.stdout.write(' → ffmpeg 转换... '); + tmpPath = convertToWebp(inputPath); + tmpFiles.push(tmpPath); + console.log('✓'); + } catch (err) { + console.log(`✗ 转换失败: ${err.message}`); + continue; + } + + // 2. 上传 + let imageUrl; + try { + process.stdout.write(' → 上传图片... '); + const uploadRes = await uploadFile(tmpPath); + console.log(`✓ HTTP ${uploadRes.status}`); + console.log(' upload res.data:', JSON.stringify(uploadRes.body, null, 2)); + + const body = uploadRes.body; + imageUrl = body?.data?.url ?? body?.data ?? body?.url ?? null; + if (!imageUrl) { + console.log(' ⚠ 无法自动提取 image_url,请确认上方打印的字段结构后修改脚本'); + continue; + } + } catch (err) { + console.log(`✗ 上传失败: ${err.message}`); + continue; + } + + // 3. 创建案例 + try { + process.stdout.write(` → 创建案例 (sort=${i + 1})... `); + const createRes = await createCase(caseTypeId, imageUrl, path.basename(file, path.extname(file)), i + 1); + if (createRes.status >= 200 && createRes.status < 300) { + console.log(`✓ HTTP ${createRes.status}`); + } else { + console.log(`✗ HTTP ${createRes.status}: ${JSON.stringify(createRes.body)}`); + } + } catch (err) { + console.log(`✗ 创建失败: ${err.message}`); + } + + console.log(); + } + + for (const f of tmpFiles) { + try { fs.unlinkSync(f); } catch {} + } + + console.log('完成。'); +} + +main();