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",
|
detail="Session must be completed with a generated tree before importing",
|
||||||
)
|
)
|
||||||
|
|
||||||
if session.generated_tree_id:
|
# Always create a new Tree record (no duplicate check — user may
|
||||||
return AIChatImportResponse(
|
# want multiple copies or re-import after edits)
|
||||||
tree_id=session.generated_tree_id,
|
|
||||||
tree_type=session.flow_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
metadata = session.tree_metadata or {}
|
metadata = session.tree_metadata or {}
|
||||||
tree = Tree(
|
tree = Tree(
|
||||||
name=data.name or metadata.get("name", "AI-Generated Flow"),
|
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 { PhaseIndicator } from './PhaseIndicator'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import type { InterviewPhase } from '@/types'
|
import type { InterviewPhase } from '@/types'
|
||||||
@@ -8,8 +8,9 @@ interface ChatToolbarProps {
|
|||||||
status: 'idle' | 'active' | 'completed' | 'abandoned'
|
status: 'idle' | 'active' | 'completed' | 'abandoned'
|
||||||
isGenerating: boolean
|
isGenerating: boolean
|
||||||
hasGeneratedTree: boolean
|
hasGeneratedTree: boolean
|
||||||
|
isSaving: boolean
|
||||||
onGenerate: () => void
|
onGenerate: () => void
|
||||||
onImport: () => void
|
onSave: () => void
|
||||||
onReset: () => void
|
onReset: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,8 +19,9 @@ export function ChatToolbar({
|
|||||||
status,
|
status,
|
||||||
isGenerating,
|
isGenerating,
|
||||||
hasGeneratedTree,
|
hasGeneratedTree,
|
||||||
|
isSaving,
|
||||||
onGenerate,
|
onGenerate,
|
||||||
onImport,
|
onSave,
|
||||||
onReset,
|
onReset,
|
||||||
}: ChatToolbarProps) {
|
}: ChatToolbarProps) {
|
||||||
return (
|
return (
|
||||||
@@ -44,22 +46,42 @@ export function ChatToolbar({
|
|||||||
'disabled:opacity-50 disabled:cursor-not-allowed'
|
'disabled:opacity-50 disabled:cursor-not-allowed'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Download className="h-3.5 w-3.5" />
|
{isGenerating ? (
|
||||||
{isGenerating ? 'Generating...' : 'Generate Flow'}
|
<>
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
Generating...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Sparkles className="h-3.5 w-3.5" />
|
||||||
|
Generate Flow
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{hasGeneratedTree && (
|
{hasGeneratedTree && (
|
||||||
<button
|
<button
|
||||||
onClick={onImport}
|
onClick={onSave}
|
||||||
|
disabled={isSaving}
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm font-medium',
|
'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',
|
'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" />
|
{isSaving ? (
|
||||||
Import to Editor
|
<>
|
||||||
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||||
|
Saving...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Save className="h-3.5 w-3.5" />
|
||||||
|
Save to Flow Library
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { useAIChatStore } from '@/store/aiChatStore'
|
import { useAIChatStore } from '@/store/aiChatStore'
|
||||||
import { ChatPanel } from '@/components/ai-chat/ChatPanel'
|
import { ChatPanel } from '@/components/ai-chat/ChatPanel'
|
||||||
@@ -62,11 +62,15 @@ export function AIChatBuilderPage() {
|
|||||||
[sendMessage]
|
[sendMessage]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
|
||||||
const handleGenerate = useCallback(() => {
|
const handleGenerate = useCallback(() => {
|
||||||
generateTree()
|
generateTree()
|
||||||
}, [generateTree])
|
}, [generateTree])
|
||||||
|
|
||||||
const handleImport = useCallback(async () => {
|
const handleSave = useCallback(async () => {
|
||||||
|
if (isSaving) return
|
||||||
|
setIsSaving(true)
|
||||||
try {
|
try {
|
||||||
const treeId = await importToEditor({
|
const treeId = await importToEditor({
|
||||||
name: treeMetadata?.name,
|
name: treeMetadata?.name,
|
||||||
@@ -75,11 +79,13 @@ export function AIChatBuilderPage() {
|
|||||||
})
|
})
|
||||||
const path = getTreeEditorPath(treeId, flowType)
|
const path = getTreeEditorPath(treeId, flowType)
|
||||||
navigate(path)
|
navigate(path)
|
||||||
toast.success('Flow imported to editor')
|
toast.success('Flow saved to library')
|
||||||
} catch {
|
} 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 () => {
|
const handleReset = useCallback(async () => {
|
||||||
await abandonSession()
|
await abandonSession()
|
||||||
@@ -116,8 +122,9 @@ export function AIChatBuilderPage() {
|
|||||||
status={status}
|
status={status}
|
||||||
isGenerating={isGenerating}
|
isGenerating={isGenerating}
|
||||||
hasGeneratedTree={!!generatedTree}
|
hasGeneratedTree={!!generatedTree}
|
||||||
|
isSaving={isSaving}
|
||||||
onGenerate={handleGenerate}
|
onGenerate={handleGenerate}
|
||||||
onImport={handleImport}
|
onSave={handleSave}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user