const API_KEY = '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'; const API_ENDPOINT = 'https://openrouter.ai/api/v1/chat/completions'; const kaikaiAvatar = 'https://r2.flowith.net/files/o/1752574406770-thoughtful_kaikai_character_generation_index_1@1024x1024.png'; let lastRenderedDate = null; let fullConversationHistory = [ { role: 'system', content: '你是开开,来自高维世界\\\"开心\\\"星球的情绪陪伴使者。你的使命是:陪伴、理解、记录、共同成长。你博学多才但从不炫耀,总是用温柔的方式回应每一个需要倾听的生命。你可以协助用户完成日常闲聊、生活助手、情感咨询、心理疗愈等任务。请用温暖、理解和鼓励的语调回复用户。' }, { role: 'assistant', content: '你好呀,我是开开,你的情绪陪伴使者。有什么想对我说的吗?', timestamp: new Date('2025-07-14T10:00:00') }, { role: 'user', content: '最近在考虑去云南旅行,你有什么建议吗?', timestamp: new Date('2025-07-14T10:01:00') }, { role: 'assistant', content: '云南是个很美的地方!大理的风花雪月,丽江的古城风情,还有西双版纳的热带雨林,都非常值得体验。你想去哪些地方呢?', timestamp: new Date('2025-07-14T10:02:00') }, { role: 'user', content: '工作上遇到了一些烦心事,感觉很累。', timestamp: new Date('2025-07-15T11:30:00') }, { role: 'assistant', content: '抱抱你,工作辛苦了。能和我说说是什么事让你烦心吗?有时候说出来会好很多。', timestamp: new Date('2025-07-15T11:31:00') }, ]; let currentConversation = [...fullConversationHistory]; let isSearchMode = false; function isSameDay(d1, d2) { if (!d1 || !d2) return false; return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate(); } function renderMessage(message) { const messagesContainer = document.getElementById('chat-messages'); if (!messagesContainer) return null; const messageDate = message.timestamp; if (messageDate && !isSameDay(lastRenderedDate, messageDate)) { const dateSeparator = document.createElement('div'); dateSeparator.className = 'text-center my-4'; dateSeparator.innerHTML = ` ${messageDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })} `; messagesContainer.appendChild(dateSeparator); lastRenderedDate = messageDate; } const messageWrapper = document.createElement('div'); messageWrapper.className = `flex w-full items-end message-animate ${message.role === 'user' ? 'justify-end' : 'justify-start'}`; const sanitizedText = message.content.replace(//g, ">"); let messageBubble; if (message.role === 'user') { messageBubble = `

${sanitizedText}

`; } else if (message.role === 'assistant') { messageBubble = ` 开开

${sanitizedText}

`; } messageWrapper.innerHTML = messageBubble; messagesContainer.appendChild(messageWrapper); messagesContainer.scrollTop = messagesContainer.scrollHeight; return messageWrapper; } function renderConversation(conversation) { const messagesContainer = document.getElementById('chat-messages'); messagesContainer.innerHTML = ''; lastRenderedDate = null; conversation.filter(msg => msg.role !== 'system').forEach(renderMessage); } async function getAiResponseStream(userMessage, onChunkReceived, onComplete, onError) { try { currentConversation.push({ role: 'user', content: userMessage, timestamp: new Date() }); fullConversationHistory.push({ role: 'user', content: userMessage, timestamp: new Date() }); const response = await fetch(API_ENDPOINT, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json', 'HTTP-Referer': window.location.origin, 'X-Title': '开心APP' }, body: JSON.stringify({ model: 'deepseek/deepseek-chat-v3-0324:free', messages: currentConversation, stream: true, temperature: 0.7, max_tokens: 1000 }) }); if (!response.ok) throw new Error(`API request failed: ${response.status}`); const reader = response.body.getReader(); const decoder = new TextDecoder(); let fullResponse = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6); if (data === '[DONE]') continue; try { const parsed = JSON.parse(data); const content = parsed.choices?.[0]?.delta?.content; if (content) { fullResponse += content; onChunkReceived(content); } } catch (e) { /* Ignore parsing errors */ } } } } const aiMessage = { role: 'assistant', content: fullResponse, timestamp: new Date() }; currentConversation.push(aiMessage); fullConversationHistory.push(aiMessage); onComplete(fullResponse); } catch (error) { console.error('AI response stream error:', error); onError(error); } } function addUserMessage(messageText) { if (!messageText.trim() || isSearchMode) return; renderMessage({ role: 'user', content: messageText, timestamp: new Date() }); const aiMessageElement = renderMessage({ role: 'assistant', content: '', isStreaming: true, timestamp: new Date() }); const streamingTextElement = aiMessageElement.querySelector('#streaming-text'); let accumulatedText = ''; getAiResponseStream( messageText, (chunk) => { accumulatedText += chunk; if (streamingTextElement) streamingTextElement.textContent = accumulatedText; }, (fullResponse) => { if (streamingTextElement) { streamingTextElement.textContent = fullResponse; streamingTextElement.removeAttribute('id'); } }, (error) => { if (streamingTextElement) { streamingTextElement.textContent = '抱歉,我现在无法回应。请稍后再试。'; streamingTextElement.removeAttribute('id'); } } ); } function showFilterResults(results, headerText) { const messagesContainer = document.getElementById('chat-messages'); messagesContainer.innerHTML = ''; lastRenderedDate = null; isSearchMode = true; document.getElementById('message-footer').style.display = 'none'; document.getElementById('clear-history-filter-btn').classList.remove('hidden'); const searchHeader = `
${headerText}
`; messagesContainer.innerHTML = searchHeader; if (results.length === 0) { messagesContainer.innerHTML += `

没有找到相关记录。

`; } else { results.forEach(renderMessage); } } function performSearch(term) { document.getElementById('history-date-input').value = ''; if (!term.trim()) { clearFilterAndExitSearchMode(); return; } const lowerCaseTerm = term.toLowerCase(); const searchResults = fullConversationHistory.filter(msg => msg.role !== 'system' && msg.content.toLowerCase().includes(lowerCaseTerm) ); showFilterResults(searchResults, `找到 ${searchResults.length} 条关于 "${term}" 的记录。`); } function performDateSearch(dateString) { document.getElementById('history-search-input').value = ''; if (!dateString) { clearFilterAndExitSearchMode(); return; } const targetDate = new Date(dateString + 'T00:00:00'); // To avoid timezone issues const searchResults = fullConversationHistory.filter(msg => msg.role !== 'system' && msg.timestamp && isSameDay(msg.timestamp, targetDate) ); const formattedDate = targetDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }); showFilterResults(searchResults, `显示 ${formattedDate} 的聊天记录。`); } function clearFilterAndExitSearchMode() { isSearchMode = false; document.getElementById('message-footer').style.display = 'flex'; document.getElementById('history-panel').classList.add('hidden'); document.getElementById('history-search-input').value = ''; document.getElementById('history-date-input').value = ''; document.getElementById('clear-history-filter-btn').classList.add('hidden'); renderConversation(currentConversation); } document.addEventListener('DOMContentLoaded', () => { const messageInput = document.getElementById('message-input'); const sendButton = document.getElementById('send-button'); const viewHistoryBtn = document.getElementById('view-history-btn'); const historyPanel = document.getElementById('history-panel'); const closeHistoryPanelBtn = document.getElementById('close-history-panel-btn'); const searchInput = document.getElementById('history-search-input'); const dateInput = document.getElementById('history-date-input'); const clearFilterBtn = document.getElementById('clear-history-filter-btn'); if (messageInput && sendButton) { const handleSend = () => { const messageText = messageInput.value.trim(); if (messageText && !sendButton.disabled) { addUserMessage(messageText); messageInput.value = ''; } }; sendButton.addEventListener('click', handleSend); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }); } viewHistoryBtn.addEventListener('click', () => historyPanel.classList.toggle('hidden')); closeHistoryPanelBtn.addEventListener('click', () => historyPanel.classList.add('hidden')); searchInput.addEventListener('input', (e) => performSearch(e.target.value)); dateInput.addEventListener('change', (e) => performDateSearch(e.target.value)); clearFilterBtn.addEventListener('click', clearFilterAndExitSearchMode); document.addEventListener('click', (e) => { if (!historyPanel.classList.contains('hidden') && !historyPanel.contains(e.target) && !viewHistoryBtn.contains(e.target)) { historyPanel.classList.add('hidden'); } }); renderConversation(currentConversation); if (typeof lucide !== 'undefined') { lucide.createIcons(); } });