- Glassmorphism chat window matching Proux design rules (glass layers, gradient backgrounds, glow effects, blur intensities, inner glows) - Conversation ID displayed in header, side-switching (left/right layout) - Chat store with Pinia: conversations, messages, streaming state - OpenRouter AI integration with SSE streaming (Llama 4 Maverick free model) - Chat components: ChatWindow, ChatHeader, ChatInput, ChatMessage, StreamingDots (typing indicator) - Embeddable widget system: floating action button (FAB) + modal popup with scale-in animation, side-aware positioning - Widget demo page (/widget-demo) showing AIUI embedded in a mock "Acme App" with documentation of 4 integration methods: script tag, web component, npm package, and iframe - Theme composable with dark/light mode, system preference detection - Custom CSS: glass variants (subtle/medium/strong/light/dark), glow effects, gradient text, animation keyframes, thin scrollbar - Responsive: mobile-first, 44px touch targets, dvh viewport Made-with: Cursor
64 lines
2.3 KiB
Vue
64 lines
2.3 KiB
Vue
<template>
|
|
<div class="glass-subtle rounded-t-2xl flex items-center justify-between px-4 py-3">
|
|
<div class="flex items-center gap-3 min-w-0">
|
|
<div class="w-8 h-8 rounded-full bg-accent/20 flex items-center justify-center shrink-0">
|
|
<span class="text-accent text-sm font-bold">AI</span>
|
|
</div>
|
|
<div class="min-w-0">
|
|
<h2 class="text-sm font-semibold text-white truncate">{{ title }}</h2>
|
|
<p class="text-[10px] text-white/40 truncate">{{ conversationId }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-1">
|
|
<button
|
|
class="p-2 rounded-lg text-white/40 hover:text-white/80 hover:bg-white/5 transition-all duration-200"
|
|
:title="side === 'right' ? 'Move panel to left' : 'Move panel to right'"
|
|
aria-label="Switch panel side"
|
|
@click="$emit('switchSide')"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path v-if="side === 'right'" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
|
|
<path v-else stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7" />
|
|
</svg>
|
|
</button>
|
|
|
|
<button
|
|
class="p-2 rounded-lg text-white/40 hover:text-white/80 hover:bg-white/5 transition-all duration-200"
|
|
aria-label="New conversation"
|
|
@click="$emit('newChat')"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
</svg>
|
|
</button>
|
|
|
|
<button
|
|
v-if="showClose"
|
|
class="p-2 rounded-lg text-white/40 hover:text-white/80 hover:bg-white/5 transition-all duration-200"
|
|
aria-label="Close"
|
|
@click="$emit('close')"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
defineProps<{
|
|
title: string
|
|
conversationId: string
|
|
side: 'left' | 'right'
|
|
showClose?: boolean
|
|
}>()
|
|
|
|
defineEmits<{
|
|
switchSide: []
|
|
newChat: []
|
|
close: []
|
|
}>()
|
|
</script>
|