feat: add Script Builder page with chat UI, code blocks, preview modal, and save dialog
- ScriptCodeBlock: collapsed code preview with syntax highlighting (first 5 lines) - ScriptBuilderInput: auto-resize chat input with Enter-to-send - ScriptBuilderChat: message list with markdown rendering and code blocks - ScriptPreviewModal: fullscreen script viewer with line numbers - SaveToLibraryDialog: save script with name, description, category, team sharing - ScriptBuilderPage: language selector, session management, FlowPilot handoff - Added route, sidebar nav item (fuchsia), and mobile nav entry Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
114
frontend/src/components/script-builder/ScriptBuilderChat.tsx
Normal file
114
frontend/src/components/script-builder/ScriptBuilderChat.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Bot, User, Loader2 } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { MarkdownContent } from '@/components/ui/MarkdownContent'
|
||||
import { ScriptCodeBlock } from './ScriptCodeBlock'
|
||||
import type { ScriptBuilderMessage } from '@/types'
|
||||
|
||||
interface ScriptBuilderChatProps {
|
||||
messages: ScriptBuilderMessage[]
|
||||
language: string
|
||||
onViewScript: (script: string, filename: string | null) => void
|
||||
onSaveScript: () => void
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export function ScriptBuilderChat({
|
||||
messages,
|
||||
language,
|
||||
onViewScript,
|
||||
onSaveScript,
|
||||
isLoading,
|
||||
}: ScriptBuilderChatProps) {
|
||||
const bottomRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
||||
}, [messages.length, isLoading])
|
||||
|
||||
if (messages.length === 0 && !isLoading) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center p-8">
|
||||
<div className="text-center max-w-md">
|
||||
<div className="w-14 h-14 rounded-2xl bg-gradient-brand flex items-center justify-center mx-auto mb-4">
|
||||
<Bot size={28} className="text-[#101114]" />
|
||||
</div>
|
||||
<h2 className="text-lg font-heading font-bold text-foreground mb-2">
|
||||
Script Builder
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Describe the script you need and AI will generate it for you. You can iterate on the script,
|
||||
preview it, and save it to your library.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||
{messages.map((msg, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={cn(
|
||||
"flex gap-3",
|
||||
msg.role === 'user' ? "justify-end" : "justify-start"
|
||||
)}
|
||||
>
|
||||
{msg.role === 'assistant' && (
|
||||
<div className="shrink-0 w-8 h-8 rounded-lg bg-[rgba(6,182,212,0.1)] flex items-center justify-center mt-0.5">
|
||||
<Bot size={16} className="text-cyan-400" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"max-w-[85%] rounded-xl px-4 py-3 text-sm",
|
||||
msg.role === 'user'
|
||||
? "bg-[rgba(6,182,212,0.08)] border border-[rgba(6,182,212,0.15)] text-foreground"
|
||||
: "glass-card-static"
|
||||
)}
|
||||
>
|
||||
{msg.role === 'assistant' ? (
|
||||
<>
|
||||
<MarkdownContent content={msg.content} />
|
||||
{msg.script && (
|
||||
<ScriptCodeBlock
|
||||
script={msg.script}
|
||||
filename={msg.script_filename ?? null}
|
||||
lineCount={msg.line_count ?? null}
|
||||
language={language}
|
||||
onViewFull={() => onViewScript(msg.script!, msg.script_filename ?? null)}
|
||||
onSave={onSaveScript}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="whitespace-pre-wrap">{msg.content}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{msg.role === 'user' && (
|
||||
<div className="shrink-0 w-8 h-8 rounded-lg bg-[rgba(255,255,255,0.06)] flex items-center justify-center mt-0.5">
|
||||
<User size={16} className="text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex gap-3 justify-start">
|
||||
<div className="shrink-0 w-8 h-8 rounded-lg bg-[rgba(6,182,212,0.1)] flex items-center justify-center">
|
||||
<Bot size={16} className="text-cyan-400" />
|
||||
</div>
|
||||
<div className="glass-card-static rounded-xl px-4 py-3 text-sm flex items-center gap-2">
|
||||
<Loader2 size={14} className="animate-spin text-cyan-400" />
|
||||
<span className="text-muted-foreground">Generating script...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user