init: Kira — AI body double with Honcho memory
Full voice pipeline (Whisper STT -> DeepSeek LLM -> OpenAI TTS), animated SVG avatar (Live2D-ready), girly-pop UI, lofi music, timer/notes/pets/wardrobe widgets, 10 background scenes with particle effects, Honcho cross-session memory.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { useRef, useEffect } from 'react';
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
role: 'user' | 'kira';
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
messages: Message[];
|
||||
isKiraSpeaking: boolean;
|
||||
}
|
||||
|
||||
export default function ChatBubble({ messages, isKiraSpeaking }: Props) {
|
||||
const bottomRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
}, [messages]);
|
||||
|
||||
return (
|
||||
<div className="glass-card p-4 flex flex-col" style={{ minHeight: 200, maxHeight: 320 }}>
|
||||
<h3 className="text-sm font-bold text-kira-plum mb-3 flex items-center gap-2">
|
||||
<span>💬</span> Conversation
|
||||
<span className={`w-2 h-2 rounded-full ${isKiraSpeaking ? 'bg-kira-pink animate-pulse' : 'bg-kira-mint'}`} />
|
||||
</h3>
|
||||
|
||||
<div className="flex-1 overflow-y-auto space-y-2 scrollbar-thin pr-1">
|
||||
{messages.length === 0 && (
|
||||
<div className="text-xs text-kira-plum/30 text-center py-6">
|
||||
click the mic or type to talk to Kira
|
||||
</div>
|
||||
)}
|
||||
|
||||
{messages.map((msg, i) => {
|
||||
const isLastKira = msg.role === 'kira' && i === messages.length - 1 && isKiraSpeaking;
|
||||
return (
|
||||
<div
|
||||
key={msg.id}
|
||||
className={`flex gap-2 ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
||||
>
|
||||
{msg.role === 'kira' && (
|
||||
<span className="text-sm mt-1">🌸</span>
|
||||
)}
|
||||
<div
|
||||
className={`
|
||||
max-w-[80%] px-3 py-2 rounded-2xl text-sm leading-relaxed
|
||||
${msg.role === 'user'
|
||||
? 'bg-kira-lav/30 text-kira-plum rounded-br-md'
|
||||
: 'bg-white/60 text-kira-plum rounded-bl-md'
|
||||
}
|
||||
${isLastKira ? 'animate-fade-in' : ''}
|
||||
`}
|
||||
>
|
||||
{msg.text}
|
||||
{isLastKira && (
|
||||
<span className="inline-block w-1.5 h-4 bg-kira-pink/60 ml-0.5 animate-blink" />
|
||||
)}
|
||||
</div>
|
||||
{msg.role === 'user' && (
|
||||
<span className="text-sm mt-1">👤</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
|
||||
<style>{`
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; transform: translateY(4px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
.animate-fade-in { animation: fade-in 0.3s ease-out; }
|
||||
.animate-blink { animation: blink 0.8s infinite; }
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user