Initial commit — AI-powered coding tutor (Professor)
Next.js 16, React 19, Monaco editor, Anthropic SDK, multi-provider AI, Wandbox Python execution, iframe HTML preview, SQLite auth + session persistence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
69
components/chat/ChatInput.tsx
Normal file
69
components/chat/ChatInput.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useRef, type KeyboardEvent } from 'react';
|
||||
|
||||
interface Props {
|
||||
isDisabled: boolean;
|
||||
onSend: (text: string) => void;
|
||||
}
|
||||
|
||||
export default function ChatInput({ isDisabled, onSend }: Props) {
|
||||
const [text, setText] = useState('');
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
function handleSend() {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed || isDisabled) return;
|
||||
onSend(trimmed);
|
||||
setText('');
|
||||
// Reset height
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.style.height = 'auto';
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSend();
|
||||
}
|
||||
}
|
||||
|
||||
function handleInput() {
|
||||
const el = textareaRef.current;
|
||||
if (!el) return;
|
||||
el.style.height = 'auto';
|
||||
el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border-t border-zinc-700 bg-zinc-900 p-3">
|
||||
<div className="flex items-end gap-2 rounded-xl border border-zinc-700 bg-zinc-800 px-3 py-2 focus-within:border-zinc-500 transition-colors">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onInput={handleInput}
|
||||
disabled={isDisabled}
|
||||
placeholder="Ask a question or chat with Professor…"
|
||||
rows={1}
|
||||
className="flex-1 resize-none bg-transparent text-sm text-zinc-200 placeholder-zinc-500 outline-none disabled:opacity-50"
|
||||
style={{ maxHeight: '160px' }}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSend}
|
||||
disabled={isDisabled || !text.trim()}
|
||||
className="flex-shrink-0 rounded-lg bg-blue-600 p-1.5 text-white hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-50 transition-colors"
|
||||
aria-label="Send message"
|
||||
>
|
||||
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
||||
<path d="M22 2L11 13" />
|
||||
<path d="M22 2L15 22 11 13 2 9l20-7z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-1 text-center text-xs text-zinc-600">Enter to send · Shift+Enter for newline</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user