feat: add message input with send button and keyboard shortcuts
- MessageInput component with textarea, send button, Enter-to-send - Shift+Enter for newline, auto-resize textarea - Disabled state while streaming, auto-focus after send - Integrated into /chat page with user message handling Closes #6 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,3 +7,4 @@
|
||||
| #3 | Configure Caddy for gRPC-Web support | COMPLETED | [issue-003.md](issue-003.md) |
|
||||
| #4 | gRPC-Web client service layer | COMPLETED | [issue-004.md](issue-004.md) |
|
||||
| #5 | Chat page layout and message list component | COMPLETED | [issue-005.md](issue-005.md) |
|
||||
| #6 | Message input with send and keyboard shortcuts | COMPLETED | [issue-006.md](issue-006.md) |
|
||||
|
||||
17
implementation-plans/issue-006.md
Normal file
17
implementation-plans/issue-006.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
---
|
||||
|
||||
# Issue #6: Message input with send and keyboard shortcuts
|
||||
|
||||
**Status:** COMPLETED
|
||||
**Issue:** https://git.shahondin1624.de/llm-multiverse/llm-multiverse-ui/issues/6
|
||||
**Branch:** `feature/issue-6-message-input`
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] Text input/textarea component for composing messages
|
||||
- [x] Send button triggers message submission
|
||||
- [x] Enter key sends message
|
||||
- [x] Shift+Enter inserts newline
|
||||
- [x] Input and send button disabled while a response is streaming
|
||||
- [x] Input auto-focuses on page load and after send
|
||||
63
src/lib/components/MessageInput.svelte
Normal file
63
src/lib/components/MessageInput.svelte
Normal file
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
let {
|
||||
onSend,
|
||||
disabled = false
|
||||
}: { onSend: (message: string) => void; disabled?: boolean } = $props();
|
||||
|
||||
let input = $state('');
|
||||
let textarea: HTMLTextAreaElement | undefined = $state();
|
||||
|
||||
function handleSubmit() {
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed || disabled) return;
|
||||
onSend(trimmed);
|
||||
input = '';
|
||||
resizeTextarea();
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
function resizeTextarea() {
|
||||
if (!textarea) return;
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!disabled && textarea) {
|
||||
textarea.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="border-t border-gray-200 bg-white p-4">
|
||||
<form onsubmit={handleSubmit} class="flex items-end gap-2">
|
||||
<textarea
|
||||
bind:this={textarea}
|
||||
bind:value={input}
|
||||
onkeydown={handleKeydown}
|
||||
oninput={resizeTextarea}
|
||||
{disabled}
|
||||
placeholder="Type a message..."
|
||||
rows="1"
|
||||
class="flex-1 resize-none rounded-xl border border-gray-300 px-4 py-2.5 text-sm
|
||||
focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none
|
||||
disabled:bg-gray-100 disabled:text-gray-400"
|
||||
></textarea>
|
||||
<button
|
||||
type="button"
|
||||
onclick={handleSubmit}
|
||||
disabled={disabled || !input.trim()}
|
||||
class="rounded-xl bg-blue-600 px-4 py-2.5 text-sm font-medium text-white
|
||||
hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none
|
||||
disabled:bg-gray-300 disabled:cursor-not-allowed"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -1,8 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { ChatMessage } from '$lib/types';
|
||||
import MessageList from '$lib/components/MessageList.svelte';
|
||||
import MessageInput from '$lib/components/MessageInput.svelte';
|
||||
|
||||
let messages: ChatMessage[] = $state([]);
|
||||
let isStreaming = $state(false);
|
||||
|
||||
function handleSend(content: string) {
|
||||
const userMessage: ChatMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
role: 'user',
|
||||
content,
|
||||
timestamp: new Date()
|
||||
};
|
||||
messages.push(userMessage);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-screen flex-col">
|
||||
@@ -11,4 +23,5 @@
|
||||
</header>
|
||||
|
||||
<MessageList {messages} />
|
||||
<MessageInput onSend={handleSend} disabled={isStreaming} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user