mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-10 06:48:04 -05:00
WIP: Restyling Chat page
This commit is contained in:
@@ -4,56 +4,52 @@
|
||||
import ModelConfig from "./ModelConfig.svelte";
|
||||
import Models from "./Models.svelte";
|
||||
import Patterns from "./Patterns.svelte";
|
||||
import NoteDrawer from '$lib/components/ui/noteDrawer/NoteDrawer.svelte';
|
||||
import { getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
import { page } from '$app/stores';
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
|
||||
const drawerStore = getDrawerStore();
|
||||
function openDrawer() {
|
||||
drawerStore.open({});
|
||||
}
|
||||
|
||||
beforeNavigate(() => {
|
||||
drawerStore.close();
|
||||
});
|
||||
|
||||
$: isVisible = $page.url.pathname.startsWith('/chat');
|
||||
//import NoteDrawer from '$lib/components/ui/noteDrawer/NoteDrawer.svelte';
|
||||
//import { getDrawerStore } from '@skeletonlabs/skeleton';
|
||||
//import { Button } from '$lib/components/ui/button';
|
||||
//import { page } from '$app/stores';
|
||||
//import { beforeNavigate } from '$app/navigation';
|
||||
//
|
||||
//const drawerStore = getDrawerStore();
|
||||
//function openDrawer() {
|
||||
// drawerStore.open({});
|
||||
//}
|
||||
//
|
||||
//beforeNavigate(() => {
|
||||
// drawerStore.close();
|
||||
//});
|
||||
//
|
||||
//$: isVisible = $page.url.pathname.startsWith('/chat');
|
||||
</script>
|
||||
|
||||
<div class="flex-1 mx-auto p-4 min-h-screen">
|
||||
<div class="grid grid-cols-1 auto-fit lg:grid-cols-[250px_minmax(250px,_1.5fr)_minmax(250px,_1.5fr)] gap-4 h-[calc(100vh-2rem)]">
|
||||
<div class="flex flex-col space-y-1 order-3 lg:order-1">
|
||||
<div class="space-y-2 max-w-full">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div content="width=device-width, height=device-height, initial-scale=1.0">
|
||||
<div class="h-screen overflow-auto">
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="flex-1 overflow-hidden mx-auto p-2">
|
||||
<!-- <div class="flex flex-col columns-3 m-2 p-1 h-screen"> -->
|
||||
<div class="h-full flex gap-2">
|
||||
<aside class="w-1/5 overflow-y-auto">
|
||||
<!-- <div class="space-y-2"> -->
|
||||
<!-- <div class="flex flex-col gap-2"> -->
|
||||
<Patterns />
|
||||
<Models />
|
||||
<ModelConfig />
|
||||
<!-- </div> -->
|
||||
|
||||
<!-- </div> -->
|
||||
</aside>
|
||||
<div class="w-1/2">
|
||||
<!-- <div class="flex flex-col space-y-4 order-2 lg:order-2 h-screen"> -->
|
||||
<ChatInput />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
|
||||
{#if isVisible}
|
||||
<div class="flex justify-start mt-2">
|
||||
<button type="button"
|
||||
class="btn btn-sm border variant-filled-primary"
|
||||
on:click={openDrawer}
|
||||
>Open Drawer
|
||||
</button>
|
||||
</div>
|
||||
<NoteDrawer />
|
||||
{/if}
|
||||
<br>
|
||||
<div class="w-1/2">
|
||||
<!-- <div class="flex flex-col rounded-lg order-1 lg:order-3 h-screen w-full"> -->
|
||||
<ChatMessages />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <button class="primary" on:click={openDrawer}>Open Drawer</button> -->
|
||||
</div>
|
||||
<div class="flex flex-col space-y-4 order-2 lg:order-2">
|
||||
<ChatInput />
|
||||
</div>
|
||||
<div class="flex flex-col border rounded-lg bg-muted/50 p-4 order-1 lg:order-3 max-h-[695px]">
|
||||
<ChatMessages />
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
// Clear input before sending to improve perceived performance
|
||||
userInput = "";
|
||||
|
||||
await sendMessage(trimmedSystemPrompt + trimmedInput);
|
||||
await sendMessage(trimmedSystemPrompt + '\n' + trimmedInput);
|
||||
} catch (error) {
|
||||
console.error('Chat submission error:', error);
|
||||
toastStore.trigger({
|
||||
@@ -45,42 +45,45 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex-none">
|
||||
<Textarea
|
||||
bind:value={$systemPrompt}
|
||||
on:input={(e) => $systemPrompt || ''}
|
||||
placeholder="Enter system instructions..."
|
||||
class="min-h-[330px] resize-none bg-background"
|
||||
/>
|
||||
<div class="h-full">
|
||||
<div class="flex flex-col gap-2 h-screen">
|
||||
<div class="flex-1 rounded-lg border-current">
|
||||
<Textarea
|
||||
bind:value={$systemPrompt}
|
||||
on:input={(e) => $systemPrompt || ''}
|
||||
placeholder="Enter system instructions..."
|
||||
class="w-full h-full resize-none bg-primary-800/30 rounded-lg border-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 py-2 relative">
|
||||
<Textarea
|
||||
bind:value={userInput}
|
||||
on:input={(e) => userInput}
|
||||
on:keydown={handleKeydown}
|
||||
placeholder="Enter your message..."
|
||||
class="min-h-[350px] resize-none bg-background"
|
||||
/>
|
||||
<div class="absolute bottom-5 right-2 gap-2 flex justify-end end-7">
|
||||
|
||||
<FileButton
|
||||
name="file-upload"
|
||||
button="btn btn-sm variant-soft-surface"
|
||||
bind:files={files}
|
||||
on:change={(e) => {
|
||||
// Workin on the file selection
|
||||
// Check out `https://www.skeleton.dev/components/file-buttons` for more info
|
||||
}}
|
||||
>
|
||||
<Paperclip class="w-4" />
|
||||
</FileButton>
|
||||
<Button type="button" name="submit" variant="secondary" on:click={handleSubmit}>
|
||||
<Send class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Textarea
|
||||
bind:value={userInput}
|
||||
on:input={(e) => userInput}
|
||||
on:keydown={handleKeydown}
|
||||
placeholder="Enter your message..."
|
||||
class="w-full h-full resize-none bg-primary-800/30 rounded-lg border-none"
|
||||
/>
|
||||
<div class="absolute bottom-5 right-2 gap-2 flex justify-end end-7">
|
||||
|
||||
<FileButton
|
||||
name="file-upload"
|
||||
button="btn btn-sm variant-soft-surface"
|
||||
bind:files={files}
|
||||
on:change={(e) => {
|
||||
// Workin on the file selection
|
||||
// Check out `https://www.skeleton.dev/components/file-buttons` for more info
|
||||
// Check 24-12-08 for half-baked implementation
|
||||
}}
|
||||
>
|
||||
<Paperclip class="w-4" />
|
||||
</FileButton>
|
||||
<Button type="button" name="submit" variant="secondary" on:click={handleSubmit}>
|
||||
<Send class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { chatState, errorStore, streamingStore } from '$lib/store/chat';
|
||||
import { afterUpdate } from 'svelte';
|
||||
import { toastStore } from '$lib/store/toast-store';
|
||||
import { marked } from 'marked';
|
||||
import SessionManager from './SessionManager.svelte';
|
||||
import { fade, slide } from 'svelte/transition';
|
||||
@@ -29,111 +30,123 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="chat-messages-wrapper flex flex-col h-full">
|
||||
<div class="flex justify-between items-center mb-4 flex-none">
|
||||
<span class="text-sm font-medium">Chat History</span>
|
||||
<SessionManager />
|
||||
<div class="bg-primary-800/30 rounded-lg flex flex-col h-full shadow-lg">
|
||||
<div class="flex justify-between items-center mb-1 mt-1 flex-none">
|
||||
<div class="flex items-center gap-2 pl-4">
|
||||
<b class="text-sm text-muted-foreground font-bold">Chat History</b>
|
||||
</div>
|
||||
<SessionManager />
|
||||
</div>
|
||||
|
||||
{#if $errorStore}
|
||||
<div class="error-message" transition:slide>
|
||||
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" role="alert">
|
||||
<p>{$errorStore}</p>
|
||||
{#if $errorStore}
|
||||
<div class="error-message" transition:slide>
|
||||
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" role="alert">
|
||||
<p>{$errorStore}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Check 24-12-08 chat-interface for more updates here -->
|
||||
|
||||
<div class="messages-container p-4 flex-1 overflow-y-auto max-h-dvh" bind:this={messagesContainer}>
|
||||
<div class="messages-content flex flex-col gap-4">
|
||||
{#each $chatState.messages as message}
|
||||
<div
|
||||
class="message-item {message.role === 'assistant' ? 'pl-4 bg-primary/5 rounded-lg p-2' : 'pr-4 ml-auto'}"
|
||||
transition:fade
|
||||
>
|
||||
<div class="message-header flex items-center gap-2 mb-1 {message.role === 'assistant' ? '' : 'justify-end'}">
|
||||
<span class="text-xs text-muted-foreground rounded-lg p-1 variant-glass-secondary font-bold uppercase">
|
||||
{message.role === 'assistant' ? 'AI' : 'You'}
|
||||
</span>
|
||||
{#if message.role === 'assistant' && $streamingStore}
|
||||
<span class="loading-indicator flex gap-1">
|
||||
<span class="dot animate-bounce">.</span>
|
||||
<span class="dot animate-bounce delay-100">.</span>
|
||||
<span class="dot animate-bounce delay-200">.</span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if message.role === 'assistant'}
|
||||
<div class="prose prose-slate dark:prose-invert text-inherit prose-headings:text-inherit prose-pre:bg-primary/10 prose-pre:text-inherit text-sm max-w-none">
|
||||
{@html renderMarkdown(message.content, true)}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="whitespace-pre-wrap text-sm">
|
||||
{message.content}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="messages-container" bind:this={messagesContainer}>
|
||||
<div class="messages-content">
|
||||
{#each $chatState.messages as message}
|
||||
<div
|
||||
class="message-item {message.role === 'assistant' ? 'pl-4' : 'font-medium'}"
|
||||
transition:fade
|
||||
>
|
||||
<div class="message-header flex items-center gap-2 mb-1">
|
||||
<span class="text-xs border rounded-lg p-1 variant-glass-secondary font-bold uppercase">{message.role}</span>
|
||||
{#if message.role === 'assistant' && $streamingStore}
|
||||
<span class="loading-indicator">
|
||||
<span class="dot">.</span>
|
||||
<span class="dot">.</span>
|
||||
<span class="dot">.</span>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if message.role === 'assistant'}
|
||||
<div class="prose prose-sm text-inherit max-w-none">
|
||||
{@html renderMarkdown(message.content, true)}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="whitespace-pre-wrap text-sm">
|
||||
{message.content}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.chat-messages-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
/*.chat-messages-wrapper {*/
|
||||
/* display: flex;*/
|
||||
/* flex-direction: column;*/
|
||||
/* min-height: 0;*/
|
||||
/*}*/
|
||||
|
||||
.messages-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
.messages-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
-ms-overflow-style: thin;
|
||||
}
|
||||
|
||||
.messages-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.messages-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
position: relative;
|
||||
}
|
||||
.message-header {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
}
|
||||
.message-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dot {
|
||||
animation: blink 1.4s infinite;
|
||||
opacity: 0;
|
||||
}
|
||||
.loading-indicator {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
.dot {
|
||||
animation: blink 1.4s infinite;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
:global(.prose pre) {
|
||||
background-color: rgb(40, 44, 52);
|
||||
color: rgb(171, 178, 191);
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
:global(.prose code) {
|
||||
color: rgb(171, 178, 191);
|
||||
background-color: rgba(40, 44, 52, 0.1);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
:global(.prose pre) {
|
||||
background-color: rgb(40, 44, 52);
|
||||
color: rgb(171, 178, 191);
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
:global(.prose code) {
|
||||
color: rgb(171, 178, 191);
|
||||
background-color: rgba(40, 44, 52, 0.1);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="p-1 m-1 mr-2">
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" size="icon" aria-label="Revert Last Message" on:click={revertLastMessage}>
|
||||
<RotateCcw class="h-4 w-4" />
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<div
|
||||
in:fly={{ duration: 500, delay: 100, y: 100 }}
|
||||
>
|
||||
<main class="m-auto p-4">
|
||||
<main class="main m-auto">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user