fix: replace Import button with "Save to Flow Library" and remove duplicate check

- After generation, toolbar shows "Save to Flow Library" button
  (replaces "Import to Editor")
- Button shows "Saving..." spinner state during API call
- Generate button shows animated spinner during generation
- Backend /import endpoint always creates a new Tree record
  (removed generated_tree_id idempotency check)
- Navigates to tree editor after successful save

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-28 15:08:58 -05:00
parent 2196886cd2
commit b819236aa5
3 changed files with 46 additions and 21 deletions

View File

@@ -387,12 +387,8 @@ async def import_tree(
detail="Session must be completed with a generated tree before importing",
)
if session.generated_tree_id:
return AIChatImportResponse(
tree_id=session.generated_tree_id,
tree_type=session.flow_type,
)
# Always create a new Tree record (no duplicate check — user may
# want multiple copies or re-import after edits)
metadata = session.tree_metadata or {}
tree = Tree(
name=data.name or metadata.get("name", "AI-Generated Flow"),

View File

@@ -1,4 +1,4 @@
import { Sparkles, Download, RotateCcw, ArrowRight } from 'lucide-react'
import { Sparkles, Save, RotateCcw, Loader2 } from 'lucide-react'
import { PhaseIndicator } from './PhaseIndicator'
import { cn } from '@/lib/utils'
import type { InterviewPhase } from '@/types'
@@ -8,8 +8,9 @@ interface ChatToolbarProps {
status: 'idle' | 'active' | 'completed' | 'abandoned'
isGenerating: boolean
hasGeneratedTree: boolean
isSaving: boolean
onGenerate: () => void
onImport: () => void
onSave: () => void
onReset: () => void
}
@@ -18,8 +19,9 @@ export function ChatToolbar({
status,
isGenerating,
hasGeneratedTree,
isSaving,
onGenerate,
onImport,
onSave,
onReset,
}: ChatToolbarProps) {
return (
@@ -44,22 +46,42 @@ export function ChatToolbar({
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
<Download className="h-3.5 w-3.5" />
{isGenerating ? 'Generating...' : 'Generate Flow'}
{isGenerating ? (
<>
<Loader2 className="h-3.5 w-3.5 animate-spin" />
Generating...
</>
) : (
<>
<Sparkles className="h-3.5 w-3.5" />
Generate Flow
</>
)}
</button>
)}
{hasGeneratedTree && (
<button
onClick={onImport}
onClick={onSave}
disabled={isSaving}
className={cn(
'flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium',
'bg-gradient-brand text-white shadow-lg shadow-primary/20',
'hover:opacity-90 transition-opacity'
'hover:opacity-90 transition-opacity',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
>
<ArrowRight className="h-3.5 w-3.5" />
Import to Editor
{isSaving ? (
<>
<Loader2 className="h-3.5 w-3.5 animate-spin" />
Saving...
</>
) : (
<>
<Save className="h-3.5 w-3.5" />
Save to Flow Library
</>
)}
</button>
)}

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useAIChatStore } from '@/store/aiChatStore'
import { ChatPanel } from '@/components/ai-chat/ChatPanel'
@@ -62,11 +62,15 @@ export function AIChatBuilderPage() {
[sendMessage]
)
const [isSaving, setIsSaving] = useState(false)
const handleGenerate = useCallback(() => {
generateTree()
}, [generateTree])
const handleImport = useCallback(async () => {
const handleSave = useCallback(async () => {
if (isSaving) return
setIsSaving(true)
try {
const treeId = await importToEditor({
name: treeMetadata?.name,
@@ -75,11 +79,13 @@ export function AIChatBuilderPage() {
})
const path = getTreeEditorPath(treeId, flowType)
navigate(path)
toast.success('Flow imported to editor')
toast.success('Flow saved to library')
} catch {
toast.error('Failed to import flow')
toast.error('Failed to save flow')
} finally {
setIsSaving(false)
}
}, [importToEditor, treeMetadata, flowType, navigate])
}, [isSaving, importToEditor, treeMetadata, flowType, navigate])
const handleReset = useCallback(async () => {
await abandonSession()
@@ -116,8 +122,9 @@ export function AIChatBuilderPage() {
status={status}
isGenerating={isGenerating}
hasGeneratedTree={!!generatedTree}
isSaving={isSaving}
onGenerate={handleGenerate}
onImport={handleImport}
onSave={handleSave}
onReset={handleReset}
/>