mixvideo-v2/apps/desktop/src/components/AiClassificationPreviewDial...

205 lines
8.5 KiB
TypeScript

import React, { useState } from 'react';
import { XMarkIcon, ClipboardIcon, CheckIcon } from '@heroicons/react/24/outline';
import { AiClassificationPreview } from '../types/aiClassification';
import { LoadingSpinner } from './LoadingSpinner';
interface AiClassificationPreviewDialogProps {
/** 是否显示对话框 */
isOpen: boolean;
/** 预览数据 */
preview: AiClassificationPreview | null;
/** 是否正在加载 */
loading: boolean;
/** 关闭回调 */
onClose: () => void;
}
/**
* AI分类预览对话框组件
* 遵循前端开发规范的对话框设计,实现提示词实时预览功能
*/
export const AiClassificationPreviewDialog: React.FC<AiClassificationPreviewDialogProps> = ({
isOpen,
preview,
loading,
onClose,
}) => {
const [copied, setCopied] = useState(false);
// 复制到剪贴板
const handleCopy = async () => {
if (!preview?.full_prompt) return;
try {
await navigator.clipboard.writeText(preview.full_prompt);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('复制失败:', err);
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 overflow-y-auto">
<div className="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
{/* 背景遮罩 */}
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
onClick={onClose}
/>
{/* 对话框 */}
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-4xl sm:w-full">
{/* 标题栏 */}
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4 border-b border-gray-200">
<div className="flex items-center justify-between">
<div>
<h3 className="text-lg leading-6 font-medium text-gray-900">
AI分类提示词预览
</h3>
<p className="mt-1 text-sm text-gray-500">
</p>
</div>
<button
onClick={onClose}
className="bg-white rounded-md text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
<XMarkIcon className="h-6 w-6" />
</button>
</div>
</div>
{/* 内容区域 */}
<div className="bg-white px-4 pb-4 sm:p-6 sm:pt-4">
{loading ? (
<div className="flex items-center justify-center py-12">
<LoadingSpinner size="large" />
<span className="ml-3 text-gray-600">...</span>
</div>
) : preview ? (
<div className="space-y-6">
{/* 分类统计 */}
<div className="bg-blue-50 rounded-lg p-4">
<div className="flex items-center justify-between">
<div>
<h4 className="text-sm font-medium text-blue-900">
</h4>
<p className="text-2xl font-bold text-blue-600">
{preview.classifications.length}
</p>
</div>
<div className="text-blue-400">
<svg className="h-8 w-8" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clipRule="evenodd" />
</svg>
</div>
</div>
</div>
{/* 分类列表 */}
<div>
<h4 className="text-sm font-medium text-gray-900 mb-3">
</h4>
<div className="space-y-2">
{preview.classifications.map((classification, index) => (
<div key={classification.id} className="flex items-start space-x-3 p-3 bg-gray-50 rounded-lg">
<span className="flex-shrink-0 w-6 h-6 bg-blue-100 text-blue-800 text-xs font-medium rounded-full flex items-center justify-center">
{index + 1}
</span>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-gray-900">
{classification.name}
</p>
<p className="text-sm text-gray-600 mt-1">
{classification.prompt_text}
</p>
</div>
</div>
))}
</div>
</div>
{/* 完整提示词 */}
<div>
<div className="flex items-center justify-between mb-3">
<h4 className="text-sm font-medium text-gray-900">
</h4>
<button
onClick={handleCopy}
className="inline-flex items-center px-3 py-1.5 border border-gray-300 shadow-sm text-xs font-medium rounded text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{copied ? (
<>
<CheckIcon className="h-3 w-3 mr-1 text-green-500" />
</>
) : (
<>
<ClipboardIcon className="h-3 w-3 mr-1" />
</>
)}
</button>
</div>
<div className="bg-gray-900 rounded-lg p-4 overflow-auto max-h-96">
<pre className="text-sm text-gray-100 whitespace-pre-wrap font-mono">
{preview.full_prompt}
</pre>
</div>
<p className="mt-2 text-xs text-gray-500">
: {preview.full_prompt.length}
</p>
</div>
{/* 使用说明 */}
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<h5 className="text-sm font-medium text-yellow-800 mb-2">
使
</h5>
<ul className="text-sm text-yellow-700 space-y-1">
<li> AI视频分类功能</li>
<li> </li>
<li> </li>
<li> </li>
</ul>
</div>
</div>
) : (
<div className="text-center py-12">
<div className="text-gray-400 mb-4">
<svg className="mx-auto h-12 w-12" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<h3 className="text-sm font-medium text-gray-900 mb-1">
</h3>
<p className="text-sm text-gray-500">
AI分类
</p>
</div>
)}
</div>
{/* 按钮栏 */}
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
onClick={onClose}
className="w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:w-auto sm:text-sm"
>
</button>
</div>
</div>
</div>
</div>
);
};