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
- 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%
160 lines
5.2 KiB
TypeScript
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;
|