Merge pull request 'feat: add message input with send and keyboard shortcuts' (#26) from feature/issue-6-message-input into main

This commit was merged in pull request #26.
This commit is contained in:
2026-03-12 11:27:56 +01:00
4 changed files with 94 additions and 0 deletions

View File

@@ -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) |

View 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

View 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>

View File

@@ -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>