import { useState, useEffect, useCallback } from 'react'; import { api, DocTreeNode, DocContent } from './api'; import { DocTree } from './components/DocTree'; import { Editor } from './components/Editor'; import { SearchBar } from './components/SearchBar'; import { VersionHistory } from './components/VersionHistory'; import AgentChat from './components/AgentChat'; type View = 'editor' | 'history'; export default function App() { const agentApiBase = window.location.protocol === 'file:' ? 'http://127.0.0.1:3890' : ''; const [tree, setTree] = useState([]); const [currentDoc, setCurrentDoc] = useState(null); const [currentPath, setCurrentPath] = useState(''); const [view, setView] = useState('editor'); const [sidebarOpen, setSidebarOpen] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [agentPanelOpen, setAgentPanelOpen] = useState(true); const [importing, setImporting] = useState(false); const [importMessage, setImportMessage] = useState(null); const refreshTree = useCallback(async () => { try { const t = await api.getTree(); setTree(t); } catch (err: any) { setError(`加载文档树失败: ${err.message}`); } }, []); const openDoc = useCallback(async (docPath: string) => { setLoading(true); setError(null); try { const doc = await api.getDoc(docPath); setCurrentDoc(doc); setCurrentPath(docPath); setView('editor'); } catch (err: any) { setError(`加载文档失败: ${err.message}`); } finally { setLoading(false); } }, []); const saveDoc = useCallback(async (title: string, body: string) => { if (!currentPath) return; setLoading(true); try { const doc = await api.updateDoc(currentPath, title, body); setCurrentDoc(doc); await refreshTree(); } catch (err: any) { setError(`保存失败: ${err.message}`); } finally { setLoading(false); } }, [currentPath, refreshTree]); const createDoc = useCallback(async (parentPath: string) => { const name = prompt('文档文件名(不含 .md):'); if (!name) return; const title = prompt('文档标题:') || name; const docPath = parentPath ? `${parentPath}/${name}.md` : `${name}.md`; try { const doc = await api.createDoc(docPath, title, `# ${title}\n\n在这里开始写作...\n`); setCurrentDoc(doc); setCurrentPath(docPath); await refreshTree(); } catch (err: any) { setError(`创建失败: ${err.message}`); } }, [refreshTree]); const deleteDoc = useCallback(async () => { if (!currentPath) return; if (!confirm(`确认删除 ${currentPath}?`)) return; try { await api.deleteDoc(currentPath); setCurrentDoc(null); setCurrentPath(''); await refreshTree(); } catch (err: any) { setError(`删除失败: ${err.message}`); } }, [currentPath, refreshTree]); const importFolder = useCallback(async () => { const bridge = (window as any).hololake?.knowledge; if (!bridge?.importFolder) { setError('本地文件夹导入只在 HoloLake 桌面 App 中提供'); return; } setImporting(true); setError(null); setImportMessage(null); try { const result = await bridge.importFolder(); if (result.cancelled) return; if (!result.imported) { setImportMessage(`没有找到可导入的文档;已跳过 ${result.skipped || 0} 个文件`); return; } await refreshTree(); if (result.firstDocument) await openDoc(result.firstDocument); const details = [ `已导入 ${result.imported} 篇文档`, result.assets ? `${result.assets} 个图片资源` : '', result.skipped ? `跳过 ${result.skipped} 个暂不支持的文件` : '', result.failed?.length ? `${result.failed.length} 个文件失败` : '', ].filter(Boolean).join(' · '); setImportMessage(details); } catch (err: any) { setError(`导入失败: ${err.message}`); } finally { setImporting(false); } }, [openDoc, refreshTree]); useEffect(() => { refreshTree(); }, [refreshTree]); return (
{/* 顶栏 */}
{currentDoc && ( <> )}
{/* 主体 */}
{/* 侧栏 */} {sidebarOpen && ( )} {/* 内容区 */}
{error && (
{error}
)} {loading &&
加载中...
} {!currentDoc && !loading && (

HOLOLAKE KNOWLEDGE

把已有资料带进知识空间

选择一个本地文件夹。HoloLake 会把可读文档整理成页面,并为这次导入保留 Git 版本记录。

支持 Markdown、TXT、CSV、JSON 与 YAML
)} {currentDoc && view === 'editor' && ( )} {currentDoc && view === 'history' && ( )}
{/* Agent 面板 */} {agentPanelOpen && ( )}
); }