diff --git a/app/admin/(protected)/autogen/page.js b/app/admin/(protected)/autogen/page.js index 7803d18..23942de 100644 --- a/app/admin/(protected)/autogen/page.js +++ b/app/admin/(protected)/autogen/page.js @@ -1,5 +1,8 @@ import { requireAdminAuth } from '@/lib/adminAuth'; import AutogenPanel from '@/components/admin/AutogenPanel'; +import ZeroAutogenCard from '@/components/admin/ZeroAutogenCard'; +import Link from 'next/link'; +import { Coffee, ArrowRight } from 'lucide-react'; export const dynamic = 'force-dynamic'; export const metadata = { title: 'Автогенерация' }; @@ -18,11 +21,33 @@ async function engineCall(path) { export default async function AutogenPage() { await requireAdminAuth(); - const [status, queue, topics] = await Promise.all([ + const [status, queue, topics, zeroConfig, zeroNotes] = await Promise.all([ engineCall('/api/autogen/status'), engineCall('/api/autogen/queue'), engineCall('/api/autogen/topics'), + engineCall('/api/admin/zero/config'), + engineCall('/api/admin/zero/notes?limit=5'), ]); - return ; + return ( +
+ + + {/* Блок Зеро — отдельная карточка рядом с категориями статей */} +
+
+
+

+ Заметки от Зеро +

+

AI-персонаж в @zeropostru — отдельный пайплайн

+
+ + Полный раздел + +
+ +
+
+ ); } diff --git a/app/admin/(protected)/zero/page.js b/app/admin/(protected)/zero/page.js new file mode 100644 index 0000000..f9ad21b --- /dev/null +++ b/app/admin/(protected)/zero/page.js @@ -0,0 +1,10 @@ +import { requireAdminAuth } from '@/lib/adminAuth'; +import AdminZero from '@/components/admin/AdminZero'; + +export const dynamic = 'force-dynamic'; +export const metadata = { title: 'Заметки от Зеро' }; + +export default async function AdminZeroPage() { + await requireAdminAuth(); + return ; +} diff --git a/app/admin/api/zero/[...path]/route.js b/app/admin/api/zero/[...path]/route.js new file mode 100644 index 0000000..b84f376 --- /dev/null +++ b/app/admin/api/zero/[...path]/route.js @@ -0,0 +1,41 @@ +/** + * Catch-all proxy для /admin/api/zero/* → engine /api/admin/zero/* + * Auth: session cookie через checkAdminAuth(). + */ +import { NextResponse } from 'next/server'; +import { checkAdminAuth } from '@/lib/adminAuth'; + +const E = process.env.ENGINE_URL || 'http://127.0.0.1:3030'; +const S = process.env.ENGINE_SECRET || 'zeropost_internal_2026'; + +async function proxy(req, { params }) { + if (!(await checkAdminAuth())) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + const resolved = await params; + const tail = (resolved?.path || []).join('/'); + const qs = req.url.split('?')[1]; + const url = `${E}/api/admin/zero${tail ? '/' + tail : ''}${qs ? '?' + qs : ''}`; + + const headers = { + 'x-internal-secret': S, + 'x-user-id': '1', // engine requireAdmin требует is_admin=true; на проде у нас один админ + }; + + let body; + if (req.method !== 'GET' && req.method !== 'HEAD') { + headers['Content-Type'] = 'application/json'; + const raw = await req.text(); + body = raw || undefined; + } + + const res = await fetch(url, { method: req.method, headers, body, cache: 'no-store' }); + const data = await res.json().catch(() => ({ error: 'invalid engine response' })); + return NextResponse.json(data, { status: res.status }); +} + +export const GET = proxy; +export const POST = proxy; +export const PATCH = proxy; +export const PUT = proxy; +export const DELETE = proxy; diff --git a/app/page.js b/app/page.js index 8dfcc10..cf7797d 100644 --- a/app/page.js +++ b/app/page.js @@ -6,12 +6,13 @@ import HeroImage from '@/components/HeroImage'; import Stats from '@/components/Stats'; import NowBlock from '@/components/NowBlock'; import NotesBlock from '@/components/NotesBlock'; +import ZeroBlock from '@/components/ZeroBlock'; import SeriesGrid from '@/components/SeriesGrid'; import CategoryRow from '@/components/CategoryRow'; import PopularBlock from '@/components/PopularBlock'; import RecentBlock from '@/components/RecentBlock'; import Reveal from '@/components/Reveal'; -import { getHomeData, listTags, getStats, getLive, listNotes, listSeries, listCategories } from '@/lib/engine'; +import { getHomeData, listTags, getStats, getLive, listNotes, listSeries, listCategories, listZeroNotes } from '@/lib/engine'; import { Sparkles, ArrowRight } from 'lucide-react'; export const dynamic = 'force-dynamic'; @@ -20,10 +21,10 @@ const CATEGORY_ORDER = ['ai-tools', 'ai-dev', 'automation', 'cybersec']; export default async function HomePage() { let home = { hero: null, byCategory: {}, popular: [], recent: [] }; - let tags = [], stats = null, live = null, notes = [], series = [], categories = []; + let tags = [], stats = null, live = null, notes = [], series = [], categories = [], zeroNotes = []; try { - [home, tags, stats, live, notes, series, categories] = await Promise.all([ + [home, tags, stats, live, notes, series, categories, zeroNotes] = await Promise.all([ getHomeData(), listTags(), getStats(), @@ -31,6 +32,7 @@ export default async function HomePage() { listNotes({ limit: 6 }), listSeries(), listCategories(), + listZeroNotes({ limit: 6 }), ]); } catch (err) { console.error('Home load failed:', err.message); @@ -131,6 +133,15 @@ export default async function HomePage() { )} + {/* ЗЕРО — короткие заметки AI-персонажа */} + {zeroNotes.length > 0 && ( + +
+ +
+
+ )} + {/* КАТЕГОРИЙНЫЕ РЯДЫ */} {CATEGORY_ORDER.map(cat => ( diff --git a/app/zero/page.js b/app/zero/page.js new file mode 100644 index 0000000..2f87467 --- /dev/null +++ b/app/zero/page.js @@ -0,0 +1,75 @@ +import Header from '@/components/Header'; +import Footer from '@/components/Footer'; +import ZeroBlock from '@/components/ZeroBlock'; +import { listZeroNotes, getZeroCharacter } from '@/lib/engine'; +import { Coffee } from 'lucide-react'; + +export const dynamic = 'force-dynamic'; +export const metadata = { + title: 'Заметки от Зеро', + description: 'Короткие посты от AI-персонажа Зеро — мысли программиста о работе, инструментах и забавных багах', +}; + +export default async function ZeroPage() { + const [notes, character] = await Promise.all([ + listZeroNotes({ limit: 100 }), + getZeroCharacter(), + ]); + + return ( + <> +
+
+
+
+ AI-персонаж +
+

+ Заметки от Зеро +

+

+ Короткие посты от первого лица в Telegram-канале{' '} + @zeropostru. + Программист с многолетним опытом, любит копаться под капотом, постоянно носится с кофе. +

+ + {character?.character?.bio && ( +
+
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Зеро +
+
Кто такой Зеро
+
    + {character.character.bio.map((line, i) => ( +
  • — {line}
  • + ))} +
+
+
+
+ )} +
+ + {notes.length > 0 ? ( + + ) : ( +
+
+ +

Зеро ещё не написал ни одной заметки. Скоро появится.

+
+
+ )} +
+