Files
app/components/ChatInterface.tsx
Albert 68728b2987 fix: Make ChatInterface theme-aware for light mode support
Updated ChatInterface to use dynamic colors based on color scheme:
- Added useComputedColorScheme hook
- Chat message bubbles now use gray.1/gray.0 in light mode
- Chat message bubbles use dark.6/dark.7 in dark mode
- User messages vs AI messages have different shades in both modes

This fixes the issue where chat messages had hardcoded dark backgrounds
even when the app was in light mode.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 22:24:21 +00:00

120 lines
3.9 KiB
TypeScript

'use client';
import { useChat } from '@ai-sdk/react';
import { Container, ScrollArea, Paper, Group, TextInput, Button, Stack, Text, Box, useComputedColorScheme } from '@mantine/core';
import { useEffect, useRef, useState } from 'react';
import { MicrophoneRecorder } from './MicrophoneRecorder';
export function ChatInterface() {
const viewport = useRef<HTMLDivElement>(null);
const [input, setInput] = useState('');
const colorScheme = useComputedColorScheme('light');
const isDark = colorScheme === 'dark';
const {
messages,
sendMessage,
status,
} = useChat();
// Auto-scroll to bottom when new messages arrive
useEffect(() => {
if (viewport.current) {
viewport.current.scrollTo({
top: viewport.current.scrollHeight,
behavior: 'smooth',
});
}
}, [messages]);
return (
<Container size="md" h="100vh" style={{ display: 'flex', flexDirection: 'column' }}>
<Stack h="100%" gap="md" py="md">
{/* Chat messages area */}
<ScrollArea
flex={1}
type="auto"
viewportRef={viewport}
>
<Stack gap="md">
{messages.length === 0 && (
<Text c="dimmed" ta="center" mt="xl">
Start a conversation by typing or speaking...
</Text>
)}
{messages.map((message) => (
<Box
key={message.id}
style={{
alignSelf: message.role === 'user' ? 'flex-end' : 'flex-start',
maxWidth: '70%',
}}
>
<Paper
p="sm"
radius="md"
bg={message.role === 'user'
? (isDark ? 'dark.6' : 'gray.1')
: (isDark ? 'dark.7' : 'gray.0')
}
>
<Text size="sm">
{/* Extract text from parts */}
{('parts' in message && Array.isArray((message as any).parts))
? (message as any).parts.find((p: any) => p.type === 'text')?.text || ''
: (message as any).content || ''}
</Text>
</Paper>
</Box>
))}
</Stack>
</ScrollArea>
{/* Input area */}
<form onSubmit={(e) => {
e.preventDefault();
if (!input.trim() || status === 'submitted' || status === 'streaming') return;
sendMessage({ text: input });
setInput('');
}}>
<Paper withBorder p="sm" radius="xl">
<Group gap="xs">
<TextInput
value={input}
onChange={(e) => setInput(e.currentTarget.value)}
placeholder="Speak or type your thoughts..."
style={{ flex: 1 }}
variant="unstyled"
disabled={status === 'submitted' || status === 'streaming'}
/>
{/* Microphone Recorder */}
<MicrophoneRecorder
onTranscriptUpdate={(transcript) => {
// Update the input field in real-time
setInput(transcript);
}}
onTranscriptFinalized={(transcript) => {
// Set the input and submit
setInput(transcript);
// Trigger form submission
setTimeout(() => {
const form = document.querySelector('form');
if (form) {
form.requestSubmit();
}
}, 100);
}}
/>
<Button type="submit" radius="xl" loading={status === 'submitted' || status === 'streaming'}>
Send
</Button>
</Group>
</Paper>
</form>
</Stack>
</Container>
);
}