Files
resolutionflow/frontend/src/pages/ScriptBuilderPage.tsx
chihlasm 34b0f2ade9 fix: eliminate deprecated cyan, glass-border, and off-palette colors site-wide
- Replace all rgba(6,182,212,...) cyan focus borders and accents with
  rgba(249,115,22,...) ember orange across 21+ component files
- Remove all var(--glass-border) references (undefined variable) with
  var(--color-border-default) across 24 files
- Remove deprecated blur orbs and glass-morphism effects from
  SurveyPage, SurveyThankYouPage, and LoginPage
- Migrate landing.css from hardcoded hex to CSS custom properties
  (~97 replacements for single-source theming)
- Fix off-palette grays in FlowPilotAnalyticsPage chart styling
  (#8891a0 → #848b9b, #18191f → var(--color-bg-card))
- Update stale comments: "cyan brand" → "accent brand" in GlowEdge,
  "gradient cyan square" → "gradient orange square" in BrandLogo
- Rename glow-cyan SVG filter ID to glow-accent
- Fix category color comment: "cyan" → "deep orange"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 05:42:08 +00:00

205 lines
6.8 KiB
TypeScript

import { useState, useEffect } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Terminal } from 'lucide-react'
import { cn } from '@/lib/utils'
import { scriptBuilderApi } from '@/api'
import { ScriptBuilderChat } from '@/components/script-builder/ScriptBuilderChat'
import { ScriptBuilderInput } from '@/components/script-builder/ScriptBuilderInput'
import { ScriptPreviewModal } from '@/components/script-builder/ScriptPreviewModal'
import { SaveToLibraryDialog } from '@/components/script-builder/SaveToLibraryDialog'
import type { ScriptBuilderSessionDetail, ScriptBuilderMessage } from '@/types'
const LANGUAGES = [
{ value: 'powershell', label: 'PowerShell' },
{ value: 'bash', label: 'Bash' },
{ value: 'python', label: 'Python' },
] as const
export default function ScriptBuilderPage() {
const [searchParams] = useSearchParams()
const [session, setSession] = useState<ScriptBuilderSessionDetail | null>(null)
const [messages, setMessages] = useState<ScriptBuilderMessage[]>([])
const [language, setLanguage] = useState('powershell')
const [isLoading, setIsLoading] = useState(false)
const [previewScript, setPreviewScript] = useState<{ script: string; filename: string | null } | null>(null)
const [showSaveDialog, setShowSaveDialog] = useState(false)
const [handoffProcessed, setHandoffProcessed] = useState(false)
const hasMessages = messages.length > 0
// Handle FlowPilot handoff on mount
useEffect(() => {
if (handoffProcessed) return
setHandoffProcessed(true)
const contextRaw = sessionStorage.getItem('scriptBuilderContext')
if (!contextRaw) return
try {
const context = JSON.parse(contextRaw) as {
from_session?: string
prompt?: string
language?: string
}
sessionStorage.removeItem('scriptBuilderContext')
if (context.language) {
setLanguage(context.language)
}
if (context.prompt) {
// Auto-send the prompt
handleSend(context.prompt, context.language || 'powershell')
}
} catch {
sessionStorage.removeItem('scriptBuilderContext')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// Suppress unused searchParams warning — used to detect ?from=flowpilot context
void searchParams
const handleSend = async (content: string, langOverride?: string) => {
const effectiveLanguage = langOverride || language
// Optimistically add user message
const userMessage: ScriptBuilderMessage = {
role: 'user',
content,
created_at: new Date().toISOString(),
}
setMessages((prev) => [...prev, userMessage])
setIsLoading(true)
try {
// Create session if needed
let currentSession = session
if (!currentSession) {
currentSession = await scriptBuilderApi.createSession(effectiveLanguage)
setSession(currentSession)
}
// Send message
const response = await scriptBuilderApi.sendMessage(currentSession.id, content)
const assistantMessage: ScriptBuilderMessage = {
role: 'assistant',
content: response.content,
script: response.script,
script_filename: response.script_filename,
line_count: response.line_count,
created_at: response.timestamp,
}
setMessages((prev) => [...prev, assistantMessage])
} catch (err) {
// Add error message
const errorMessage: ScriptBuilderMessage = {
role: 'assistant',
content: `An error occurred: ${err instanceof Error ? err.message : 'Failed to generate response. Please try again.'}`,
created_at: new Date().toISOString(),
}
setMessages((prev) => [...prev, errorMessage])
} finally {
setIsLoading(false)
}
}
const handleViewScript = (script: string, filename: string | null) => {
setPreviewScript({ script, filename })
}
const handleSaveScript = () => {
setShowSaveDialog(true)
}
const handleSaved = () => {
setShowSaveDialog(false)
}
// Derive default name from session title or filename
const defaultSaveName = session?.title
|| session?.latest_script_filename
|| 'Untitled Script'
return (
<div className="flex flex-col h-full min-h-0">
{/* Header with language selector */}
<div className="shrink-0 px-6 py-4 border-b" style={{ borderColor: 'var(--color-border-default)' }}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<span className="flex items-center justify-center w-9 h-9 rounded-xl bg-[rgba(232,121,249,0.1)]">
<Terminal size={18} className="text-fuchsia-400" />
</span>
<div>
<h1 className="text-base font-heading font-bold text-foreground">Script Builder</h1>
<p className="text-xs text-muted-foreground">Describe what you need, AI generates the script</p>
</div>
</div>
{/* Language pills */}
<div className="flex items-center gap-1 p-0.5 rounded-lg bg-[rgba(255,255,255,0.03)] border border-[rgba(255,255,255,0.06)]">
{LANGUAGES.map((lang) => (
<button
key={lang.value}
onClick={() => !hasMessages && setLanguage(lang.value)}
disabled={hasMessages}
className={cn(
"px-3 py-1.5 rounded-md text-xs font-sans text-xs font-medium transition-all",
language === lang.value
? "bg-primary text-white"
: hasMessages
? "text-text-muted cursor-not-allowed"
: "text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.04)]"
)}
>
{lang.label}
</button>
))}
</div>
</div>
</div>
{/* Chat area */}
<ScriptBuilderChat
messages={messages}
language={language}
onViewScript={handleViewScript}
onSaveScript={handleSaveScript}
isLoading={isLoading}
/>
{/* Input */}
<div className="shrink-0">
<ScriptBuilderInput
onSend={(content) => handleSend(content)}
disabled={isLoading}
/>
</div>
{/* Preview modal */}
{previewScript && (
<ScriptPreviewModal
script={previewScript.script}
filename={previewScript.filename}
language={language}
onClose={() => setPreviewScript(null)}
onSave={() => {
setPreviewScript(null)
setShowSaveDialog(true)
}}
/>
)}
{/* Save dialog */}
{showSaveDialog && session && (
<SaveToLibraryDialog
sessionId={session.id}
defaultName={defaultSaveName}
onClose={() => setShowSaveDialog(false)}
onSaved={handleSaved}
/>
)}
</div>
)
}