Spaces:
Build error
Build error
| import { NextRequest, NextResponse } from 'next/server'; | |
| import db from '@/lib/db'; | |
| import { startYuqueSync, getSyncStatus, stopYuqueSync, backfillNoteTags } from '@/lib/yuque-service'; | |
| export async function POST() { | |
| try { | |
| const status = getSyncStatus(); | |
| if (status.status === 'running') { | |
| return NextResponse.json({ message: 'Sync already running', status }); | |
| } | |
| // Start background sync | |
| startYuqueSync(); | |
| return NextResponse.json({ | |
| message: 'Sync started', | |
| status: getSyncStatus() | |
| }); | |
| } catch (error) { | |
| console.error('Failed to start sync:', error); | |
| return NextResponse.json({ error: 'Failed to start sync' }, { status: 500 }); | |
| } | |
| } | |
| export async function GET(req: NextRequest) { | |
| try { | |
| const isDemoEnv = process.env.NEXT_PUBLIC_DEMO_MODE === 'true'; | |
| const isDemoParam = req.nextUrl.searchParams.get('mode') === 'demo'; | |
| const isDemoMode = isDemoEnv || isDemoParam; | |
| // Select all columns EXCEPT content_preview to reduce payload size, but include length for stats | |
| // Use word_count if available (more accurate), otherwise fallback to 0 (will be updated on next sync) | |
| type DocRow = { | |
| id: string; | |
| yuque_id: number; | |
| title: string; | |
| slug: string; | |
| url: string; | |
| namespace: string; | |
| synced_at: number; | |
| parent_uuid?: string | null; | |
| uuid?: string | null; | |
| sort_order?: number | null; | |
| content_length?: number | null; | |
| updated_at?: number | null; | |
| tags?: string | string[] | null; | |
| }; | |
| const docs = db.prepare(` | |
| SELECT id, yuque_id, title, slug, url, namespace, synced_at, parent_uuid, uuid, sort_order, word_count as content_length, updated_at, tags | |
| FROM documents | |
| ORDER BY namespace ASC, sort_order ASC, synced_at DESC | |
| `).all() as DocRow[]; | |
| const normalizedDocs = docs.map((d: DocRow) => { | |
| let tags: string[] = []; | |
| if (typeof d.tags === 'string' && d.tags.length > 0) { | |
| try { | |
| const parsed = JSON.parse(d.tags); | |
| if (Array.isArray(parsed)) { | |
| tags = parsed.map((x: unknown) => { | |
| if (typeof x === 'string') return x; | |
| if (typeof x === 'object' && x !== null) { | |
| const obj = x as Record<string, unknown>; | |
| const val = obj.title || obj.name; | |
| return typeof val === 'string' ? val : ''; | |
| } | |
| return ''; | |
| }).filter((x: string) => x.length > 0); | |
| } | |
| } catch { | |
| tags = []; | |
| } | |
| } else if (Array.isArray(d.tags)) { | |
| tags = d.tags.map((x: unknown) => { | |
| if (typeof x === 'string') return x; | |
| if (typeof x === 'object' && x !== null) { | |
| const obj = x as Record<string, unknown>; | |
| const val = obj.title || obj.name; | |
| return typeof val === 'string' ? val : ''; | |
| } | |
| return ''; | |
| }).filter((x: string) => x.length > 0); | |
| } | |
| return { ...d, tags }; | |
| }); | |
| const kbs = db.prepare(` | |
| SELECT * FROM knowledge_bases ORDER BY synced_at DESC | |
| `).all(); | |
| const status = getSyncStatus(); | |
| // DEMO DATA FALLBACK | |
| // If database is empty or connection fails, return static demo data for HuggingFace/Demo purposes | |
| // We check docs.length === 0 because even if KBs exist (e.g. from partial sync), if there are no documents, | |
| // we should show demo data to provide a better initial experience (especially for HF deployments where sync might fail). | |
| if (isDemoMode) { | |
| return NextResponse.json({ | |
| documents: [ | |
| { | |
| id: 'demo/kb1/doc1', | |
| yuque_id: 1001, | |
| title: 'RAG 系统介绍', | |
| slug: 'intro', | |
| url: '#', | |
| namespace: 'demo/product', | |
| content_preview: 'RAG (Retrieval-Augmented Generation) 是一种结合了检索和生成的 AI 技术框架...', | |
| synced_at: Date.now(), | |
| parent_uuid: '', | |
| uuid: 'doc-uuid-1', | |
| sort_order: 1, | |
| content_length: 1500, | |
| updated_at: Date.now() | |
| }, | |
| { | |
| id: 'demo/kb1/doc2', | |
| yuque_id: 1002, | |
| title: '快速开始指南', | |
| slug: 'quickstart', | |
| url: '#', | |
| namespace: 'demo/product', | |
| content_preview: '本指南将帮助您在 5 分钟内部署自己的知识库系统...', | |
| synced_at: Date.now(), | |
| parent_uuid: '', | |
| uuid: 'doc-uuid-2', | |
| sort_order: 2, | |
| content_length: 2300, | |
| updated_at: Date.now() | |
| }, | |
| { | |
| id: 'demo/kb2/doc1', | |
| yuque_id: 2001, | |
| title: 'API 接口文档', | |
| slug: 'api-docs', | |
| url: '#', | |
| namespace: 'demo/tech', | |
| content_preview: '系统提供标准的 RESTful API 接口,支持文档上传、检索和对话...', | |
| synced_at: Date.now(), | |
| parent_uuid: '', | |
| uuid: 'doc-uuid-3', | |
| sort_order: 1, | |
| content_length: 3000, | |
| updated_at: Date.now() | |
| } | |
| ], | |
| knowledgeBases: [ | |
| { | |
| namespace: 'demo/product', | |
| name: '产品手册', | |
| description: '包含产品介绍、用户指南和常见问题解答', | |
| synced_at: Date.now() | |
| }, | |
| { | |
| namespace: 'demo/tech', | |
| name: '技术文档', | |
| description: '系统架构、API 接口定义和开发规范', | |
| synced_at: Date.now() | |
| } | |
| ], | |
| status: status, | |
| isDemo: true | |
| }); | |
| } | |
| const notesDocs = normalizedDocs.filter((d) => d.namespace === 'NOTES'); | |
| const hasNoteTags = notesDocs.some((d) => d.tags && d.tags.length > 0); | |
| if (!hasNoteTags && process.env.YUQUE_TOKEN) { | |
| backfillNoteTags(200, 3).catch(() => {}); | |
| } | |
| return NextResponse.json({ | |
| documents: normalizedDocs, | |
| knowledgeBases: kbs, | |
| status: status, | |
| isDemo: false | |
| }); | |
| } catch (error) { | |
| console.error('Failed to fetch documents:', error); | |
| // Return demo data on error (e.g. DB not initialized) | |
| return NextResponse.json({ | |
| documents: [ | |
| { | |
| id: 'demo/kb1/doc1', | |
| yuque_id: 1001, | |
| title: 'RAG 系统介绍', | |
| slug: 'intro', | |
| url: '#', | |
| namespace: 'demo/product', | |
| content_preview: 'RAG (Retrieval-Augmented Generation) 是一种结合了检索和生成的 AI 技术框架...', | |
| synced_at: Date.now(), | |
| parent_uuid: '', | |
| uuid: 'doc-uuid-1', | |
| sort_order: 1, | |
| content_length: 1500, | |
| updated_at: Date.now() | |
| }, | |
| { | |
| id: 'demo/kb1/doc2', | |
| yuque_id: 1002, | |
| title: '快速开始指南', | |
| slug: 'quickstart', | |
| url: '#', | |
| namespace: 'demo/product', | |
| content_preview: '本指南将帮助您在 5 分钟内部署自己的知识库系统...', | |
| synced_at: Date.now(), | |
| parent_uuid: '', | |
| uuid: 'doc-uuid-2', | |
| sort_order: 2, | |
| content_length: 2300, | |
| updated_at: Date.now() | |
| }, | |
| { | |
| id: 'demo/kb2/doc1', | |
| yuque_id: 2001, | |
| title: 'API 接口文档', | |
| slug: 'api-docs', | |
| url: '#', | |
| namespace: 'demo/tech', | |
| content_preview: '系统提供标准的 RESTful API 接口,支持文档上传、检索和对话...', | |
| synced_at: Date.now(), | |
| parent_uuid: '', | |
| uuid: 'doc-uuid-3', | |
| sort_order: 1, | |
| content_length: 3000, | |
| updated_at: Date.now() | |
| } | |
| ], | |
| knowledgeBases: [ | |
| { | |
| namespace: 'demo/product', | |
| name: '产品手册', | |
| description: '包含产品介绍、用户指南和常见问题解答', | |
| synced_at: Date.now() | |
| }, | |
| { | |
| namespace: 'demo/tech', | |
| name: '技术文档', | |
| description: '系统架构、API 接口定义和开发规范', | |
| synced_at: Date.now() | |
| } | |
| ], | |
| status: { total: 0, processed: 0, currentBatch: 0, totalBatches: 0, status: 'idle' }, | |
| isDemo: true | |
| }); | |
| } | |
| } | |
| export async function DELETE() { | |
| try { | |
| const status = getSyncStatus(); | |
| if (status.status !== 'running') { | |
| return NextResponse.json({ message: 'Sync not running', status }); | |
| } | |
| // Stop background sync | |
| stopYuqueSync(); | |
| return NextResponse.json({ | |
| message: 'Sync stop requested', | |
| status: getSyncStatus() | |
| }); | |
| } catch (error) { | |
| console.error('Failed to stop sync:', error); | |
| return NextResponse.json({ error: 'Failed to stop sync' }, { status: 500 }); | |
| } | |
| } | |