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:
@@ -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"),
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user