apix/lib/prompts-service.ts
Khoa.vo 8741e3b89f
Some checks are pending
CI / build (18.x) (push) Waiting to run
CI / build (20.x) (push) Waiting to run
feat: Initial commit with multi-provider image generation
2026-01-05 13:50:35 +07:00

97 lines
3.2 KiB
TypeScript

import fs from 'fs/promises';
import path from 'path';
import { Prompt, PromptCache } from '@/lib/types';
import { JimmyLvCrawler, YouMindCrawler, ZeroLuCrawler } from '@/lib/crawler';
const DATA_FILE = path.join(process.cwd(), 'data', 'prompts.json');
export async function getPrompts(): Promise<PromptCache> {
try {
const fileContent = await fs.readFile(DATA_FILE, 'utf-8');
return JSON.parse(fileContent);
} catch (e) {
return { prompts: [], last_updated: null, categories: {}, total_count: 0, sources: [] };
}
}
export async function syncPromptsService(): Promise<{ success: boolean, count: number, added: number }> {
console.log("[SyncService] Starting sync...");
// 1. Crawl all sources
const jimmyCrawler = new JimmyLvCrawler();
const youMindCrawler = new YouMindCrawler();
const zeroLuCrawler = new ZeroLuCrawler();
const [jimmyPrompts, youMindPrompts, zeroLuPrompts] = await Promise.all([
jimmyCrawler.crawl(),
youMindCrawler.crawl(),
zeroLuCrawler.crawl()
]);
const crawledPrompts = [...jimmyPrompts, ...youMindPrompts, ...zeroLuPrompts];
console.log(`[SyncService] Total crawled ${crawledPrompts.length} prompts (Jimmy: ${jimmyPrompts.length}, YouMind: ${youMindPrompts.length}, ZeroLu: ${zeroLuPrompts.length}).`);
// 2. Read existing
const cache = await getPrompts();
const existingPrompts = cache.prompts || [];
// 3. Merge
let addedCount = 0;
const now = Date.now();
const finalPrompts: Prompt[] = [];
const existingMap = new Map<string, Prompt>();
existingPrompts.forEach(p => {
if (p.source_url) {
existingMap.set(p.source_url, p);
} else {
finalPrompts.push(p);
}
});
crawledPrompts.forEach(newP => {
const existing = existingMap.get(newP.source_url);
if (existing) {
finalPrompts.push({
...newP,
id: existing.id,
images: existing.images,
createdAt: existing.createdAt || (existing.published ? new Date(existing.published).getTime() : undefined),
useCount: existing.useCount,
lastUsedAt: existing.lastUsedAt
});
existingMap.delete(newP.source_url);
} else {
addedCount++;
finalPrompts.push({
...newP,
createdAt: now,
useCount: 0
});
}
});
// Add remaining existing
existingMap.forEach(p => finalPrompts.push(p));
// Re-ID
finalPrompts.forEach((p, i) => p.id = i + 1);
// Meta
const categories: Record<string, string[]> = {
"style": Array.from(new Set(finalPrompts.map(p => p.category)))
};
const newCache: PromptCache = {
last_updated: new Date(now).toISOString(),
lastSync: now,
categories,
total_count: finalPrompts.length,
sources: Array.from(new Set(finalPrompts.map(p => p.source))),
prompts: finalPrompts
};
await fs.writeFile(DATA_FILE, JSON.stringify(newCache, null, 2), 'utf-8');
return { success: true, count: finalPrompts.length, added: addedCount };
}