Add React/TypeScript web interface with Chakra UI and SignalR integration
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
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user