import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import Sidebar from '../components/Sidebar'; import { Video, Globe, Layout, Check, Plus, X } from 'lucide-react'; import { User, SystemSettings as ISystemSettings, Template } from '../types'; import { storage } from '../utils/storage'; export default function SystemSettings() { const navigate = useNavigate(); const [currentUser, setCurrentUser] = useState(null); const [settings, setSettings] = useState({ frameCount: 12, framePositions: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60], apiEndpoint: '', apiKey: '', defaultTemplate: '', frameMode: 'uniform' }); const [templates, setTemplates] = useState([]); const [isSaved, setIsSaved] = useState(false); const [pendingFrameCount, setPendingFrameCount] = useState(null); const [modeModalOpen, setModeModalOpen] = useState(false); useEffect(() => { const user = storage.get('currentUser', null); if (!user) { navigate('/'); return; } setCurrentUser(user); const savedSettings = storage.get('systemSettings', {} as ISystemSettings & { frameMode?: 'uniform' | 'keep' }); const savedTemplates = storage.get('templates', []); if (savedSettings.frameCount) { if (!savedSettings.defaultTemplate && savedTemplates.length > 0) { savedSettings.defaultTemplate = savedTemplates[0].id; } if (!savedSettings.frameMode) savedSettings.frameMode = 'uniform'; if (typeof savedSettings.autoInsertFrames !== 'boolean') savedSettings.autoInsertFrames = false; if (typeof savedSettings.autoInsertDelay !== 'number') savedSettings.autoInsertDelay = 0; setSettings(savedSettings); } else if (savedTemplates.length > 0) { setSettings(prev => ({ ...prev, defaultTemplate: savedTemplates[0].id, frameMode: prev.frameMode || 'uniform', autoInsertFrames: typeof prev.autoInsertFrames === 'boolean' ? prev.autoInsertFrames : false, autoInsertDelay: typeof prev.autoInsertDelay === 'number' ? prev.autoInsertDelay : 0 })); } setTemplates(savedTemplates); }, [navigate]); const round1 = (n: number) => Math.round(n * 10) / 10; const computeFramePositions = (count: number, mode: 'uniform' | 'keep', currentPositions: number[]) => { if (mode === 'uniform') { const positions: number[] = []; for (let i = 1; i <= count; i++) { positions.push(round1((100 / (count + 1)) * i)); } return positions; } const sorted = [...currentPositions].sort((a, b) => a - b); if (count <= sorted.length) { return sorted.slice(0, count); } const need = count - sorted.length; const last = sorted[sorted.length - 1] || 0; const range = 100 - last; for (let i = 1; i <= need; i++) { sorted.push(round1(last + (range / (need + 1)) * i)); } return sorted; }; const handleSave = (e: React.FormEvent) => { e.preventDefault(); const sortedPositions = [...settings.framePositions].sort((a, b) => a - b); const finalSettings = { ...settings, framePositions: sortedPositions, frameCount: sortedPositions.length }; storage.set('systemSettings', finalSettings); setSettings(finalSettings); setIsSaved(true); setTimeout(() => setIsSaved(false), 3000); }; const testApi = async () => { if (!settings.apiEndpoint) { alert('请先输入 API 接口地址'); return; } alert(`正在测试连接到: ${settings.apiEndpoint}\n(模拟测试: 连接成功)`); }; const resetToDefault = () => { if (window.confirm('确定要恢复系统设置出厂设置吗?所有自定义配置将被清除。')) { const defaultSettings: ISystemSettings & { frameMode?: 'uniform' | 'keep' } = { frameCount: 12, framePositions: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60], apiEndpoint: '', apiKey: '', defaultTemplate: templates[0]?.id || '', frameMode: 'uniform', autoInsertFrames: true, autoInsertDelay: 1, autoInsertFrameIndices: [0, 2, 4, 6, 8, 10] }; setSettings(defaultSettings); storage.set('systemSettings', defaultSettings); } }; const resetAllData = () => { if (window.confirm('确定要重置全部数据吗?这将清除所有报告、模板和用户设置。')) { localStorage.clear(); window.location.reload(); } }; if (!currentUser) return null; return (

系统设置

{currentUser.role === 'super' ? '配置全局参数,包括视频抽帧策略与外部 AI API 对接。' : '设置您的默认报告模板。'}

{currentUser.role === 'super' && (

当前共 {settings.framePositions.length} 帧
{ const count = Math.max(1, Math.min(100, parseInt(e.target.value) || 1)); setSettings({ ...settings, frameCount: count }); }} className="input-minimal bg-white" />
{settings.frameMode === 'uniform' ? '整体均匀抽取' : '保持当前抽帧'}
setSettings({ ...settings, autoInsertFrames: e.target.checked })} className="w-4 h-4 accent-accent cursor-pointer" />
setSettings({ ...settings, autoInsertDelay: Math.max(0, parseFloat(e.target.value) || 0) })} className="input-minimal bg-white w-full" />

开启后,选中的视频自动抽帧位置将按顺序自动插入到报告的空置图片占位符中,插满后不再提示。

{settings.framePositions.map((pos, idx) => (
{ const newPos = [...settings.framePositions]; newPos[idx] = Math.min(100, Math.max(0, parseFloat(e.target.value) || 0)); setSettings({ ...settings, framePositions: newPos }); }} className="input-minimal w-full pr-6 text-center" /> % {settings.autoInsertFrames && ( { const current = settings.autoInsertFrameIndices || []; const next = current.includes(idx) ? current.filter(i => i !== idx) : [...current, idx].sort((a, b) => a - b); setSettings({ ...settings, autoInsertFrameIndices: next }); }} className={`absolute top-1 left-1 cursor-pointer transition-colors ${ (settings.autoInsertFrameIndices || []).includes(idx) ? 'text-green-500' : 'text-slate-300' }`} > )}
))}

指定视频进度的百分比位置进行自动抽帧。系统将按照这些位置提取关键帧供 AI 分析。

)} {currentUser.role === 'super' && (

AI 接口集成

setSettings({ ...settings, apiEndpoint: e.target.value })} placeholder="https://api.example.com/v1/generate" className="input-minimal" />
setSettings({ ...settings, apiKey: e.target.value })} placeholder="sk-xxxxxxxxxxxxxxxx" className="input-minimal" />
)}

默认报告模板

新建报告时将自动加载此模板内容,减少重复操作。

{currentUser.role === 'super' && (
)}
{isSaved && ( 设置已保存 )}
{modeModalOpen && (

选择抽帧方式

您将抽取帧数设置为 {pendingFrameCount} 帧,请选择重新计算抽帧位置的方式:

)}
); }