- 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
124 lines
3.0 KiB
Vue
124 lines
3.0 KiB
Vue
<template>
|
|
<div class="flex flex-col h-full rounded-2xl overflow-hidden transition-all duration-300">
|
|
<ChatHeader
|
|
:title="title"
|
|
:conversation-id="displayId"
|
|
:side="side"
|
|
:show-close="showClose"
|
|
@switch-side="$emit('switchSide')"
|
|
@new-chat="handleNewChat"
|
|
@close="$emit('close')"
|
|
/>
|
|
|
|
<div
|
|
ref="messageListRef"
|
|
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" :class="isDark ? 'text-white/30' : 'text-gray-400'">
|
|
Start a conversation
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<ChatMessage
|
|
v-for="(msg, i) in messages"
|
|
:key="msg.id"
|
|
:message="msg"
|
|
:index="i"
|
|
/>
|
|
|
|
<StreamingDots v-if="isStreaming && lastMessageEmpty" />
|
|
</div>
|
|
|
|
<ChatInput
|
|
:disabled="isStreaming"
|
|
:placeholder="isStreaming ? 'Waiting for response...' : 'Message AIUI...'"
|
|
@send="handleSend"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
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'
|
|
import StreamingDots from './StreamingDots.vue'
|
|
|
|
withDefaults(
|
|
defineProps<{
|
|
variant?: 'standalone' | 'modal'
|
|
side?: 'left' | 'right'
|
|
showClose?: boolean
|
|
}>(),
|
|
{
|
|
variant: 'standalone',
|
|
side: 'right',
|
|
showClose: false,
|
|
}
|
|
)
|
|
|
|
defineEmits<{
|
|
switchSide: []
|
|
close: []
|
|
}>()
|
|
|
|
const chatStore = useChatStore()
|
|
const { sendMessage } = useAI()
|
|
const { isDark } = useTheme()
|
|
const messageListRef = ref<HTMLElement | null>(null)
|
|
|
|
const messages = computed(() => chatStore.messages)
|
|
const isStreaming = computed(() => chatStore.isStreaming)
|
|
|
|
const title = computed(
|
|
() => chatStore.activeConversation?.title ?? 'New Chat'
|
|
)
|
|
|
|
const displayId = computed(
|
|
() => chatStore.activeConversationId?.slice(0, 8) ?? '—'
|
|
)
|
|
|
|
const lastMessageEmpty = computed(() => {
|
|
const msgs = messages.value
|
|
if (msgs.length === 0) return true
|
|
return msgs[msgs.length - 1].content === ''
|
|
})
|
|
|
|
function handleNewChat() {
|
|
chatStore.createConversation()
|
|
}
|
|
|
|
async function handleSend(text: string) {
|
|
await sendMessage(text)
|
|
}
|
|
|
|
watch(
|
|
() => messages.value.length,
|
|
() => {
|
|
nextTick(() => {
|
|
const el = messageListRef.value
|
|
if (el) el.scrollTop = el.scrollHeight
|
|
})
|
|
}
|
|
)
|
|
|
|
watch(
|
|
() => messages.value[messages.value.length - 1]?.content,
|
|
() => {
|
|
nextTick(() => {
|
|
const el = messageListRef.value
|
|
if (el) el.scrollTop = el.scrollHeight
|
|
})
|
|
}
|
|
)
|
|
</script>
|