fix(pitch-deck): Waiting-Indicator in ChatFAB (richtiges Komponente)
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 25s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 26s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 25s
CI / test-python-voice (push) Successful in 29s
CI / test-bqas (push) Successful in 26s
ChatInterface.tsx war falsch — der echte Investor Agent laeuft in ChatFAB.tsx. Animierte Punkte + firstChunk-Logik dort implementiert. Session-History laeuft bereits korrekt (FAB permanent gemountet). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,7 @@ export default function ChatFAB({ lang, currentSlide, currentIndex, visitedSlide
|
|||||||
const [messages, setMessages] = useState<ChatMessage[]>([])
|
const [messages, setMessages] = useState<ChatMessage[]>([])
|
||||||
const [input, setInput] = useState('')
|
const [input, setInput] = useState('')
|
||||||
const [isStreaming, setIsStreaming] = useState(false)
|
const [isStreaming, setIsStreaming] = useState(false)
|
||||||
|
const [isWaiting, setIsWaiting] = useState(false)
|
||||||
const [parsedResponses, setParsedResponses] = useState<Map<number, ParsedMessage>>(new Map())
|
const [parsedResponses, setParsedResponses] = useState<Map<number, ParsedMessage>>(new Map())
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
@@ -126,6 +127,7 @@ export default function ChatFAB({ lang, currentSlide, currentIndex, visitedSlide
|
|||||||
setInput('')
|
setInput('')
|
||||||
setMessages(prev => [...prev, { role: 'user', content: message }])
|
setMessages(prev => [...prev, { role: 'user', content: message }])
|
||||||
setIsStreaming(true)
|
setIsStreaming(true)
|
||||||
|
setIsWaiting(true)
|
||||||
|
|
||||||
abortRef.current = new AbortController()
|
abortRef.current = new AbortController()
|
||||||
|
|
||||||
@@ -152,14 +154,19 @@ export default function ChatFAB({ lang, currentSlide, currentIndex, visitedSlide
|
|||||||
const reader = res.body!.getReader()
|
const reader = res.body!.getReader()
|
||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
let content = ''
|
let content = ''
|
||||||
|
let firstChunk = true
|
||||||
setMessages(prev => [...prev, { role: 'assistant', content: '' }])
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read()
|
const { done, value } = await reader.read()
|
||||||
if (done) break
|
if (done) break
|
||||||
|
|
||||||
content += decoder.decode(value, { stream: true })
|
content += decoder.decode(value, { stream: true })
|
||||||
|
|
||||||
|
if (firstChunk) {
|
||||||
|
firstChunk = false
|
||||||
|
setIsWaiting(false)
|
||||||
|
setMessages(prev => [...prev, { role: 'assistant', content }])
|
||||||
|
} else {
|
||||||
const currentText = content
|
const currentText = content
|
||||||
setMessages(prev => {
|
setMessages(prev => {
|
||||||
const updated = [...prev]
|
const updated = [...prev]
|
||||||
@@ -167,9 +174,11 @@ export default function ChatFAB({ lang, currentSlide, currentIndex, visitedSlide
|
|||||||
return updated
|
return updated
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof Error && err.name === 'AbortError') return
|
if (err instanceof Error && err.name === 'AbortError') return
|
||||||
console.error('Chat error:', err)
|
console.error('Chat error:', err)
|
||||||
|
setIsWaiting(false)
|
||||||
setMessages(prev => [
|
setMessages(prev => [
|
||||||
...prev,
|
...prev,
|
||||||
{ role: 'assistant', content: lang === 'de'
|
{ role: 'assistant', content: lang === 'de'
|
||||||
@@ -179,6 +188,7 @@ export default function ChatFAB({ lang, currentSlide, currentIndex, visitedSlide
|
|||||||
])
|
])
|
||||||
} finally {
|
} finally {
|
||||||
setIsStreaming(false)
|
setIsStreaming(false)
|
||||||
|
setIsWaiting(false)
|
||||||
abortRef.current = null
|
abortRef.current = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -344,6 +354,32 @@ export default function ChatFAB({ lang, currentSlide, currentIndex, visitedSlide
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Waiting indicator */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{isWaiting && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 6 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
className="flex gap-2.5"
|
||||||
|
>
|
||||||
|
<div className="w-7 h-7 rounded-full bg-indigo-500/20 flex items-center justify-center shrink-0 mt-0.5">
|
||||||
|
<Bot className="w-3.5 h-3.5 text-indigo-400" />
|
||||||
|
</div>
|
||||||
|
<div className="bg-white/[0.06] rounded-2xl px-3.5 py-3 flex items-center gap-1">
|
||||||
|
{[0, 1, 2].map(i => (
|
||||||
|
<motion.span
|
||||||
|
key={i}
|
||||||
|
className="block w-1.5 h-1.5 rounded-full bg-indigo-400/70"
|
||||||
|
animate={{ opacity: [0.3, 1, 0.3], y: [0, -3, 0] }}
|
||||||
|
transition={{ duration: 0.7, repeat: Infinity, delay: i * 0.15 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
{messages.map((msg, idx) => (
|
{messages.map((msg, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
|
|||||||
Reference in New Issue
Block a user