Nitish kumar
Upload folder using huggingface_hub
c20f20c verified
'use client';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Plus, Trash2, ChevronUp, ChevronDown } from 'lucide-react';
import { nanoid } from 'nanoid';
import type { SceneOutline } from '@/lib/types/generation';
interface OutlinesEditorProps {
outlines: SceneOutline[];
onChange: (outlines: SceneOutline[]) => void;
onConfirm: () => void;
onBack: () => void;
isLoading?: boolean;
}
export function OutlinesEditor({
outlines,
onChange,
onConfirm,
onBack,
isLoading = false,
}: OutlinesEditorProps) {
const addOutline = () => {
const newOutline: SceneOutline = {
id: nanoid(8),
type: 'slide',
title: '',
description: '',
keyPoints: [],
order: outlines.length + 1,
};
onChange([...outlines, newOutline]);
};
const updateOutline = (index: number, updates: Partial<SceneOutline>) => {
const newOutlines = [...outlines];
newOutlines[index] = { ...newOutlines[index], ...updates };
onChange(newOutlines);
};
const removeOutline = (index: number) => {
const newOutlines = outlines.filter((_, i) => i !== index);
// Update order
newOutlines.forEach((outline, i) => {
outline.order = i + 1;
});
onChange(newOutlines);
};
const moveOutline = (index: number, direction: 'up' | 'down') => {
const newIndex = direction === 'up' ? index - 1 : index + 1;
if (newIndex < 0 || newIndex >= outlines.length) return;
const newOutlines = [...outlines];
[newOutlines[index], newOutlines[newIndex]] = [newOutlines[newIndex], newOutlines[index]];
// Update order
newOutlines.forEach((outline, i) => {
outline.order = i + 1;
});
onChange(newOutlines);
};
const updateKeyPoints = (index: number, keyPointsText: string) => {
const keyPoints = keyPointsText
.split('\n')
.map((p) => p.trim())
.filter(Boolean);
updateOutline(index, { keyPoints });
};
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h2 className="text-lg font-semibold">场景大纲</h2>
<p className="text-sm text-muted-foreground">
共 {outlines.length} 个场景,可编辑、添加、删除或重排序
</p>
</div>
<Button variant="outline" onClick={addOutline} disabled={isLoading}>
<Plus className="size-4 mr-1" />
添加场景
</Button>
</div>
<div className="space-y-4">
{outlines.map((outline, index) => (
<Card key={outline.id} className="relative">
<CardHeader className="pb-3">
<div className="flex items-center gap-3">
<div className="flex flex-col gap-1">
<Button
variant="ghost"
size="icon"
onClick={() => moveOutline(index, 'up')}
disabled={index === 0 || isLoading}
className="size-6"
>
<ChevronUp className="size-4" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => moveOutline(index, 'down')}
disabled={index === outlines.length - 1 || isLoading}
className="size-6"
>
<ChevronDown className="size-4" />
</Button>
</div>
<div className="flex-1">
<CardTitle className="text-base flex items-center gap-2">
<span className="bg-primary text-primary-foreground size-6 rounded-full flex items-center justify-center text-sm">
{index + 1}
</span>
<Input
value={outline.title}
onChange={(e) => updateOutline(index, { title: e.target.value })}
placeholder="场景标题"
className="flex-1"
disabled={isLoading}
/>
</CardTitle>
</div>
<Select
value={outline.type}
onValueChange={(value) =>
updateOutline(index, {
type: value as SceneOutline['type'],
})
}
disabled={isLoading}
>
<SelectTrigger className="w-28">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="slide">幻灯片</SelectItem>
<SelectItem value="quiz">测验</SelectItem>
</SelectContent>
</Select>
<Button
variant="ghost"
size="icon"
onClick={() => removeOutline(index)}
disabled={isLoading}
>
<Trash2 className="size-4 text-destructive" />
</Button>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label>场景描述</Label>
<Textarea
value={outline.description}
onChange={(e) => updateOutline(index, { description: e.target.value })}
placeholder="简短描述这个场景的目的和内容"
rows={2}
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label>关键要点(每行一个)</Label>
<Textarea
value={outline.keyPoints?.join('\n') || ''}
onChange={(e) => updateKeyPoints(index, e.target.value)}
placeholder="输入关键要点,每行一个"
rows={3}
disabled={isLoading}
/>
</div>
{outline.type === 'quiz' && (
<div className="p-3 bg-muted/50 rounded-lg space-y-3">
<Label className="text-sm font-medium">测验配置</Label>
<div className="grid grid-cols-3 gap-3">
<div className="space-y-1">
<Label className="text-xs">题目数量</Label>
<Input
type="number"
value={outline.quizConfig?.questionCount || 3}
onChange={(e) =>
updateOutline(index, {
quizConfig: {
...outline.quizConfig,
questionCount: parseInt(e.target.value) || 3,
difficulty: outline.quizConfig?.difficulty || 'medium',
questionTypes: outline.quizConfig?.questionTypes || ['single'],
},
})
}
min={1}
max={10}
disabled={isLoading}
/>
</div>
<div className="space-y-1">
<Label className="text-xs">难度</Label>
<Select
value={outline.quizConfig?.difficulty || 'medium'}
onValueChange={(value) =>
updateOutline(index, {
quizConfig: {
...outline.quizConfig,
difficulty: value as 'easy' | 'medium' | 'hard',
questionCount: outline.quizConfig?.questionCount || 3,
questionTypes: outline.quizConfig?.questionTypes || ['single'],
},
})
}
disabled={isLoading}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="easy">简单</SelectItem>
<SelectItem value="medium">中等</SelectItem>
<SelectItem value="hard">困难</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1">
<Label className="text-xs">题型</Label>
<Select
value={outline.quizConfig?.questionTypes?.[0] || 'single'}
onValueChange={(value) =>
updateOutline(index, {
quizConfig: {
...outline.quizConfig,
questionTypes: [value as 'single' | 'multiple' | 'text'],
questionCount: outline.quizConfig?.questionCount || 3,
difficulty: outline.quizConfig?.difficulty || 'medium',
},
})
}
disabled={isLoading}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="single">单选</SelectItem>
<SelectItem value="multiple">多选</SelectItem>
<SelectItem value="text">简答</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
)}
</CardContent>
</Card>
))}
</div>
{outlines.length === 0 && (
<Card className="p-8 text-center">
<p className="text-muted-foreground mb-4">暂无场景大纲</p>
<Button variant="outline" onClick={addOutline} disabled={isLoading}>
<Plus className="size-4 mr-1" />
添加第一个场景
</Button>
</Card>
)}
{/* Actions */}
<div className="flex justify-between pt-4">
<Button variant="outline" onClick={onBack} disabled={isLoading}>
返回修改需求
</Button>
<Button onClick={onConfirm} disabled={isLoading || outlines.length === 0}>
{isLoading ? '生成中...' : '确认并生成课程'}
</Button>
</div>
</div>
);
}