优化调整
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
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 = `
|
||||
<span class="bg-gray-200 text-gray-600 text-xs font-semibold px-3 py-1 rounded-full">${messageDate.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })}</span>
|
||||
`;
|
||||
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, "<").replace(/>/g, ">");
|
||||
|
||||
let messageBubble;
|
||||
if (message.role === 'user') {
|
||||
messageBubble = `
|
||||
<div class="max-w-xs md:max-w-md lg:max-w-lg">
|
||||
<div class="bg-tech-blue text-white rounded-l-2xl rounded-tr-2xl p-3 px-4 shadow-md inline-block">
|
||||
<p class="leading-relaxed">${sanitizedText}</p>
|
||||
</div>
|
||||
</div>`;
|
||||
} else if (message.role === 'assistant') {
|
||||
messageBubble = `
|
||||
<img src="${kaikaiAvatar}" alt="开开" class="w-10 h-10 rounded-full mr-3 self-start flex-shrink-0">
|
||||
<div class="max-w-xs md:max-w-md lg:max-w-lg">
|
||||
<div class="bg-white text-text-dark rounded-r-2xl rounded-tl-2xl p-3 px-4 shadow-md inline-block border border-gray-100">
|
||||
<p class="leading-relaxed" ${message.isStreaming ? 'id="streaming-text"' : ''}>${sanitizedText}</p>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
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 = `
|
||||
<div id="search-results-header" class="text-center my-2 p-2 bg-blue-100/50 text-tech-blue rounded-lg text-sm">
|
||||
${headerText}
|
||||
</div>`;
|
||||
messagesContainer.innerHTML = searchHeader;
|
||||
|
||||
if (results.length === 0) {
|
||||
messagesContainer.innerHTML += `<p class="text-center text-text-medium mt-4">没有找到相关记录。</p>`;
|
||||
} 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} 条关于 "<strong>${term}</strong>" 的记录。`);
|
||||
}
|
||||
|
||||
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, `显示 <strong>${formattedDate}</strong> 的聊天记录。`);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user