BanglaSumQA / frontend /src /components /InputBar.jsx
Nanny7's picture
Rename summary options to BanglaSum/refined and remove banglat5+gpt text references
2f188e0
Raw
History Blame Contribute Delete
8.36 kB
import { useState, useRef, useEffect, useCallback } from 'react'
export default function InputBar({ isLoading, hasDocument, onSendText, onFileSelect, onTranscript }) {
const [text, setText] = useState('')
const [file, setFile] = useState(null)
const [isRecording, setIsRecording] = useState(false)
const [isTranscribing, setIsTranscribing] = useState(false)
const textareaRef = useRef(null)
const fileInputRef = useRef(null)
const mediaRecRef = useRef(null)
const audioChunksRef = useRef([])
/* ── Auto-resize textarea ──────────────────────────────── */
useEffect(() => {
const ta = textareaRef.current
if (!ta) return
ta.style.height = 'auto'
ta.style.height = Math.min(ta.scrollHeight, 180) + 'px'
}, [text])
/* ── Send logic ────────────────────────────────────────── */
const handleSend = useCallback(async () => {
if (isLoading || isTranscribing) return
if (file) {
await onFileSelect(file)
setFile(null)
setText('')
return
}
const msg = text.trim()
if (!msg) return
setText('')
if (textareaRef.current) {
textareaRef.current.style.height = 'auto'
}
await onSendText(msg)
}, [isLoading, isTranscribing, file, text, onFileSelect, onSendText])
/* ── Keyboard: Enter to send, Shift+Enter for newline ───── */
const handleKeyDown = (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSend()
}
}
/* ── File picker ────────────────────────────────────────── */
const handleFileChange = (e) => {
const f = e.target.files?.[0]
if (f) {
setFile(f)
setText('')
}
e.target.value = ''
}
const removeFile = () => setFile(null)
/* ── Voice recording ────────────────────────────────────── */
const startRecording = useCallback(async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
audioChunksRef.current = []
const mimeType = MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
? 'audio/webm;codecs=opus'
: 'audio/webm'
const recorder = new MediaRecorder(stream, { mimeType })
mediaRecRef.current = recorder
recorder.ondataavailable = (e) => {
if (e.data.size > 0) audioChunksRef.current.push(e.data)
}
recorder.onstop = async () => {
stream.getTracks().forEach(t => t.stop())
const blob = new Blob(audioChunksRef.current, { type: mimeType })
if (blob.size < 500) return // too short / silence
setIsTranscribing(true)
try {
const transcript = await onTranscript(blob)
if (transcript) {
setText(prev => prev ? prev + ' ' + transcript : transcript)
textareaRef.current?.focus()
}
} finally {
setIsTranscribing(false)
}
}
recorder.start(200) // collect chunks every 200ms
setIsRecording(true)
} catch (err) {
alert('মাইক্রোফোন অ্যাক্সেস করতে পারা যাচ্ছে না। অনুগ্রহ করে ব্রাউজার অনুমতি দিন।')
}
}, [onTranscript])
const stopRecording = useCallback(() => {
if (mediaRecRef.current && isRecording) {
mediaRecRef.current.stop()
setIsRecording(false)
}
}, [isRecording])
const toggleRecording = () => {
if (isRecording) stopRecording()
else startRecording()
}
/* ── Can send? ──────────────────────────────────────────── */
const canSend = !isLoading && !isTranscribing && (file || text.trim().length > 0)
/* ── Placeholder ────────────────────────────────────────── */
const placeholder = isTranscribing
? 'কণ্ঠস্বর লেখায় রূপান্তরিত হচ্ছে...'
: isRecording
? '🔴 রেকর্ড হচ্ছে... থামাতে মাইক্রোফোনে ক্লিক করুন'
: hasDocument
? 'নথি সম্পর্কে প্রশ্ন করুন...'
: 'বাংলায় টাইপ করুন বা PDF/কণ্ঠস্বর দিন...'
return (
<div className="input-bar-wrapper">
<div className="input-bar-inner">
{/* ── Attached file chip ── */}
{file && (
<div>
<span className="attachment-chip">
<span className="attachment-chip-icon">📄</span>
{file.name}
<button
className="attachment-chip-remove"
onClick={removeFile}
aria-label="ফাইল সরান"
>
×
</button>
</span>
</div>
)}
{/* ── Input box ── */}
<div className="input-box">
<textarea
ref={textareaRef}
id="chat-input"
className="input-textarea bangla"
value={text}
onChange={e => setText(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
rows={1}
disabled={isLoading || isRecording}
aria-label="বার্তা লিখুন"
/>
<div className="input-actions">
{/* PDF Attachment button */}
<button
className="input-action-btn"
onClick={() => fileInputRef.current?.click()}
disabled={isLoading || isRecording}
title="PDF আপলোড করুন"
aria-label="PDF আপলোড"
id="btn-attach-pdf"
>
📎
</button>
{/* Hidden file input */}
<input
ref={fileInputRef}
type="file"
accept=".pdf"
style={{ display: 'none' }}
onChange={handleFileChange}
/>
{/* Voice record button */}
<button
className={`input-action-btn${isRecording ? ' recording' : ''}`}
onClick={toggleRecording}
disabled={isLoading || isTranscribing || Boolean(file)}
title={isRecording ? 'করিং বন কর' : 'কণঠসবর ইনপ'}
aria-label={isRecording ? 'করিং বন' : 'কণঠসবর কর'}
id="btn-voice"
>
{isTranscribing ? (
<span style={{ fontSize: 12, animation: 'spin 0.8s linear infinite', display: 'inline-block' }}></span>
) : isRecording ? (
'⏹'
) : (
'🎙️'
)}
</button>
{/* Send button */}
<button
className="send-btn"
onClick={handleSend}
disabled={!canSend}
title="পাঠান (Enter)"
aria-label="বার্তা পাঠান"
id="btn-send"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
<line x1="22" y1="2" x2="11" y2="13" />
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
</button>
</div>
</div>
<p className="input-hint">
{hasDocument
? 'নথির প্রসঙ্গে উত্তর দেওয়া হবে'
: 'PDF আপলোড করুন অথবা সরাসরি বাংলায় প্রশ্ন করুন'}
</p>
</div>
</div>
)
}