import { useEffect, useMemo, useState, type MouseEvent } from 'react'; import { useLocation } from 'react-router-dom'; import { cancelMindSpaceAgentJob, createMindSpaceAgentJob, createMindSpacePage, createMindSpacePageFromAsset, deleteMindSpaceAsset, deleteMindSpacePage, getMindSpaceAgentJob, getMindSpace, getMindSpacePageDeletePreview, listMindSpaceAgentJobs, listMindSpaceAssets, listMindSpaceCleanupItems, listMindSpacePages, retryMindSpaceAgentJob, runMindSpaceAgentJob, runMindSpaceCleanup, uploadMindSpaceAsset, ApiError, } from '../api/client'; import type { MindSpace, MindSpaceAsset, MindSpaceAgentJob, MindSpaceCategory, MindSpaceCleanupItem, MindSpacePage, MindSpacePageDeletePreview, MindSpaceSaveCategory, PortalUser, } from '../types'; import { buildMindSpaceChatContext } from '../utils/mindspaceChatContext'; import { MindSpaceSpaceChat } from './MindSpaceSpaceChat'; import { assetIcon, assetSourceLabel, buildAssetPreviewFrameConfig, isPreviewableAsset, isWorkAsset, } from '../utils/mindspaceCards'; import { PREVIEW_ASSETS, PREVIEW_PAGES, PREVIEW_SPACE } from '../dev/mindspacePreviewData'; import { MindSpaceFeedCard } from './MindSpaceFeedCard'; import { MindSpacePageDetail } from './MindSpacePageDetail'; import { MindSpaceModal } from './MindSpaceModal'; import { ShareSheet } from './ShareSheet'; import { WorkCover } from './WorkCover'; import { buildAssetSharePayload, type SharePayload } from '../utils/shareChannels'; import { resolvePublicPageUrl, resolvePlazaHomeUrl } from '../utils/publicUrl'; import { PagePreviewFrame } from './PagePreviewFrame'; const CATEGORY_DESCRIPTIONS: Record = { oa: '上传文档、表格和资料,让 AI 生成报告与页面', private: '保存敏感资料,发布前必须完成风险检查与脱敏', public: '管理可公开使用的页面、图片和静态资源', draft: '继续编辑尚未发布或等待安全检查的页面', archive: '保存不再活跃但仍需要保留的内容', }; const CATEGORY_ACTIONS: Partial> = { oa: '进入工作区', private: '进入私人区', public: '查看发布', draft: '继续创作', }; const AGENT_JOBS_PAGE_SIZE = 10; const JOB_STATUS_LABELS: Record = { queued: '排队中', running: '执行中', completed: '已完成', failed: '失败', cancelled: '已取消', timed_out: '超时', }; function formatBytes(bytes: number) { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(1)} MB`; } type AssetFilter = 'all' | 'files' | 'pages'; function canGenerateWithAgent(asset: MindSpaceAsset) { const textLikeMimeTypes = new Set([ 'text/plain', 'text/markdown', 'text/csv', 'text/html', 'application/json', ]); return ( asset.status === 'ready' && asset.scanStatus !== 'blocked' && textLikeMimeTypes.has(asset.mimeType) ); } type MindSpaceRouteSync = { pushHome: () => void; pushCategory: (code: MindSpaceSaveCategory | MindSpaceCategory['code']) => void; pushPage: (pageId: string) => void; }; export function MindSpaceView({ user, previewMode = false, initialPageId, initialCategoryCode, onBack, onLogout, routeSync, }: { user: PortalUser; previewMode?: boolean; initialPageId?: string | null; initialCategoryCode?: MindSpaceSaveCategory | null; onBack: () => void; onLogout: () => void; routeSync?: MindSpaceRouteSync; }) { const [space, setSpace] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const [selectedCategory, setSelectedCategory] = useState(null); const [assets, setAssets] = useState([]); const [pages, setPages] = useState([]); const [selectedPageId, setSelectedPageId] = useState(initialPageId ?? null); const [selectedPageAutoPlaza, setSelectedPageAutoPlaza] = useState(false); const [assetsLoading, setAssetsLoading] = useState(false); const [uploadOpen, setUploadOpen] = useState(false); const [uploading, setUploading] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [pendingDeleteId, setPendingDeleteId] = useState(null); const [newPageOpen, setNewPageOpen] = useState(false); const [newPageTitle, setNewPageTitle] = useState(''); const [newPageContent, setNewPageContent] = useState(''); const [newPageTemplate, setNewPageTemplate] = useState('editorial'); const [creatingPage, setCreatingPage] = useState(false); const [assetFilter, setAssetFilter] = useState('all'); const [previewAssetId, setPreviewAssetId] = useState(null); const [editingAssetId, setEditingAssetId] = useState(null); const [cleanupOpen, setCleanupOpen] = useState(false); const [cleanupItems, setCleanupItems] = useState([]); const [cleanupLoading, setCleanupLoading] = useState(false); const [cleanupRunning, setCleanupRunning] = useState(false); const [selectedCleanupIds, setSelectedCleanupIds] = useState([]); const [agentAsset, setAgentAsset] = useState(null); const [agentInstruction, setAgentInstruction] = useState(''); const [agentJob, setAgentJob] = useState(null); const [agentSubmitting, setAgentSubmitting] = useState(false); const [agentJobsPanelOpen, setAgentJobsPanelOpen] = useState(false); const [agentJobsList, setAgentJobsList] = useState([]); const [agentJobsTotal, setAgentJobsTotal] = useState(0); const [agentJobsPage, setAgentJobsPage] = useState(0); const [agentJobsLoading, setAgentJobsLoading] = useState(false); const [sharePayload, setSharePayload] = useState(null); const [previewPageTarget, setPreviewPageTarget] = useState(null); const [pageDeleteTarget, setPageDeleteTarget] = useState(null); const [pageDeletePreview, setPageDeletePreview] = useState( null, ); const [pageDeleteLoading, setPageDeleteLoading] = useState(false); const [deletingPageId, setDeletingPageId] = useState(null); const [assetDeleteDialog, setAssetDeleteDialog] = useState<{ message: string; references: Array<{ id: string; title: string; publishedOnline?: boolean }>; } | null>(null); const [pageLiveContext, setPageLiveContext] = useState<{ page: MindSpacePage; title: string; summary: string; content: string; } | null>(null); const location = useLocation(); const fromPlaza = useMemo(() => { const params = new URLSearchParams(location.search); return params.get('utm_source') === 'plaza'; }, [location.search]); const plazaReturnUrl = useMemo(() => { const params = new URLSearchParams(location.search); return params.get('return_to') || resolvePlazaHomeUrl(); }, [location.search]); useEffect(() => { if (!selectedPageId) { setPageLiveContext(null); } }, [selectedPageId]); const mindspaceChatContext = useMemo(() => { if (!space) return null; return buildMindSpaceChatContext({ space, ownerUsername: user.username, selectedCategory, selectedPageId, pages, pageLive: pageLiveContext, assets, focusedAsset: agentAsset, route: `${location.pathname}${location.search}`, }); }, [ space, user.username, selectedCategory, selectedPageId, pages, pageLiveContext, assets, agentAsset, location.pathname, location.search, ]); const truncateLabel = (text: string, max = 48) => { const value = String(text ?? '').replace(/\s+/g, ' ').trim(); if (value.length <= max) return value; return `${value.slice(0, max - 1)}…`; }; const showPage = (pageId: string, autoPlaza = false) => { setSelectedPageId(pageId); setSelectedPageAutoPlaza(autoPlaza); routeSync?.pushPage(pageId); }; const openPagePreview = (page: MindSpacePage) => { setPreviewPageTarget(page); }; const openPageDeleteDialog = async (page: MindSpacePage) => { if (previewMode) { setError('预览模式下无法删除页面'); return; } setPageDeleteTarget(page); setPageDeletePreview(null); setPageDeleteLoading(true); setError(null); try { setPageDeletePreview(await getMindSpacePageDeletePreview(page.id)); } catch (err) { setPageDeleteTarget(null); setError(err instanceof Error ? err.message : '无法加载删除预览'); } finally { setPageDeleteLoading(false); } }; const closePageDeleteDialog = () => { if (deletingPageId) return; setPageDeleteTarget(null); setPageDeletePreview(null); }; const confirmDeletePage = async () => { if (!pageDeleteTarget) return; setDeletingPageId(pageDeleteTarget.id); setError(null); try { await deleteMindSpacePage(pageDeleteTarget.id); if (selectedPageId === pageDeleteTarget.id) { closePage(); } setPageDeleteTarget(null); setPageDeletePreview(null); await load(); } catch (err) { setError(err instanceof Error ? err.message : '删除页面失败'); } finally { setDeletingPageId(null); } }; const buildPageCardActions = (page: MindSpacePage) => [ { label: '预览', onClick: (event: MouseEvent) => { event.stopPropagation(); openPagePreview(page); }, }, { label: '编辑', onClick: (event: MouseEvent) => { event.stopPropagation(); showPage(page.id); }, }, { label: '删除', onClick: (event: MouseEvent) => { event.stopPropagation(); void openPageDeleteDialog(page); }, }, ]; const closePage = () => { setSelectedPageId(null); if (selectedCategory) { routeSync?.pushCategory(selectedCategory.code); } else { routeSync?.pushHome(); } }; const closeCategory = () => { setSelectedCategory(null); setUploadOpen(false); setPendingDeleteId(null); routeSync?.pushHome(); }; const loadCleanupItems = async () => { if (previewMode) { setCleanupItems([]); return; } setCleanupLoading(true); try { const result = await listMindSpaceCleanupItems(); setCleanupItems(result.items); setSelectedCleanupIds(result.items.map((item) => item.id)); } catch (err) { setError(err instanceof Error ? err.message : '临时文件扫描失败'); } finally { setCleanupLoading(false); } }; const runCleanup = async () => { if (!selectedCleanupIds.length) return; if (previewMode) { setError(previewBlocked().message); return; } setCleanupRunning(true); setError(null); try { await runMindSpaceCleanup(selectedCleanupIds); setCleanupOpen(false); setCleanupItems([]); setSelectedCleanupIds([]); await load(); } catch (err) { setError(err instanceof Error ? err.message : '清理失败'); } finally { setCleanupRunning(false); } }; const refreshAgentJobsSummary = async () => { if (previewMode) return; const { page } = await listMindSpaceAgentJobs({ limit: 1, offset: 0 }); setAgentJobsTotal(page.total ?? 0); }; const loadAgentJobsPage = async (pageIndex: number) => { if (previewMode) { setAgentJobsList([]); setAgentJobsTotal(0); return; } setAgentJobsLoading(true); setError(null); try { const { items, page } = await listMindSpaceAgentJobs({ limit: AGENT_JOBS_PAGE_SIZE, offset: pageIndex * AGENT_JOBS_PAGE_SIZE, }); setAgentJobsList(items); setAgentJobsPage(pageIndex); setAgentJobsTotal(page.total ?? 0); } catch (err) { setError(err instanceof Error ? err.message : '任务记录加载失败'); } finally { setAgentJobsLoading(false); } }; const openAgentJobsPanel = () => { setSelectedCategory(null); setSelectedPageId(null); setNewPageOpen(false); setAgentJobsPanelOpen(true); routeSync?.pushHome(); void loadAgentJobsPage(0); }; const closeAgentJobsPanel = () => { setAgentJobsPanelOpen(false); }; const refreshAgentJobsPanel = async () => { await refreshAgentJobsSummary(); if (agentJobsPanelOpen) { await loadAgentJobsPage(agentJobsPage); } }; const load = async () => { setLoading(true); setError(null); try { if (previewMode) { setSpace(PREVIEW_SPACE); setPages(PREVIEW_PAGES); return; } const [nextSpace, nextPages] = await Promise.all([ getMindSpace(), listMindSpacePages(), ]); setSpace(nextSpace); setPages(nextPages); await refreshAgentJobsSummary(); } catch (err) { setError(err instanceof Error ? err.message : '空间加载失败'); } finally { setLoading(false); } }; useEffect(() => { void load(); }, []); useEffect(() => { if (initialPageId) setSelectedPageId(initialPageId); }, [initialPageId]); useEffect(() => { if (!space || !initialCategoryCode || initialCategoryCode === 'draft') return; const category = space.categories.find((item) => item.code === initialCategoryCode); if (category) void openCategory(category); }, [space, initialCategoryCode]); useEffect(() => { if (previewMode || !agentJob || !['queued', 'running'].includes(agentJob.status)) return; const timer = window.setInterval(() => { void getMindSpaceAgentJob(agentJob.id) .then(async (nextJob) => { setAgentJob(nextJob); await refreshAgentJobsPanel(); if (nextJob.status === 'completed' && nextJob.resultPageId) { await load(); setAgentAsset(null); setAgentInstruction(''); setSelectedPageId(nextJob.resultPageId); routeSync?.pushPage(nextJob.resultPageId); } }) .catch((err) => { setError(err instanceof Error ? err.message : '任务状态刷新失败'); }); }, 1200); return () => window.clearInterval(timer); }, [agentJob, previewMode]); const openCategory = async (category: MindSpaceCategory) => { setAgentJobsPanelOpen(false); setSelectedCategory(category); setSelectedPageId(null); setNewPageOpen(false); setUploadOpen(false); setPendingDeleteId(null); setAssetsLoading(true); setError(null); try { if (previewMode) { setAssets(PREVIEW_ASSETS[category.code] ?? []); if (category.code === 'draft') { setPages(PREVIEW_PAGES); } return; } setAssets(await listMindSpaceAssets(category.code)); } catch (err) { setError(err instanceof Error ? err.message : '资产加载失败'); } finally { setAssetsLoading(false); } routeSync?.pushCategory(category.code); }; const openUpload = (category?: MindSpaceCategory) => { const target = category ?? selectedCategory ?? space?.categories.find((item) => item.code === 'oa') ?? null; if (target) setSelectedCategory(target); setSelectedFile(null); setUploadOpen(true); }; const closeUpload = () => { if (uploading) return; setUploadOpen(false); setSelectedFile(null); }; const uploadDialogTitle = (category: MindSpaceCategory) => category.code === 'oa' ? '上传资料' : `上传到 ${category.name}`; const submitUpload = async () => { if (!selectedCategory || !selectedFile) return; if (previewMode) { setError(previewBlocked().message); return; } setUploading(true); setError(null); try { await uploadMindSpaceAsset(selectedCategory.id, selectedFile); setUploadOpen(false); setSelectedFile(null); await load(); await openCategory(selectedCategory); } catch (err) { setError(err instanceof Error ? err.message : '上传失败'); } finally { setUploading(false); } }; const removeAsset = async (assetId: string) => { if (!selectedCategory) return; if (previewMode) { setError(previewBlocked().message); return; } setError(null); setAssetDeleteDialog(null); try { await deleteMindSpaceAsset(assetId); setPendingDeleteId(null); await load(); await openCategory(selectedCategory); } catch (err) { if (err instanceof ApiError && err.code === 'asset_in_use') { const details = err.details as | { hint?: string; references?: Array<{ id: string; title: string; publishedOnline?: boolean; }>; } | undefined; const references = details?.references ?? []; setAssetDeleteDialog({ message: details?.hint ?? (references.length ? `无法删除:页面「${truncateLabel(references[0]?.title ?? '未命名')}」正在使用该资产。` : '资产正在被页面使用,不能删除'), references, }); return; } setAssetDeleteDialog({ message: err instanceof Error ? err.message : '删除失败', references: [], }); } }; const usedPercent = space ? Math.min( 100, ((space.quota.usedBytes + space.quota.reservedBytes) / space.quota.quotaBytes) * 100, ) : 0; const openAssetEditor = async (asset: MindSpaceAsset) => { if (previewMode) { setError(previewBlocked().message); return; } const linkedPage = pages.find( (page) => page.sourceAssetId === asset.id && page.status !== 'deleted', ); if (linkedPage) { setPreviewAssetId(null); showPage(linkedPage.id); return; } setEditingAssetId(asset.id); setError(null); try { const page = await createMindSpacePageFromAsset({ assetId: asset.id, title: asset.displayName, }); setPreviewAssetId(null); showPage(page.id); void load(); } catch (err) { setError(err instanceof Error ? err.message : '打开编辑失败'); } finally { setEditingAssetId(null); } }; const openAgentGenerator = (asset: MindSpaceAsset) => { setAgentAsset(asset); setAgentJob(null); setAgentInstruction(`请根据《${asset.displayName}》生成一个中文页面草稿,提炼核心结论、重点信息和可执行建议。`); }; const closeAgentGenerator = () => { if (agentSubmitting) return; setAgentAsset(null); setAgentInstruction(''); setAgentJob(null); }; const submitAgentGenerator = async () => { if (!agentAsset || !agentInstruction.trim()) return; if (previewMode) { setError(previewBlocked().message); return; } setAgentSubmitting(true); setError(null); try { const created = await createMindSpaceAgentJob({ jobType: 'generate_page', instruction: agentInstruction.trim(), allowedAssetIds: [agentAsset.id], outputType: 'page_draft', }); setAgentJob(created); await refreshAgentJobsSummary(); await runMindSpaceAgentJob(created.id); const running = await getMindSpaceAgentJob(created.id); setAgentJob(running); if (agentJobsPanelOpen) await loadAgentJobsPage(0); } catch (err) { setError(err instanceof Error ? err.message : '启动 AI 生成失败'); } finally { setAgentSubmitting(false); } }; const cancelAgentJob = async (jobId: string) => { try { const cancelled = await cancelMindSpaceAgentJob(jobId); setAgentJob((current) => (current?.id === jobId ? cancelled : current)); await refreshAgentJobsPanel(); } catch (err) { setError(err instanceof Error ? err.message : '取消任务失败'); } }; const retryAgentJob = async (jobId: string) => { try { const retried = await retryMindSpaceAgentJob(jobId); setAgentJob((current) => (current?.id === jobId ? retried : current)); await refreshAgentJobsPanel(); await runMindSpaceAgentJob(jobId); const running = await getMindSpaceAgentJob(jobId); setAgentJob((current) => (current?.id === jobId ? running : current)); await refreshAgentJobsPanel(); } catch (err) { setError(err instanceof Error ? err.message : '重试任务失败'); } }; const filteredAssets = assets.filter((asset) => { if (assetFilter === 'files') return !isWorkAsset(asset); if (assetFilter === 'pages') return isWorkAsset(asset); return true; }); const previewAsset = useMemo( () => assets.find((asset) => asset.id === previewAssetId) ?? null, [assets, previewAssetId], ); const previewAssetFrame = previewAsset ? buildAssetPreviewFrameConfig(previewAsset) : null; const createPage = async () => { if (!newPageTitle.trim() || !newPageContent.trim()) return; if (previewMode) { setError(previewBlocked().message); return; } setCreatingPage(true); setError(null); try { const page = await createMindSpacePage({ title: newPageTitle.trim(), content: newPageContent.trim(), templateId: newPageTemplate, }); setNewPageOpen(false); setNewPageTitle(''); setNewPageContent(''); await load(); showPage(page.id); } catch (err) { setError(err instanceof Error ? err.message : '页面创建失败'); } finally { setCreatingPage(false); } }; const agentJobsPageCount = Math.max(1, Math.ceil(agentJobsTotal / AGENT_JOBS_PAGE_SIZE)); const renderAgentJobCard = (job: MindSpaceAgentJob) => (
{job.assets[0]?.displayName ?? '未命名资料'}

{job.instruction}

{JOB_STATUS_LABELS[job.status]}
阶段:{job.progress.stage} {new Date(job.queuedAt).toLocaleString()}
{job.errorMessage &&
{job.errorMessage}
}
{job.resultPageId && ( )} {['queued', 'running'].includes(job.status) && ( )} {job.retryable && ['failed', 'cancelled', 'timed_out'].includes(job.status) && ( )}
); return (
{previewMode && (
UI 预览模式 · 示例数据,无需登录 退出预览
)}
{fromPlaza && ( 返回广场 )}
{!fromPlaza && ( 发现广场 )} {user.displayName}

MINDSPACE

{space?.name ?? '我的空间'}

让每一次 AI 对话、分析和创作,都成为可以继续使用的作品。

逛 Plaza
{space && ( )} {cleanupOpen && (
{cleanupLoading &&

正在扫描可清理的临时文件…

} {!cleanupLoading && cleanupItems.length === 0 &&

当前没有可清理的临时文件。

} {!cleanupLoading && cleanupItems.length > 0 && ( <>

可释放约{' '} {formatBytes( cleanupItems .filter((item) => selectedCleanupIds.includes(item.id)) .reduce((sum, item) => sum + item.sizeBytes, 0), )}

    {cleanupItems.map((item) => (
  • ))}
)}
)}
{loading &&
正在加载个人空间…
} {error && !assetDeleteDialog && (
{error}
)} {space && ( <> {selectedPageId ? ( previewMode ? (

预览模式不加载页面详情,返回后可继续调整首页布局。

) : ( ) ) : newPageOpen ? (

CREATE PAGE

创建页面草稿