mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 08:09:40 +02:00
feat(chat): redesign chat UI with glassmorphism styling
- Redesign chat homepage with glass-styled empty state and suggestion pills - Add floating search bar with backdrop blur - Style conversation list items as glass cards - Add consistent borders (border-black/10 dark:border-white/20) - Remove hard borders, use subtle glassmorphism effects - Add hover effects with shadow and lift animations - Update model/template selector with glass pill styling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8f5859921d
commit
7819c9c265
2 changed files with 141 additions and 96 deletions
|
|
@ -103,17 +103,21 @@
|
|||
>
|
||||
<!-- Left Column: Conversation List -->
|
||||
<div
|
||||
class="relative flex flex-shrink-0 flex-col bg-muted/30 border-r border-border"
|
||||
class="relative flex flex-shrink-0 flex-col bg-background"
|
||||
style="width: {leftColumnWidth}px;"
|
||||
>
|
||||
<!-- Search Bar -->
|
||||
<div class="px-3 py-3 border-b border-border">
|
||||
<!-- Floating Search Bar -->
|
||||
<div
|
||||
class="absolute top-0 left-0 right-0 z-20 py-3 pr-3 transition-all duration-300"
|
||||
class:pl-16={$isNavCollapsed}
|
||||
class:pl-4={!$isNavCollapsed}
|
||||
>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={searchQuery}
|
||||
placeholder="Konversationen durchsuchen..."
|
||||
class="w-full rounded-xl border border-border bg-white/70 dark:bg-black/50 backdrop-blur-xl px-4 py-2.5 pl-10 text-sm font-medium text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary shadow-sm"
|
||||
class="w-full rounded-xl bg-white/70 dark:bg-white/10 backdrop-blur-xl border border-black/10 dark:border-white/20 px-4 py-2.5 pl-10 text-sm font-medium text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary shadow-lg"
|
||||
/>
|
||||
<MagnifyingGlass
|
||||
size={16}
|
||||
|
|
@ -134,15 +138,18 @@
|
|||
|
||||
<!-- Conversation List (Scrollable) -->
|
||||
<div class="flex-1 overflow-hidden flex flex-col">
|
||||
<div class="flex-1 overflow-y-auto py-3 px-3 scrollbar-hide">
|
||||
<div class="flex-1 overflow-y-auto pl-4 pr-3 pt-[68px] pb-3 scrollbar-hide">
|
||||
<!-- New Chat Button -->
|
||||
<a
|
||||
href="/chat"
|
||||
class="flex items-center gap-2 w-full mb-3 px-4 py-2.5 rounded-xl
|
||||
bg-primary text-primary-foreground font-medium text-sm
|
||||
shadow-md hover:bg-primary/90 transition-colors"
|
||||
bg-white/80 dark:bg-white/10 backdrop-blur-xl
|
||||
border border-black/10 dark:border-white/20
|
||||
text-foreground font-medium text-sm
|
||||
shadow-md hover:shadow-lg hover:bg-white dark:hover:bg-white/20
|
||||
transition-all duration-200 hover:-translate-y-0.5"
|
||||
>
|
||||
<Plus size={18} weight="bold" />
|
||||
<Plus size={18} weight="bold" class="text-primary" />
|
||||
Neuer Chat
|
||||
</a>
|
||||
|
||||
|
|
@ -162,7 +169,7 @@
|
|||
</p>
|
||||
<button
|
||||
onclick={() => (searchQuery = '')}
|
||||
class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
||||
class="rounded-xl bg-white/80 dark:bg-white/10 backdrop-blur-xl border border-black/10 dark:border-white/20 px-4 py-2 text-sm font-medium text-foreground shadow-md hover:shadow-lg transition-all"
|
||||
>
|
||||
Suche leeren
|
||||
</button>
|
||||
|
|
@ -176,10 +183,10 @@
|
|||
{#each filteredConversations as conv (conv.id)}
|
||||
<a
|
||||
href="/chat/{conv.id}"
|
||||
class="block w-full rounded-xl border bg-surface p-4 text-left transition-all mb-3 hover:shadow-md
|
||||
class="block w-full rounded-xl bg-white/60 dark:bg-white/5 backdrop-blur-sm border border-black/10 dark:border-white/20 p-4 text-left transition-all mb-3 hover:shadow-md hover:bg-white/80 dark:hover:bg-white/10
|
||||
{isActive(conv.id)
|
||||
? 'border-primary bg-primary/5 shadow-md'
|
||||
: 'border-border hover:bg-muted/50'}"
|
||||
? 'bg-white/90 dark:bg-white/15 shadow-md border-primary/30'
|
||||
: ''}"
|
||||
>
|
||||
<!-- Title Row -->
|
||||
<div class="mb-1.5 flex items-center gap-2">
|
||||
|
|
@ -230,7 +237,7 @@
|
|||
></button>
|
||||
|
||||
<!-- Right Column: Chat Content -->
|
||||
<div class="flex flex-1 flex-col bg-surface overflow-hidden">
|
||||
<div class="flex flex-1 flex-col bg-background overflow-hidden">
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -59,18 +59,18 @@
|
|||
// Add optimistic user message
|
||||
const tempUserMessage: Message = {
|
||||
id: `temp-${Date.now()}`,
|
||||
conversation_id: '',
|
||||
conversationId: '',
|
||||
sender: 'user',
|
||||
message_text: text,
|
||||
created_at: new Date().toISOString(),
|
||||
messageText: text,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
messages = [...messages, tempUserMessage];
|
||||
|
||||
try {
|
||||
// Determine mode and model based on template
|
||||
const mode = selectedTemplate ? 'template' : 'free';
|
||||
const modelToUse = selectedTemplate?.model_id || selectedModelId;
|
||||
const docMode = selectedTemplate?.document_mode || documentMode;
|
||||
const modelToUse = selectedTemplate?.modelId || selectedModelId;
|
||||
const docMode = selectedTemplate?.documentMode || documentMode;
|
||||
|
||||
// Create new conversation
|
||||
const conversationId = await conversationService.createConversation(
|
||||
|
|
@ -115,11 +115,11 @@
|
|||
|
||||
// If template has a model, update selected model
|
||||
const template = templates.find((t) => t.id === target.value);
|
||||
if (template?.model_id) {
|
||||
selectedModelId = template.model_id;
|
||||
if (template?.modelId) {
|
||||
selectedModelId = template.modelId;
|
||||
}
|
||||
// If template has document mode, enable it
|
||||
if (template?.document_mode) {
|
||||
if (template?.documentMode) {
|
||||
documentMode = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,105 +135,143 @@
|
|||
|
||||
<ChatLayout>
|
||||
{#snippet children()}
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Welcome Header -->
|
||||
<div class="flex-shrink-0 border-b border-border bg-surface/50 backdrop-blur-sm px-6 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary to-primary/60 flex items-center justify-center"
|
||||
>
|
||||
<Sparkle size={22} weight="fill" class="text-primary-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-foreground">Neuer Chat</h2>
|
||||
<p class="text-sm text-muted-foreground">Starte eine neue Unterhaltung</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<!-- Model Selector -->
|
||||
<ModelSelector
|
||||
{models}
|
||||
{selectedModelId}
|
||||
onSelect={handleModelSelect}
|
||||
disabled={isSending}
|
||||
/>
|
||||
|
||||
<!-- Template Selector -->
|
||||
{#if templates.length > 0}
|
||||
<select
|
||||
onchange={handleTemplateSelect}
|
||||
value={selectedTemplateId}
|
||||
disabled={isSending}
|
||||
class="px-3 py-1.5 text-sm border border-border rounded-lg
|
||||
bg-surface text-foreground
|
||||
focus:ring-2 focus:ring-primary focus:border-transparent
|
||||
disabled:opacity-50"
|
||||
>
|
||||
<option value="">Ohne Vorlage</option>
|
||||
{#each templates as template}
|
||||
<option value={template.id}>
|
||||
{template.name}
|
||||
{template.is_default ? ' (Standard)' : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
{/if}
|
||||
|
||||
<!-- Document Mode Toggle -->
|
||||
<button
|
||||
onclick={toggleDocumentMode}
|
||||
disabled={isSending}
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg transition-colors
|
||||
{documentMode
|
||||
? 'bg-primary/10 text-primary border border-primary/30'
|
||||
: 'bg-muted text-muted-foreground border border-border'}
|
||||
hover:bg-opacity-80 disabled:opacity-50"
|
||||
title="Dokumentmodus aktivieren"
|
||||
>
|
||||
<FileText size={16} weight="bold" />
|
||||
Dokument
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col h-full bg-background">
|
||||
<!-- Messages Area -->
|
||||
<main class="flex-1 overflow-hidden">
|
||||
{#if messages.length === 0 && !isSending}
|
||||
<!-- Empty State -->
|
||||
<!-- Empty State - Centered Content -->
|
||||
<div class="h-full flex flex-col items-center justify-center px-6">
|
||||
<div class="text-center max-w-md">
|
||||
<div class="text-center max-w-xl w-full">
|
||||
<!-- Icon -->
|
||||
<div
|
||||
class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-primary/20 to-primary/5 flex items-center justify-center"
|
||||
class="w-20 h-20 mx-auto mb-6 rounded-2xl bg-white/70 dark:bg-white/10 backdrop-blur-xl border border-black/10 dark:border-white/20 shadow-lg flex items-center justify-center"
|
||||
>
|
||||
<Sparkle size={32} weight="duotone" class="text-primary" />
|
||||
<Sparkle size={40} weight="duotone" class="text-primary" />
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-foreground mb-2">Worüber möchtest du reden?</h3>
|
||||
<p class="text-muted-foreground mb-6">
|
||||
|
||||
<!-- Title -->
|
||||
<h3 class="text-2xl font-semibold text-foreground mb-3">Worüber möchtest du reden?</h3>
|
||||
<p class="text-muted-foreground mb-8">
|
||||
Stelle eine Frage, bitte um Hilfe bei einem Projekt oder starte einfach eine
|
||||
Unterhaltung.
|
||||
</p>
|
||||
<div class="flex flex-wrap justify-center gap-2">
|
||||
|
||||
<!-- Suggestion Pills -->
|
||||
<div class="flex flex-wrap justify-center gap-3 mb-8">
|
||||
<button
|
||||
onclick={() => handleSend('Erkläre mir, wie KI funktioniert')}
|
||||
class="px-4 py-2 text-sm rounded-xl bg-muted hover:bg-muted/80 text-foreground transition-colors"
|
||||
class="px-5 py-2.5 text-sm font-medium rounded-full bg-white/80 dark:bg-white/10 backdrop-blur-xl border border-black/10 dark:border-white/20 text-foreground shadow-md hover:shadow-lg hover:bg-white dark:hover:bg-white/20 transition-all duration-200 hover:-translate-y-0.5"
|
||||
>
|
||||
Erkläre mir KI
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleSend('Hilf mir beim Schreiben eines Textes')}
|
||||
class="px-4 py-2 text-sm rounded-xl bg-muted hover:bg-muted/80 text-foreground transition-colors"
|
||||
class="px-5 py-2.5 text-sm font-medium rounded-full bg-white/80 dark:bg-white/10 backdrop-blur-xl border border-black/10 dark:border-white/20 text-foreground shadow-md hover:shadow-lg hover:bg-white dark:hover:bg-white/20 transition-all duration-200 hover:-translate-y-0.5"
|
||||
>
|
||||
Beim Schreiben helfen
|
||||
</button>
|
||||
<button
|
||||
onclick={() => handleSend('Was sind aktuelle Technologie-Trends?')}
|
||||
class="px-4 py-2 text-sm rounded-xl bg-muted hover:bg-muted/80 text-foreground transition-colors"
|
||||
class="px-5 py-2.5 text-sm font-medium rounded-full bg-white/80 dark:bg-white/10 backdrop-blur-xl border border-black/10 dark:border-white/20 text-foreground shadow-md hover:shadow-lg hover:bg-white dark:hover:bg-white/20 transition-all duration-200 hover:-translate-y-0.5"
|
||||
>
|
||||
Tech-Trends
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Options Bar -->
|
||||
<div
|
||||
class="inline-flex items-center gap-2 p-1.5 rounded-full bg-white/70 dark:bg-white/10 backdrop-blur-xl border border-black/10 dark:border-white/20 shadow-md"
|
||||
>
|
||||
<!-- Model Selector -->
|
||||
<div class="relative">
|
||||
<select
|
||||
value={selectedModelId}
|
||||
onchange={(e) => handleModelSelect((e.target as HTMLSelectElement).value)}
|
||||
disabled={isSending}
|
||||
class="appearance-none bg-transparent text-foreground text-sm font-medium rounded-full pl-4 pr-8 py-2 border-0 focus:outline-none focus:ring-0 disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
{#if models.length === 0}
|
||||
<option value="">Laden...</option>
|
||||
{:else}
|
||||
{#each models as model (model.id)}
|
||||
<option value={model.id}>{model.name}</option>
|
||||
{/each}
|
||||
{/if}
|
||||
</select>
|
||||
<div
|
||||
class="absolute inset-y-0 right-2 flex items-center pointer-events-none"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="w-px h-6 bg-black/10 dark:bg-white/20"></div>
|
||||
|
||||
<!-- Template Selector -->
|
||||
{#if templates.length > 0}
|
||||
<div class="relative">
|
||||
<select
|
||||
onchange={handleTemplateSelect}
|
||||
value={selectedTemplateId}
|
||||
disabled={isSending}
|
||||
class="appearance-none bg-transparent text-foreground text-sm font-medium rounded-full pl-4 pr-8 py-2 border-0 focus:outline-none focus:ring-0 disabled:opacity-50 cursor-pointer"
|
||||
>
|
||||
<option value="">Ohne Vorlage</option>
|
||||
{#each templates as template}
|
||||
<option value={template.id}>
|
||||
{template.name}
|
||||
{template.isDefault ? ' ★' : ''}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div
|
||||
class="absolute inset-y-0 right-2 flex items-center pointer-events-none"
|
||||
>
|
||||
<svg
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-px h-6 bg-black/10 dark:bg-white/20"></div>
|
||||
{/if}
|
||||
|
||||
<!-- Document Mode Toggle -->
|
||||
<button
|
||||
onclick={toggleDocumentMode}
|
||||
disabled={isSending}
|
||||
class="flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-full transition-all duration-200 disabled:opacity-50
|
||||
{documentMode
|
||||
? 'bg-primary/20 text-primary'
|
||||
: 'text-foreground hover:bg-black/5 dark:hover:bg-white/10'}"
|
||||
title="Dokumentmodus aktivieren"
|
||||
>
|
||||
<FileText size={16} weight={documentMode ? 'fill' : 'bold'} />
|
||||
<span>Dokument</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
|
|
@ -244,7 +282,7 @@
|
|||
</main>
|
||||
|
||||
<!-- Floating Chat Input -->
|
||||
<div class="flex-shrink-0 p-4 bg-gradient-to-t from-surface via-surface to-transparent">
|
||||
<div class="flex-shrink-0 p-4 bg-gradient-to-t from-background via-background to-transparent">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<ChatInput onSend={handleSend} disabled={isSending || isLoading} />
|
||||
</div>
|
||||
|
|
@ -253,7 +291,7 @@
|
|||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<div
|
||||
class="fixed bottom-24 left-1/2 -translate-x-1/2 px-4 py-2 bg-destructive text-destructive-foreground rounded-lg shadow-lg z-50"
|
||||
class="fixed bottom-24 left-1/2 -translate-x-1/2 px-6 py-3 bg-white/90 dark:bg-black/80 backdrop-blur-xl border border-destructive/30 text-destructive rounded-full shadow-lg z-50"
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue