Implement web frontend using Vite + React + TypeScript with: - Chakra UI component library for theming and styling - SignalR bridge (lunaBridge) for real-time server communication - Chat components (ChatPanel, ChatInput, ChatMessage) - Layout components (AppLayout, Sidebar, StatusBar, SystemPanel) - Custom useChat hook for conversation state management - Dark/light theme support via ColorModeProvider - Mock conversation data for development
87 lines
2.4 KiB
TypeScript
87 lines
2.4 KiB
TypeScript
import { Box, Flex, Input } from '@chakra-ui/react';
|
|
import { useState, useEffect, useRef } from 'react';
|
|
import type { KeyboardEvent } from 'react';
|
|
|
|
interface ChatInputProps {
|
|
onSend: (message: string) => void;
|
|
isTyping?: boolean;
|
|
}
|
|
|
|
export const ChatInput = ({ onSend, isTyping }: ChatInputProps) => {
|
|
const [value, setValue] = useState('');
|
|
const [isFocused, setIsFocused] = useState(false);
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
if (value.trim()) {
|
|
onSend(value.trim());
|
|
setValue('');
|
|
}
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
inputRef.current?.focus();
|
|
}, []);
|
|
|
|
return (
|
|
<Box
|
|
px={8}
|
|
py={3}
|
|
bg="black"
|
|
borderTop="1px solid"
|
|
borderColor="whiteAlpha.100"
|
|
fontFamily="'JetBrains Mono', monospace"
|
|
fontSize="sm"
|
|
onClick={() => inputRef.current?.focus()}
|
|
cursor="text"
|
|
>
|
|
<Flex align="center" gap={0}>
|
|
<Box color="green.400" fontWeight="bold" flexShrink={0}>
|
|
{isTyping ? (
|
|
<Box as="span" color="gray.500">luna is processing...</Box>
|
|
) : (
|
|
'guest@luna:~ $'
|
|
)}
|
|
</Box>
|
|
<Box position="relative" flex="1" ml={2}>
|
|
<Input
|
|
ref={inputRef}
|
|
variant="flushed"
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
onFocus={() => setIsFocused(true)}
|
|
onBlur={() => setIsFocused(false)}
|
|
disabled={isTyping}
|
|
color="gray.200"
|
|
css={{ caretColor: 'transparent' }}
|
|
autoComplete="off"
|
|
spellCheck={false}
|
|
p={0}
|
|
h="auto"
|
|
lineHeight="1.5"
|
|
borderBottom="none"
|
|
_focus={{ borderBottom: 'none', boxShadow: 'none' }}
|
|
_placeholder={{ color: 'transparent' }}
|
|
/>
|
|
{!isTyping && isFocused && (
|
|
<Box
|
|
position="absolute"
|
|
top="50%"
|
|
transform="translateY(-50%)"
|
|
left={`${value.length}ch`}
|
|
w="8px"
|
|
h="1.1em"
|
|
bg="green.400"
|
|
css={{ animation: 'blink 1s step-end infinite' }}
|
|
pointerEvents="none"
|
|
/>
|
|
)}
|
|
</Box>
|
|
</Flex>
|
|
</Box>
|
|
);
|
|
}; |