feat(chat): enhance chat functionality with song integration and UI improvements

- Updated ChatHeader and ChatMessage components to support song selection and display.
- Enhanced useContentPanel to handle both film and song content, allowing for richer interactions.
- Improved FilmCard and added SongCard components for better media representation.
- Refactored environment variables for better configuration management.
- Updated .gitignore to include development chat history.

Made-with: Cursor
This commit is contained in:
Dorian
2026-03-02 18:16:04 +00:00
parent 6f79f0860d
commit 80aeefa4bf
27 changed files with 1751 additions and 143 deletions
+70
View File
@@ -2,18 +2,87 @@ import { defineStore } from 'pinia'
import { ref, computed, watch } from 'vue'
import type { Message, Conversation } from '@aiui/core/types/message'
const isDev = import.meta.env.DEV
let saveTimer: ReturnType<typeof setTimeout> | null = null
const SAVE_DEBOUNCE = 800
// Server-side persistence via Vite dev middleware
async function loadServerChats(): Promise<{ conversations: Map<string, Conversation>; activeId: string | null }> {
const empty = { conversations: new Map<string, Conversation>(), activeId: null }
if (!isDev) return empty
try {
const res = await fetch('/api/dev-chats')
if (!res.ok) return empty
const data = await res.json() as {
conversations?: Record<string, Conversation>
activeConversationId?: string | null
}
if (!data.conversations) return empty
const conversations = new Map(Object.entries(data.conversations))
return {
conversations,
activeId: data.activeConversationId ?? null,
}
} catch {
return empty
}
}
let _loaded = false
function saveServerChats(conversations: Map<string, Conversation>, activeId: string | null) {
if (!isDev || !_loaded) return
if (saveTimer) clearTimeout(saveTimer)
saveTimer = setTimeout(() => {
const obj = Object.fromEntries(conversations)
fetch('/api/dev-chats', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ conversations: obj, activeConversationId: activeId }),
}).catch(() => {})
}, SAVE_DEBOUNCE)
}
export const useChatStore = defineStore('chat', () => {
const conversations = ref<Map<string, Conversation>>(new Map())
const activeConversationId = ref<string | null>(null)
const isStreaming = ref(false)
const loaded = ref(false)
const savedSide = localStorage.getItem('aiui-panel-side') as 'left' | 'right' | null
const panelSide = ref<'left' | 'right'>(savedSide ?? 'right')
// Load chats from server on startup — _loaded gate prevents the watcher
// from overwriting the file with empty data before the load completes
if (isDev) {
loadServerChats().then((data) => {
if (data.conversations.size > 0) {
conversations.value = data.conversations
activeConversationId.value =
data.activeId && data.conversations.has(data.activeId)
? data.activeId
: [...data.conversations.keys()][0] ?? null
}
loaded.value = true
_loaded = true
})
} else {
loaded.value = true
_loaded = true
}
watch(panelSide, (val) => {
localStorage.setItem('aiui-panel-side', val)
})
watch(
[conversations, activeConversationId],
([conv, active]) => {
saveServerChats(conv as Map<string, Conversation>, active as string | null)
},
{ deep: true }
)
const activeConversation = computed(() => {
if (!activeConversationId.value) return null
return conversations.value.get(activeConversationId.value) ?? null
@@ -90,6 +159,7 @@ export const useChatStore = defineStore('chat', () => {
messages,
conversationList,
isStreaming,
loaded,
panelSide,
createConversation,
addMessage,