feat: add chat page layout with message list and bubble components
- Create /chat route with scrollable message list - MessageBubble component with distinct user/assistant styles - MessageList with auto-scroll-to-bottom on new messages - Empty state display when no messages - ChatMessage type definition in src/lib/types.ts - Add browser globals to ESLint config for Svelte files Closes #5 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
20
src/lib/components/MessageBubble.svelte
Normal file
20
src/lib/components/MessageBubble.svelte
Normal file
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import type { ChatMessage } from '$lib/types';
|
||||
|
||||
let { message }: { message: ChatMessage } = $props();
|
||||
|
||||
const isUser = $derived(message.role === 'user');
|
||||
</script>
|
||||
|
||||
<div class="flex {isUser ? 'justify-end' : 'justify-start'} mb-3">
|
||||
<div
|
||||
class="max-w-[75%] rounded-2xl px-4 py-2.5 {isUser
|
||||
? 'bg-blue-600 text-white rounded-br-md'
|
||||
: 'bg-gray-200 text-gray-900 rounded-bl-md'}"
|
||||
>
|
||||
<p class="text-sm whitespace-pre-wrap">{message.content}</p>
|
||||
<time class="mt-1 block text-xs {isUser ? 'text-blue-200' : 'text-gray-500'}">
|
||||
{message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
37
src/lib/components/MessageList.svelte
Normal file
37
src/lib/components/MessageList.svelte
Normal file
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { tick } from 'svelte';
|
||||
import type { ChatMessage } from '$lib/types';
|
||||
import MessageBubble from './MessageBubble.svelte';
|
||||
|
||||
let { messages }: { messages: ChatMessage[] } = $props();
|
||||
|
||||
let container: Element | undefined = $state();
|
||||
|
||||
async function scrollToBottom() {
|
||||
await tick();
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (messages.length > 0) {
|
||||
scrollToBottom();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={container} class="flex-1 overflow-y-auto p-4">
|
||||
{#if messages.length === 0}
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<div class="text-center text-gray-400">
|
||||
<p class="text-lg font-medium">No messages yet</p>
|
||||
<p class="mt-1 text-sm">Send a message to start a conversation</p>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
{#each messages as message (message.id)}
|
||||
<MessageBubble {message} />
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
6
src/lib/types.ts
Normal file
6
src/lib/types.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
role: 'user' | 'assistant';
|
||||
content: string;
|
||||
timestamp: Date;
|
||||
}
|
||||
14
src/routes/chat/+page.svelte
Normal file
14
src/routes/chat/+page.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { ChatMessage } from '$lib/types';
|
||||
import MessageList from '$lib/components/MessageList.svelte';
|
||||
|
||||
let messages: ChatMessage[] = $state([]);
|
||||
</script>
|
||||
|
||||
<div class="flex h-screen flex-col">
|
||||
<header class="border-b border-gray-200 bg-white px-4 py-3">
|
||||
<h1 class="text-lg font-semibold text-gray-900">Chat</h1>
|
||||
</header>
|
||||
|
||||
<MessageList {messages} />
|
||||
</div>
|
||||
Reference in New Issue
Block a user