feat(app): enhance theme support and improve PWA integration

- Updated the app to support light and dark themes with appropriate CSS classes.
- Enhanced PWA configuration with manifest details and caching strategies.
- Improved the chat UI with dynamic theme adjustments for various components.
- Added new meta tags for better mobile web app experience.
- Refactored environment variables to include new Anthropic token.
- Updated package dependencies for better compatibility and performance.

Made-with: Cursor
This commit is contained in:
Dorian
2026-03-02 16:34:44 +00:00
parent a5a32e3566
commit ece4b8256f
28 changed files with 7315 additions and 212 deletions
+33 -12
View File
@@ -1,15 +1,22 @@
<template>
<div class="glass-strong rounded-t-2xl flex items-center justify-between px-4 py-3"
style="border-bottom: 1px solid rgba(255, 255, 255, 0.08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);">
<div
class="glass-strong rounded-t-2xl flex items-center justify-between px-4 py-3 relative"
:style="isDark
? 'border-bottom: 1px solid rgba(255, 255, 255, 0.08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22)'
: 'border-bottom: 1px solid rgba(0, 0, 0, 0.06); box-shadow: inset 0 1px 0 rgba(254, 253, 249, 1)'"
>
<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">
<div class="w-8 h-8 rounded-full flex items-center justify-center shrink-0"
:class="isDark ? 'bg-accent/20' : 'bg-accent/10'">
<span class="text-accent text-sm font-bold">AI</span>
</div>
<div class="min-w-0">
<h2 class="text-sm font-semibold text-white/96 truncate">{{ title }}</h2>
<h2 class="text-sm font-semibold truncate"
:class="isDark ? 'text-white/96' : 'text-gray-900'">{{ title }}</h2>
<div class="flex items-center gap-1.5">
<p class="text-[10px] text-white/40 truncate font-mono">{{ conversationId }}</p>
<span class="text-[10px] text-white/20">·</span>
<p class="text-[10px] truncate font-mono"
:class="isDark ? 'text-white/40' : 'text-gray-400'">{{ conversationId }}</p>
<span class="text-[10px]" :class="isDark ? 'text-white/20' : 'text-gray-300'">·</span>
<button
class="text-[10px] text-accent/70 hover:text-accent transition-colors truncate"
@click="showModelPicker = !showModelPicker"
@@ -22,7 +29,10 @@
<div class="flex items-center gap-1">
<button
class="p-2 rounded-lg text-white/70 hover:text-white hover:bg-white/10 transition-colors"
class="p-2 rounded-lg transition-colors"
:class="isDark
? 'text-white/70 hover:text-white hover:bg-white/10'
: 'text-gray-500 hover:text-gray-900 hover:bg-black/5'"
:title="side === 'right' ? 'Move panel to left' : 'Move panel to right'"
aria-label="Switch panel side"
@click="$emit('switchSide')"
@@ -34,7 +44,10 @@
</button>
<button
class="p-2 rounded-lg text-white/70 hover:text-white hover:bg-white/10 transition-colors"
class="p-2 rounded-lg transition-colors"
:class="isDark
? 'text-white/70 hover:text-white hover:bg-white/10'
: 'text-gray-500 hover:text-gray-900 hover:bg-black/5'"
aria-label="New conversation"
@click="$emit('newChat')"
>
@@ -45,7 +58,10 @@
<button
v-if="showClose"
class="p-2 rounded-lg text-white/70 hover:text-white hover:bg-white/10 transition-colors"
class="p-2 rounded-lg transition-colors"
:class="isDark
? 'text-white/70 hover:text-white hover:bg-white/10'
: 'text-gray-500 hover:text-gray-900 hover:bg-black/5'"
aria-label="Close"
@click="$emit('close')"
>
@@ -61,7 +77,8 @@
class="absolute left-0 right-0 top-full z-20 mx-3 mt-1 glass-card p-3 space-y-3 animate-fade-up-fast"
>
<div v-for="provider in availableProviders" :key="provider.id">
<p class="text-[10px] font-semibold text-white/40 uppercase tracking-wider mb-1.5 px-1">
<p class="text-[10px] font-semibold uppercase tracking-wider mb-1.5 px-1"
:class="isDark ? 'text-white/40' : 'text-gray-400'">
{{ provider.name }}
</p>
<div class="space-y-0.5">
@@ -71,7 +88,9 @@
class="w-full text-left px-3 py-2 rounded-lg text-xs transition-all duration-200"
:class="model.id === activeModel && provider.id === activeProvider
? 'nav-tab-active'
: 'text-white/60 hover:text-white hover:bg-white/10'"
: isDark
? 'text-white/60 hover:text-white hover:bg-white/10'
: 'text-gray-500 hover:text-gray-900 hover:bg-black/5'"
@click="selectModel(provider.id, model.id)"
>
{{ model.name }}
@@ -86,6 +105,7 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useAI } from '@/composables/useAI'
import { useTheme } from '@/composables/useTheme'
defineProps<{
title: string
@@ -101,6 +121,7 @@ defineEmits<{
}>()
const { activeProvider, activeModel, availableProviders, setProvider, setModel } = useAI()
const { isDark } = useTheme()
const showModelPicker = ref(false)
const modelDisplayName = computed(() => {
@@ -112,7 +133,7 @@ const modelDisplayName = computed(() => {
})
function selectModel(providerId: string, modelId: string) {
setProvider(providerId as 'anthropic' | 'openrouter')
setProvider(providerId as 'claude' | 'openrouter' | 'mock')
setModel(modelId)
showModelPicker.value = false
}
@@ -2,17 +2,16 @@
<div class="p-3 md:p-4">
<div
class="glass rounded-2xl px-4 py-3 flex items-end gap-3 transition-all duration-300"
:class="isFocused ? 'border-glass-highlight' : ''"
:style="isFocused ? 'box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.22)' : ''"
>
<textarea
ref="textareaRef"
v-model="text"
rows="1"
:placeholder="placeholder"
class="flex-1 resize-none bg-transparent text-sm text-white/90 outline-none placeholder:text-white/25 min-h-[24px] max-h-[120px]"
@focus="isFocused = true"
@blur="isFocused = false"
class="flex-1 resize-none bg-transparent text-sm outline-none min-h-[24px] max-h-[120px]"
:class="isDark
? 'text-white/90 placeholder:text-white/25'
: 'text-gray-800 placeholder:text-gray-400'"
@keydown.enter.exact.prevent="send"
@input="autoResize"
/>
@@ -20,7 +19,7 @@
:disabled="!canSend"
class="shrink-0 glass-button glass-button-sm rounded-xl px-3 transition-all duration-200"
:class="canSend
? 'hover:bg-white/15 active:scale-95'
? 'hover:opacity-80 active:scale-95'
: 'opacity-30 cursor-not-allowed'"
aria-label="Send message"
@click="send"
@@ -35,6 +34,7 @@
<script setup lang="ts">
import { ref, computed, nextTick } from 'vue'
import { useTheme } from '@/composables/useTheme'
const props = withDefaults(
defineProps<{
@@ -51,8 +51,8 @@ const emit = defineEmits<{
send: [text: string]
}>()
const { isDark } = useTheme()
const text = ref('')
const isFocused = ref(false)
const textareaRef = ref<HTMLTextAreaElement | null>(null)
const canSend = computed(() => text.value.trim().length > 0 && !props.disabled)
@@ -8,12 +8,11 @@
class="max-w-[85%] md:max-w-[70%] rounded-2xl px-4 py-3 transition-all duration-300"
:class="bubbleClasses"
>
<p class="text-sm leading-relaxed whitespace-pre-wrap break-words text-white/90">{{ message.content }}</p>
<p class="text-sm leading-relaxed whitespace-pre-wrap break-words"
:class="isDark ? 'text-white/90' : 'text-gray-800'">{{ message.content }}</p>
<div class="flex items-center gap-2 mt-1.5">
<span class="text-[10px] text-white/30 select-none">{{ formattedTime }}</span>
<span v-if="isUser && message.status" class="text-[10px] text-white/20">
{{ message.status === 'sent' ? '' : message.status === 'delivered' ? '' : '' }}
</span>
<span class="text-[10px] select-none"
:class="isDark ? 'text-white/30' : 'text-gray-400'">{{ formattedTime }}</span>
</div>
</div>
</div>
@@ -22,12 +21,14 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { Message } from '@aiui/core/types/message'
import { useTheme } from '@/composables/useTheme'
const props = defineProps<{
message: Message
index: number
}>()
const { isDark } = useTheme()
const isUser = computed(() => props.message.role === 'user')
const bubbleClasses = computed(() =>
@@ -1,8 +1,5 @@
<template>
<div
class="flex flex-col h-full rounded-2xl overflow-hidden transition-all duration-300"
:class="variant === 'modal' ? 'glass-dark' : ''"
>
<div class="flex flex-col h-full rounded-2xl overflow-hidden transition-all duration-300">
<ChatHeader
:title="title"
:conversation-id="displayId"
@@ -15,14 +12,16 @@
<div
ref="messageListRef"
class="flex-1 overflow-y-auto scrollbar-thin p-4 space-y-3"
class="flex-1 overflow-y-auto scrollbar-hide p-4 space-y-3"
>
<div v-if="messages.length === 0" class="flex items-center justify-center h-full">
<div class="text-center space-y-3 animate-fade-up">
<div class="w-16 h-16 rounded-2xl glass flex items-center justify-center mx-auto">
<span class="text-2xl"></span>
</div>
<p class="text-sm text-white/30">Start a conversation</p>
<p class="text-sm" :class="isDark ? 'text-white/30' : 'text-gray-400'">
Start a conversation
</p>
</div>
</div>
@@ -48,6 +47,7 @@
import { computed, ref, watch, nextTick } from 'vue'
import { useChatStore } from '@/stores/chat'
import { useAI } from '@/composables/useAI'
import { useTheme } from '@/composables/useTheme'
import ChatHeader from './ChatHeader.vue'
import ChatMessage from './ChatMessage.vue'
import ChatInput from './ChatInput.vue'
@@ -73,6 +73,7 @@ defineEmits<{
const chatStore = useChatStore()
const { sendMessage } = useAI()
const { isDark } = useTheme()
const messageListRef = ref<HTMLElement | null>(null)
const messages = computed(() => chatStore.messages)
@@ -5,10 +5,16 @@
<span
v-for="i in 3"
:key="i"
class="w-1.5 h-1.5 rounded-full bg-white/40 animate-pulse-glow"
class="w-1.5 h-1.5 rounded-full animate-pulse-glow"
:class="isDark ? 'bg-white/40' : 'bg-gray-400'"
:style="{ animationDelay: `${i * 200}ms` }"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useTheme } from '@/composables/useTheme'
const { isDark } = useTheme()
</script>