Files
zclaw_openfang/desktop/src/components/presentation/PresentationContainer.tsx
iven 30b2515f07
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
feat(audit): 审计修复第四轮 — 跨会话搜索、LLM压缩集成、Presentation渲染器
- S9: MessageSearch 新增 Session/Global 双模式,Global 调用 VikingStorage memory_search
- M4b: LLM 压缩器集成到 kernel AgentLoop,支持 use_llm 配置切换
- M4c: 压缩时自动提取记忆到 VikingStorage (runtime + tauri 双路径)
- H6: 新增 ChartRenderer(recharts)、Document/Slideshow 完整渲染
- 累计修复 23 项,整体完成度 ~72%,真实可用率 ~80%
2026-03-27 11:44:14 +08:00

160 lines
5.2 KiB
TypeScript

/**
* Presentation Container
*
* Main container for smart presentation rendering.
*
* Features:
* - Auto-detects presentation type from data structure
* - Supports manual type switching
* - Manages presentation state
* - Provides export functionality
*/
import React, { useState, useMemo, useCallback } from 'react';
import { invoke } from '@tauri-apps/api/core';
import type { PresentationType, PresentationAnalysis } from './types';
import { TypeSwitcher } from './TypeSwitcher';
import { QuizRenderer } from './renderers/QuizRenderer';
const SlideshowRenderer = React.lazy(() => import('./renderers/SlideshowRenderer').then(m => ({ default: m.SlideshowRenderer })));
const DocumentRenderer = React.lazy(() => import('./renderers/DocumentRenderer').then(m => ({ default: m.DocumentRenderer })));
const ChartRenderer = React.lazy(() => import('./renderers/ChartRenderer').then(m => ({ default: m.ChartRenderer })));
interface PresentationContainerProps {
/** Pipeline output data */
data: unknown;
/** Pipeline ID (reserved for future use) */
pipelineId?: string;
/** Supported presentation types (from pipeline config) */
supportedTypes?: PresentationType[];
/** Default presentation type */
defaultType?: PresentationType;
/** Allow user to switch types */
allowSwitch?: boolean;
/** Called when export is triggered (reserved for future use) */
onExport?: (format: string) => void;
/** Custom className */
className?: string;
}
export function PresentationContainer({
data,
supportedTypes,
defaultType,
allowSwitch = true,
className = '',
}: PresentationContainerProps) {
const [analysis, setAnalysis] = useState<PresentationAnalysis | null>(null);
const [currentType, setCurrentType] = useState<PresentationType | null>(null);
const [isAnalyzing, setIsAnalyzing] = useState(true);
useMemo(() => {
const runAnalysis = async () => {
setIsAnalyzing(true);
try {
const result = await invoke<PresentationAnalysis>('analyze_presentation', { data });
setAnalysis(result);
if (defaultType) {
setCurrentType(defaultType);
} else if (result) {
setCurrentType(result.recommendedType);
}
} catch (error) {
console.error('Failed to analyze presentation:', error);
setCurrentType('document');
} finally {
setIsAnalyzing(false);
}
};
runAnalysis();
}, [data, defaultType]);
const handleTypeChange = useCallback((type: PresentationType) => {
setCurrentType(type);
}, []);
const availableTypes = useMemo(() => {
if (supportedTypes && supportedTypes.length > 0) {
return supportedTypes.filter((t): t is PresentationType => t !== 'auto');
}
return (['quiz', 'slideshow', 'document', 'chart', 'whiteboard'] as PresentationType[]);
}, [supportedTypes]);
const renderContent = () => {
if (isAnalyzing) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
<p className="ml-3 text-gray-500">...</p>
</div>
);
}
switch (currentType) {
case 'quiz':
return <QuizRenderer data={data as Parameters<typeof QuizRenderer>[0]['data']} />;
case 'slideshow':
return (
<React.Suspense fallback={<div className="h-64 animate-pulse bg-gray-100" />}>
<SlideshowRenderer data={data as Parameters<typeof SlideshowRenderer>[0]['data']} />
</React.Suspense>
);
case 'document':
return (
<React.Suspense fallback={<div className="h-64 animate-pulse bg-gray-100" />}>
<DocumentRenderer data={data as Parameters<typeof DocumentRenderer>[0]['data']} />
</React.Suspense>
);
case 'whiteboard':
return (
<div className="flex flex-col items-center justify-center h-64 bg-gray-50 gap-3">
<span className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-amber-100 text-amber-700">
</span>
<p className="text-gray-500"></p>
</div>
);
case 'chart':
return (
<React.Suspense fallback={<div className="h-64 animate-pulse bg-gray-100" />}>
<ChartRenderer data={data as Parameters<typeof ChartRenderer>[0]['data']} />
</React.Suspense>
);
default:
return (
<div className="flex items-center justify-center h-64 bg-gray-50">
<p className="text-gray-500"></p>
</div>
);
}
};
return (
<div className={`flex flex-col h-full ${className}`}>
{allowSwitch && (
<div className="border-b border-gray-200 bg-gray-50 p-3">
<TypeSwitcher
availableTypes={availableTypes}
currentType={currentType || 'document'}
analysis={analysis || undefined}
onTypeChange={handleTypeChange}
/>
</div>
)}
<div className="flex-1 overflow-auto">
{renderContent()}
</div>
</div>
);
}
export default PresentationContainer;