duqing2026's picture
同步 hf
9ed89c8
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 });
}
}