feat(app): glassmorphism chat UI with widget embed system
- 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
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { Message, Conversation } from '@aiui/core/types/message'
|
||||
|
||||
export const useChatStore = defineStore('chat', () => {
|
||||
const conversations = ref<Map<string, Conversation>>(new Map())
|
||||
const activeConversationId = ref<string | null>(null)
|
||||
const isStreaming = ref(false)
|
||||
const panelSide = ref<'left' | 'right'>('right')
|
||||
|
||||
const activeConversation = computed(() => {
|
||||
if (!activeConversationId.value) return null
|
||||
return conversations.value.get(activeConversationId.value) ?? null
|
||||
})
|
||||
|
||||
const messages = computed(() => activeConversation.value?.messages ?? [])
|
||||
|
||||
const conversationList = computed(() =>
|
||||
Array.from(conversations.value.values()).sort((a, b) => b.updatedAt - a.updatedAt)
|
||||
)
|
||||
|
||||
function createConversation(title = 'New Chat'): string {
|
||||
const id = crypto.randomUUID()
|
||||
const conversation: Conversation = {
|
||||
id,
|
||||
title,
|
||||
messages: [],
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
conversations.value.set(id, conversation)
|
||||
activeConversationId.value = id
|
||||
return id
|
||||
}
|
||||
|
||||
function addMessage(conversationId: string, message: Omit<Message, 'id' | 'timestamp'>) {
|
||||
const conv = conversations.value.get(conversationId)
|
||||
if (!conv) return
|
||||
|
||||
const msg: Message = {
|
||||
...message,
|
||||
id: crypto.randomUUID(),
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
conv.messages.push(msg)
|
||||
conv.updatedAt = Date.now()
|
||||
|
||||
if (conv.messages.length === 1 && message.role === 'user') {
|
||||
conv.title = message.content.slice(0, 60) + (message.content.length > 60 ? '...' : '')
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
function appendToLastMessage(conversationId: string, text: string) {
|
||||
const conv = conversations.value.get(conversationId)
|
||||
if (!conv || conv.messages.length === 0) return
|
||||
const last = conv.messages[conv.messages.length - 1]
|
||||
last.content += text
|
||||
}
|
||||
|
||||
function switchSide() {
|
||||
panelSide.value = panelSide.value === 'right' ? 'left' : 'right'
|
||||
}
|
||||
|
||||
function setActiveConversation(id: string) {
|
||||
if (conversations.value.has(id)) {
|
||||
activeConversationId.value = id
|
||||
}
|
||||
}
|
||||
|
||||
function deleteConversation(id: string) {
|
||||
conversations.value.delete(id)
|
||||
if (activeConversationId.value === id) {
|
||||
const remaining = conversationList.value
|
||||
activeConversationId.value = remaining.length > 0 ? remaining[0].id : null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
conversations,
|
||||
activeConversationId,
|
||||
activeConversation,
|
||||
messages,
|
||||
conversationList,
|
||||
isStreaming,
|
||||
panelSide,
|
||||
createConversation,
|
||||
addMessage,
|
||||
appendToLastMessage,
|
||||
switchSide,
|
||||
setActiveConversation,
|
||||
deleteConversation,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user