import { Alert, Button, Card, Empty, Input, Space, Table, Typography } from 'antd' import { useCallback, useEffect, useMemo, useState } from 'react' import { useSearchParams } from 'react-router-dom' import type { ApiError } from '../lib/api' import { isOrchestratorDbUnavailable, listRawNotes, type RawNoteRecord } from '../lib/content' export default function RawNotesPage() { const [searchParams, setSearchParams] = useSearchParams() const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [notes, setNotes] = useState([]) const [total, setTotal] = useState(0) const page = useMemo(() => { const raw = Number(searchParams.get('page') || '1') return Number.isFinite(raw) && raw > 0 ? raw : 1 }, [searchParams]) const pageSize = useMemo(() => { const raw = Number(searchParams.get('page_size') || '20') return Number.isFinite(raw) && raw > 0 ? Math.min(raw, 200) : 20 }, [searchParams]) const query = useMemo(() => String(searchParams.get('query') || '').trim(), [searchParams]) const [queryInput, setQueryInput] = useState(query) useEffect(() => { setQueryInput(query) }, [query]) const setParam = useCallback( (patch: Record) => { const next = new URLSearchParams(searchParams) Object.entries(patch).forEach(([k, v]) => { const val = String(v || '').trim() if (val) next.set(k, val) else next.delete(k) }) setSearchParams(next) }, [searchParams, setSearchParams], ) const loadList = useCallback(async () => { setLoading(true) setError(null) try { const res = await listRawNotes({ limit: pageSize, offset: (page - 1) * pageSize, query: query || undefined, }) setNotes(res.notes || []) setTotal(res.total || 0) } catch (e) { setError(e as ApiError) } finally { setLoading(false) } }, [page, pageSize, query]) useEffect(() => { void loadList() }, [loadList]) const columns = useMemo( () => [ { title: 'ID', dataIndex: 'id', width: 90 }, { title: '作者', dataIndex: 'author', width: 160, render: (v: unknown) => (v ? String(v) : '—'), }, { title: 'URL', dataIndex: 'url', width: 340, ellipsis: true, render: (v: unknown) => { const url = String(v || '').trim() if (!url) return '—' return ( {url} ) }, }, { title: '内容', dataIndex: 'content', ellipsis: true, render: (v: unknown) => { const text = String(v || '').trim() return text ? {text} : '—' }, }, { title: '来源', dataIndex: 'source_platform', width: 120, render: (v: unknown) => (v ? String(v) : '—'), }, { title: '创建时间', dataIndex: 'created_at', width: 200, render: (v: unknown) => (v ? String(v) : '—'), }, ], [], ) const dbMissing = isOrchestratorDbUnavailable(error) return (
原始笔记库 setQueryInput(e.target.value)} onSearch={(value) => setParam({ query: value.trim() || undefined, page: '1' })} style={{ width: 320 }} /> } loading={loading} > {dbMissing ? ( 内容库数据库不可用 服务端返回 503,通常表示 ORCHESTRATOR_DB_PATH 未配置、路径不存在或 DB 尚未初始化。
处理方式:
1)配置 ORCHESTRATOR_DB_PATH 指向可读的 sqlite 文件(默认 orchestrator/data/mvp.db)
2)初始化 DB:python Spider_XHS/orchestrator/db_init.py
} /> ) : error ? ( ) : null} setParam({ page: String(nextPage), page_size: String(nextSize) }), }} /> ) }