Files

1 line
130 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "bilarius1-tech/proektmap", "files": {"src/app/api/blog/auto-publish/route.ts": {"3e832a87dce00ff53a610489a1b0edf5226ba106": {"t": 1785745598.9472103, "n": 6617, "h": "a2da58cef74bda3a", "c": "import { NextResponse } from \"next/server\";\r\nimport { getDb } from \"@/lib/db/index\";\r\n\r\nfunction translit(text: string): string {\r\n const map: any = { а:\"a\",б:\"b\",в:\"v\",г:\"g\",д:\"d\",е:\"e\",ё:\"yo\",ж:\"zh\",з:\"z\",и:\"i\",й:\"y\",к:\"k\",л:\"l\",м:\"m\",н:\"n\",о:\"o\",п:\"p\",р:\"r\",с:\"s\",т:\"t\",у:\"u\",ф:\"f\",х:\"h\",ц:\"ts\",ч:\"ch\",ш:\"sh\",щ:\"sch\",ъ:\"\",ы:\"y\",ь:\"\",э:\"e\",ю:\"yu\",я:\"ya\" };\r\n return text.toLowerCase().split(\"\").map((c: string) => map[c] || c).join(\"\");\r\n}\r\n\r\nexport async function POST() {\r\n const db = await getDb();\r\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\r\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\r\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\r\n\r\n let key = process.env.DEEPSEEK_API_KEY;\r\n try { const s = await db.siteSettings.findUnique({ where: { id: \"main\" } }); if (s?.deepseekApiKey) key = s.deepseekApiKey; } catch {}\r\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\r\n\r\n const results: any[] = [];\r\n\r\n for (const feed of feeds) {\r\n try {\r\n const res = await fetch(feed.url, {\r\n headers: { \"Accept\": \"application/json\", \"User-Agent\": \"ProektMap/1.0\" },\r\n signal: AbortSignal.timeout(15000),\r\n });\r\n const raw = await res.text();\r\n\r\n let items: { title: string; link: string; description: string; image?: string }[] = [];\r\n if (feed.type === \"json\") {\r\n try {\r\n const json = JSON.parse(raw);\r\n if (json.hits) items = json.hits.map((h: any) => ({ title: h.title || h.story_title || \"\", link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, description: h.story_text || h.comment_text || \"\" }));\r\n else if (json.data?.children) items = json.data.children.map((c: any) => { const d = c.data; let img = d.thumbnail?.startsWith(\"http\") ? d.thumbnail : d.preview?.images?.[0]?.source?.url?.replace(/&amp;/g, \"&\"); return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img }; });\r\n else if (json.items) items = json.items.slice(0, 5).map((i: any) => ({ title: i.full_name || i.name, link: i.html_url || i.url, description: i.description || \"\", image: i.owner?.avatar_url || null }));\r\n } catch {}\r\n } else if (feed.type === \"xml\") {\r\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\r\n items = entries.slice(0, 3).map((e: any) => ({ title: (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim(), link: e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\", description: (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim() }));\r\n }\r\n\r\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\r\n\r\n for (const item of items.slice(0, 2)) {\r\n if (!item.title || item.title.length < 15) continue;\r\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\r\n if (exists) continue;\r\n\r\n // Download image\r\n let coverImage = `https://proektmap.ru/api/og?title=${encodeURIComponent(item.title)}&category=${encodeURIComponent(feed.category)}`;\r\n if (item.image) {\r\n try {\r\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\r\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\r\n const buffer = Buffer.from(await imgRes.arrayBuffer());\r\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\r\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.${ext}`;\r\n const { writeFile, mkdir } = await import(\"fs/promises\");\r\n const { join } = await import(\"path\");\r\n await mkdir(join(process.cwd(), \"public\", \"uploads\"), { recursive: true });\r\n await writeFile(join(process.cwd(), \"public\", \"uploads\", filename), buffer);\r\n coverImage = `/uploads/${filename}`;\r\n }\r\n } catch {}\r\n }\r\n\r\n // AI translates\r\n const prompt = `Ты редактор блога об AI. Напиши заметку на РУССКОМ языке (250-350 слов).\r\n\r\nЗаголовок оригинала: ${item.title}\r\nИсточник: ${item.link}\r\n\r\nВАЖНО:\r\n1. Начни с заголовка строго в формате: \"ЗАГОЛОВОК: русский перевод\"\r\n2. Заголовок должен быть ПОЛНОСТЬЮ на русском, без английских слов\r\n3. Затем сам текст заметки\r\n4. В конце добавь: \"📎 [Источник](${item.link})\"\r\n5. Все ссылки на GitHub делай в формате [название](url)`;\r\n\r\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\r\n method: \"POST\", headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\r\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 1000 }),\r\n });\r\n\r\n const aiData = await aiRes.json();\r\n const fullText = aiData.choices?.[0]?.message?.content;\r\n if (!fullText) continue;\r\n\r\n // Extract title\r\n let title = item.title;\r\n const tm = fullText.match(/ЗАГОЛОВОК:\\s*(.+)/);\r\n if (tm) title = tm[1].trim();\r\n\r\n // Remove title line from content\r\n const content = fullText.replace(/ЗАГОЛОВОК:\\s*.+(\\n|$)/, \"\").trim();\r\n const excerpt = content.replace(/[#*\\[\\]()]/g, \"\").slice(0, 200).replace(/\\n/g, \" \");\r\n\r\n // Generate clean transliterated slug\r\n const clean = title.replace(/[^a-zа-я0-9\\s-]/gi, \"\").trim();\r\n const slug = translit(clean).replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 70);\r\n\r\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\r\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\r\n\r\n await db.blogPost.create({\r\n data: { title, slug, content, excerpt, coverImage, status: \"published\", authorId: admin.id, categoryId: cat.id, tags: \"AI,новости\", aiGenerated: true, aiModel: \"deepseek-chat\", metaTitle: title + \" — Карта роста\", metaDesc: excerpt, publishedAt: new Date() },\r\n });\r\n\r\n results.push({ feed: feed.name, title: title.slice(0, 60), slug, status: \"published\" });\r\n }\r\n\r\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\r\n } catch (e: any) {\r\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\r\n }\r\n }\r\n\r\n return NextResponse.json({ results });\r\n}\r\n"}, "0142b33de5140d1eb6b54545800e5944302dd4c2": {"t": 1785754644.7276707, "n": 6617, "h": "a2da58cef74bda3a", "c": "import { NextResponse } from \"next/server\";\r\nimport { getDb } from \"@/lib/db/index\";\r\n\r\nfunction translit(text: string): string {\r\n const map: any = { а:\"a\",б:\"b\",в:\"v\",г:\"g\",д:\"d\",е:\"e\",ё:\"yo\",ж:\"zh\",з:\"z\",и:\"i\",й:\"y\",к:\"k\",л:\"l\",м:\"m\",н:\"n\",о:\"o\",п:\"p\",р:\"r\",с:\"s\",т:\"t\",у:\"u\",ф:\"f\",х:\"h\",ц:\"ts\",ч:\"ch\",ш:\"sh\",щ:\"sch\",ъ:\"\",ы:\"y\",ь:\"\",э:\"e\",ю:\"yu\",я:\"ya\" };\r\n return text.toLowerCase().split(\"\").map((c: string) => map[c] || c).join(\"\");\r\n}\r\n\r\nexport async function POST() {\r\n const db = await getDb();\r\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\r\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\r\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\r\n\r\n let key = process.env.DEEPSEEK_API_KEY;\r\n try { const s = await db.siteSettings.findUnique({ where: { id: \"main\" } }); if (s?.deepseekApiKey) key = s.deepseekApiKey; } catch {}\r\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\r\n\r\n const results: any[] = [];\r\n\r\n for (const feed of feeds) {\r\n try {\r\n const res = await fetch(feed.url, {\r\n headers: { \"Accept\": \"application/json\", \"User-Agent\": \"ProektMap/1.0\" },\r\n signal: AbortSignal.timeout(15000),\r\n });\r\n const raw = await res.text();\r\n\r\n let items: { title: string; link: string; description: string; image?: string }[] = [];\r\n if (feed.type === \"json\") {\r\n try {\r\n const json = JSON.parse(raw);\r\n if (json.hits) items = json.hits.map((h: any) => ({ title: h.title || h.story_title || \"\", link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, description: h.story_text || h.comment_text || \"\" }));\r\n else if (json.data?.children) items = json.data.children.map((c: any) => { const d = c.data; let img = d.thumbnail?.startsWith(\"http\") ? d.thumbnail : d.preview?.images?.[0]?.source?.url?.replace(/&amp;/g, \"&\"); return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img }; });\r\n else if (json.items) items = json.items.slice(0, 5).map((i: any) => ({ title: i.full_name || i.name, link: i.html_url || i.url, description: i.description || \"\", image: i.owner?.avatar_url || null }));\r\n } catch {}\r\n } else if (feed.type === \"xml\") {\r\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\r\n items = entries.slice(0, 3).map((e: any) => ({ title: (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim(), link: e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\", description: (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim() }));\r\n }\r\n\r\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\r\n\r\n for (const item of items.slice(0, 2)) {\r\n if (!item.title || item.title.length < 15) continue;\r\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\r\n if (exists) continue;\r\n\r\n // Download image\r\n let coverImage = `https://proektmap.ru/api/og?title=${encodeURIComponent(item.title)}&category=${encodeURIComponent(feed.category)}`;\r\n if (item.image) {\r\n try {\r\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\r\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\r\n const buffer = Buffer.from(await imgRes.arrayBuffer());\r\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\r\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.${ext}`;\r\n const { writeFile, mkdir } = await import(\"fs/promises\");\r\n const { join } = await import(\"path\");\r\n await mkdir(join(process.cwd(), \"public\", \"uploads\"), { recursive: true });\r\n await writeFile(join(process.cwd(), \"public\", \"uploads\", filename), buffer);\r\n coverImage = `/uploads/${filename}`;\r\n }\r\n } catch {}\r\n }\r\n\r\n // AI translates\r\n const prompt = `Ты редактор блога об AI. Напиши заметку на РУССКОМ языке (250-350 слов).\r\n\r\nЗаголовок оригинала: ${item.title}\r\nИсточник: ${item.link}\r\n\r\nВАЖНО:\r\n1. Начни с заголовка строго в формате: \"ЗАГОЛОВОК: русский перевод\"\r\n2. Заголовок должен быть ПОЛНОСТЬЮ на русском, без английских слов\r\n3. Затем сам текст заметки\r\n4. В конце добавь: \"📎 [Источник](${item.link})\"\r\n5. Все ссылки на GitHub делай в формате [название](url)`;\r\n\r\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\r\n method: \"POST\", headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\r\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 1000 }),\r\n });\r\n\r\n const aiData = await aiRes.json();\r\n const fullText = aiData.choices?.[0]?.message?.content;\r\n if (!fullText) continue;\r\n\r\n // Extract title\r\n let title = item.title;\r\n const tm = fullText.match(/ЗАГОЛОВОК:\\s*(.+)/);\r\n if (tm) title = tm[1].trim();\r\n\r\n // Remove title line from content\r\n const content = fullText.replace(/ЗАГОЛОВОК:\\s*.+(\\n|$)/, \"\").trim();\r\n const excerpt = content.replace(/[#*\\[\\]()]/g, \"\").slice(0, 200).replace(/\\n/g, \" \");\r\n\r\n // Generate clean transliterated slug\r\n const clean = title.replace(/[^a-zа-я0-9\\s-]/gi, \"\").trim();\r\n const slug = translit(clean).replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 70);\r\n\r\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\r\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\r\n\r\n await db.blogPost.create({\r\n data: { title, slug, content, excerpt, coverImage, status: \"published\", authorId: admin.id, categoryId: cat.id, tags: \"AI,новости\", aiGenerated: true, aiModel: \"deepseek-chat\", metaTitle: title + \" — Карта роста\", metaDesc: excerpt, publishedAt: new Date() },\r\n });\r\n\r\n results.push({ feed: feed.name, title: title.slice(0, 60), slug, status: \"published\" });\r\n }\r\n\r\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\r\n } catch (e: any) {\r\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\r\n }\r\n }\r\n\r\n return NextResponse.json({ results });\r\n}\r\n"}, "66dae74d590d7ae68e46caf1938ed51cfd4f154e": {"t": 1785754646.055078, "n": 6498, "h": "8c8f457d9a6a5f7e", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nfunction translit(text: string): string {\n const map: any = { а:\"a\",б:\"b\",в:\"v\",г:\"g\",д:\"d\",е:\"e\",ё:\"yo\",ж:\"zh\",з:\"z\",и:\"i\",й:\"y\",к:\"k\",л:\"l\",м:\"m\",н:\"n\",о:\"o\",п:\"p\",р:\"r\",с:\"s\",т:\"t\",у:\"u\",ф:\"f\",х:\"h\",ц:\"ts\",ч:\"ch\",ш:\"sh\",щ:\"sch\",ъ:\"\",ы:\"y\",ь:\"\",э:\"e\",ю:\"yu\",я:\"ya\" };\n return text.toLowerCase().split(\"\").map((c: string) => map[c] || c).join(\"\");\n}\n\nexport async function POST() {\n const db = await getDb();\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n let key = process.env.DEEPSEEK_API_KEY;\n try { const s = await db.siteSettings.findUnique({ where: { id: \"main\" } }); if (s?.deepseekApiKey) key = s.deepseekApiKey; } catch {}\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\n\n const results: any[] = [];\n\n for (const feed of feeds) {\n try {\n const res = await fetch(feed.url, {\n headers: { \"Accept\": \"application/json\", \"User-Agent\": \"ProektMap/1.0\" },\n signal: AbortSignal.timeout(15000),\n });\n const raw = await res.text();\n\n let items: { title: string; link: string; description: string; image?: string }[] = [];\n if (feed.type === \"json\") {\n try {\n const json = JSON.parse(raw);\n if (json.hits) items = json.hits.map((h: any) => ({ title: h.title || h.story_title || \"\", link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, description: h.story_text || h.comment_text || \"\" }));\n else if (json.data?.children) items = json.data.children.map((c: any) => { const d = c.data; let img = d.thumbnail?.startsWith(\"http\") ? d.thumbnail : d.preview?.images?.[0]?.source?.url?.replace(/&amp;/g, \"&\"); return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img }; });\n else if (json.items) items = json.items.slice(0, 5).map((i: any) => ({ title: i.full_name || i.name, link: i.html_url || i.url, description: i.description || \"\", image: i.owner?.avatar_url || null }));\n } catch {}\n } else if (feed.type === \"xml\") {\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\n items = entries.slice(0, 3).map((e: any) => ({ title: (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim(), link: e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\", description: (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim() }));\n }\n\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\n\n for (const item of items.slice(0, 2)) {\n if (!item.title || item.title.length < 15) continue;\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\n if (exists) continue;\n\n // Download image\n let coverImage = `https://proektmap.ru/api/og?title=${encodeURIComponent(item.title)}&category=${encodeURIComponent(feed.category)}`;\n if (item.image) {\n try {\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\n const buffer = Buffer.from(await imgRes.arrayBuffer());\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.${ext}`;\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n await mkdir(join(process.cwd(), \"public\", \"uploads\"), { recursive: true });\n await writeFile(join(process.cwd(), \"public\", \"uploads\", filename), buffer);\n coverImage = `/uploads/${filename}`;\n }\n } catch {}\n }\n\n // AI translates\n const prompt = `Ты редактор блога об AI. Напиши заметку на РУССКОМ языке (250-350 слов).\n\nЗаголовок оригинала: ${item.title}\nИсточник: ${item.link}\n\nВАЖНО:\n1. Начни с заголовка строго в формате: \"ЗАГОЛОВОК: русский перевод\"\n2. Заголовок должен быть ПОЛНОСТЬЮ на русском, без английских слов\n3. Затем сам текст заметки\n4. В конце добавь: \"📎 [Источник](${item.link})\"\n5. Все ссылки на GitHub делай в формате [название](url)`;\n\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 1000 }),\n });\n\n const aiData = await aiRes.json();\n const fullText = aiData.choices?.[0]?.message?.content;\n if (!fullText) continue;\n\n // Extract title\n let title = item.title;\n const tm = fullText.match(/ЗАГОЛОВОК:\\s*(.+)/);\n if (tm) title = tm[1].trim();\n\n // Remove title line from content\n const content = fullText.replace(/ЗАГОЛОВОК:\\s*.+(\\n|$)/, \"\").trim();\n const excerpt = content.replace(/[#*\\[\\]()]/g, \"\").slice(0, 200).replace(/\\n/g, \" \");\n\n // Generate clean transliterated slug\n const clean = title.replace(/[^a-zа-я0-9\\s-]/gi, \"\").trim();\n const slug = translit(clean).replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 70);\n\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n await db.blogPost.create({\n data: { title, slug, content, excerpt, coverImage, status: \"published\", authorId: admin.id, categoryId: cat.id, tags: \"AI,новости\", aiGenerated: true, aiModel: \"deepseek-chat\", metaTitle: title + \" — Карта роста\", metaDesc: excerpt, publishedAt: new Date() },\n });\n\n results.push({ feed: feed.name, title: title.slice(0, 60), slug, status: \"published\" });\n }\n\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\n } catch (e: any) {\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\n }\n }\n\n return NextResponse.json({ results });\n}\n"}, "603b931ce7945175f6029cd555486e1272a0276a": {"t": 1785754647.4200015, "n": 6526, "h": "cdd104fa2630a243", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nfunction translit(text: string): string {\n const map: any = { а:\"a\",б:\"b\",в:\"v\",г:\"g\",д:\"d\",е:\"e\",ё:\"yo\",ж:\"zh\",з:\"z\",и:\"i\",й:\"y\",к:\"k\",л:\"l\",м:\"m\",н:\"n\",о:\"o\",п:\"p\",р:\"r\",с:\"s\",т:\"t\",у:\"u\",ф:\"f\",х:\"h\",ц:\"ts\",ч:\"ch\",ш:\"sh\",щ:\"sch\",ъ:\"\",ы:\"y\",ь:\"\",э:\"e\",ю:\"yu\",я:\"ya\" };\n return text.toLowerCase().split(\"\").map((c: string) => map[c] || c).join(\"\");\n}\n\nexport async function POST() {\n const db = await getDb();\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n let key = process.env.DEEPSEEK_API_KEY;\n try { const s = await db.siteSettings.findUnique({ where: { id: \"main\" } }); if (s?.deepseekApiKey) key = s.deepseekApiKey; } catch {}\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\n\n const results: any[] = [];\n\n for (const feed of feeds) {\n try {\n const res = await fetch(feed.url, {\n headers: feed.type === \"json\" ? { \"Accept\": \"application/json\" } : { \"User-Agent\": \"ProektMap/1.0\" },\n signal: AbortSignal.timeout(15000),\n });\n const raw = await res.text();\n\n let items: { title: string; link: string; description: string; image?: string }[] = [];\n if (feed.type === \"json\") {\n try {\n const json = JSON.parse(raw);\n if (json.hits) items = json.hits.map((h: any) => ({ title: h.title || h.story_title || \"\", link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, description: h.story_text || h.comment_text || \"\" }));\n else if (json.data?.children) items = json.data.children.map((c: any) => { const d = c.data; let img = d.thumbnail?.startsWith(\"http\") ? d.thumbnail : d.preview?.images?.[0]?.source?.url?.replace(/&amp;/g, \"&\"); return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img }; });\n else if (json.items) items = json.items.slice(0, 5).map((i: any) => ({ title: i.full_name || i.name, link: i.html_url || i.url, description: i.description || \"\", image: i.owner?.avatar_url || null }));\n } catch {}\n } else if (feed.type === \"xml\") {\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\n items = entries.slice(0, 3).map((e: any) => ({ title: (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim(), link: e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\", description: (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim() }));\n }\n\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\n\n for (const item of items.slice(0, 2)) {\n if (!item.title || item.title.length < 15) continue;\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\n if (exists) continue;\n\n // Download image\n let coverImage = `https://proektmap.ru/api/og?title=${encodeURIComponent(item.title)}&category=${encodeURIComponent(feed.category)}`;\n if (item.image) {\n try {\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\n const buffer = Buffer.from(await imgRes.arrayBuffer());\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.${ext}`;\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n await mkdir(join(process.cwd(), \"public\", \"uploads\"), { recursive: true });\n await writeFile(join(process.cwd(), \"public\", \"uploads\", filename), buffer);\n coverImage = `/uploads/${filename}`;\n }\n } catch {}\n }\n\n // AI translates\n const prompt = `Ты редактор блога об AI. Напиши заметку на РУССКОМ языке (250-350 слов).\n\nЗаголовок оригинала: ${item.title}\nИсточник: ${item.link}\n\nВАЖНО:\n1. Начни с заголовка строго в формате: \"ЗАГОЛОВОК: русский перевод\"\n2. Заголовок должен быть ПОЛНОСТЬЮ на русском, без английских слов\n3. Затем сам текст заметки\n4. В конце добавь: \"📎 [Источник](${item.link})\"\n5. Все ссылки на GitHub делай в формате [название](url)`;\n\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 1000 }),\n });\n\n const aiData = await aiRes.json();\n const fullText = aiData.choices?.[0]?.message?.content;\n if (!fullText) continue;\n\n // Extract title\n let title = item.title;\n const tm = fullText.match(/ЗАГОЛОВОК:\\s*(.+)/);\n if (tm) title = tm[1].trim();\n\n // Remove title line from content\n const content = fullText.replace(/ЗАГОЛОВОК:\\s*.+(\\n|$)/, \"\").trim();\n const excerpt = content.replace(/[#*\\[\\]()]/g, \"\").slice(0, 200).replace(/\\n/g, \" \");\n\n // Generate clean transliterated slug\n const clean = title.replace(/[^a-zа-я0-9\\s-]/gi, \"\").trim();\n const slug = translit(clean).replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 70);\n\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n await db.blogPost.create({\n data: { title, slug, content, excerpt, coverImage, status: \"published\", authorId: admin.id, categoryId: cat.id, tags: \"AI,новости\", aiGenerated: true, aiModel: \"deepseek-chat\", metaTitle: title + \" — Карта роста\", metaDesc: excerpt, publishedAt: new Date() },\n });\n\n results.push({ feed: feed.name, title: title.slice(0, 60), slug, status: \"published\" });\n }\n\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\n } catch (e: any) {\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\n }\n }\n\n return NextResponse.json({ results });\n}\n"}, "5aad9fde474155560362eb39471caf495294ee95": {"t": 1785754650.9834259, "n": 6391, "h": "9eb10980cd9f3f42", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nfunction translit(text: string): string {\n const map: any = { а:\"a\",б:\"b\",в:\"v\",г:\"g\",д:\"d\",е:\"e\",ё:\"yo\",ж:\"zh\",з:\"z\",и:\"i\",й:\"y\",к:\"k\",л:\"l\",м:\"m\",н:\"n\",о:\"o\",п:\"p\",р:\"r\",с:\"s\",т:\"t\",у:\"u\",ф:\"f\",х:\"h\",ц:\"ts\",ч:\"ch\",ш:\"sh\",щ:\"sch\",ъ:\"\",ы:\"y\",ь:\"\",э:\"e\",ю:\"yu\",я:\"ya\" };\n return text.toLowerCase().split(\"\").map((c: string) => map[c] || c).join(\"\");\n}\n\nexport async function POST() {\n const db = await getDb();\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n const key = process.env.DEEPSEEK_API_KEY;\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\n\n const results: any[] = [];\n\n for (const feed of feeds) {\n try {\n const res = await fetch(feed.url, {\n headers: feed.type === \"json\" ? { \"Accept\": \"application/json\" } : { \"User-Agent\": \"ProektMap/1.0\" },\n signal: AbortSignal.timeout(15000),\n });\n const raw = await res.text();\n\n let items: { title: string; link: string; description: string; image?: string }[] = [];\n if (feed.type === \"json\") {\n try {\n const json = JSON.parse(raw);\n if (json.hits) items = json.hits.map((h: any) => ({ title: h.title || h.story_title || \"\", link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, description: h.story_text || h.comment_text || \"\" }));\n else if (json.data?.children) items = json.data.children.map((c: any) => { const d = c.data; let img = d.thumbnail?.startsWith(\"http\") ? d.thumbnail : d.preview?.images?.[0]?.source?.url?.replace(/&amp;/g, \"&\"); return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img }; });\n else if (json.items) items = json.items.slice(0, 5).map((i: any) => ({ title: i.full_name || i.name, link: i.html_url || i.url, description: i.description || \"\", image: i.owner?.avatar_url || null }));\n } catch {}\n } else if (feed.type === \"xml\") {\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\n items = entries.slice(0, 3).map((e: any) => ({ title: (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim(), link: e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\", description: (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim() }));\n }\n\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\n\n for (const item of items.slice(0, 2)) {\n if (!item.title || item.title.length < 15) continue;\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\n if (exists) continue;\n\n // Download image\n let coverImage = `https://proektmap.ru/api/og?title=${encodeURIComponent(item.title)}&category=${encodeURIComponent(feed.category)}`;\n if (item.image) {\n try {\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\n const buffer = Buffer.from(await imgRes.arrayBuffer());\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.${ext}`;\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n await mkdir(join(process.cwd(), \"public\", \"uploads\"), { recursive: true });\n await writeFile(join(process.cwd(), \"public\", \"uploads\", filename), buffer);\n coverImage = `/uploads/${filename}`;\n }\n } catch {}\n }\n\n // AI translates\n const prompt = `Ты редактор блога об AI. Напиши заметку на РУССКОМ языке (250-350 слов).\n\nЗаголовок оригинала: ${item.title}\nИсточник: ${item.link}\n\nВАЖНО:\n1. Начни с заголовка строго в формате: \"ЗАГОЛОВОК: русский перевод\"\n2. Заголовок должен быть ПОЛНОСТЬЮ на русском, без английских слов\n3. Затем сам текст заметки\n4. В конце добавь: \"📎 [Источник](${item.link})\"\n5. Все ссылки на GitHub делай в формате [название](url)`;\n\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\", headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 1000 }),\n });\n\n const aiData = await aiRes.json();\n const fullText = aiData.choices?.[0]?.message?.content;\n if (!fullText) continue;\n\n // Extract title\n let title = item.title;\n const tm = fullText.match(/ЗАГОЛОВОК:\\s*(.+)/);\n if (tm) title = tm[1].trim();\n\n // Remove title line from content\n const content = fullText.replace(/ЗАГОЛОВОК:\\s*.+(\\n|$)/, \"\").trim();\n const excerpt = content.replace(/[#*\\[\\]()]/g, \"\").slice(0, 200).replace(/\\n/g, \" \");\n\n // Generate clean transliterated slug\n const clean = title.replace(/[^a-zа-я0-9\\s-]/gi, \"\").trim();\n const slug = translit(clean).replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 70);\n\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n await db.blogPost.create({\n data: { title, slug, content, excerpt, coverImage, status: \"published\", authorId: admin.id, categoryId: cat.id, tags: \"AI,новости\", aiGenerated: true, aiModel: \"deepseek-chat\", metaTitle: title + \" — Карта роста\", metaDesc: excerpt, publishedAt: new Date() },\n });\n\n results.push({ feed: feed.name, title: title.slice(0, 60), slug, status: \"published\" });\n }\n\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\n } catch (e: any) {\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\n }\n }\n\n return NextResponse.json({ results });\n}\n"}, "eaae1de86424e4f63e308998f4ccc3d4d6f4e508": {"t": 1785754652.3774946, "n": 6865, "h": "cca429143dfd3c7c", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nexport async function POST() {\n const db = await getDb();\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n const key = process.env.DEEPSEEK_API_KEY;\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\n\n const results: any[] = [];\n\n for (const feed of feeds) {\n try {\n const res = await fetch(feed.url, {\n headers: feed.type === \"json\" ? { \"Accept\": \"application/json\" } : { \"User-Agent\": \"ProektMap/1.0\" },\n signal: AbortSignal.timeout(15000),\n });\n const raw = await res.text();\n\n let items: { title: string; link: string; description: string; image?: string }[] = [];\n\n if (feed.type === \"json\") {\n try {\n const json = JSON.parse(raw);\n if (json.hits) {\n items = json.hits.map((h: any) => ({ title: h.title || h.story_title || \"\", link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, description: h.story_text || h.comment_text || \"\" }));\n } else if (json.data?.children) {\n items = json.data.children.map((c: any) => {\n const d = c.data;\n let img: string | undefined;\n if (d.thumbnail && d.thumbnail.startsWith(\"http\")) img = d.thumbnail;\n else if (d.preview?.images?.[0]?.source?.url) img = d.preview.images[0].source.url.replace(/&amp;/g, \"&\");\n return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img };\n });\n } else if (json.items) {\n items = json.items.slice(0, 5).map((i: any) => ({\n title: i.full_name || i.name,\n link: i.html_url || i.url,\n description: i.description || \"\",\n image: i.owner?.avatar_url || null,\n }));\n }\n } catch {}\n } else if (feed.type === \"xml\") {\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\n items = entries.slice(0, 3).map(e => {\n const title = (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim();\n const link = e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\";\n const summary = (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim();\n return { title, link, description: summary };\n });\n }\n\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\n\n for (const item of items.slice(0, 2)) {\n if (!item.title || item.title.length < 15) continue;\n\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\n if (exists) continue;\n\n // Download image if available\n let coverImage = `https://proektmap.ru/api/og?title=${encodeURIComponent(item.title)}&category=${encodeURIComponent(feed.category)}`;\n if (item.image) {\n try {\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\n const buffer = Buffer.from(await imgRes.arrayBuffer());\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.${ext}`;\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n await mkdir(join(process.cwd(), \"public\", \"uploads\"), { recursive: true });\n await writeFile(join(process.cwd(), \"public\", \"uploads\", filename), buffer);\n coverImage = `/uploads/${filename}`;\n }\n } catch {}\n }\n\n // AI translates and writes in Russian\n const prompt = `Ты — редактор блога об AI на русском языке. Переведи И заголовок И содержание на русский. Сделай пересказ на 250-350 слов живым языком.\n\nЗаголовок оригинала: ${item.title}\nИсточник: ${item.link}\nОписание: ${(item.description || \"\").slice(0, 500)}\n\nВАЖНО:\n1. СНАЧАЛА переведи заголовок на русский. Формат: \"ЗАГОЛОВОК: <перевод>\"\n2. Затем напиши саму заметку.\n3. В конце добавь: \"📎 [Источник](${item.link})\" — это ДОЛЖНО быть в markdown-формате с квадратными и круглыми скобками, чтобы получилась кликабельная ссылка.\n4. Если в тексте упоминаешь репозиторий на GitHub — вставляй его как ссылку в формате [название](url).`;\n\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 1000 }),\n });\n\n const aiData = await aiRes.json();\n const fullResponse = aiData.choices?.[0]?.message?.content;\n if (!fullResponse) continue;\n\n // Extract translated title from AI response\n let title = item.title;\n const titleMatch = fullResponse.match(/ЗАГОЛОВОК:\\s*(.+)/);\n if (titleMatch) title = titleMatch[1].trim();\n\n // Remove the title line from content and use the rest as content\n let content = fullResponse.replace(/ЗАГОЛОВОК:\\s*.+(\\n|$)/, \"\").trim();\n\n const slug = title.replace(/[^a-zа-я0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").toLowerCase().slice(0, 70) + \"-\" + Date.now().toString(36).slice(-4);\n const excerpt = content.replace(/[#*\\[\\]()]/g, \"\").slice(0, 200).replace(/\\n/g, \" \");\n\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n // PUBLISH immediately, not draft\n await db.blogPost.create({\n data: {\n title, slug, content, excerpt, coverImage, status: \"published\", authorId: admin.id,\n categoryId: cat.id, tags: \"AI,новости\", aiGenerated: true, aiModel: \"deepseek-chat\",\n metaTitle: title + \" — Карта роста\", metaDesc: excerpt, publishedAt: new Date(),\n },\n });\n\n results.push({ feed: feed.name, title: title.slice(0, 60), image: coverImage ? \"✅\" : \"—\", status: \"published\" });\n }\n\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\n } catch (e: any) {\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\n }\n }\n\n return NextResponse.json({ results, total: results.length });\n}\n"}, "305060ee158f0fbc5b3b65fbd2c8b0718704bc19": {"t": 1785754653.8113222, "n": 6720, "h": "5905052869a47665", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nexport async function POST() {\n const db = await getDb();\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n const key = process.env.DEEPSEEK_API_KEY;\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\n\n const results: any[] = [];\n\n for (const feed of feeds) {\n try {\n const res = await fetch(feed.url, {\n headers: feed.type === \"json\" ? { \"Accept\": \"application/json\" } : { \"User-Agent\": \"ProektMap/1.0\" },\n signal: AbortSignal.timeout(15000),\n });\n const raw = await res.text();\n\n let items: { title: string; link: string; description: string; image?: string }[] = [];\n\n if (feed.type === \"json\") {\n try {\n const json = JSON.parse(raw);\n if (json.hits) {\n items = json.hits.map((h: any) => ({ title: h.title || h.story_title || \"\", link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, description: h.story_text || h.comment_text || \"\" }));\n } else if (json.data?.children) {\n items = json.data.children.map((c: any) => {\n const d = c.data;\n let img: string | undefined;\n if (d.thumbnail && d.thumbnail.startsWith(\"http\")) img = d.thumbnail;\n else if (d.preview?.images?.[0]?.source?.url) img = d.preview.images[0].source.url.replace(/&amp;/g, \"&\");\n return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img };\n });\n } else if (json.items) {\n items = json.items.slice(0, 5).map((i: any) => ({\n title: i.full_name || i.name,\n link: i.html_url || i.url,\n description: i.description || \"\",\n image: i.owner?.avatar_url || null,\n }));\n }\n } catch {}\n } else if (feed.type === \"xml\") {\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\n items = entries.slice(0, 3).map(e => {\n const title = (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim();\n const link = e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\";\n const summary = (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim();\n return { title, link, description: summary };\n });\n }\n\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\n\n for (const item of items.slice(0, 2)) {\n if (!item.title || item.title.length < 15) continue;\n\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\n if (exists) continue;\n\n // Download image if available\n let coverImage = \"\";\n if (item.image) {\n try {\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\n const buffer = Buffer.from(await imgRes.arrayBuffer());\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.${ext}`;\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n await mkdir(join(process.cwd(), \"public\", \"uploads\"), { recursive: true });\n await writeFile(join(process.cwd(), \"public\", \"uploads\", filename), buffer);\n coverImage = `/uploads/${filename}`;\n }\n } catch {}\n }\n\n // AI translates and writes in Russian\n const prompt = `Ты — редактор блога об AI на русском языке. Переведи И заголовок И содержание на русский. Сделай пересказ на 250-350 слов живым языком.\n\nЗаголовок оригинала: ${item.title}\nИсточник: ${item.link}\nОписание: ${(item.description || \"\").slice(0, 500)}\n\nВАЖНО:\n1. СНАЧАЛА переведи заголовок на русский. Формат: \"ЗАГОЛОВОК: <перевод>\"\n2. Затем напиши саму заметку.\n3. В конце добавь: \"📎 [Источник](${item.link})\" — это ДОЛЖНО быть в markdown-формате с квадратными и круглыми скобками, чтобы получилась кликабельная ссылка.\n4. Если в тексте упоминаешь репозиторий на GitHub — вставляй его как ссылку в формате [название](url).`;\n\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 1000 }),\n });\n\n const aiData = await aiRes.json();\n const fullResponse = aiData.choices?.[0]?.message?.content;\n if (!fullResponse) continue;\n\n // Extract translated title from AI response\n let title = item.title;\n const titleMatch = fullResponse.match(/ЗАГОЛОВОК:\\s*(.+)/);\n if (titleMatch) title = titleMatch[1].trim();\n\n // Remove the title line from content and use the rest as content\n let content = fullResponse.replace(/ЗАГОЛОВОК:\\s*.+(\\n|$)/, \"\").trim();\n\n const slug = (title.replace(/[^a-zа-я0-9]+/g, \"-\") + \"-\" + Date.now().toString(36)).toLowerCase().slice(0, 80);\n const excerpt = content.replace(/[#*\\[\\]()]/g, \"\").slice(0, 200).replace(/\\n/g, \" \");\n\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n // PUBLISH immediately, not draft\n await db.blogPost.create({\n data: {\n title, slug, content, excerpt, coverImage, status: \"published\", authorId: admin.id,\n categoryId: cat.id, tags: \"AI,новости\", aiGenerated: true, aiModel: \"deepseek-chat\",\n metaTitle: title + \" — Карта роста\", metaDesc: excerpt, publishedAt: new Date(),\n },\n });\n\n results.push({ feed: feed.name, title: title.slice(0, 60), image: coverImage ? \"✅\" : \"—\", status: \"published\" });\n }\n\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\n } catch (e: any) {\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\n }\n }\n\n return NextResponse.json({ results, total: results.length });\n}\n"}, "88aba9728f2a86bc0706defbbbe4dd289fc07df5": {"t": 1785754656.281343, "n": 6378, "h": "d9768278ffb70f93", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nexport async function POST() {\n const db = await getDb();\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n const key = process.env.DEEPSEEK_API_KEY;\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\n\n const results: any[] = [];\n\n for (const feed of feeds) {\n try {\n const res = await fetch(feed.url, {\n headers: feed.type === \"json\" ? { \"Accept\": \"application/json\" } : { \"User-Agent\": \"ProektMap/1.0\" },\n signal: AbortSignal.timeout(15000),\n });\n const raw = await res.text();\n\n let items: { title: string; link: string; description: string; image?: string }[] = [];\n \n if (feed.type === \"json\") {\n try {\n const json = JSON.parse(raw);\n \n if (json.hits) {\n // Hacker News\n items = json.hits.map((h: any) => ({\n title: h.title || h.story_title || \"\",\n link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`,\n description: h.story_text || h.comment_text || \"\",\n image: null,\n }));\n } else if (json.data?.children) {\n // Reddit\n items = json.data.children.map((c: any) => {\n const d = c.data;\n let img: string | undefined;\n if (d.thumbnail && d.thumbnail.startsWith(\"http\")) img = d.thumbnail;\n else if (d.preview?.images?.[0]?.source?.url) img = d.preview.images[0].source.url.replace(/&amp;/g, \"&\");\n else if (d.url && /\\.(jpg|jpeg|png|gif|webp)(\\?|$)/i.test(d.url)) img = d.url;\n return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img };\n });\n } else if (json.items) {\n // GitHub\n items = json.items.slice(0, 5).map((i: any) => ({\n title: i.full_name || i.name,\n link: i.html_url || i.url,\n description: i.description || \"\",\n image: i.owner?.avatar_url || null,\n }));\n }\n } catch {}\n } else if (feed.type === \"xml\") {\n // arXiv\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\n items = entries.slice(0, 3).map(e => {\n const title = (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim();\n const link = e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\";\n const summary = (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim();\n return { title, link, description: summary };\n });\n }\n\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\n\n for (const item of items.slice(0, 2)) {\n if (!item.title || item.title.length < 15) continue;\n\n // Check duplicate\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\n if (exists) continue;\n\n // Try to download image to our server\n let coverImage = \"\";\n if (item.image) {\n try {\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\n const buffer = Buffer.from(await imgRes.arrayBuffer());\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2,6)}.${ext}`;\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n const uploadDir = join(process.cwd(), \"public\", \"uploads\");\n await mkdir(uploadDir, { recursive: true });\n await writeFile(join(uploadDir, filename), buffer);\n coverImage = `/uploads/${filename}`;\n }\n } catch {}\n }\n\n // AI generates Russian summary\n const prompt = `Ты — редактор блога об AI. Напиши краткую заметку на русском (250-350 слов) на основе этой новости:\n\nЗаголовок: ${item.title}\nИсточник: ${item.link}\nОписание: ${(item.description || \"\").slice(0, 500)}\n\nФормат: краткий пересказ сути новости + 1-2 предложения почему это важно для AI-инженеров + ссылка на источник.\n\nПиши живым языком, как для коллеги. Без шаблонных фраз.`;\n\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 800 }),\n });\n\n const aiData = await aiRes.json();\n const content = aiData.choices?.[0]?.message?.content;\n if (!content) continue;\n\n const title = item.title.slice(0, 120);\n const slug = (title.replace(/[^a-zа-я0-9]+/g, \"-\") + \"-\" + Date.now().toString(36)).toLowerCase().slice(0, 80);\n const excerpt = content.slice(0, 200).replace(/\\n/g, \" \");\n\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n await db.blogPost.create({\n data: {\n title, slug, content, excerpt, coverImage, status: \"draft\", authorId: admin.id,\n categoryId: cat.id, tags: `AI,${feed.category}`, aiGenerated: true, aiModel: \"deepseek-chat\",\n metaTitle: title + \" — Карта роста\", metaDesc: excerpt,\n },\n });\n\n results.push({ feed: feed.name, title: title.slice(0, 60), image: coverImage ? \"✅\" : \"—\", status: \"draft\" });\n }\n\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\n } catch (e: any) {\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\n }\n }\n\n return NextResponse.json({ results, total: results.length });\n}\n"}, "c411d3d4cdfc0b2921a8b2cfda2d97dca03a47ca": {"t": 1785754657.6593282, "n": 6366, "h": "8b9db036b9a9874e", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nexport async function POST() {\n const db = await getDb();\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n const key = process.env.DEEPSEEK_API_KEY;\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\n\n const results: any[] = [];\n\n for (const feed of feeds) {\n try {\n const res = await fetch(feed.url, {\n headers: feed.type === \"json\" ? { \"Accept\": \"application/json\" } : { \"User-Agent\": \"ProektMap/1.0\" },\n signal: AbortSignal.timeout(15000),\n });\n const raw = await res.text();\n\n let items: { title: string; link: string; description: string; image?: string }[] = [];\n \n if (feed.type === \"json\") {\n try {\n const json = JSON.parse(raw);\n \n if (json.hits) {\n // Hacker News\n items = json.hits.map((h: any) => ({\n title: h.title || h.story_title || \"\",\n link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`,\n description: h.story_text || h.comment_text || \"\",\n image: null,\n }));\n } else if (json.data?.children) {\n // Reddit\n items = json.data.children.map((c: any) => {\n const d = c.data;\n let img: string | undefined;\n if (d.thumbnail && d.thumbnail.startsWith(\"http\")) img = d.thumbnail;\n else if (d.preview?.images?.[0]?.source?.url) img = d.preview.images[0].source.url.replace(/&amp;/g, \"&\");\n else if (d.url && /\\.(jpg|jpeg|png|gif|webp)(\\?|$)/i.test(d.url)) img = d.url;\n return { title: d.title, link: `https://reddit.com${d.permalink}`, description: d.selftext || \"\", image: img };\n });\n } else if (json.items) {\n // GitHub\n items = json.items.slice(0, 5).map((i: any) => ({\n title: i.full_name || i.name,\n link: i.html_url || i.url,\n description: i.description || \"\",\n image: i.owner?.avatar_url || null,\n }));\n }\n } catch {}\n } else if (feed.type === \"xml\") {\n // arXiv\n const entries = raw.match(/<entry>[\\s\\S]*?<\\/entry>/g) || [];\n items = entries.slice(0, 3).map(e => {\n const title = (e.match(/<title>(.*?)<\\/title>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim();\n const link = e.match(/<id>(.*?)<\\/id>/)?.[1] || \"\";\n const summary = (e.match(/<summary>(.*?)<\\/summary>/)?.[1] || \"\").replace(/\\s+/g, \" \").trim();\n return { title, link, description: summary };\n });\n }\n\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\n\n for (const item of items.slice(0, 2)) {\n if (!item.title || item.title.length < 15) continue;\n\n // Check duplicate\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\n if (exists) continue;\n\n // Try to download image to our server\n let coverImage = \"\";\n if (item.image) {\n try {\n const imgRes = await fetch(item.image, { signal: AbortSignal.timeout(5000) });\n if (imgRes.ok && imgRes.headers.get(\"content-type\")?.startsWith(\"image\")) {\n const buffer = Buffer.from(await imgRes.arrayBuffer());\n const ext = item.image.split(\".\").pop()?.split(\"?\")[0] || \"jpg\";\n const filename = `blog-${Date.now()}-${Math.random().toString(36).slice(2,6)}.${ext}`;\n const { writeFile, mkdir } = await import(\"fs/promises\");\n const { join } = await import(\"path\");\n const uploadDir = join(process.cwd(), \"public\", \"uploads\");\n await mkdir(uploadDir, { recursive: true });\n await writeFile(join(uploadDir, filename), buffer);\n coverImage = `/uploads/${filename}`;\n }\n } catch {}\n }\n\n // AI generates Russian summary\n const prompt = `Ты — редактор блога об AI. Напиши краткую заметку на русском (250-350 слов) на основе этой новости:\n\nЗаголовок: ${item.title}\nИсточник: ${item.link}\nОписание: ${(item.description || \"\").slice(0, 500)}\n\nФормат: краткий пересказ сути новости + 1-2 предложения почему это важно для AI-инженеров + ссылка на источник.\n\nПиши живым языком, как для коллеги. Без шаблонных фраз.`;\n\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 800 }),\n });\n\n const aiData = await aiRes.json();\n const content = aiData.choices?.[0]?.message?.content;\n if (!content) continue;\n\n const title = item.title.slice(0, 120);\n const slug = title.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\").replace(/^-|-$/g, \"\").slice(0, 80);\n const excerpt = content.slice(0, 200).replace(/\\n/g, \" \");\n\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n await db.blogPost.create({\n data: {\n title, slug, content, excerpt, coverImage, status: \"draft\", authorId: admin.id,\n categoryId: cat.id, tags: `AI,${feed.category}`, aiGenerated: true, aiModel: \"deepseek-chat\",\n metaTitle: title + \" — Карта роста\", metaDesc: excerpt,\n },\n });\n\n results.push({ feed: feed.name, title: title.slice(0, 60), image: coverImage ? \"✅\" : \"—\", status: \"draft\" });\n }\n\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\n } catch (e: any) {\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\n }\n }\n\n return NextResponse.json({ results, total: results.length });\n}\n"}, "2d29ca17e9fec87aec0801fad2e16fa1020608d4": {"t": 1785754659.0390773, "n": 4137, "h": "2ed8d198a414c369", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nexport async function POST() {\n const db = await getDb();\n const feeds = await db.blogFeed.findMany({ where: { isActive: true } });\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n const key = process.env.DEEPSEEK_API_KEY;\n if (!key) return NextResponse.json({ error: \"Нет DEEPSEEK_API_KEY\" }, { status: 500 });\n\n const results: any[] = [];\n\n for (const feed of feeds) {\n try {\n // Fetch feed\n const res = await fetch(feed.url, {\n headers: feed.type === \"json\" ? { \"Accept\": \"application/json\" } : {},\n signal: AbortSignal.timeout(15000),\n });\n const raw = await res.text();\n\n // Extract items based on type\n let items: { title: string; link: string; description: string }[] = [];\n if (feed.type === \"json\") {\n try {\n const json = JSON.parse(raw);\n if (json.hits) items = json.hits.map((h: any) => ({ title: h.title || h.story_title || \"\", link: h.url || `https://news.ycombinator.com/item?id=${h.objectID}`, description: h.story_text || h.comment_text || \"\" }));\n else if (json.data?.children) items = json.data.children.map((c: any) => ({ title: c.data.title, link: `https://reddit.com${c.data.permalink}`, description: c.data.selftext }));\n else if (json.items) items = json.items.slice(0, 5).map((i: any) => ({ title: i.name || i.full_name, link: i.html_url || i.url, description: i.description || \"\" }));\n } catch {}\n }\n\n if (items.length === 0) { results.push({ feed: feed.name, status: \"empty\" }); continue; }\n\n // Pick top 2 items\n for (const item of items.slice(0, 2)) {\n if (!item.title || item.title.length < 20) continue;\n\n // Check if already exists\n const exists = await db.blogPost.findFirst({ where: { metaDesc: { contains: item.link.slice(0, 50) } } });\n if (exists) continue;\n\n // AI generates Russian summary\n const prompt = `Ты — редактор блога об AI. Напиши краткую заметку на русском (200-300 слов) на основе этой новости:\n\nЗаголовок: ${item.title}\nИсточник: ${item.link}\nОписание: ${(item.description || \"\").slice(0, 500)}\n\nФормат: краткий пересказ сути новости + 1-2 предложения почему это важно для AI-инженеров + ссылка на источник.\n\nПиши живым языком, как для коллеги. Не используй шаблонные фразы.`;\n\n const aiRes = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 800 }),\n });\n\n const aiData = await aiRes.json();\n const content = aiData.choices?.[0]?.message?.content;\n if (!content) continue;\n\n const title = item.title.slice(0, 120);\n const slug = title.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\").replace(/^-|-$/g, \"\").slice(0, 80);\n const excerpt = content.slice(0, 200);\n\n // Find category\n let cat = await db.blogCategory.findFirst({ where: { name: feed.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: feed.category, slug: feed.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n await db.blogPost.create({\n data: {\n title, slug, content, excerpt, status: \"draft\", authorId: admin.id,\n categoryId: cat.id, tags: `AI,${feed.category}`, aiGenerated: true, aiModel: \"deepseek-chat\",\n metaTitle: title + \" — Карта роста\", metaDesc: excerpt,\n },\n });\n\n results.push({ feed: feed.name, title: title.slice(0, 60), status: \"draft\" });\n }\n\n await db.blogFeed.update({ where: { id: feed.id }, data: { lastFetched: new Date() } });\n } catch (e: any) {\n results.push({ feed: feed.name, status: \"error\", reason: e.message.slice(0, 80) });\n }\n }\n\n return NextResponse.json({ results });\n}\n"}, "f3206e171e953d7a5c92e36a442815c2790f74fe": {"t": 1785754661.3881485, "n": 2539, "h": "25b23e7c5adcd50c", "c": "import { NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nexport async function POST() {\n const db = await getDb();\n const jobs = await db.autoPublish.findMany({ where: { isActive: true } });\n \n if (jobs.length === 0) {\n return NextResponse.json({ message: \"Нет активных заданий\" });\n }\n\n const results: any[] = [];\n const admin = await db.user.findFirst({ where: { role: \"admin\" } });\n if (!admin) return NextResponse.json({ error: \"Админ не найден\" }, { status: 500 });\n\n for (const job of jobs) {\n try {\n const prompt = `Напиши статью для блога на русском языке. Тема: ${job.topic}. 800-1200 слов, 3-5 H2, практическая польза. Markdown.`;\n\n const key = process.env.DEEPSEEK_API_KEY;\n if (!key) { results.push({ topic: job.topic, status: \"skipped\", reason: \"Нет DEEPSEEK_API_KEY\" }); continue; }\n\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", \"Authorization\": `Bearer ${key}` },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"user\", content: prompt }], max_tokens: 3000 }),\n });\n\n const data = await res.json();\n const content = data.choices?.[0]?.message?.content;\n if (!content) { results.push({ topic: job.topic, status: \"error\" }); continue; }\n\n const titleMatch = content.match(/^# (.+)/m);\n const title = titleMatch ? titleMatch[1] : job.topic;\n const slug = title.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\").replace(/^-|-$/g, \"\");\n const excerpt = content.split(\"\\n\").find((l: string) => l.length > 50 && !l.startsWith(\"#\"))?.slice(0, 200) || \"\";\n\n let cat = await db.blogCategory.findFirst({ where: { name: job.category } });\n if (!cat) cat = await db.blogCategory.create({ data: { name: job.category, slug: job.category.toLowerCase().replace(/[^a-zа-я0-9]+/g, \"-\") } });\n\n await db.blogPost.create({\n data: { title, slug, content, excerpt, status: \"draft\", authorId: admin.id,\n categoryId: cat.id, tags: job.category, aiGenerated: true, aiModel: \"deepseek-chat\",\n metaTitle: title + \" — Карта роста\", metaDesc: excerpt },\n });\n\n await db.autoPublish.update({ where: { id: job.id }, data: { lastRunAt: new Date() } });\n results.push({ topic: job.topic, status: \"draft\", title });\n } catch (e: any) {\n results.push({ topic: job.topic, status: \"error\", reason: e.message });\n }\n }\n\n return NextResponse.json({ results });\n}\n"}}, "src/app/api/architect/route.ts": {"3e832a87dce00ff53a610489a1b0edf5226ba106": {"t": 1785745605.710367, "n": 5106, "h": "184866b661b12057", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план для бизнес-идеи. План должен быть настолько конкретным, чтобы AI-агент (Cursor/Claude) смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — с ПОЛНЫМИ полями: имя, тип, описание. Пример: CrawlTarget (id UUID PK, url string, depth number, status enum, createdAt timestamp).\n2. Стек технологий — каждая технология с ОБОСНОВАНИЕМ. Пример: \"BullMQ + Redis — фоновый парсинг тысяч URL без блокировки запросов\".\n3. План — по НЕДЕЛЯМ с конкретными файлами.\n4. Ошибки — специфичные для ниши.\n5. Атомарные промпты — 3 конкретных промпта для Cursor/Claude Code, каждый для одного файла/функции.\n6. Итоговый промпт — один большой промпт для старта проекта.\n\nВерни ТОЛЬКО JSON:\n\nТАКЖЕ ПРОВЕДИ АНАЛИЗ РЫНКА И ДОБАВЬ ПОЛЯ:\n \"marketAnalysis\": {\n \"marketSize\": \"Оценка рынка (1-2 предложения)\",\n \"targetAudience\": \"Целевая аудитория\",\n \"competitors\": [\"Конкурент 1: описание\", \"Конкурент 2: описание\", \"Конкурент 3: описание\"],\n \"differentiation\": \"Чем можно выделиться (2-3 предложения)\",\n \"risks\": [\"Риск 1\", \"Риск 2\", \"Риск 3\"],\n \"opportunities\": [\"Возможность 1\", \"Возможность 2\"],\n \"monetizationStrategy\": \"Стратегия монетизации (1-2 предложения)\",\n \"goToMarket\": \"Как выйти на рынок (1-2 предложения)\"\n },\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Рекомендация\",\n \"recommendedStack\": \"Стек одной строкой\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"+\"],\n \"cons\": [\"-\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40ч\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"Entity (id UUID PK, field1 type, field2 type) - описание\"],\n \"stack\": [{\"tech\": \"Технология\", \"reason\": \"Обоснование\"}],\n \"plan\": [\"Неделя 1: ...\", \"Неделя 2: ...\"],\n \"mcpServers\": [\"slug\"],\n \"patternSlugs\": [\"slug\"],\n \"promptTypes\": [\"тип\"],\n \"mistakes\": [\"Ошибка 1\"],\n \"atomicPrompts\": [\"Промпт 1: В файле lib/X.ts создай функцию...\", \"Промпт 2: ...\", \"Промпт 3: ...\"],\n \"masterPrompt\": \"Ты — senior разработчик. Реализуй проект... (полный промпт)\",\n \"summary\": \"Резюме\",\n \"toolRecommendation\": \"Инструмент\",\n \"aiModelRecommendation\": \"Модель\"\n }]\n}\n3 варианта. Минимум 3 недели в плане. 3 атомарных промпта.`\n\nexport async function POST(req: NextRequest) {\n const { idea, isPro } = await req.json();\n // Free users get limited analysis — only 3 option names/pros/cons, no details\n const isLimited = !isPro;\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", { signal: AbortSignal.timeout(35000),\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-chat\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 8000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n // Clean JSON\n let cleaned = text.replace(/```json\\s*/gi, \"\").replace(/\\s*```$/gi, \"\");\n cleaned = cleaned.replace(/\\\\(?![\"\\\\/bfnrtu])/g, \"\\\\\");\n const jsonMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\n let result;\n if (jsonMatch) {\n try { result = JSON.parse(jsonMatch[0]); } catch(e) {\n const c2 = jsonMatch[0].replace(/[\u0000-\u001f]/g, \" \");\n try { result = JSON.parse(c2); } catch(e2) { result = null; }\n }\n }\n if (!result) result = { error: \"JSON parse failed\", raw: text.substring(0, 500) };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: \"Ошибка генерации: \" + (err.message || \"неизвестная\"), raw: \"\" }, { status: 200 });\n }\n}\n"}, "e69321b1765365a5a3b382c556d8b56d15c3cd7a": {"t": 1785754643.763084, "n": 4673, "h": "2c9dd89c39517bd9", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план для бизнес-идеи. План должен быть настолько конкретным, чтобы AI-агент (Cursor/Claude) смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — с ПОЛНЫМИ полями: имя, тип, описание. Пример: CrawlTarget (id UUID PK, url string, depth number, status enum, createdAt timestamp).\n2. Стек технологий — каждая технология с ОБОСНОВАНИЕМ. Пример: \"BullMQ + Redis — фоновый парсинг тысяч URL без блокировки запросов\".\n3. План — по НЕДЕЛЯМ с конкретными файлами.\n4. Ошибки — специфичные для ниши.\n5. Атомарные промпты — 3 конкретных промпта для Cursor/Claude Code, каждый для одного файла/функции.\n6. Итоговый промпт — один большой промпт для старта проекта.\n\nВерни ТОЛЬКО JSON:\n\nТАКЖЕ ПРОВЕДИ АНАЛИЗ РЫНКА И ДОБАВЬ ПОЛЯ:\n \"marketAnalysis\": {\n \"marketSize\": \"Оценка рынка (1-2 предложения)\",\n \"targetAudience\": \"Целевая аудитория\",\n \"competitors\": [\"Конкурент 1: описание\", \"Конкурент 2: описание\", \"Конкурент 3: описание\"],\n \"differentiation\": \"Чем можно выделиться (2-3 предложения)\",\n \"risks\": [\"Риск 1\", \"Риск 2\", \"Риск 3\"],\n \"opportunities\": [\"Возможность 1\", \"Возможность 2\"],\n \"monetizationStrategy\": \"Стратегия монетизации (1-2 предложения)\",\n \"goToMarket\": \"Как выйти на рынок (1-2 предложения)\"\n },\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Рекомендация\",\n \"recommendedStack\": \"Стек одной строкой\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"+\"],\n \"cons\": [\"-\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40ч\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"Entity (id UUID PK, field1 type, field2 type) - описание\"],\n \"stack\": [{\"tech\": \"Технология\", \"reason\": \"Обоснование\"}],\n \"plan\": [\"Неделя 1: ...\", \"Неделя 2: ...\"],\n \"mcpServers\": [\"slug\"],\n \"patternSlugs\": [\"slug\"],\n \"promptTypes\": [\"тип\"],\n \"mistakes\": [\"Ошибка 1\"],\n \"atomicPrompts\": [\"Промпт 1: В файле lib/X.ts создай функцию...\", \"Промпт 2: ...\", \"Промпт 3: ...\"],\n \"masterPrompt\": \"Ты — senior разработчик. Реализуй проект... (полный промпт)\",\n \"summary\": \"Резюме\",\n \"toolRecommendation\": \"Инструмент\",\n \"aiModelRecommendation\": \"Модель\"\n }]\n}\n3 варианта. Минимум 3 недели в плане. 3 атомарных промпта.`\n\nexport async function POST(req: NextRequest) {\n const { idea, isPro } = await req.json();\n // Free users get limited analysis — only 3 option names/pros/cons, no details\n const isLimited = !isPro;\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", { signal: AbortSignal.timeout(35000),\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 8000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "fe85813f6cd5875d776fb37b95ab60da08844b35": {"t": 1785754646.0010245, "n": 4637, "h": "74f38f0f6602bbed", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план для бизнес-идеи. План должен быть настолько конкретным, чтобы AI-агент (Cursor/Claude) смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — с ПОЛНЫМИ полями: имя, тип, описание. Пример: CrawlTarget (id UUID PK, url string, depth number, status enum, createdAt timestamp).\n2. Стек технологий — каждая технология с ОБОСНОВАНИЕМ. Пример: \"BullMQ + Redis — фоновый парсинг тысяч URL без блокировки запросов\".\n3. План — по НЕДЕЛЯМ с конкретными файлами.\n4. Ошибки — специфичные для ниши.\n5. Атомарные промпты — 3 конкретных промпта для Cursor/Claude Code, каждый для одного файла/функции.\n6. Итоговый промпт — один большой промпт для старта проекта.\n\nВерни ТОЛЬКО JSON:\n\nТАКЖЕ ПРОВЕДИ АНАЛИЗ РЫНКА И ДОБАВЬ ПОЛЯ:\n \"marketAnalysis\": {\n \"marketSize\": \"Оценка рынка (1-2 предложения)\",\n \"targetAudience\": \"Целевая аудитория\",\n \"competitors\": [\"Конкурент 1: описание\", \"Конкурент 2: описание\", \"Конкурент 3: описание\"],\n \"differentiation\": \"Чем можно выделиться (2-3 предложения)\",\n \"risks\": [\"Риск 1\", \"Риск 2\", \"Риск 3\"],\n \"opportunities\": [\"Возможность 1\", \"Возможность 2\"],\n \"monetizationStrategy\": \"Стратегия монетизации (1-2 предложения)\",\n \"goToMarket\": \"Как выйти на рынок (1-2 предложения)\"\n },\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Рекомендация\",\n \"recommendedStack\": \"Стек одной строкой\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"+\"],\n \"cons\": [\"-\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40ч\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"Entity (id UUID PK, field1 type, field2 type) - описание\"],\n \"stack\": [{\"tech\": \"Технология\", \"reason\": \"Обоснование\"}],\n \"plan\": [\"Неделя 1: ...\", \"Неделя 2: ...\"],\n \"mcpServers\": [\"slug\"],\n \"patternSlugs\": [\"slug\"],\n \"promptTypes\": [\"тип\"],\n \"mistakes\": [\"Ошибка 1\"],\n \"atomicPrompts\": [\"Промпт 1: В файле lib/X.ts создай функцию...\", \"Промпт 2: ...\", \"Промпт 3: ...\"],\n \"masterPrompt\": \"Ты — senior разработчик. Реализуй проект... (полный промпт)\",\n \"summary\": \"Резюме\",\n \"toolRecommendation\": \"Инструмент\",\n \"aiModelRecommendation\": \"Модель\"\n }]\n}\n3 варианта. Минимум 3 недели в плане. 3 атомарных промпта.`\n\nexport async function POST(req: NextRequest) {\n const { idea, isPro } = await req.json();\n // Free users get limited analysis — only 3 option names/pros/cons, no details\n const isLimited = !isPro;\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 8000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "00b5046e2c720b0a2190bee39eb7553e26ab9754": {"t": 1785754647.271821, "n": 4081, "h": "1f8d3d603ddbda66", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план для бизнес-идеи. План должен быть настолько конкретным, чтобы AI-агент (Cursor/Claude) смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — с ПОЛНЫМИ полями: имя, тип, описание. Пример: CrawlTarget (id UUID PK, url string, depth number, status enum, createdAt timestamp).\n2. Стек технологий — каждая технология с ОБОСНОВАНИЕМ. Пример: \"BullMQ + Redis — фоновый парсинг тысяч URL без блокировки запросов\".\n3. План — по НЕДЕЛЯМ с конкретными файлами.\n4. Ошибки — специфичные для ниши.\n5. Атомарные промпты — 3 конкретных промпта для Cursor/Claude Code, каждый для одного файла/функции.\n6. Итоговый промпт — один большой промпт для старта проекта.\n\nВерни ТОЛЬКО JSON:\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Рекомендация\",\n \"recommendedStack\": \"Стек одной строкой\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"+\"],\n \"cons\": [\"-\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40ч\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"Entity (id UUID PK, field1 type, field2 type) - описание\"],\n \"stack\": [{\"tech\": \"Технология\", \"reason\": \"Обоснование\"}],\n \"plan\": [\"Неделя 1: ...\", \"Неделя 2: ...\"],\n \"mcpServers\": [\"slug\"],\n \"patternSlugs\": [\"slug\"],\n \"promptTypes\": [\"тип\"],\n \"mistakes\": [\"Ошибка 1\"],\n \"atomicPrompts\": [\"Промпт 1: В файле lib/X.ts создай функцию...\", \"Промпт 2: ...\", \"Промпт 3: ...\"],\n \"masterPrompt\": \"Ты — senior разработчик. Реализуй проект... (полный промпт)\",\n \"summary\": \"Резюме\",\n \"toolRecommendation\": \"Инструмент\",\n \"aiModelRecommendation\": \"Модель\"\n }]\n}\n3 варианта. Минимум 3 недели в плане. 3 атомарных промпта.`\n\nexport async function POST(req: NextRequest) {\n const { idea, isPro } = await req.json();\n // Free users get limited analysis — only 3 option names/pros/cons, no details\n const isLimited = !isPro;\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 8000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "9fe36be870eb44555a72c1cabe6340fff333c0e1": {"t": 1785754649.203, "n": 4081, "h": "34688fdca198554e", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план для бизнес-идеи. План должен быть настолько конкретным, чтобы AI-агент (Cursor/Claude) смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — с ПОЛНЫМИ полями: имя, тип, описание. Пример: CrawlTarget (id UUID PK, url string, depth number, status enum, createdAt timestamp).\n2. Стек технологий — каждая технология с ОБОСНОВАНИЕМ. Пример: \"BullMQ + Redis — фоновый парсинг тысяч URL без блокировки запросов\".\n3. План — по НЕДЕЛЯМ с конкретными файлами.\n4. Ошибки — специфичные для ниши.\n5. Атомарные промпты — 3 конкретных промпта для Cursor/Claude Code, каждый для одного файла/функции.\n6. Итоговый промпт — один большой промпт для старта проекта.\n\nВерни ТОЛЬКО JSON:\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Рекомендация\",\n \"recommendedStack\": \"Стек одной строкой\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"+\"],\n \"cons\": [\"-\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40ч\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"Entity (id UUID PK, field1 type, field2 type) - описание\"],\n \"stack\": [{\"tech\": \"Технология\", \"reason\": \"Обоснование\"}],\n \"plan\": [\"Неделя 1: ...\", \"Неделя 2: ...\"],\n \"mcpServers\": [\"slug\"],\n \"patternSlugs\": [\"slug\"],\n \"promptTypes\": [\"тип\"],\n \"mistakes\": [\"Ошибка 1\"],\n \"atomicPrompts\": [\"Промпт 1: В файле lib/X.ts создай функцию...\", \"Промпт 2: ...\", \"Промпт 3: ...\"],\n \"masterPrompt\": \"Ты — senior разработчик. Реализуй проект... (полный промпт)\",\n \"summary\": \"Резюме\",\n \"toolRecommendation\": \"Инструмент\",\n \"aiModelRecommendation\": \"Модель\"\n }]\n}\n3 варианта. Минимум 3 недели в плане. 3 атомарных промпта.`\n\nexport async function POST(req: NextRequest) {\n const { idea, isPro } = await req.json();\n // Free users get limited analysis — only 3 option names/pros/cons, no details\n const isLimited = !isPro;\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 6000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "ec85868795e72213a7181351c2a67ce757a2e3af": {"t": 1785754650.4705367, "n": 4203, "h": "693b4380eeb4f4f1", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план. Он должен быть настолько конкретным, чтобы новичок понял суть, а AI-агент смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — бизнес-специфичные имена с типами полей. НЕ использовать User/Project/Settings. Пример: CrawlTarget (id, url, lastCrawled, status), AuditReport (id, siteId, score, errors[], createdAt).\n2. Не противоречить в стеке. Выбрал Next.js — весь документ про Next.js.\n3. Ошибки — нишевые. Не \"пишите тесты\", а \"Блокировка IP парсера через Cloudflare\".\n4. План — 5-8 КОНКРЕТНЫХ шагов с файлами/таблицами/пакетами. Не \"Основной функционал\", а \"Создать коллекцию Article в Strapi: поля title, content, category. Связь one-to-many с Category. Права: editor read/write\".\n5. Каждый шаг: (а) что создаётся, (б) ключевые пакеты, (в) что работает после шага.\n\nВерни ТОЛЬКО JSON:\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Какой вариант выбрать и почему. 2-3 предложения.\",\n \"recommendedStack\": \"Стек (Next.js + Prisma + PostgreSQL + Tailwind)\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание подхода\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"Плюс 1\", \"Плюс 2\"],\n \"cons\": [\"Минус 1\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40 часов\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"EntityName (id, field1, field2) - описание\", \"...\"],\n \"plan\": [\n \"Шаг 1: Конкретно что создаётся, какие пакеты, что заработает\",\n \"Шаг 2: ...\"\n ],\n \"mcpServers\": [\"slug1\"],\n \"patternSlugs\": [\"slug1\"],\n \"promptTypes\": [\"тип1\"],\n \"mistakes\": [\"Нишевая ошибка 1\", \"Нишевая ошибка 2\"],\n \"summary\": \"Резюме, 2-3 предложения\",\n \"toolRecommendation\": \"Каким инструментом делать и почему\",\n \"aiModelRecommendation\": \"Какую модель использовать и почему\"\n }]\n}\n3 варианта. От простого к сложному. План — 5-8 шагов.`;\n\nexport async function POST(req: NextRequest) {\n const { idea, isPro } = await req.json();\n // Free users get limited analysis — only 3 option names/pros/cons, no details\n const isLimited = !isPro;\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 6000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "f46d66cf537c89a97b2b89337fee7ea4cc89362f": {"t": 1785754652.0823817, "n": 4692, "h": "af35d38df94bb89d", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план. Он должен быть настолько конкретным, чтобы новичок понял суть, а AI-агент смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — бизнес-специфичные имена с типами полей. НЕ использовать User/Project/Settings. Пример: CrawlTarget (id, url, lastCrawled, status), AuditReport (id, siteId, score, errors[], createdAt).\n2. Не противоречить в стеке. Выбрал Next.js — весь документ про Next.js.\n3. Ошибки — нишевые. Не \"пишите тесты\", а \"Блокировка IP парсера через Cloudflare\".\n4. План — 5-8 КОНКРЕТНЫХ шагов с файлами/таблицами/пакетами. Не \"Основной функционал\", а \"Создать коллекцию Article в Strapi: поля title, content, category. Связь one-to-many с Category. Права: editor read/write\".\n5. Каждый шаг: (а) что создаётся, (б) ключевые пакеты, (в) что работает после шага.\n\nВерни ТОЛЬКО JSON:\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Какой вариант выбрать и почему. 2-3 предложения.\",\n \"recommendedStack\": \"Стек (Next.js + Prisma + PostgreSQL + Tailwind)\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание подхода\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"Плюс 1\", \"Плюс 2\"],\n \"cons\": [\"Минус 1\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40 часов\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"EntityName (id, field1, field2) - описание\", \"...\"],\n \"plan\": [\n \"Шаг 1: Конкретно что создаётся, какие пакеты, что заработает\",\n \"Шаг 2: ...\"\n ],\n \"mcpServers\": [\"slug1\"],\n \"patternSlugs\": [\"slug1\"],\n \"promptTypes\": [\"тип1\"],\n \"mistakes\": [\"Нишевая ошибка 1\", \"Нишевая ошибка 2\"],\n \"summary\": \"Резюме, 2-3 предложения\",\n \"toolRecommendation\": \"Каким инструментом делать и почему\",\n \"aiModelRecommendation\": \"Какую модель использовать и почему\"\n }]\n}\n3 варианта. От простого к сложному. План — 5-8 шагов.`;\n\nexport async function POST(req: NextRequest) {\n const { idea, isPro } = await req.json();\n // Free users get limited analysis — only 3 option names/pros/cons, no details\n const isLimited = !isPro;\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 6000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n if (isLimited && result.options) {\n // Strip detailed fields for free users\n result.options = result.options.map((opt: any) => ({\n name: opt.name, description: opt.description, complexity: opt.complexity,\n mvpDays: opt.mvpDays, pros: opt.pros, cons: opt.cons, monetization: opt.monetization,\n costDev: opt.costDev, costAi: opt.costAi, costServer: opt.costServer,\n summary: opt.summary, toolRecommendation: opt.toolRecommendation,\n }));\n }\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "2965a8b19dd5487494e1c05a213d90118feac27b": {"t": 1785754653.4249659, "n": 4087, "h": "f2543559932d7f66", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план. Он должен быть настолько конкретным, чтобы новичок понял суть, а AI-агент смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — бизнес-специфичные имена с типами полей. НЕ использовать User/Project/Settings. Пример: CrawlTarget (id, url, lastCrawled, status), AuditReport (id, siteId, score, errors[], createdAt).\n2. Не противоречить в стеке. Выбрал Next.js — весь документ про Next.js.\n3. Ошибки — нишевые. Не \"пишите тесты\", а \"Блокировка IP парсера через Cloudflare\".\n4. План — 5-8 КОНКРЕТНЫХ шагов с файлами/таблицами/пакетами. Не \"Основной функционал\", а \"Создать коллекцию Article в Strapi: поля title, content, category. Связь one-to-many с Category. Права: editor read/write\".\n5. Каждый шаг: (а) что создаётся, (б) ключевые пакеты, (в) что работает после шага.\n\nВерни ТОЛЬКО JSON:\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Какой вариант выбрать и почему. 2-3 предложения.\",\n \"recommendedStack\": \"Стек (Next.js + Prisma + PostgreSQL + Tailwind)\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание подхода\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"Плюс 1\", \"Плюс 2\"],\n \"cons\": [\"Минус 1\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40 часов\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"EntityName (id, field1, field2) - описание\", \"...\"],\n \"plan\": [\n \"Шаг 1: Конкретно что создаётся, какие пакеты, что заработает\",\n \"Шаг 2: ...\"\n ],\n \"mcpServers\": [\"slug1\"],\n \"patternSlugs\": [\"slug1\"],\n \"promptTypes\": [\"тип1\"],\n \"mistakes\": [\"Нишевая ошибка 1\", \"Нишевая ошибка 2\"],\n \"summary\": \"Резюме, 2-3 предложения\",\n \"toolRecommendation\": \"Каким инструментом делать и почему\",\n \"aiModelRecommendation\": \"Какую модель использовать и почему\"\n }]\n}\n3 варианта. От простого к сложному. План — 5-8 шагов.`;\n\nexport async function POST(req: NextRequest) {\n const { idea } = await req.json();\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 6000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "02e4ce90cf2d6daca5c1996b8befa1aa459e5e43": {"t": 1785754654.81164, "n": 4087, "h": "4cb40dadc74399a4", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nconst SYSTEM_PROMPT = `Ты — Senior Product Manager и Staff Engineer. Создай детальный архитектурный план. Он должен быть настолько конкретным, чтобы новичок понял суть, а AI-агент смог по нему писать код без галлюцинаций.\n\nЖЁСТКИЕ ПРАВИЛА:\n1. Сущности БД — бизнес-специфичные имена с типами полей. НЕ использовать User/Project/Settings. Пример: CrawlTarget (id, url, lastCrawled, status), AuditReport (id, siteId, score, errors[], createdAt).\n2. Не противоречить в стеке. Выбрал Next.js — весь документ про Next.js.\n3. Ошибки — нишевые. Не \"пишите тесты\", а \"Блокировка IP парсера через Cloudflare\".\n4. План — 5-8 КОНКРЕТНЫХ шагов с файлами/таблицами/пакетами. Не \"Основной функционал\", а \"Создать коллекцию Article в Strapi: поля title, content, category. Связь one-to-many с Category. Права: editor read/write\".\n5. Каждый шаг: (а) что создаётся, (б) ключевые пакеты, (в) что работает после шага.\n\nВерни ТОЛЬКО JSON:\n{\n \"productType\": \"Тип\",\n \"expertRecommendation\": \"Какой вариант выбрать и почему. 2-3 предложения.\",\n \"recommendedStack\": \"Стек (Next.js + Prisma + PostgreSQL + Tailwind)\",\n \"options\": [{\n \"name\": \"Название\",\n \"description\": \"Описание подхода\",\n \"complexity\": 5,\n \"mvpDays\": \"7-10 дней\",\n \"pros\": [\"Плюс 1\", \"Плюс 2\"],\n \"cons\": [\"Минус 1\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"30-40 часов\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"EntityName (id, field1, field2) - описание\", \"...\"],\n \"plan\": [\n \"Шаг 1: Конкретно что создаётся, какие пакеты, что заработает\",\n \"Шаг 2: ...\"\n ],\n \"mcpServers\": [\"slug1\"],\n \"patternSlugs\": [\"slug1\"],\n \"promptTypes\": [\"тип1\"],\n \"mistakes\": [\"Нишевая ошибка 1\", \"Нишевая ошибка 2\"],\n \"summary\": \"Резюме, 2-3 предложения\",\n \"toolRecommendation\": \"Каким инструментом делать и почему\",\n \"aiModelRecommendation\": \"Какую модель использовать и почему\"\n }]\n}\n3 варианта. От простого к сложному. План — 5-8 шагов.`;\n\nexport async function POST(req: NextRequest) {\n const { idea } = await req.json();\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Too short\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"No key\" }, { status: 500 });\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: SYSTEM_PROMPT }, { role: \"user\", content: idea }], max_tokens: 4000, temperature: 0.4 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "4a528d3da2946ab713d42d9a410218d4f58ee8ec": {"t": 1785754656.101596, "n": 3486, "h": "c87cb8dd9d220fd2", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nexport async function POST(req: NextRequest) {\n const { idea } = await req.json();\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Слишком короткое описание\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"API key not configured\" }, { status: 500 });\n\n const systemPrompt = `Ты — AI-Архитектор. Проанализируй бизнес-идею и верни ТОЛЬКО JSON (без markdown):\n\n{\n \"productType\": \"Тип продукта (SaaS/Бот/Лендинг/CRM/Маркетплейс/API)\",\n \"expertRecommendation\": \"Краткая экспертная рекомендация: какой вариант выбрать и почему. 2-3 предложения.\",\n \"options\": [\n {\n \"name\": \"Название варианта (например: Быстрый MVP)\",\n \"description\": \"Краткое описание подхода, 1-2 предложения\",\n \"complexity\": число 1-10,\n \"mvpDays\": \"Оценка (5-7 дней)\",\n \"pros\": [\"Плюс 1\", \"Плюс 2\", \"Плюс 3\"],\n \"cons\": [\"Минус 1\", \"Минус 2\"],\n \"monetization\": \"Модель\",\n \"costDev\": \"20-30 часов\",\n \"costAi\": \"15/мес\",\n \"costServer\": \"5/мес\",\n \"entities\": [\"Сущность1 - описание\", \"Сущность2\"],\n \"plan\": [\"Этап 1: ...\", \"Этап 2: ...\", \"Этап 3: ...\"],\n \"mcpServers\": [\"slug1\", \"slug2\"],\n \"patternSlugs\": [\"slug1\", \"slug2\"],\n \"promptTypes\": [\"тип1\", \"тип2\"],\n \"mistakes\": [\"Ошибка 1\", \"Ошибка 2\"],\n \"summary\": \"Резюме варианта, 2-3 предложения\"\n }\n ]\n}\n\nСоздай РОВНО 3 варианта: от простого к сложному. Первый — быстрый MVP, второй — сбалансированный, третий — максимальный. Варианты должны реально отличаться архитектурой и стеком, а не формулировками.`;\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: idea }], max_tokens: 3000, temperature: 0.5 }),\n });\n const data = await res.json();\n const text = data.choices?.[0]?.message?.content || \"\";\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n // Enrich each option: resolve slugs to actual DB entities\n if (result.options) {\n for (const opt of result.options) {\n if (opt.patternSlugs?.length) opt.patterns = await db.buildPattern.findMany({ where: { slug: { in: opt.patternSlugs }, isPublished: true }, select: { title: true, slug: true, difficulty: true } });\n else opt.patterns = [];\n if (opt.mcpServers?.length) opt.mcp = await db.mCPServer.findMany({ where: { slug: { in: opt.mcpServers } }, select: { name: true, slug: true, category: true } });\n else opt.mcp = [];\n if (opt.promptTypes?.length) opt.prompts = await db.promptBlueprint.findMany({ where: { OR: opt.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) }, select: { title: true, slug: true, category: true }, take: 5 });\n else opt.prompts = [];\n }\n }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}, "6da0191c05584a150c6568c1e535cff96ba4e03e": {"t": 1785754657.5105467, "n": 3452, "h": "2e1b90cc6aaf4764", "c": "import { NextRequest, NextResponse } from \"next/server\";\nimport { getDb } from \"@/lib/db/index\";\n\nexport async function POST(req: NextRequest) {\n const { idea } = await req.json();\n if (!idea || idea.length < 10) return NextResponse.json({ error: \"Слишком короткое описание\" }, { status: 400 });\n\n const db = await getDb();\n const settings = await db.siteSettings.findUnique({ where: { id: \"main\" } });\n const key = settings?.deepseekApiKey || process.env.DEEPSEEK_API_KEY || \"\";\n if (!key) return NextResponse.json({ error: \"API key not configured\" }, { status: 500 });\n\n const systemPrompt = `Ты — AI-Архитектор. Проанализируй бизнес-идею и верни ТОЛЬКО JSON (без markdown):\n\n{\n \"productType\": \"Тип продукта (SaaS/Бот/Лендинг/CRM/Маркетплейс/API/Другое)\",\n \"complexity\": число 1-10,\n \"mvpDays\": \"Оценка MVP (например: 5-7 дней)\",\n \"monetization\": \"Модель монетизации\",\n \"entities\": [\"Сущность1 - описание\", \"Сущность2 - описание\", ...],\n \"integrations\": [\"Интеграция1\", \"Интеграция2\", ...],\n \"mcpServers\": [\"mcp-сервер1\", \"mcp-сервер2\", ...],\n \"promptTypes\": [\"тип промпта1\", \"тип промпта2\", ...],\n \"patternSlugs\": [\"slug-паттерна1\", ...],\n \"mistakes\": [\"Типичная ошибка 1\", \"Типичная ошибка 2\", ...],\n \"costDev\": \"Часы разработки (например: 20-30 часов)\",\n \"costAi\": \"AI-расходы (например: 15/мес)\",\n \"costServer\": \"Сервер (например: 5/мес)\",\n \"plan\": [\"Этап 1: ...\", \"Этап 2: ...\", \"Этап 3: ...\"],\n \"summary\": \"Краткое резюме проекта, 2-3 предложения\"\n}`;\n\n try {\n const res = await fetch(\"https://api.deepseek.com/v1/chat/completions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\", Authorization: \"Bearer \" + key },\n body: JSON.stringify({ model: \"deepseek-v4-flash\", messages: [{ role: \"system\", content: systemPrompt }, { role: \"user\", content: idea }], max_tokens: 2000, temperature: 0.5 }),\n });\n const data = await res.json();\n if (!res.ok) return NextResponse.json({ error: \"DeepSeek API error: \" + (data.error?.message || res.status), raw: \"\", patterns: [], mcp: [], prompts: [] });\n const text = data.choices?.[0]?.message?.content || \"\";\n\n // Extract JSON from response\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n const result = jsonMatch ? JSON.parse(jsonMatch[0]) : { error: \"Parse failed\", raw: text };\n\n // Enrich: resolve pattern slugs to actual patterns\n if (result.patternSlugs?.length > 0) {\n result.patterns = await db.buildPattern.findMany({\n where: { slug: { in: result.patternSlugs }, isPublished: true },\n select: { title: true, slug: true, difficulty: true, stack: true },\n });\n } else { result.patterns = []; }\n\n // Enrich: resolve MCP servers\n if (result.mcpServers?.length > 0) {\n result.mcp = await db.mCPServer.findMany({\n where: { slug: { in: result.mcpServers } },\n select: { name: true, slug: true, category: true },\n });\n } else { result.mcp = []; }\n\n // Enrich: resolve prompts\n if (result.promptTypes?.length > 0) {\n result.prompts = await db.promptBlueprint.findMany({\n where: { OR: result.promptTypes.map((t: string) => ({ category: { contains: t, mode: \"insensitive\" as const } })) },\n select: { title: true, slug: true, category: true }, take: 5,\n });\n } else { result.prompts = []; }\n\n return NextResponse.json(result);\n } catch (err: any) {\n return NextResponse.json({ error: err.message }, { status: 500 });\n }\n}\n"}}}}