turash/bugulma/frontend/components/chatbot/ChatInput.tsx
Damir Mukimov 6347f42e20
Consolidate repositories: Remove nested frontend .git and merge into main repository
- Remove nested git repository from bugulma/frontend/.git
- Add all frontend files to main repository tracking
- Convert from separate frontend/backend repos to unified monorepo
- Preserve all frontend code and development history as tracked files
- Eliminate nested repository complexity for simpler development workflow

This creates a proper monorepo structure with frontend and backend
coexisting in the same repository for easier development and deployment.
2025-11-25 06:02:57 +01:00

130 lines
4.0 KiB
TypeScript

import React, { useCallback } from 'react';
import { useTranslation } from '@/hooks/useI18n.tsx';
import { Mic, Paperclip, Send, X } from 'lucide-react';
import Button from '@/components/ui/Button.tsx';
interface ChatInputProps {
inputValue: string;
onInputChange: (value: string) => void;
onSendMessage: () => void;
attachedImage: { previewUrl: string } | null;
onClearAttachment: () => void;
onFileChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
fileInputRef: React.RefObject<HTMLInputElement>;
inputRef: React.RefObject<HTMLInputElement>;
isListening: boolean;
onStartListening: () => void;
onStopListening: () => void;
isSpeechSupported: boolean;
isLoading: boolean;
}
const ChatInput = ({
inputValue,
onInputChange,
onSendMessage,
attachedImage,
onClearAttachment,
onFileChange,
fileInputRef,
inputRef,
isListening,
onStartListening,
onStopListening,
isSpeechSupported,
isLoading,
}: ChatInputProps) => {
const { t } = useTranslation();
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
onSendMessage();
},
[onSendMessage]
);
return (
<div className="p-3 border-t shrink-0 space-y-2">
{attachedImage && (
<div className="relative w-20 h-20 rounded-lg overflow-hidden border">
<img
src={attachedImage.previewUrl}
alt="Preview"
className="w-full h-full object-cover"
loading="eager"
decoding="sync"
/>
<Button
variant="ghost"
size="sm"
className="absolute top-1 right-1 h-6 w-6 p-1 rounded-full bg-background/50 hover:bg-background/80"
onClick={onClearAttachment}
aria-label={t('chatbot.removeImageLabel')}
>
<X className="h-3 h-4 text-current w-3 w-4" />
</Button>
</div>
)}
<form onSubmit={handleSubmit} className="relative flex items-center gap-2">
<input
ref={fileInputRef}
type="file"
id="chatbot-file-input"
name="chatbot-file-input"
onChange={onFileChange}
className="hidden"
accept="image/*"
/>
<Button
type="button"
variant="ghost"
size="sm"
className="p-2 h-9 w-9 shrink-0"
onClick={() => fileInputRef.current?.click()}
aria-label={t('chatbot.attachFileLabel')}
>
<Paperclip className="h-4 text-current w-4" />
</Button>
<input
ref={inputRef}
type="text"
id="chatbot-message-input"
name="chatbot-message-input"
value={inputValue}
onChange={(e) => onInputChange(e.target.value)}
placeholder={t('chatbot.placeholder')}
className="w-full h-10 rounded-md border bg-muted pl-4 pr-10 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
disabled={isLoading}
/>
<div className="flex items-center absolute right-2 top-1/2 -translate-y-1/2">
{isSpeechSupported && (
<Button
type="button"
variant="ghost"
size="sm"
className="p-1.5"
onClick={isListening ? onStopListening : onStartListening}
aria-label={isListening ? t('chatbot.stopRecordLabel') : t('chatbot.recordLabel')}
>
<Mic className={`h-4 w-4 ${isListening ? 'text-destructive animate-pulse' : ''}`} />
</Button>
)}
<Button
type="submit"
variant="ghost"
size="sm"
disabled={isLoading || (!inputValue.trim() && !attachedImage)}
className="p-1.5 text-primary disabled:text-muted-foreground"
aria-label={t('chatbot.sendLabel')}
>
<Send className="h-4 text-current w-4" />
</Button>
</div>
</form>
</div>
);
};
export default React.memo(ChatInput);