优化调整

This commit is contained in:
2025-07-26 00:37:18 +08:00
parent 08bbd4df0f
commit 0dfabc35d7
90 changed files with 3594 additions and 2294 deletions
@@ -0,0 +1,37 @@
const navItems = [
{ icon: 'message-square', text: '聊天', href: './chat.html' },
{ icon: 'book-open', text: '日记', href: './diary.html' },
{ icon: 'crosshair', text: '话题', href: './topic_tracker.html' },
{ icon: 'milestone', text: '人生轨迹', href: './life_milestones.html' },
{ icon: 'layout-dashboard', text: '个人展板', href: './personal_dashboard.html' }
];
function createBottomNav() {
const navPlaceholder = document.getElementById('bottom-nav-placeholder');
if (!navPlaceholder) return;
const navContainer = document.createElement('nav');
navContainer.className = 'fixed bottom-0 left-0 right-0 z-50 bg-white/95 backdrop-blur-sm shadow-[0_-2px_10px_rgba(0,0,0,0.05)] flex justify-around py-2 border-t border-gray-200/80';
const currentPath = window.location.pathname.split('/').pop();
navItems.forEach(item => {
const itemPath = item.href.substring(2); // remove './' for comparison
const isActive = currentPath === itemPath;
const link = document.createElement('a');
link.href = item.href;
link.className = `flex flex-col items-center justify-center text-xs p-2 rounded-md transition-colors w-20 ${isActive ? 'text-tech-blue bg-tech-blue/10 font-semibold' : 'text-text-medium hover:bg-gray-100 hover:text-tech-blue'}`;
link.innerHTML = `
<i data-lucide="${item.icon}" class="w-5 h-5 mb-1"></i>
<span>${item.text}</span>
`;
navContainer.appendChild(link);
});
navPlaceholder.appendChild(navContainer);
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
}
document.addEventListener('DOMContentLoaded', createBottomNav);
@@ -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, "&lt;").replace(/>/g, "&gt;");
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();
}
});
@@ -0,0 +1,58 @@
import { navLinks } from '../data.js';
export const createNavLinks = (menuId, isMobile) => {
const menu = document.getElementById(menuId);
if (!menu) return;
navLinks.forEach(link => {
const a = document.createElement('a');
a.href = link.href;
a.textContent = link.name;
if (isMobile) {
a.className = 'text-xl text-text-dark hover:text-tech-blue transition-colors';
} else {
a.className = 'text-base font-medium text-text-medium hover:text-tech-blue transition-colors';
if (window.location.pathname.endsWith('/' + link.href) ||
(window.location.pathname === '/' && link.href === 'index.html')) {
a.classList.add('text-tech-blue', 'font-semibold');
}
}
menu.appendChild(a);
});
};
export const handleHeaderScroll = () => {
const header = document.getElementById('main-header');
if (!header) return;
if (window.scrollY > 10) {
header.classList.add('scrolled');
} else {
header.classList.remove('scrolled');
}
};
export const setupMobileMenu = () => {
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
});
}
};
export const initializeSharedUI = () => {
createNavLinks('nav-menu', false);
createNavLinks('mobile-nav-menu', true);
window.addEventListener('scroll', handleHeaderScroll);
setupMobileMenu();
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
};
document.addEventListener('DOMContentLoaded', () => {
initializeSharedUI();
});