Files
life-echo/app-ios/demo/chat.html
iammm0 71c70275a8 docs: 更新文档与 demo
- 更新 docs/ 与 app-ios/docs/
- 更新 app-ios/demo、assets/demo、app-ios 代码与计划

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 10:05:20 +08:00

1043 lines
30 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>岁月时书 - 聊天</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--deep-purple: #200028;
--slate-purple: #8C8EA3;
--medium-purple: #A177A6;
--lavender: #CEB0DA;
--blush: #DBBABA;
--cream: #FAF7F8;
--white: #FFFFFF;
--font-serif: 'Noto Serif SC', serif;
--font-sans: 'Noto Sans SC', sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: var(--font-sans);
background: #1a1a2e;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
/* Phone Frame */
.phone-frame {
width: 393px;
height: 852px;
background: var(--cream);
border-radius: 55px;
overflow: hidden;
position: relative;
box-shadow:
0 0 0 11px #1a1a1a,
0 0 0 13px #3a3a3a,
0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
/* Dynamic Island */
.dynamic-island {
position: absolute;
top: 11px;
left: 50%;
transform: translateX(-50%);
width: 126px;
height: 37px;
background: #000;
border-radius: 24px;
z-index: 1000;
}
/* Screen */
.screen {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
}
/* Chat Header */
.chat-header {
display: flex;
align-items: center;
padding: 54px 16px 14px;
background: var(--medium-purple);
flex-shrink: 0;
}
.chat-back-btn {
width: 40px;
height: 40px;
border-radius: 12px;
background: rgba(255,255,255,0.2);
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
transition: background 0.2s ease;
}
.chat-back-btn:active {
background: rgba(255,255,255,0.3);
}
.chat-back-btn svg {
width: 24px;
height: 24px;
stroke: var(--white);
fill: none;
stroke-width: 2.5;
}
.chat-title-area {
flex: 1;
text-align: center;
padding-right: 40px;
}
.chat-name {
font-size: 18px;
font-weight: 600;
color: var(--white);
}
.chat-status {
font-size: 12px;
color: rgba(255,255,255,0.8);
margin-top: 2px;
}
/* Chat Messages */
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 16px 14px 20px;
background: var(--cream);
-webkit-overflow-scrolling: touch;
}
.chat-messages::-webkit-scrollbar {
display: none;
}
.chat-time {
text-align: center;
font-size: 12px;
color: var(--slate-purple);
margin: 16px 0;
padding: 4px 12px;
background: rgba(140, 142, 163, 0.1);
border-radius: 12px;
display: inline-block;
position: relative;
left: 50%;
transform: translateX(-50%);
}
.chat-message {
display: flex;
margin-bottom: 16px;
animation: messageIn 0.3s ease;
}
@keyframes messageIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.chat-message.ai {
flex-direction: row;
}
.chat-message.user {
flex-direction: row-reverse;
}
.chat-avatar {
width: 40px;
height: 40px;
border-radius: 10px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
.chat-message.ai .chat-avatar {
background: var(--lavender);
margin-right: 10px;
}
.chat-message.user .chat-avatar {
background: var(--blush);
margin-left: 10px;
}
.chat-bubble {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
font-size: 15px;
line-height: 1.5;
position: relative;
word-break: break-word;
}
.chat-message.ai .chat-bubble {
background: var(--white);
color: var(--deep-purple);
border-top-left-radius: 4px;
box-shadow: 0 2px 8px rgba(32, 0, 40, 0.06);
}
.chat-message.user .chat-bubble {
background: var(--medium-purple);
color: var(--white);
border-top-right-radius: 4px;
box-shadow: 0 2px 8px rgba(161, 119, 166, 0.3);
}
.chat-bubble img {
max-width: 100%;
border-radius: 8px;
display: block;
}
.chat-bubble .voice-msg {
display: flex;
align-items: center;
gap: 8px;
min-width: 80px;
}
.chat-bubble .voice-msg svg {
width: 20px;
height: 20px;
}
.chat-message.ai .chat-bubble .voice-msg svg {
fill: var(--deep-purple);
}
.chat-message.user .chat-bubble .voice-msg svg {
fill: var(--white);
}
/* Typing indicator */
.typing-indicator {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 0;
}
.typing-indicator span {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--slate-purple);
animation: typing 1.4s infinite;
}
.typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
.typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typing {
0%, 100% { opacity: 0.3; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1); }
}
/* Chat Input Area */
.chat-input-area {
background: var(--white);
border-top: 1px solid rgba(32, 0, 40, 0.08);
padding: 10px 14px;
padding-bottom: calc(34px + 10px);
flex-shrink: 0;
}
.chat-input-row {
display: flex;
align-items: flex-end;
gap: 8px;
}
.chat-input-btn {
width: 40px;
height: 40px;
border-radius: 12px;
border: none;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
transition: background 0.2s ease;
}
.chat-input-btn svg {
width: 26px;
height: 26px;
stroke: var(--slate-purple);
fill: none;
stroke-width: 1.8;
}
.chat-input-btn:active {
background: var(--lavender);
}
.chat-text-input {
flex: 1;
min-height: 40px;
max-height: 100px;
padding: 10px 16px;
border: none;
border-radius: 20px;
background: var(--cream);
font-size: 16px;
line-height: 1.4;
resize: none;
outline: none;
font-family: var(--font-sans);
color: var(--deep-purple);
}
.chat-text-input::placeholder {
color: var(--slate-purple);
}
.chat-text-input:focus {
outline: none;
background: rgba(206, 176, 218, 0.2);
}
.chat-send-btn {
width: 40px;
height: 40px;
border-radius: 12px;
border: none;
background: var(--medium-purple);
display: none;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
box-shadow: 0 2px 8px rgba(161, 119, 166, 0.3);
transition: all 0.2s ease;
}
.chat-send-btn.visible {
display: flex;
}
.chat-send-btn:active {
transform: scale(0.95);
}
.chat-send-btn svg {
width: 20px;
height: 20px;
fill: var(--white);
}
/* Voice Input Button */
.voice-input-btn {
flex: 1;
height: 40px;
border: none;
border-radius: 20px;
background: var(--cream);
font-size: 15px;
color: var(--slate-purple);
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
user-select: none;
-webkit-user-select: none;
transition: background 0.2s ease;
}
.voice-input-btn.visible {
display: flex;
}
.voice-input-btn:active {
background: var(--lavender);
}
.voice-input-btn.recording {
background: var(--lavender);
}
/* Emoji Picker */
.emoji-picker {
display: none;
background: var(--white);
border-top: 1px solid rgba(32, 0, 40, 0.08);
padding: 12px 14px;
padding-bottom: 34px;
max-height: 220px;
overflow-y: auto;
flex-shrink: 0;
}
.emoji-picker.visible {
display: block;
}
.emoji-picker.visible + .chat-input-area {
padding-bottom: 10px;
}
.emoji-grid {
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 8px;
}
.emoji-item {
font-size: 24px;
text-align: center;
padding: 6px;
cursor: pointer;
border-radius: 8px;
transition: background 0.2s ease;
}
.emoji-item:active {
background: var(--lavender);
}
/* More Options Panel */
.more-options {
display: none;
background: var(--white);
border-top: 1px solid rgba(32, 0, 40, 0.08);
padding: 20px 14px;
padding-bottom: 34px;
flex-shrink: 0;
}
.more-options.visible {
display: block;
}
.more-options.visible + .emoji-picker + .chat-input-area,
.more-options.visible ~ .chat-input-area {
padding-bottom: 10px;
}
.more-options-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.more-option-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
cursor: pointer;
}
.more-option-icon {
width: 56px;
height: 56px;
border-radius: 14px;
background: var(--cream);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease;
}
.more-option-icon svg {
width: 28px;
height: 28px;
stroke: var(--medium-purple);
fill: none;
stroke-width: 1.5;
}
.more-option-label {
font-size: 12px;
color: var(--slate-purple);
}
.more-option-item:active .more-option-icon {
background: var(--lavender);
}
/* Voice Recording Overlay */
.voice-recording-overlay {
position: absolute;
inset: 0;
background: rgba(32, 0, 40, 0.7);
display: none;
align-items: center;
justify-content: center;
z-index: 500;
border-radius: 55px;
}
.voice-recording-overlay.visible {
display: flex;
}
.voice-recording-box {
background: var(--deep-purple);
border-radius: 16px;
padding: 28px 36px;
text-align: center;
color: var(--white);
}
.voice-recording-waves {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
height: 40px;
margin-bottom: 12px;
}
.voice-recording-waves span {
width: 4px;
background: var(--medium-purple);
border-radius: 2px;
animation: voiceWave 0.5s ease-in-out infinite alternate;
}
.voice-recording-waves span:nth-child(1) { height: 15px; animation-delay: 0s; }
.voice-recording-waves span:nth-child(2) { height: 25px; animation-delay: 0.1s; }
.voice-recording-waves span:nth-child(3) { height: 20px; animation-delay: 0.2s; }
.voice-recording-waves span:nth-child(4) { height: 30px; animation-delay: 0.3s; }
.voice-recording-waves span:nth-child(5) { height: 20px; animation-delay: 0.4s; }
.voice-recording-waves span:nth-child(6) { height: 25px; animation-delay: 0.5s; }
.voice-recording-waves span:nth-child(7) { height: 15px; animation-delay: 0.6s; }
@keyframes voiceWave {
from { transform: scaleY(1); }
to { transform: scaleY(1.5); }
}
.voice-recording-text {
font-size: 14px;
color: var(--lavender);
}
.voice-recording-cancel {
color: #FF6B6B;
}
/* Hidden file input */
.hidden-file-input {
display: none;
}
/* Toast */
.toast {
position: absolute;
bottom: 120px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: var(--deep-purple);
color: var(--white);
padding: 12px 24px;
border-radius: 12px;
font-size: 14px;
opacity: 0;
transition: all 0.3s ease;
z-index: 200;
}
.toast.visible {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
</style>
</head>
<body>
<div class="phone-frame">
<div class="dynamic-island"></div>
<div class="screen">
<!-- Chat Header -->
<div class="chat-header">
<a href="chat-list.html" class="chat-back-btn">
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></svg>
</a>
<div class="chat-title-area">
<div class="chat-name">回忆录助手</div>
<div class="chat-status">在线</div>
</div>
</div>
<!-- Chat Messages -->
<div class="chat-messages" id="chatMessages">
<div class="chat-time">今天 14:30</div>
<div class="chat-message ai">
<div class="chat-avatar">📖</div>
<div class="chat-bubble">您好!我是您的回忆录助手,很高兴能陪您聊聊往事。😊<br><br>您想从哪里开始呢?可以聊聊童年、上学时光,或者任何您想分享的故事。</div>
</div>
</div>
<!-- More Options Panel -->
<div class="more-options" id="moreOptions">
<div class="more-options-grid">
<div class="more-option-item" id="optionPhoto">
<div class="more-option-icon">
<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
</div>
<span class="more-option-label">照片</span>
</div>
<div class="more-option-item" id="optionCamera">
<div class="more-option-icon">
<svg viewBox="0 0 24 24"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
</div>
<span class="more-option-label">拍摄</span>
</div>
</div>
</div>
<!-- Emoji Picker -->
<div class="emoji-picker" id="emojiPicker">
<div class="emoji-grid">
<span class="emoji-item">😊</span>
<span class="emoji-item">😂</span>
<span class="emoji-item">🥰</span>
<span class="emoji-item">😢</span>
<span class="emoji-item">😍</span>
<span class="emoji-item">🤔</span>
<span class="emoji-item">👍</span>
<span class="emoji-item">❤️</span>
<span class="emoji-item">😭</span>
<span class="emoji-item">😘</span>
<span class="emoji-item">🙏</span>
<span class="emoji-item">😅</span>
<span class="emoji-item">😆</span>
<span class="emoji-item">🥺</span>
<span class="emoji-item">😳</span>
<span class="emoji-item">😤</span>
<span class="emoji-item">🤗</span>
<span class="emoji-item">😎</span>
<span class="emoji-item">🥳</span>
<span class="emoji-item">😴</span>
<span class="emoji-item">🤩</span>
<span class="emoji-item">😇</span>
<span class="emoji-item">🤭</span>
<span class="emoji-item">😋</span>
<span class="emoji-item">🌸</span>
<span class="emoji-item">🌺</span>
<span class="emoji-item">🌻</span>
<span class="emoji-item">🌷</span>
<span class="emoji-item">🍀</span>
<span class="emoji-item">🌙</span>
<span class="emoji-item"></span>
<span class="emoji-item">☀️</span>
</div>
</div>
<!-- Chat Input Area -->
<div class="chat-input-area">
<div class="chat-input-row">
<button class="chat-input-btn" id="voiceToggleBtn">
<svg viewBox="0 0 24 24" id="voiceIcon"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" fill="currentColor"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
<svg viewBox="0 0 24 24" id="keyboardIcon" style="display:none;"><rect x="2" y="4" width="20" height="16" rx="2" ry="2"/><line x1="6" y1="8" x2="6" y2="8"/><line x1="10" y1="8" x2="10" y2="8"/><line x1="14" y1="8" x2="14" y2="8"/><line x1="18" y1="8" x2="18" y2="8"/><line x1="6" y1="12" x2="6" y2="12"/><line x1="10" y1="12" x2="10" y2="12"/><line x1="14" y1="12" x2="14" y2="12"/><line x1="18" y1="12" x2="18" y2="12"/><line x1="8" y1="16" x2="16" y2="16"/></svg>
</button>
<textarea class="chat-text-input" id="chatInput" placeholder="说点什么..." rows="1"></textarea>
<button class="voice-input-btn" id="voiceInputBtn">按住 说话</button>
<button class="chat-input-btn" id="emojiBtn">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
</button>
<button class="chat-input-btn" id="moreBtn">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
</button>
<button class="chat-send-btn" id="sendBtn">
<svg viewBox="0 0 24 24"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>
</button>
</div>
</div>
<!-- Voice Recording Overlay -->
<div class="voice-recording-overlay" id="voiceRecordingOverlay">
<div class="voice-recording-box">
<div class="voice-recording-waves">
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
</div>
<div class="voice-recording-text" id="voiceRecordingText">松开 发送,上滑 取消</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
</div>
<input type="file" accept="image/*" class="hidden-file-input" id="imageInput">
</div>
<script>
// State
let isVoiceMode = false;
let isVoiceRecording = false;
let voiceStartY = 0;
// Elements
const chatMessages = document.getElementById('chatMessages');
const chatInput = document.getElementById('chatInput');
const sendBtn = document.getElementById('sendBtn');
const voiceToggleBtn = document.getElementById('voiceToggleBtn');
const voiceIcon = document.getElementById('voiceIcon');
const keyboardIcon = document.getElementById('keyboardIcon');
const voiceInputBtn = document.getElementById('voiceInputBtn');
const emojiBtn = document.getElementById('emojiBtn');
const emojiPicker = document.getElementById('emojiPicker');
const moreBtn = document.getElementById('moreBtn');
const moreOptions = document.getElementById('moreOptions');
const imageInput = document.getElementById('imageInput');
const voiceRecordingOverlay = document.getElementById('voiceRecordingOverlay');
const voiceRecordingText = document.getElementById('voiceRecordingText');
const toast = document.getElementById('toast');
// AI Responses for memoir conversation
const aiResponses = [
"这个故事真让人感动!😊 能再详细说说当时的场景吗?比如那时候是什么季节?",
"听起来是一段珍贵的回忆呢!那个时候您多大年纪?身边有哪些人陪着您?",
"太有画面感了!这件事对您的人生有什么特别的影响吗?",
"我能感受到您讲述时的情感。这段经历中有没有什么特别难忘的细节?",
"真是有意思的往事!那个年代的生活和现在很不一样吧?能说说当时的日常生活是什么样的吗?",
"您的故事很生动!除了这件事,那个时期还有什么印象深刻的事情吗?",
"这段记忆很宝贵呢。您有没有保留那个时候的照片或者物件?",
"听您这么说,我仿佛也看到了当时的情景。后来这件事有什么后续吗?",
"这是您人生中很重要的一段经历呢。您觉得它塑造了现在的您吗?"
];
// Show toast
function showToast(message) {
toast.textContent = message;
toast.classList.add('visible');
setTimeout(() => {
toast.classList.remove('visible');
}, 2000);
}
// Add chat message
function addChatMessage(type, content, isImage = false) {
const msgEl = document.createElement('div');
msgEl.className = `chat-message ${type}`;
const avatar = type === 'ai' ? '📖' : '👤';
let bubbleContent = content;
if (isImage) {
bubbleContent = `<img src="${content}" alt="图片">`;
}
msgEl.innerHTML = `
<div class="chat-avatar">${avatar}</div>
<div class="chat-bubble">${bubbleContent}</div>
`;
chatMessages.appendChild(msgEl);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// Add typing indicator
function showTypingIndicator() {
const typingEl = document.createElement('div');
typingEl.className = 'chat-message ai';
typingEl.id = 'typingIndicator';
typingEl.innerHTML = `
<div class="chat-avatar">📖</div>
<div class="chat-bubble">
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
`;
chatMessages.appendChild(typingEl);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// Remove typing indicator
function removeTypingIndicator() {
const indicator = document.getElementById('typingIndicator');
if (indicator) {
indicator.remove();
}
}
// Get AI response
function getAIResponse() {
return aiResponses[Math.floor(Math.random() * aiResponses.length)];
}
// Send message
function sendMessage(text) {
if (!text.trim()) return;
// Add user message
addChatMessage('user', text);
// Clear input
chatInput.value = '';
chatInput.style.height = 'auto';
sendBtn.classList.remove('visible');
// Hide panels
emojiPicker.classList.remove('visible');
moreOptions.classList.remove('visible');
// Show typing indicator
setTimeout(() => {
showTypingIndicator();
}, 500);
// AI response after delay
setTimeout(() => {
removeTypingIndicator();
addChatMessage('ai', getAIResponse());
}, 1500 + Math.random() * 1000);
}
// Send image
function sendImage(imageUrl) {
addChatMessage('user', imageUrl, true);
// Show typing indicator
setTimeout(() => {
showTypingIndicator();
}, 500);
// AI response
setTimeout(() => {
removeTypingIndicator();
addChatMessage('ai', '这张照片很有意义!📷 能跟我说说这是什么时候拍的吗?照片里有什么特别的故事?');
}, 2000);
}
// Send voice message
function sendVoiceMessage(duration) {
const msgEl = document.createElement('div');
msgEl.className = 'chat-message user';
msgEl.innerHTML = `
<div class="chat-avatar">👤</div>
<div class="chat-bubble">
<div class="voice-msg">
<svg viewBox="0 0 24 24"><path d="M3 18v-6a9 9 0 0 1 18 0v6"/><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3v5z"/><path d="M3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3v5z"/></svg>
<span>${duration}"</span>
</div>
</div>
`;
chatMessages.appendChild(msgEl);
chatMessages.scrollTop = chatMessages.scrollHeight;
// AI response
setTimeout(() => {
showTypingIndicator();
}, 500);
setTimeout(() => {
removeTypingIndicator();
addChatMessage('ai', getAIResponse());
}, 2000);
}
// Toggle voice mode
function toggleVoiceMode() {
isVoiceMode = !isVoiceMode;
if (isVoiceMode) {
voiceIcon.style.display = 'none';
keyboardIcon.style.display = 'block';
chatInput.style.display = 'none';
voiceInputBtn.classList.add('visible');
sendBtn.classList.remove('visible');
} else {
voiceIcon.style.display = 'block';
keyboardIcon.style.display = 'none';
chatInput.style.display = 'block';
voiceInputBtn.classList.remove('visible');
if (chatInput.value.trim()) {
sendBtn.classList.add('visible');
}
}
emojiPicker.classList.remove('visible');
moreOptions.classList.remove('visible');
}
// Voice recording handlers
function startVoiceRecording(e) {
e.preventDefault();
isVoiceRecording = true;
voiceStartY = e.touches ? e.touches[0].clientY : e.clientY;
voiceInputBtn.classList.add('recording');
voiceRecordingOverlay.classList.add('visible');
voiceRecordingText.textContent = '松开 发送,上滑 取消';
voiceRecordingText.classList.remove('voice-recording-cancel');
}
function moveVoiceRecording(e) {
if (!isVoiceRecording) return;
const currentY = e.touches ? e.touches[0].clientY : e.clientY;
const diff = voiceStartY - currentY;
if (diff > 50) {
voiceRecordingText.textContent = '松开手指,取消发送';
voiceRecordingText.classList.add('voice-recording-cancel');
} else {
voiceRecordingText.textContent = '松开 发送,上滑 取消';
voiceRecordingText.classList.remove('voice-recording-cancel');
}
}
function endVoiceRecording(e) {
if (!isVoiceRecording) return;
isVoiceRecording = false;
voiceInputBtn.classList.remove('recording');
voiceRecordingOverlay.classList.remove('visible');
const currentY = e.changedTouches ? e.changedTouches[0].clientY : e.clientY;
const diff = voiceStartY - currentY;
if (diff <= 50) {
// Send voice message (simulated duration)
const duration = Math.floor(Math.random() * 10) + 3;
sendVoiceMessage(duration);
} else {
showToast('已取消');
}
}
// Event listeners
chatInput.addEventListener('input', () => {
// Auto-resize textarea
chatInput.style.height = 'auto';
chatInput.style.height = Math.min(chatInput.scrollHeight, 100) + 'px';
// Show/hide send button
if (chatInput.value.trim()) {
sendBtn.classList.add('visible');
} else {
sendBtn.classList.remove('visible');
}
});
chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage(chatInput.value);
}
});
chatInput.addEventListener('focus', () => {
emojiPicker.classList.remove('visible');
moreOptions.classList.remove('visible');
});
sendBtn.addEventListener('click', () => {
sendMessage(chatInput.value);
});
voiceToggleBtn.addEventListener('click', toggleVoiceMode);
// Voice input events
voiceInputBtn.addEventListener('mousedown', startVoiceRecording);
voiceInputBtn.addEventListener('touchstart', startVoiceRecording);
document.addEventListener('mousemove', moveVoiceRecording);
document.addEventListener('touchmove', moveVoiceRecording);
document.addEventListener('mouseup', endVoiceRecording);
document.addEventListener('touchend', endVoiceRecording);
// Emoji picker
emojiBtn.addEventListener('click', () => {
emojiPicker.classList.toggle('visible');
moreOptions.classList.remove('visible');
chatInput.blur();
});
document.querySelectorAll('.emoji-item').forEach(item => {
item.addEventListener('click', () => {
chatInput.value += item.textContent;
chatInput.dispatchEvent(new Event('input'));
if (!isVoiceMode) {
sendBtn.classList.add('visible');
}
});
});
// More options
moreBtn.addEventListener('click', () => {
moreOptions.classList.toggle('visible');
emojiPicker.classList.remove('visible');
chatInput.blur();
});
// Photo option
document.getElementById('optionPhoto').addEventListener('click', () => {
imageInput.click();
});
document.getElementById('optionCamera').addEventListener('click', () => {
imageInput.click();
});
imageInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
sendImage(e.target.result);
};
reader.readAsDataURL(file);
}
moreOptions.classList.remove('visible');
imageInput.value = '';
});
// Close panels when clicking messages
chatMessages.addEventListener('click', () => {
emojiPicker.classList.remove('visible');
moreOptions.classList.remove('visible');
});
// Scroll to bottom on load
chatMessages.scrollTop = chatMessages.scrollHeight;
</script>
</body>
</html>