feat(pitch-deck): Waiting-Indicator im Investor Agent Chat
All checks were successful
CI / test-go-consent (push) Successful in 27s
CI / test-bqas (push) Successful in 29s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Successful in 31s
All checks were successful
CI / test-go-consent (push) Successful in 27s
CI / test-bqas (push) Successful in 29s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-python-voice (push) Successful in 31s
Drei animierte Punkte (iMessage-Style) erscheinen sofort nach dem Absenden und verschwinden wenn der erste Token eintrifft. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export default function ChatInterface({ lang }: ChatInterfaceProps) {
|
|||||||
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 messagesEndRef = useRef<HTMLDivElement>(null)
|
const messagesEndRef = useRef<HTMLDivElement>(null)
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ export default function ChatInterface({ lang }: ChatInterfaceProps) {
|
|||||||
setInput('')
|
setInput('')
|
||||||
setMessages(prev => [...prev, { role: 'user', content: message }])
|
setMessages(prev => [...prev, { role: 'user', content: message }])
|
||||||
setIsStreaming(true)
|
setIsStreaming(true)
|
||||||
|
setIsWaiting(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/chat', {
|
const res = await fetch('/api/chat', {
|
||||||
@@ -47,21 +49,28 @@ export default function ChatInterface({ lang }: ChatInterfaceProps) {
|
|||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
let content = ''
|
let content = ''
|
||||||
|
|
||||||
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 })
|
const chunk = decoder.decode(value, { stream: true })
|
||||||
setMessages(prev => {
|
content += chunk
|
||||||
const updated = [...prev]
|
|
||||||
updated[updated.length - 1] = { role: 'assistant', content }
|
if (isWaiting || content.length === chunk.length) {
|
||||||
return updated
|
// First chunk arrived — replace waiting indicator with real content
|
||||||
})
|
setIsWaiting(false)
|
||||||
|
setMessages(prev => [...prev, { role: 'assistant', content }])
|
||||||
|
} else {
|
||||||
|
setMessages(prev => {
|
||||||
|
const updated = [...prev]
|
||||||
|
updated[updated.length - 1] = { role: 'assistant', content }
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
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'
|
||||||
@@ -71,6 +80,7 @@ export default function ChatInterface({ lang }: ChatInterfaceProps) {
|
|||||||
])
|
])
|
||||||
} finally {
|
} finally {
|
||||||
setIsStreaming(false)
|
setIsStreaming(false)
|
||||||
|
setIsWaiting(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +145,33 @@ export default function ChatInterface({ lang }: ChatInterfaceProps) {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
|
{/* Waiting indicator — shown between send and first token */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{isWaiting && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 8 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -4 }}
|
||||||
|
className="flex gap-3"
|
||||||
|
>
|
||||||
|
<div className="w-8 h-8 rounded-full bg-indigo-500/20 flex items-center justify-center shrink-0">
|
||||||
|
<Bot className="w-4 h-4 text-indigo-400" />
|
||||||
|
</div>
|
||||||
|
<div className="bg-white/[0.06] rounded-2xl px-4 py-3 flex items-center gap-1">
|
||||||
|
{[0, 1, 2].map(i => (
|
||||||
|
<motion.span
|
||||||
|
key={i}
|
||||||
|
className="block w-2 h-2 rounded-full bg-indigo-400/70"
|
||||||
|
animate={{ opacity: [0.3, 1, 0.3], y: [0, -4, 0] }}
|
||||||
|
transition={{ duration: 0.8, repeat: Infinity, delay: i * 0.18 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user