update
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { PromptGenerator, type PromptGeneratorOptions } from "./prompt-geneator.ts";
|
||||
import { PromptGenerator, type PromptGeneratorOptions } from "./module/prompt-geneator.ts";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { Prompt } from "./prompt-perfect.ts";
|
||||
import { Prompt } from "./module/prompt-perfect.ts";
|
||||
import { customAlphabet } from "nanoid";
|
||||
|
||||
const letter = 'abcdefghijklmnopqrstuvwxyz'
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { useConfig } from '@kevisual/use-config';
|
||||
export const config = useConfig();
|
||||
|
||||
export const queueConfig = {
|
||||
name: 'image-generation-queue',
|
||||
concurrency: parseInt(config.QUEUE_CONCURRENCY || '1'),
|
||||
maxFailed: parseInt(config.QUEUE_MAX_FAILED || '2'),
|
||||
};
|
||||
|
||||
export const redisConfig = {
|
||||
host: config.REDIS_HOST || 'localhost',
|
||||
port: parseInt(config.REDIS_PORT || '6379'),
|
||||
|
||||
@@ -7,4 +7,6 @@ export const logger = new Logger({
|
||||
|
||||
export const feishuNotifier = new FeishuNotifier({
|
||||
webhook: config.FEISHU_NOTIFY_WEBHOOK_URL || '',
|
||||
});
|
||||
});
|
||||
|
||||
export const notify = feishuNotifier;
|
||||
343
prompts/src/module/sentence-generator.ts
Normal file
343
prompts/src/module/sentence-generator.ts
Normal file
@@ -0,0 +1,343 @@
|
||||
import { randomInt as random } from 'es-toolkit';
|
||||
|
||||
// ============================================
|
||||
// 关键词库定义
|
||||
// ============================================
|
||||
|
||||
// 主主题关键词(带子主题)
|
||||
export const MAIN_THEMES: Record<string, string[]> = {
|
||||
人生: ['旅程', '剧本', '角色', '舞台', '起点', '终点', '过程', '意义', '价值', '方向'],
|
||||
时间: ['过去', '现在', '未来', '瞬间', '永恒', '季节', '昼夜', '钟表', '沙漏', '流水'],
|
||||
成长: ['伤痕', '蜕变', '破茧', '跌倒', '镜子', '旧我', '觉醒', '代价', '忍耐', '成熟'],
|
||||
孤独: ['独处', '夜晚', '人群疏离', '无人理解', '自我对话', '清醒者', '边缘人', '静默', '影子'],
|
||||
选择: ['路口', '岔路', '方向', '代价', '后悔', '坚定', '承担', '割舍', '决定', '权衡'],
|
||||
自由: ['翅膀', '天空', '边界', '束缚', '解脱', '随心所欲', '主宰', '释放', '翱翔', '远方'],
|
||||
痛苦: ['伤疤', '火焰', '锤炼', '熔炉', '泪水', '黑夜', '黎明', '破碎', '重建', '结晶'],
|
||||
爱: ['温暖', '刺痛', '放手', '成全', '燃烧', '熄灭', '靠近', '远离', '心跳', '温柔'],
|
||||
死亡: ['终结', '新生', '虚无', '告别', '离开', '遗忘', '铭记', '永恒', '轮回', '起点'],
|
||||
真相: ['本质', '表象', '谎言', '虚假', '真实', '假象', '核心', '事实', '表面', '深层'],
|
||||
欲望: ['克制', '追求', '放下', '贪念', '满足', '渴望', '执念', '放手', '贪婪', '节制'],
|
||||
坚持: ['执着', '韧劲', '突破', '恒心', '毅力', '不放弃', '继续', '前行', '攀登', '坚守'],
|
||||
放下: ['释然', '接纳', '告别', '轻松', '释怀', '松手', '转身', '遗忘', '宽恕', '和解'],
|
||||
觉醒: ['开悟', '看见', '醒来', '明白', '领悟', '理解', '懂得', '发现', '觉悟', '洞悉'],
|
||||
平凡: ['普通', '真实', '踏实', '日常', '简单', '朴素', '寻常', '平淡', '自然', '安宁'],
|
||||
意义: ['价值', '方向', '目的', '追求', '使命', '理由', '答案', '方向感', '存在感', '理由'],
|
||||
沉默: ['安静', '力量', '倾听', '静默', '无言', '无声', '寂静', '宁静', '平和', '镇定'],
|
||||
命运: ['注定', '改变', '巧合', '安排', '转折', '际遇', '天意', '偶然', '必然', '定数'],
|
||||
人性: ['善恶', '复杂', '真实', '面具', '伪装', '本质', '脆弱', '坚强', '自私', '无私'],
|
||||
世界: ['规则', '真相', '边界', '运转', '秩序', '法则', '规律', '现实', '规则', '边界']
|
||||
};
|
||||
|
||||
// 通用意象关键词
|
||||
export const IMAGES = [
|
||||
'光', '暗', '路', '门', '风', '火', '水', '树', '种子', '根', '山', '海', '雾', '雨',
|
||||
'雪', '霜', '雷', '电', '云', '星', '月', '日', '花', '草', '叶', '果实', '飞鸟',
|
||||
'游鱼', '蝴蝶', '灯塔', '镜子', '书本', '信', '钟', '钥匙', '锁', '刀', '剑', '帆',
|
||||
'船', '桥', '窗', '塔', '影', '回声', '脚步', '呼吸', '尘埃', '烟雾', '碎片'
|
||||
];
|
||||
|
||||
// 动词关键词
|
||||
export const VERBS = [
|
||||
'燃烧', '沉淀', '穿越', '凝视', '听见', '醒来', '破碎', '重建', '等待', '放手',
|
||||
'坠落', '升起', '沉默', '挣扎', '回归', '追随', '停留', '遗忘', '铭记', '看清',
|
||||
'承认', '接受', '抵抗', '屈服', '生长', '凋零', '绽放', '枯萎', '流动', '凝固',
|
||||
'感受', '体会', '领悟', '明白', '理解', '懂得', '发现', '寻找', '追求', '失去',
|
||||
'获得', '前行', '停留', '奔跑', '行走', '停留', '守望', '等待', '出发', '到达'
|
||||
];
|
||||
|
||||
// 句式模板(带占位符)
|
||||
export const TEMPLATES = [
|
||||
// 对比型
|
||||
{ type: '对比', text: '真正的{主},不是{子},而是{子}。' },
|
||||
{ type: '对比', text: '越{子},越{子}。' },
|
||||
{ type: '对比', text: '{主}与{子},从来不是对立的。' },
|
||||
{ type: '对比', text: '不是{主}太远,而是{子}太近。' },
|
||||
{ type: '对比', text: '{主}从不说谎,{子}却常常欺骗。' },
|
||||
{ type: '对比', text: '不怕{主},只怕{动}。' },
|
||||
{ type: '对比', text: '{主}从不是{子},而是{意义}。' },
|
||||
{ type: '对比', text: '最高级的{主},是{动}{意义}。' },
|
||||
{ type: '对比', text: '最深的{主},往往{动}。' },
|
||||
{ type: '对比', text: '{主}的尽头,是{动}{意义}。' },
|
||||
|
||||
// 因果型
|
||||
{ type: '因果', text: '当你{动}{主},你才懂得{意义}。' },
|
||||
{ type: '因果', text: '只有{动}过{主}的人,才明白{意义}。' },
|
||||
{ type: '因果', text: '{主}教会我,{动}才是{意义}。' },
|
||||
{ type: '因果', text: '经历过{主},才知道{意义}的可贵。' },
|
||||
{ type: '因果', text: '{主}的代价,是{动}{意义}。' },
|
||||
{ type: '因果', text: '因为{动}{主},所以{动}{意义}。' },
|
||||
{ type: '因果', text: '{主}让我明白,{动}才是真正的{意义}。' },
|
||||
|
||||
// 隐喻型
|
||||
{ type: '隐喻', text: '{主}像{意象},终将{动}。' },
|
||||
{ type: '隐喻', text: '{意象}不会说话,却教会了我{动}{意义}。' },
|
||||
{ type: '隐喻', text: '{主}是{意象}的镜子,照见{意义}。' },
|
||||
{ type: '隐喻', text: '在{主}中,我看见了{意义}。' },
|
||||
{ type: '隐喻', text: '{主}如同{意象},{动}才是{意义}。' },
|
||||
{ type: '隐喻', text: '{意象}见证了{主}的{子}。' },
|
||||
{ type: '隐喻', text: '{主}是{意象},需要{动}才能明白{意义}。' },
|
||||
{ type: '隐喻', text: '当{意象}遇见{主},{动}才是唯一的{意义}。' },
|
||||
];
|
||||
|
||||
// ============================================
|
||||
// 生成器类
|
||||
// ============================================
|
||||
|
||||
export interface SentenceGeneratorOptions {
|
||||
/** 生成数量 */
|
||||
count?: number;
|
||||
/** 使用的模板类型 */
|
||||
templateTypes?: ('对比' | '因果' | '隐喻' | '判断')[];
|
||||
/** 使用的主主题 */
|
||||
mainThemes?: string[];
|
||||
/** 输出格式 */
|
||||
outputFormat?: 'array' | 'text' | 'json';
|
||||
/** 是否包含标签 */
|
||||
withTags?: boolean;
|
||||
}
|
||||
|
||||
export interface GeneratedSentence {
|
||||
text: string;
|
||||
theme: string;
|
||||
template: string;
|
||||
templateType: string;
|
||||
tags: string[];
|
||||
index: number;
|
||||
}
|
||||
|
||||
export class SentenceGenerator {
|
||||
private options: Required<SentenceGeneratorOptions>;
|
||||
private themeUsage: Map<string, number> = new Map();
|
||||
private generatedCount: number = 0;
|
||||
|
||||
constructor(options: SentenceGeneratorOptions = {}) {
|
||||
this.options = {
|
||||
count: options.count ?? 100,
|
||||
templateTypes: options.templateTypes ?? ['对比', '因果', '隐喻'],
|
||||
mainThemes: options.mainThemes ?? Object.keys(MAIN_THEMES),
|
||||
outputFormat: options.outputFormat ?? 'array',
|
||||
withTags: options.withTags ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全获取随机数组元素
|
||||
*/
|
||||
private pickRandom<T>(arr: T[]): T | undefined {
|
||||
if (arr.length === 0) return undefined;
|
||||
return arr[random(0, arr.length - 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用主题(考虑均衡使用)
|
||||
*/
|
||||
private pickTheme(): string {
|
||||
const { mainThemes, count } = this.options;
|
||||
const threshold = count / mainThemes.length * 1.5;
|
||||
|
||||
const availableThemes = mainThemes.filter(theme => {
|
||||
const usage = this.themeUsage.get(theme) || 0;
|
||||
return usage < threshold;
|
||||
});
|
||||
|
||||
const themes = availableThemes.length > 0 ? availableThemes : mainThemes;
|
||||
const theme = this.pickRandom(themes) || mainThemes[0];
|
||||
|
||||
this.themeUsage.set(theme, (this.themeUsage.get(theme) || 0) + 1);
|
||||
return theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充模板
|
||||
*/
|
||||
private fillTemplate(template: string, theme: string, subTheme: string, image: string, verb: string, meaning: string): string {
|
||||
return template
|
||||
.replace(/\{主\}/g, theme)
|
||||
.replace(/\{子\}/g, subTheme)
|
||||
.replace(/\{意象\}/g, image)
|
||||
.replace(/\{动\}/g, verb)
|
||||
.replace(/\{意义\}/g, meaning);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成单条句子
|
||||
*/
|
||||
private generateOne(): GeneratedSentence {
|
||||
// 选择主主题
|
||||
const theme = this.pickTheme();
|
||||
|
||||
// 选择子主题
|
||||
const subTheme = this.pickRandom(MAIN_THEMES[theme]) || theme;
|
||||
|
||||
// 选择其他关键词
|
||||
const image = this.pickRandom(IMAGES) || '';
|
||||
const verb = this.pickRandom(VERBS) || '';
|
||||
const meaning = this.pickRandom(MAIN_THEMES['意义']) || '意义';
|
||||
|
||||
// 选择模板
|
||||
const availableTemplates = TEMPLATES.filter(t =>
|
||||
this.options.templateTypes.includes(t.type as any)
|
||||
);
|
||||
const templateObj = this.pickRandom(availableTemplates);
|
||||
if (!templateObj) {
|
||||
throw new Error('没有可用的模板');
|
||||
}
|
||||
|
||||
// 填充模板
|
||||
let text = this.fillTemplate(templateObj.text, theme, subTheme, image, verb, meaning);
|
||||
|
||||
// 清理多余空格
|
||||
text = text.replace(/\s+/g, ' ').trim();
|
||||
|
||||
this.generatedCount++;
|
||||
|
||||
return {
|
||||
text,
|
||||
theme,
|
||||
template: templateObj.text,
|
||||
templateType: templateObj.type,
|
||||
tags: [theme, templateObj.type, subTheme, image, verb],
|
||||
index: this.generatedCount
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成句子
|
||||
*/
|
||||
generate(): GeneratedSentence[] {
|
||||
const results: GeneratedSentence[] = [];
|
||||
const maxAttempts = this.options.count * 10;
|
||||
let attempts = 0;
|
||||
|
||||
while (results.length < this.options.count && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
const sentence = this.generateOne();
|
||||
|
||||
// 去重
|
||||
const isDuplicate = results.some(s => s.text === sentence.text);
|
||||
if (!isDuplicate) {
|
||||
results.push(sentence);
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length < this.options.count) {
|
||||
console.warn(`只生成了 ${results.length} 条句子,达到最大尝试次数`);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并输出
|
||||
*/
|
||||
generateAndOutput(): string | GeneratedSentence[] {
|
||||
return this.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为CSV
|
||||
*/
|
||||
exportToCSV(): string {
|
||||
const results = this.generate();
|
||||
const headers = ['序号', '句子', '主题', '模板类型', '标签'];
|
||||
const rows = results.map(s => [
|
||||
s.index.toString(),
|
||||
`"${s.text}"`,
|
||||
s.theme,
|
||||
s.templateType,
|
||||
s.tags.join(';')
|
||||
]);
|
||||
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为JSON
|
||||
*/
|
||||
exportToJSON(): string {
|
||||
const results = this.generate();
|
||||
return JSON.stringify({
|
||||
meta: {
|
||||
total: results.length,
|
||||
generatedAt: new Date().toISOString(),
|
||||
generator: 'sentence-generator.ts',
|
||||
version: '1.0'
|
||||
},
|
||||
sentences: results
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): Record<string, number> {
|
||||
return Object.fromEntries(this.themeUsage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
reset(): void {
|
||||
this.generatedCount = 0;
|
||||
this.themeUsage.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 便捷函数
|
||||
// ============================================
|
||||
|
||||
export function generatePhilosophicalSentences(
|
||||
count: number = 100,
|
||||
options: Partial<SentenceGeneratorOptions> = {}
|
||||
): GeneratedSentence[] {
|
||||
const generator = new SentenceGenerator({ count, ...options });
|
||||
return generator.generate();
|
||||
}
|
||||
|
||||
export function generateByTheme(theme: string, count: number = 20): GeneratedSentence[] {
|
||||
const generator = new SentenceGenerator({ count, mainThemes: [theme] });
|
||||
return generator.generate();
|
||||
}
|
||||
|
||||
export function generateByTemplate(
|
||||
templateType: '对比' | '因果' | '隐喻' | '判断',
|
||||
count: number = 50
|
||||
): GeneratedSentence[] {
|
||||
const generator = new SentenceGenerator({ count, templateTypes: [templateType] });
|
||||
return generator.generate();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CLI入口
|
||||
// ============================================
|
||||
|
||||
const isMainModule = process.argv[1]?.includes('sentence-generator');
|
||||
|
||||
if (isMainModule) {
|
||||
const args = process.argv.slice(2);
|
||||
const count = parseInt(args[0]) || 100;
|
||||
const format = (args[1] as 'text' | 'json' | 'csv') || 'text';
|
||||
|
||||
console.log(`生成 ${count} 条哲理句...\n`);
|
||||
|
||||
const generator = new SentenceGenerator({
|
||||
count,
|
||||
templateTypes: ['对比', '因果', '隐喻']
|
||||
});
|
||||
|
||||
if (format === 'csv') {
|
||||
console.log(generator.exportToCSV());
|
||||
} else if (format === 'json') {
|
||||
console.log(generator.exportToJSON());
|
||||
} else {
|
||||
const sentences = generator.generate();
|
||||
sentences.forEach(s => {
|
||||
console.log(`${s.index}. ${s.text} [${s.theme}]`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n统计信息:');
|
||||
console.log(generator.getStats());
|
||||
}
|
||||
|
||||
export default SentenceGenerator;
|
||||
56
prompts/src/module/sentence-image.ts
Normal file
56
prompts/src/module/sentence-image.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
import { Prompt } from "./prompt-perfect.ts"
|
||||
const exampleData = {
|
||||
"text": "选择像锁,终将奔跑。",
|
||||
"theme": "选择",
|
||||
"template": "{主}像{意象},终将{动}。",
|
||||
"templateType": "隐喻",
|
||||
"tags": [
|
||||
"选择",
|
||||
"隐喻",
|
||||
"后悔",
|
||||
"锁",
|
||||
"奔跑"
|
||||
],
|
||||
"index": 7,
|
||||
"optimized": "选择如秤,终需掂量。", // 要展示的句子
|
||||
}
|
||||
const perfect2 = `你是一个专业的海报设计师和文案策划。请根据以下句子数据,设计一张富有启发性的海报:
|
||||
|
||||
**句子信息**:
|
||||
- 主题:{theme}
|
||||
- 模板:{template}
|
||||
- 模板类型:{templateType}
|
||||
- 标签:{tags}
|
||||
- 待优化句子:{text}
|
||||
- 推荐展示文案:{optimized}
|
||||
|
||||
**设计要求**:
|
||||
|
||||
1. **文案优化**
|
||||
- 以 {optimized} 为基础,进一步精炼成适合海报展示的短句(8-15字)
|
||||
- 语言要有力量感,能引起共鸣
|
||||
- 可运用比喻、排比等修辞手法增强表现力
|
||||
|
||||
2. **意象选择**
|
||||
- 根据 {tags} 和主题,选择1-2个视觉意象
|
||||
- 意象要能准确传达句子的情感和哲理
|
||||
- 推荐风格:温暖励志/清新自然/深沉思考/极简有力
|
||||
|
||||
3. **视觉风格**
|
||||
- 现代简约,有质感
|
||||
- 文字与画面和谐统一
|
||||
- 适合社交媒体传播
|
||||
|
||||
**输出格式**:
|
||||
海报短句:[精炼后的短句]
|
||||
视觉意象:[1-2个核心意象]
|
||||
设计风格:[推荐风格]
|
||||
设计说明:[50字以内的设计思路]
|
||||
|
||||
请直接输出以上内容,不要有其他解释。`
|
||||
export class SentenceImage extends Prompt {
|
||||
constructor() {
|
||||
super({ perfectPrompt: perfect2 });
|
||||
}
|
||||
}
|
||||
396
prompts/src/module/sentence-perfect.ts
Normal file
396
prompts/src/module/sentence-perfect.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
import { config } from './config.ts';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { Kevisual } from '@kevisual/ai';
|
||||
|
||||
// ============================================
|
||||
// AI优化提示词配置
|
||||
// ============================================
|
||||
|
||||
// 通用哲理句优化提示词
|
||||
export const PERFECT_PROMPT = `你是一个深谙人生哲理的作家。请将以下不完整的哲理句补充完整,使其成为一句发人深省的哲理名言。
|
||||
|
||||
要求:
|
||||
1. 保持原句的核心主题和结构
|
||||
2. 语言要简洁有力,富有哲理深度
|
||||
3. 让人读后有所思考和共鸣
|
||||
4. 符合中文表达习惯
|
||||
5. 字数控制在20-50字之间
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
请直接输出优化后的句子,不需要任何解释。`;
|
||||
|
||||
// 不同主题的专项优化提示词
|
||||
export const THEME_PROMPTS: Record<string, string> = {
|
||||
人生: `你是一个智慧的导师。请将以下关于人生的句子优化成一句深刻的人生感悟:
|
||||
|
||||
要求:
|
||||
- 语言平实却蕴含智慧
|
||||
- 让人读后有所触动
|
||||
- 适合作为人生格言
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
时间: `你是一个洞察时光的智者。请将以下关于时间的句子优化:
|
||||
|
||||
要求:
|
||||
- 体现时间的珍贵与无情
|
||||
- 让人珍惜当下
|
||||
- 富有诗意但不晦涩
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
成长: `你是一个经历过蜕变的智者。请将以下关于成长的句子优化:
|
||||
|
||||
要求:
|
||||
- 体现成长的代价与收获
|
||||
- 让人对成长有新的理解
|
||||
- 有力量感但不鸡汤
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
孤独: `你是一个深刻理解孤独的人。请将以下关于孤独的句子优化:
|
||||
|
||||
要求:
|
||||
- 准确描述孤独的本质
|
||||
- 不是消极,而是清醒
|
||||
- 让人学会与孤独和解
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
爱: `你是一个懂得爱的人。请将以下关于爱的句子优化:
|
||||
|
||||
要求:
|
||||
- 体现爱的真谛
|
||||
- 不是空洞的情话
|
||||
- 让人对爱有新的认识
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
选择: `你是一个善于抉择的人。请将以下关于选择的句子优化:
|
||||
|
||||
要求:
|
||||
- 体现选择的重量
|
||||
- 不是教条,而是智慧
|
||||
- 让人更慎重地面对选择
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
自由: `你是一个追求自由的人。请将以下关于自由的句子优化:
|
||||
|
||||
要求:
|
||||
- 准确描述自由的真谛
|
||||
- 让人理解自由的代价
|
||||
- 有深度但不晦涩
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
痛苦: `你是一个从痛苦中走过来的人。请将以下关于痛苦的句子优化:
|
||||
|
||||
要求:
|
||||
- 不是宣泄负面情绪
|
||||
- 而是让人从痛苦中获得力量
|
||||
- 转化痛苦为成长
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// AI客户端接口
|
||||
// ============================================
|
||||
|
||||
export interface PerfectOptions {
|
||||
/** Kevisual AI 实例 */
|
||||
ai?: Kevisual;
|
||||
/** 使用的提示词模板 */
|
||||
promptTemplate?: string;
|
||||
/** 并发请求数 */
|
||||
concurrency?: number;
|
||||
/** 重试次数 */
|
||||
retryTimes?: number;
|
||||
/** 成功后回调 */
|
||||
onSuccess?: (item: PerfectItem) => void;
|
||||
/** 失败后回调 */
|
||||
onError?: (item: PerfectError) => void;
|
||||
/** 进度回调 */
|
||||
onProgress?: (progress: ProgressInfo) => void;
|
||||
}
|
||||
|
||||
export interface PerfectItem {
|
||||
index: number;
|
||||
text: string;
|
||||
template: string;
|
||||
templateType: string;
|
||||
optimized: string;
|
||||
prompt?: string;
|
||||
theme: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface PerfectError {
|
||||
index: number;
|
||||
original: string;
|
||||
error: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface ProgressInfo {
|
||||
current: number;
|
||||
total: number;
|
||||
percentage: number;
|
||||
successCount: number;
|
||||
errorCount: number;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 默认AI实例
|
||||
// ============================================
|
||||
|
||||
function createDefaultAI(): Kevisual {
|
||||
return new Kevisual({
|
||||
apiKey: config.KEVISUAL_NEW_API_KEY || '',
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 句子优化器类
|
||||
// ============================================
|
||||
|
||||
export class SentencePerfect {
|
||||
private options: Required<PerfectOptions>;
|
||||
private ai: Kevisual;
|
||||
|
||||
constructor(options: PerfectOptions = {}) {
|
||||
this.ai = options.ai || createDefaultAI();
|
||||
|
||||
this.options = {
|
||||
ai: this.ai,
|
||||
promptTemplate: options.promptTemplate || PERFECT_PROMPT,
|
||||
concurrency: options.concurrency ?? 5,
|
||||
retryTimes: options.retryTimes ?? 3,
|
||||
onSuccess: options.onSuccess || (() => { }),
|
||||
onError: options.onError || (() => { }),
|
||||
onProgress: options.onProgress || (() => { })
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题对应的提示词
|
||||
*/
|
||||
private getThemePrompt(theme: string, sentence: string): string {
|
||||
const themePrompt = THEME_PROMPTS[theme];
|
||||
if (themePrompt) {
|
||||
return themePrompt.replace('{{sentence}}', sentence);
|
||||
}
|
||||
return this.options.promptTemplate.replace('{{sentence}}', sentence);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化单条句子(带重试)
|
||||
*/
|
||||
async perfectOne(item: PerfectItem): Promise<PerfectItem> {
|
||||
const prompt = this.getThemePrompt(item.theme, item.text);
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let i = 0; i < this.options.retryTimes; i++) {
|
||||
try {
|
||||
await this.ai.chat([{ role: 'user', content: prompt }]);
|
||||
let optimized = this.ai.responseText || '';
|
||||
optimized = optimized.trim().replace(/^["']|["']$/g, '');
|
||||
|
||||
const result: PerfectItem = {
|
||||
...item,
|
||||
prompt: prompt,
|
||||
optimized: optimized || item.text
|
||||
};
|
||||
|
||||
this.options.onSuccess(result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
const error: PerfectError = {
|
||||
index: item.index,
|
||||
original: item.text,
|
||||
error: lastError?.message || '未知错误',
|
||||
theme: item.theme
|
||||
};
|
||||
|
||||
this.options.onError(error);
|
||||
return {
|
||||
...item,
|
||||
optimized: item.text
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量优化句子
|
||||
*/
|
||||
async perfectBatch(
|
||||
items: PerfectItem[]
|
||||
): Promise<PerfectItem[]> {
|
||||
const total = items.length;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
const results: PerfectItem[] = [];
|
||||
|
||||
const batchSize = this.options.concurrency;
|
||||
|
||||
for (let i = 0; i < items.length; i += batchSize) {
|
||||
const batch = items.slice(i, i + batchSize);
|
||||
|
||||
const promises = batch.map(async (item) => {
|
||||
try {
|
||||
const result = await this.perfectOne(item);
|
||||
results.push(result);
|
||||
successCount++;
|
||||
} catch {
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
const current = Math.min(i + batchSize, total);
|
||||
const percentage = Math.round((current / total) * 100);
|
||||
|
||||
this.options.onProgress({
|
||||
current,
|
||||
total,
|
||||
percentage,
|
||||
successCount,
|
||||
errorCount
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
if (i + batchSize < items.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件加载句子并优化
|
||||
*/
|
||||
async perfectFromFile(
|
||||
inputPath: string,
|
||||
outputPath: string
|
||||
): Promise<{ success: number; failed: number; results: any[] }> {
|
||||
const data = JSON.parse(readFileSync(inputPath, 'utf-8'));
|
||||
const sentences = data.sentences || data;
|
||||
|
||||
const items: PerfectItem[] = sentences.map((s: any, i: number) => ({
|
||||
index: s.index || i + 1,
|
||||
text: s.text || s,
|
||||
template: s.template || '',
|
||||
templateType: s.templateType || '',
|
||||
theme: s.theme || '人生',
|
||||
tags: s.tags || [],
|
||||
optimized: ''
|
||||
}));
|
||||
|
||||
const results = await this.perfectBatch(items);
|
||||
|
||||
// 合并原始数据与优化结果
|
||||
const mergedResults = results.map((r, i) => {
|
||||
return {
|
||||
index: r.index,
|
||||
text: r.text,
|
||||
template: r.template,
|
||||
templateType: r.templateType,
|
||||
theme: r.theme,
|
||||
tags: r.tags,
|
||||
optimized: r.optimized
|
||||
};
|
||||
});
|
||||
|
||||
const successResults = results.filter(r => r.optimized !== r.text);
|
||||
const failedResults = results.filter(r => r.optimized === r.text);
|
||||
|
||||
const output = {
|
||||
meta: {
|
||||
total: results.length,
|
||||
success: successResults.length,
|
||||
failed: failedResults.length,
|
||||
optimizedAt: new Date().toISOString()
|
||||
},
|
||||
sentences: mergedResults
|
||||
};
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify(output, null, 2), 'utf-8');
|
||||
|
||||
return {
|
||||
success: successResults.length,
|
||||
failed: failedResults.length,
|
||||
results: mergedResults
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置AI实例
|
||||
*/
|
||||
setAI(ai: Kevisual): void {
|
||||
this.ai = ai;
|
||||
this.options.ai = ai;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 便捷函数
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 快速优化单条句子
|
||||
*/
|
||||
export async function quickPerfect(
|
||||
sentence: string,
|
||||
theme: string = '人生'
|
||||
): Promise<string> {
|
||||
const perfect = new SentencePerfect();
|
||||
const item: PerfectItem = {
|
||||
index: 0,
|
||||
text: sentence,
|
||||
template: '',
|
||||
templateType: '',
|
||||
theme,
|
||||
tags: [],
|
||||
optimized: ''
|
||||
};
|
||||
const result = await perfect.perfectOne(item);
|
||||
return result.optimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JSON文件优化句子
|
||||
*/
|
||||
export async function perfectFromJSON(
|
||||
inputPath: string,
|
||||
outputPath: string,
|
||||
options: Partial<PerfectOptions> = {}
|
||||
): Promise<{ success: number; failed: number }> {
|
||||
const perfect = new SentencePerfect(options);
|
||||
const result = await perfect.perfectFromFile(inputPath, outputPath);
|
||||
return { success: result.success, failed: result.failed };
|
||||
}
|
||||
|
||||
export default SentencePerfect;
|
||||
74
prompts/src/routes/create-sentence.ts
Normal file
74
prompts/src/routes/create-sentence.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { app, ossService, pbService, redis } from '@/app.ts'
|
||||
import { addImageGenerateJob } from '@/task/image-creator.job.ts';
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
type SentenceItem = {
|
||||
text: string;
|
||||
theme: string;
|
||||
template: string;
|
||||
templateType: string;
|
||||
tags: string[];
|
||||
index: number;
|
||||
prompt?: string;
|
||||
optimized?: string;
|
||||
}
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'create-sentence-list',
|
||||
description: '导入句子列表',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const data: SentenceItem[] = ctx.query?.data || [];
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
ctx.throw(400, 'Invalid input data');
|
||||
}
|
||||
const BATCH_SIZE = 50;
|
||||
for (let i = 0; i < data.length; i += BATCH_SIZE) {
|
||||
const chunk = data.slice(i, i + BATCH_SIZE);
|
||||
const batch = pbService.client.createBatch();
|
||||
for (const item of chunk) {
|
||||
batch.collection(pbService.collectionName).create({
|
||||
text: '句子生成海报',
|
||||
summary: item.optimized,
|
||||
description: '',
|
||||
status: '创建',
|
||||
tags: ['sentence', ...item.tags],
|
||||
data: {
|
||||
sentence: item
|
||||
}
|
||||
});
|
||||
}
|
||||
await batch.send();
|
||||
console.log(`Processed batch ${Math.floor(i / BATCH_SIZE) + 1}: ${chunk.length} items.`);
|
||||
await sleep(200); // 避免短时间内添加过多请求
|
||||
}
|
||||
ctx.body = { message: `Imported ${data.length} sentences.` };
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'fix-sentences',
|
||||
middleware: ['auth'],
|
||||
description: '修正句子条目的 summary 字段'
|
||||
}).define(async () => {
|
||||
const list = await pbService.collection.getFullList({
|
||||
filter: `tags~"sentence"`,
|
||||
})
|
||||
console.log(`Fetched ${list.length} sentence items from PocketBase.`);
|
||||
const BATCH_SIZE = 50;
|
||||
|
||||
for (let i = 0; i < list.length; i += BATCH_SIZE) {
|
||||
const chunk = list.slice(i, i + BATCH_SIZE);
|
||||
const batch = pbService.client.createBatch();
|
||||
for (const item of chunk) {
|
||||
const sentenceData = item.data?.sentence || {};
|
||||
if (!sentenceData.optimized) continue;
|
||||
batch.collection(pbService.collectionName).update(item.id, {
|
||||
summary: sentenceData.optimized,
|
||||
});
|
||||
}
|
||||
await batch.send();
|
||||
console.log(`Processed batch ${Math.floor(i / BATCH_SIZE) + 1}: ${chunk.length} items.`);
|
||||
}
|
||||
}).addTo(app);
|
||||
@@ -1,15 +1,43 @@
|
||||
import { app, ossService, pbService, redis } from '@/app.ts'
|
||||
import { addImageGenerateJob } from '@/task/image-creator.job.ts';
|
||||
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'create-task',
|
||||
description: '创建图片生成任务,将所有计划中的任务加入图片生成队列',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const status = ctx.status ?? '计划中';
|
||||
const list = await pbService.collection.getFullList({
|
||||
filter: 'status="计划中"',
|
||||
filter: `status="${status}"`,
|
||||
})
|
||||
for (const item of list) {
|
||||
await addImageGenerateJob(item);
|
||||
await sleep(100); // 避免短时间内添加过多任务
|
||||
}
|
||||
console.log(`Added ${list.length} image generate jobs to the queue.`);
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'batch-update-tags',
|
||||
description: '测试 Redis 连接',
|
||||
isDebug: true
|
||||
}).define(async (ctx) => {
|
||||
const list = await pbService.collection.getFullList({
|
||||
filter: `tags="[]"`,
|
||||
})
|
||||
console.log(`Fetched ${list.length} items from PocketBase.`, list[0]);
|
||||
const BATCH_SIZE = 50;
|
||||
for (let i = 0; i < list.length; i += BATCH_SIZE) {
|
||||
const chunk = list.slice(i, i + BATCH_SIZE);
|
||||
const batch = pbService.client.createBatch();
|
||||
for (const item of chunk) {
|
||||
batch.collection(pbService.collectionName).update(item.id, { tags: ["horse"] });
|
||||
}
|
||||
await batch.send();
|
||||
console.log(`Processed batch ${Math.floor(i / BATCH_SIZE) + 1}: ${chunk.length} items.`);
|
||||
}
|
||||
ctx.body = { message: `Processed ${list.length} items.` };
|
||||
}).addTo(app);
|
||||
69
prompts/src/routes/image-update.ts
Normal file
69
prompts/src/routes/image-update.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { pbService, jimengService, ossService, app } from '../index.ts';
|
||||
import type { ImageCollection } from '../services/pb.service.ts';
|
||||
|
||||
// 更新 PB 状态
|
||||
export async function updateItemStatus(
|
||||
itemId: string,
|
||||
status: string,
|
||||
extraData?: Partial<ImageCollection>
|
||||
): Promise<void> {
|
||||
const collection = pbService.getCollection<ImageCollection>(pbService.collectionName);
|
||||
if (extraData) {
|
||||
const existingItem = await pbService.collection.getOne(itemId);
|
||||
const data = existingItem.data;
|
||||
const existingImages = data?.images || [];
|
||||
const newImages = extraData.data?.images || [];
|
||||
await collection.update(itemId, {
|
||||
status,
|
||||
...extraData,
|
||||
data: {
|
||||
...extraData?.data,
|
||||
...data,
|
||||
images: [...existingImages, ...newImages],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await collection.update(itemId, {
|
||||
status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'image-update',
|
||||
description: '更新所有图片生成任务的状态',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const { itemId, status, extraData } = ctx.query;
|
||||
if (!itemId || !status) {
|
||||
ctx.throw(400, 'Missing itemId or status');
|
||||
}
|
||||
await updateItemStatus(itemId as string, status as string, extraData as Partial<ImageCollection> | undefined);
|
||||
ctx.body = { message: 'Item status updated successfully' };
|
||||
}).addTo(app);
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'image-download',
|
||||
description: '下载所有图片生成任务的图片,并上传到 OSS, 返回图片链接。参数: itemId, imageUrl, index',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const { itemId, imageUrl, index = 0 } = ctx.query;
|
||||
if (!itemId || !imageUrl || index === undefined) {
|
||||
ctx.throw(400, 'Missing itemId, imageUrl or index');
|
||||
}
|
||||
const imageBuffer = await jimengService.downloadImage(imageUrl);
|
||||
const filename = `generated_${itemId}_${index}_${Date.now()}.png`;
|
||||
await ossService.putObject(filename, imageBuffer);
|
||||
const ossUrl = ossService.getLink(filename)
|
||||
console.log(`[ImageDownload] Image uploaded to OSS: ${ossUrl}`);
|
||||
|
||||
const imageData = { type: 'tos' as const, url: ossUrl };
|
||||
await updateItemStatus(itemId as string, 'completed', {
|
||||
data: {
|
||||
images: [imageData],
|
||||
},
|
||||
});
|
||||
ctx.body = { message: 'Image downloaded and uploaded to OSS successfully', ossUrl };
|
||||
}).addTo(app);
|
||||
@@ -1 +1,14 @@
|
||||
import './create-task.ts'
|
||||
import { app } from '../index.ts';
|
||||
|
||||
import './create-task.ts'
|
||||
|
||||
import './image-update.ts'
|
||||
|
||||
import './create-sentence.ts'
|
||||
|
||||
app.route({
|
||||
path: 'auth',
|
||||
id: 'auth'
|
||||
}).define(async (ctx) => {
|
||||
// ctx.body = { message: 'Auth route' };
|
||||
}).addTo(app);
|
||||
@@ -17,13 +17,119 @@ export class PBCore {
|
||||
}
|
||||
}
|
||||
|
||||
async loginAdmin(email: string, password: string) {
|
||||
const authData = await this.client.collection("_superusers").authWithPassword(email, password);
|
||||
async loginByPassword(email: string, password: string, admin: boolean = true) {
|
||||
const collectionName = admin ? '_superusers' : 'users';
|
||||
const authData = await this.client.collection(collectionName).authWithPassword(email, password);
|
||||
this.emitter.emit('login', authData);
|
||||
console.log('PocketBase admin logged in:', authData);
|
||||
return authData;
|
||||
}
|
||||
async login({ username, password, admin = true }: { username: string; password: string, admin?: boolean }) {
|
||||
if (this.client.authStore.isValid) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
await this.loginByPassword(username, password, admin);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('PocketBase login error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* https://pocketbase.io/docs/api-collections/#create-collection
|
||||
*
|
||||
* */
|
||||
async createCollection({ name, type, fields }: CreateCollection) {
|
||||
const collections = await this.client.collections.getFullList();
|
||||
const existing = collections.find(c => c.name === name);
|
||||
if (existing) {
|
||||
const collection = this.client.collection(name);
|
||||
const schema = await this.client.collections.getOne(name);
|
||||
return {
|
||||
schema: schema,
|
||||
existing: true,
|
||||
collection
|
||||
};
|
||||
}
|
||||
const schema = await this.client.collections.create({
|
||||
name,
|
||||
type: type ?? 'base',
|
||||
fields: fields ?? defaultFields,
|
||||
});
|
||||
const collection = this.client.collection(name);
|
||||
return {
|
||||
collection,
|
||||
schema
|
||||
}
|
||||
}
|
||||
}
|
||||
export const defaultFields: CollectionFields[] = [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'summary',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'json',
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'created',
|
||||
type: 'autodate',
|
||||
onCreate: true,
|
||||
},
|
||||
{
|
||||
name: 'updated',
|
||||
type: 'autodate',
|
||||
onCreate: true,
|
||||
onUpdate: true,
|
||||
}
|
||||
]
|
||||
export type CollectionFields = {
|
||||
name: string;
|
||||
type: string;
|
||||
required?: boolean;
|
||||
options?: any;
|
||||
onCreate?: boolean;
|
||||
onUpdate?: boolean;
|
||||
}
|
||||
export type CreateCollectioRule = {
|
||||
listRule?: string;
|
||||
viewRule?: string;
|
||||
/**
|
||||
* createRule: 'id = @request.auth.id',
|
||||
*/
|
||||
createRule?: string;
|
||||
updateRule?: string;
|
||||
deleteRule?: string;
|
||||
}
|
||||
export type CreateCollection = {
|
||||
name: string;
|
||||
type?: 'base' | 'viwer' | 'auth';
|
||||
fields?: CollectionFields[];
|
||||
|
||||
/**
|
||||
* viewer:
|
||||
* viewQuery: 'SELECT id, name from posts',
|
||||
*/
|
||||
viewQuery?: string;
|
||||
} & CreateCollectioRule;
|
||||
|
||||
export class PBService extends PBCore {
|
||||
collectionName = 'images_generation_tasks';
|
||||
@@ -51,10 +157,10 @@ export class PBService extends PBCore {
|
||||
|
||||
const ImageTaskStatus = ['提示词优化中', '计划中', '生成图片中', '图片下载中', '暂停中', '已完成', '失败'] as const;
|
||||
|
||||
type Data = {
|
||||
type Data<T = {}> = {
|
||||
images: { type: 'jimeng' | 'tos', url: string }[];
|
||||
}
|
||||
export type ImageCollection = {
|
||||
} & T
|
||||
export type ImageCollection<T = any> = {
|
||||
id: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
@@ -62,6 +168,6 @@ export type ImageCollection = {
|
||||
tags: any;
|
||||
summary: string;
|
||||
description: string;
|
||||
data: Data;
|
||||
data: Data<T>;
|
||||
status: typeof ImageTaskStatus[number];
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Worker, Queue, Job } from 'bullmq';
|
||||
import { getRedisConnection } from '../module/redis.ts';
|
||||
import { pbService, jimengService, ossService } from '../index.ts';
|
||||
import { pbService, jimengService, ossService, app } from '../index.ts';
|
||||
import type { ImageCollection } from '../services/pb.service.ts';
|
||||
import { updateItemStatus } from '../routes/image-update.ts';
|
||||
import { notify } from '@/module/logger.ts';
|
||||
|
||||
export const IMAGE_CREATOR_JOB = 'image-creator';
|
||||
export const IMAGE_GENERATE_JOB = 'image-generate';
|
||||
@@ -44,34 +46,6 @@ export interface ImageDownloadJobData {
|
||||
index: number;
|
||||
}
|
||||
|
||||
// 更新 PB 状态
|
||||
async function updateItemStatus(
|
||||
itemId: string,
|
||||
status: string,
|
||||
extraData?: Partial<ImageCollection>
|
||||
): Promise<void> {
|
||||
const collection = pbService.getCollection<ImageCollection>(pbService.collectionName);
|
||||
if (extraData) {
|
||||
const existingItem = await pbService.collection.getOne(itemId);
|
||||
const data = existingItem.data;
|
||||
const existingImages = data?.images || [];
|
||||
const newImages = extraData.data?.images || [];
|
||||
await collection.update(itemId, {
|
||||
status,
|
||||
...extraData,
|
||||
data: {
|
||||
...extraData?.data,
|
||||
...data,
|
||||
images: [...existingImages, ...newImages],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await collection.update(itemId, {
|
||||
status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 单独添加生成图片任务
|
||||
@@ -183,6 +157,7 @@ export async function runImageDownloadWorker(): Promise<void> {
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
console.error(`[ImageDownload] Job failed: ${job?.id}, error: ${err.message}`);
|
||||
notify.notify(`[ImageDownload] \nJob failed: ${job?.id}, error: ${err.message}\n Job data: ${JSON.stringify(job?.data)}`);
|
||||
});
|
||||
|
||||
console.log('[ImageDownload] Worker started');
|
||||
@@ -245,6 +220,7 @@ export async function runImageGenerateWorker(): Promise<void> {
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
console.error(`[ImageGenerate] Job failed: ${job?.id}, error: ${err.message}`);
|
||||
notify.notify(`[ImageGenerate] \nJob failed: ${job?.id}, error: ${err.message}\n Job data: ${JSON.stringify(job?.data)}`);
|
||||
});
|
||||
|
||||
console.log('[ImageGenerate] Worker started');
|
||||
|
||||
@@ -2,15 +2,16 @@ import { Worker, Job } from 'bullmq';
|
||||
import { getRedisConnection } from '../module/redis.ts';
|
||||
import { Prompt, pbService, ai } from '../index.ts';
|
||||
import type { ImageCollection } from '../services/pb.service.ts';
|
||||
// 重新导出 Queue,因为需要在 addPerfectPromptJob 中使用
|
||||
import { updateItemStatus } from '../routes/image-update.ts';
|
||||
import { Queue } from 'bullmq';
|
||||
import { notify } from '@/module/logger.ts';
|
||||
|
||||
export const PERFECT_PROMPT_JOB = 'perfect-prompt';
|
||||
|
||||
// 状态常量
|
||||
export const PerfectPromptStatus = {
|
||||
PENDING: '提示词优化中' as const,
|
||||
COMPLETED: '已完成' as const,
|
||||
PLANNING: '计划中' as const,
|
||||
FAILED: '失败' as const,
|
||||
};
|
||||
|
||||
@@ -34,31 +35,6 @@ const DEFAULT_PERFECT_PROMPT = `请你将以下提示词进行完善,使其更
|
||||
7. 使用中文进行描述。
|
||||
`;
|
||||
|
||||
// 更新 PB 状态
|
||||
async function updateItemStatus(
|
||||
itemId: string,
|
||||
status: string,
|
||||
extraData?: Partial<ImageCollection>
|
||||
): Promise<void> {
|
||||
const collection = pbService.getCollection<ImageCollection>(pbService.collectionName);
|
||||
if (extraData) {
|
||||
const existingItem = await pbService.collection.getOne(itemId);
|
||||
const data = existingItem.data;
|
||||
await collection.update(itemId, {
|
||||
status,
|
||||
...extraData,
|
||||
data: {
|
||||
...extraData?.data,
|
||||
...data,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await collection.update(itemId, {
|
||||
status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单独添加优化提示词任务
|
||||
*/
|
||||
@@ -118,7 +94,7 @@ export async function runPerfectPromptWorker(): Promise<void> {
|
||||
console.log(`[PerfectPrompt] Perfect prompt generated for item: ${itemId}`);
|
||||
|
||||
// 更新状态为已完成,并保存优化后的提示词
|
||||
await updateItemStatus(itemId, PerfectPromptStatus.COMPLETED, {
|
||||
await updateItemStatus(itemId, PerfectPromptStatus.PLANNING, {
|
||||
description: perfectText,
|
||||
});
|
||||
|
||||
@@ -146,6 +122,7 @@ export async function runPerfectPromptWorker(): Promise<void> {
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
console.error(`[PerfectPrompt] Job failed: ${job?.id}, error: ${err.message}`);
|
||||
notify.notify(`[PerfectPrompt] \nJob failed: ${job?.id}, error: ${err.message}\n Job data: ${JSON.stringify(job?.data)}`);
|
||||
});
|
||||
|
||||
console.log('[PerfectPrompt] Worker started');
|
||||
|
||||
Reference in New Issue
Block a user