123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
import React from 'react'
|
||
import { GalleryHorizontal } from 'lucide-react'
|
||
import { cn } from '@/utils/cn'
|
||
|
||
interface SlideFile {
|
||
id: string
|
||
title?: string
|
||
summary?: string
|
||
path?: string
|
||
filename?: string
|
||
}
|
||
|
||
interface SlideOutlineOutput {
|
||
slide_files?: SlideFile[]
|
||
project_path?: string
|
||
}
|
||
|
||
export interface SlideOutlineActionProps {
|
||
/** action 名称(如"正在初始化PPT大纲") */
|
||
name?: string
|
||
/** 参数(如主标题) */
|
||
arguments?: string[]
|
||
/** tool_output,包含 slide_files */
|
||
toolOutput?: unknown
|
||
/** 点击整体卡片的回调(对应 BlockAction 的 onClick) */
|
||
onClick?: () => void
|
||
className?: string
|
||
}
|
||
|
||
/**
|
||
* slide_init 专属渲染组件
|
||
* 对应 Remix: BlockAction + SlideOutline (compact)
|
||
*/
|
||
function InnerSlideOutlineAction({
|
||
name,
|
||
arguments: args,
|
||
toolOutput,
|
||
onClick,
|
||
className,
|
||
}: SlideOutlineActionProps) {
|
||
const output = toolOutput as SlideOutlineOutput | undefined
|
||
const slideFiles = output?.slide_files || []
|
||
|
||
return (
|
||
<div
|
||
className={cn(
|
||
'flex flex-col my-2 rounded-lg border border-border/60 bg-background overflow-hidden',
|
||
'w-full max-w-[480px]',
|
||
onClick && 'cursor-pointer hover:border-border transition-colors',
|
||
className,
|
||
)}
|
||
onClick={onClick}
|
||
role={onClick ? 'button' : undefined}
|
||
tabIndex={onClick ? 0 : undefined}
|
||
onKeyDown={onClick ? e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick() } } : undefined}
|
||
>
|
||
{/* 标题栏 */}
|
||
<div className="shrink-0 h-9 px-3 flex items-center gap-1.5 border-b border-border/50 bg-muted/40">
|
||
<GalleryHorizontal className="w-4 h-4 text-muted-foreground shrink-0" />
|
||
<span className="text-sm font-medium text-foreground shrink-0">
|
||
{name || '初始化幻灯片'}
|
||
</span>
|
||
{args && args.length > 0 && (
|
||
<span className="text-sm text-muted-foreground truncate">
|
||
{args.join(' ')}
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
{/* 幻灯片大纲列表 */}
|
||
<div className="flex-1 overflow-auto max-h-52">
|
||
{slideFiles.length === 0 ? (
|
||
<div className="flex flex-col gap-2 p-3">
|
||
{[1, 2, 3].map(i => (
|
||
<div key={i} className="flex gap-3 animate-pulse">
|
||
<div className="w-7 h-4 bg-muted rounded mt-1 shrink-0" />
|
||
<div className="flex-1 space-y-1.5">
|
||
<div className="h-4 bg-muted rounded w-3/4" />
|
||
<div className="h-3 bg-muted rounded w-full" />
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<div className="divide-y divide-border/40">
|
||
{slideFiles.map((slide, index) => (
|
||
<div key={slide.id || index} className="flex bg-background hover:bg-muted/20 transition-colors">
|
||
<span className="shrink-0 w-8 py-3 pl-3 text-sm text-muted-foreground italic">
|
||
P{index + 1}
|
||
</span>
|
||
<div className="flex-1 px-3 py-3 flex flex-col gap-1 min-w-0">
|
||
{slide.title ? (
|
||
<h3 className="text-sm font-medium text-foreground leading-tight">
|
||
{slide.title}
|
||
</h3>
|
||
) : (
|
||
<div className="h-4 bg-muted rounded animate-pulse w-1/2" />
|
||
)}
|
||
{slide.summary ? (
|
||
<p className="text-xs text-muted-foreground leading-relaxed line-clamp-2">
|
||
{slide.summary}
|
||
</p>
|
||
) : (
|
||
<div className="h-3 bg-muted rounded animate-pulse w-3/4" />
|
||
)}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 底部渐变遮罩(对应 Remix BlockAction 的渐变效果) */}
|
||
{slideFiles.length > 3 && (
|
||
<div className="h-8 absolute left-0 bottom-0 bg-gradient-to-b from-transparent to-background pointer-events-none" />
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export const SlideOutlineAction = React.memo(InnerSlideOutlineAction)
|
||
export default SlideOutlineAction
|