PeerJS 聊天室 - 文件传输 body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; position: relative; display: flex; } #main-content { flex: 1; margin-right: 20px; } #contacts-container { width: 250px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } #contacts-header { font-weight: bold; margin-bottom: 10px; padding-bottom: 5px; border-bottom: 1px solid #ddd; display: flex; justify-content: space-between; align-items: center; } #sound-toggle { background: none; border: none; cursor: pointer; font-size: 1.2em; color: #666; } #contacts-list { list-style-type: none; padding: 0; margin: 0; max-height: 600px; overflow-y: auto; } #contacts-list li { padding: 8px 10px; margin: 5px 0; background: #f5f5f5; border-radius: 4px; cursor: pointer; transition: background 0.2s; } #contacts-list li:hover { background: #e3f2fd; } .contact-name { font-weight: bold; } .contact-id { font-size: 0.8em; color: #999; margin-top: 2px; } #login-container, #chat-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 20px; } #chat-container { display: none; } #chat-messages { height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; margin-bottom: 10px; background: #f9f9f9; } .message { margin: 10px 0; padding: 10px; border-radius: 8px; max-width: 80%; position: relative; } .my-message { background: #e3f2fd; margin-left: auto; } .other-message { background: #f1f1f1; margin-right: auto; } .message-username { font-weight: bold; font-size: 1.1em; } .message-content { margin: 5px 0; } .message-time { font-size: 0.8em; color: #666; text-align: right; } .system-message { color: #666; text-align: center; font-size: 0.9em; margin: 10px 0; } input[type="text"] { padding: 8px; width: 100%; box-sizing: border-box; margin: 5px 0; } button { padding: 8px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; margin: 5px 0; } button:disabled { background: #cccccc; } #status { margin: 10px 0; font-weight: bold; color: #2e7d32; } #file-input { display: none; } #file-input-label { display: inline-block; padding: 8px 15px; background: #2196F3; color: white; border-radius: 4px; cursor: pointer; margin-right: 10px; } .file-preview { max-width: 100%; max-height: 300px; margin-top: 10px; border: 1px solid #ddd; border-radius: 4px; } .file-message { margin-top: 10px; padding: 10px; background: #fff; border: 1px solid #ddd; border-radius: 4px; } .file-download { margin-top: 5px; } #context-menu { display: none; position: fixed; z-index: 1000; background-color: white; border: 1px solid #ddd; box-shadow: 0 2px 5px rgba(0,0,0,0.2); border-radius: 4px; padding: 5px 0; min-width: 120px; } .context-menu-item { padding: 8px 15px; cursor: pointer; } .context-menu-item:hover { background-color: #f5f5f5; } .deleted-message { color: #999; font-style: italic; } .progress-container { width: 100%; background-color: #f1f1f1; border-radius: 4px; margin: 5px 0; } .progress-bar { height: 10px; background-color: #4CAF50; border-radius: 4px; width: 0%; transition: width 0.3s; } .file-icon { font-size: 48px; color: #666; margin: 10px 0; } .geogebra-icon { width: 48px; height: 48px; margin: 10px 0; background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6bm9uZTtzdHJva2U6I2ZmZjtzdHJva2UtbGluZWNhcDpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMH0uY2xzLTJ7ZmlsbDojZmZmfTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJzZXR0aW5nc19ib3JkZXIiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjMiIGNsYXNzPSJjbHMtMSIvPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjgiIGNsYXNzPSJjbHMtMSIvPjwvZz48cGF0aCBpZD0iZWNrIiBkPSJNMjAuMTcgMTMuMDhhOC41NyA4LjU3IDAgMCAwIC4wOC0xLjA4IDguNTcgOC41NyAwIDAgMC0uMDgtMS4wOGwyLjMyLTEuODFhLjU1LjU1IDAgMCAwIC4xMy0uN0wyMC40MyA0LjZhLjU1LjU1IDAgMCAwLS42Ny0uMjRMMTcgNS40NmE4IDggMCAwIDAtMS44Ni0xLjA4bC0uNDItMi45MkEuNTQuNTQgMCAwIDAgMTQuMiAxSDkuOGEuNTQuNTQgMCAwIDAtLjU0LjQ2bC0uNDIgMi45MkE4LjQ1IDguNDUgMCAwIDAgNyA1LjQ1bC0yLjc1LTEuMWEuNTQuNTQgMCAwIDAtLjY3LjI0TDEuMzcgOC40YS41NC41NCAwIDAgMCAuMTMuN2wyLjMyIDEuODFBOC43MiA4LjcyIDAgMCAwIDMuNzUgMTJhOC43MiA4LjcyIDAgMCAwIC4wOCAxLjA4bC0yLjMyIDEuODFhLjU1LjU1IDAgMCAwLS4xMy43bDIuMiAzLjgxYS41NS41NSAwIDAgMCAuNjcuMjRMNyAxOC41NGE4IDggMCAwIDAgMS44NiAxLjA4bC40MiAyLjkxYS41NC41NCAwIDAgMCAuNTIuNDdoNC40YS41NC41NCAwIDAgMCAuNTQtLjQ2bC40Mi0yLjkxQTguNDUgOC40NSAwIDAgMCAxNyAxOC41NGwyLjc0IDEuMWEuNTQuNTQgMCAwIDAgLjY3LS4yNGwyLjItMy44MWEuNTUuNTUgMCAwIDAtLjEzLS43ek0xMiAxNS44NUEzLjg1IDMuODUgMCAxIDEgMTUuODUgMTIgMy44NSAzLjg1IDAgMCAxIDEyIDE1Ljg1eiIgY2xhc3M9ImNscy0yIi8+PHBhdGggaWQ9ImdlYXIiIGQ9Ik0xOS40MyAxM2E3Ljc5IDcuNzkgMCAwIDAgLjA3LTEgNy43OSA3Ljc5IDAgMCAwLS4wNy0xbDIuMTEtMS42NWEuNS41IDAgMCAwIC4xMi0uNjRsLTItMy40NmEuNS41IDAgMCAwLS42MS0uMjJsLTIuNDkgMWE3LjMxIDcuMzEgMCAwIDAtMS42OS0xbC0uMzgtMi42NUEuNDkuNDkgMCAwIDAgMTQgMmgtNGEuNDkuNDkgMCAwIDAtLjQ5LjQybC0uMzggMi42NWE3LjY4IDcuNjggMCAwIDAtMS42OSAxbC0yLjQ5LTFhLjQ5LjQ5IDAgMCAwLS42MS4yMmwtMiAzLjQ2YS40OS40OSAwIDAgMCAuMTIuNjRMNC41NyAxMWE3LjkzIDcuOTMgMCAwIDAtLjA3IDEgNy45MyA3LjkzIDAgMCAwIC4wNyAxbC0yLjExIDEuNjNhLjUuNSAwIDAgMC0uMTIuNjRsMiAzLjQ2YS41LjUgMCAwIDAgLjYxLjIybDIuNDktMWE3LjMxIDcuMzEgMCAwIDAgMS42OSAxbC4zOCAyLjY1YS40OS40OSAwIDAgMCAuNDkuNGg0YS40OS40OSAwIDAgMCAuNDktLjQybC4zOC0yLjY1YTcuNjggNy42OCAwIDAgMCAxLjY5LTFsMi40OSAxYS40OS40OSAwIDAgMCAuNjEtLjIybDItMy40NmEuNS41IDAgMCAwLS4xMi0uNjR6TTEyIDE1LjVhMy41IDMuNSAwIDEgMSAzLjUtMy41IDMuNSAzLjUgMCAwIDEtMy41IDMuNXoiLz48L3N2Zz4='); background-size: contain; background-repeat: no-repeat; } .word-icon { font-size: 48px; color: #2b579a; margin: 10px 0; } .excel-icon { font-size: 48px; color: #217346; margin: 10px 0; } .ppt-icon { font-size: 48px; color: #d24726; margin: 10px 0; } .text-icon { font-size: 48px; color: #666; margin: 10px 0; } .video-icon { font-size: 48px; color: #ff5722; margin: 10px 0; } .audio-icon { font-size: 48px; color: #9c27b0; margin: 10px 0; } video { max-width: 100%; max-height: 300px; margin-top: 10px; border: 1px solid #ddd; border-radius: 4px; } audio { width: 100%; margin-top: 10px; }

PeerJS 聊天室 - 文件传输

生成我的ID
        <div id="username-setup" style="display: none;">
            <input type="text" id="username-input" placeholder="设置用户名(如:张三)">
            <button id="confirm-username-btn">确认用户名</button>
        </div>
        
        <div>
            <input type="text" id="peer-id" placeholder="输入对方ID">
            <button id="connect-btn" disabled>连接</button>
        </div>
    </div>

    <div id="chat-container">
        <div id="status">状态: 等待连接...</div>
        <div id="chat-messages"></div>
        <div>
            <input type="text" id="message-input" placeholder="输入消息" disabled>
            <button id="send-btn" disabled>发送</button>
            <input type="file" id="file-input" accept="image/*,.pdf,.webp,.ggb,.docx,.xlsx,.pptx,.txt,.mp4,.mp3">
            <label for="file-input" id="file-input-label">发送文件</label>
            <button id="disconnect-btn">断开连接</button>
        </div>
        <div id="file-progress" style="display: none;">
            <div>文件传输中...</div>
            <div class="progress-container">
                <div id="progress-bar" class="progress-bar"></div>
            </div>
        </div>
    </div>
</div>

<div id="contacts-container">
    <div id="contacts-header">
        <span>联系人列表</span>
        <button id="sound-toggle" title="提示音已开启">🔔</button>
    </div>
    <ul id="contacts-list"></ul>
</div>

<div id="context-menu">
    <div id="recall-menu-item" class="context-menu-item">撤回消息</div>
    <div id="delete-menu-item" class="context-menu-item">删除消息</div>
</div>

<script>
    // 状态管理
    const state = {
        peer: null,
        myId: null,
        username: "匿名用户",
        conn: null,
        messageHistory: [],
        soundEnabled: true,
        selectedMessage: null,
        contacts: [],
        activeConnections: {},
        fileTransfer: {
            inProgress: false,
            fileName: '',
            fileType: '',
            chunks: [],
            receivedChunks: 0,
            totalChunks: 0,
            sender: ''
        }
    };

    // Web Audio API 相关变量
    let audioContext;
    let gainNode;

    // DOM元素
    const generateBtn = document.getElementById('generate-btn');
    const myIdDiv = document.getElementById('my-id');
    const usernameInput = document.getElementById('username-input');
    const confirmUsernameBtn = document.getElementById('confirm-username-btn');
    const usernameSetupDiv = document.getElementById('username-setup');
    const peerIdInput = document.getElementById('peer-id');
    const connectBtn = document.getElementById('connect-btn');
    const chatContainer = document.getElementById('chat-container');
    const chatMessages = document.getElementById('chat-messages');
    const messageInput = document.getElementById('message-input');
    const sendBtn = document.getElementById('send-btn');
    const fileInput = document.getElementById('file-input');
    const fileInputLabel = document.getElementById('file-input-label');
    const disconnectBtn = document.getElementById('disconnect-btn');
    const statusDiv = document.getElementById('status');
    const contextMenu = document.getElementById('context-menu');
    const recallMenuItem = document.getElementById('recall-menu-item');
    const deleteMenuItem = document.getElementById('delete-menu-item');
    const fileProgress = document.getElementById('file-progress');
    const progressBar = document.getElementById('progress-bar');
    const contactsList = document.getElementById('contacts-list');
    const soundToggle = document.getElementById('sound-toggle');

    // 初始化音频
    function initAudio() {
        try {
            audioContext = new (window.AudioContext || window.webkitAudioContext)();
            gainNode = audioContext.createGain();
            gainNode.gain.value = 0.3;
            gainNode.connect(audioContext.destination);
        } catch (e) {
            console.error('音频初始化失败:', e);
            state.soundEnabled = false;
            soundToggle.textContent = '🔕';
            soundToggle.title = '提示音不可用';
        }
    }

    // 播放提示音
    function playNotificationSound() {
        if (!state.soundEnabled || !audioContext) return;

        try {
            const oscillator = audioContext.createOscillator();
            oscillator.type = 'sine';
            oscillator.frequency.value = 880;
            oscillator.connect(gainNode);

            oscillator.start();
            setTimeout(() => {
                oscillator.stop();
            }, 200);
        } catch (e) {
            console.error('播放提示音失败:', e);
        }
    }

    // 切换提示音状态
    function toggleSound() {
        state.soundEnabled = !state.soundEnabled;
        soundToggle.textContent = state.soundEnabled ? '🔔' : '🔕';
        soundToggle.title = state.soundEnabled ? '提示音已开启' : '提示音已关闭';
        
        if (state.soundEnabled) {
            playNotificationSound();
        }
    }

    // 初始化右键菜单
    function initContextMenu() {
        // 右键菜单项点击事件
        recallMenuItem.addEventListener('click', recallMessage);
        deleteMenuItem.addEventListener('click', deleteMessage);
        
        // 点击页面其他地方隐藏右键菜单
        document.addEventListener('click', hideContextMenu);
        
        // 右键点击页面其他地方隐藏右键菜单
        document.addEventListener('contextmenu', (e) => {
            if (e.target !== contextMenu && !contextMenu.contains(e.target)) {
                hideContextMenu();
            }
        });
    }

    // 隐藏右键菜单
    function hideContextMenu() {
        contextMenu.style.display = 'none';
        state.selectedMessage = null;
    }

    // 撤回消息
    function recallMessage() {
        if (!state.selectedMessage || !state.conn) return;
        
        const message = state.selectedMessage;
        const isMe = message.sender === state.username;
        
        if (!isMe) {
            alert('只能撤回自己发送的消息');
            return;
        }
        
        try {
            state.conn.send({
                type: 'message-action',
                action: 'recall',
                messageId: message.id,
                username: state.username
            });
            
            message.isRecalled = true;
            displayMessages();
        } catch (e) {
            console.error('撤回消息失败:', e);
            addSystemMessage('系统: 撤回消息失败,请检查连接');
        }
        
        hideContextMenu();
    }

    // 删除消息
    function deleteMessage() {
        if (!state.selectedMessage) return;
        
        const message = state.selectedMessage;
        message.isDeleted = true;
        displayMessages();
        hideContextMenu();
    }

    // 添加系统消息
    function addSystemMessage(text) {
        const message = {
            id: 'sys-' + Date.now(),
            sender: 'system',
            content: text,
            timestamp: new Date().toLocaleTimeString(),
            isSystem: true
        };
        state.messageHistory.push(message);
        displayMessages();
    }

    // 更新联系人列表
    function updateContactsList() {
        contactsList.innerHTML = '';
        state.contacts.forEach(contact => {
            const li = document.createElement('li');
            li.innerHTML = `
                <div class="contact-name">${contact.username}</div>
                <div class="contact-id">${contact.id}</div>
            `;
            li.dataset.id = contact.id;
            
            li.addEventListener('dblclick', () => {
                const confirmDelete = confirm(`确定要删除联系人 ${contact.username} 吗?这将断开与TA的连接`);
                if (confirmDelete) {
                    if (state.activeConnections[contact.id]) {
                        state.activeConnections[contact.id].close();
                        delete state.activeConnections[contact.id];
                    }
                    
                    if (state.conn && state.conn.peer === contact.id) {
                        state.conn.close();
                        state.conn = null;
                        updateUI();
                        statusDiv.textContent = "状态: 已断开连接";
                    }
                    
                    state.contacts = state.contacts.filter(c => c.id !== contact.id);
                    updateContactsList();
                    addSystemMessage(`系统: 已断开与 ${contact.username} 的连接`);
                }
            });
            
            contactsList.appendChild(li);
        });
    }

    // 添加新联系人
    function addContact(id, username) {
        const existingContact = state.contacts.find(c => c.id === id);
        if (existingContact) {
            existingContact.username = username;
        } else {
            state.contacts.push({ id, username });
        }
        updateContactsList();
    }

    // 初始化PeerJS
    function initPeer() {
        if (typeof Peer === 'undefined') {
            alert('PeerJS 库加载失败,请刷新页面或检查网络连接');
            return;
        }
        
        state.peer = new Peer({
            host: '0.peerjs.com',
            port: 443,
            path: '/',
            config: { 
                iceServers: [
                    { urls: 'stun:stun.l.google.com:19302' },
                    { urls: 'stun:stun1.l.google.com:19302' },
                    { urls: 'stun:stun2.l.google.com:19302' }
                ]
            }
        });

        state.peer.on('open', (id) => {
            state.myId = id;
            myIdDiv.textContent = `我的ID: ${id}`;
            usernameSetupDiv.style.display = 'block';
            addSystemMessage(`系统: 你的ID已生成`);
            statusDiv.textContent = `状态: 请设置用户名`;
        });

        state.peer.on('connection', (conn) => {
            chatContainer.style.display = 'block';
            statusDiv.textContent = `状态: 检测到入站连接...`;
            addSystemMessage(`系统: 有人正在连接你`);
            
            conn.on('open', () => {
                state.conn = conn;
                state.activeConnections[conn.peer] = conn;
                updateUI();
                addSystemMessage(`系统: 连接已建立`);
                statusDiv.textContent = `状态: 已连接`;
                
                conn.send({
                    type: 'user-info',
                    username: state.username
                });
            });
            
            setupConnectionEvents(conn);
        });

        state.peer.on('error', (err) => {
            console.error('PeerJS错误:', err);
            let errorMsg = `错误: ${err.type}`;
            if (err.type === 'peer-unavailable') {
                errorMsg = "对方ID不存在或未在线";
            }
            addSystemMessage(`系统: ${errorMsg}`);
            statusDiv.textContent = `状态: ${errorMsg}`;
        });
    }

    // 设置连接事件监听
    function setupConnectionEvents(conn) {
        conn.on('data', (data) => {
            try {
                if (data.type === 'chat') {
                    state.messageHistory.push(data.message);
                    displayMessages();
                    
                    if (state.soundEnabled && data.message.sender !== state.username) {
                        playNotificationSound();
                    }
                } 
                else if (data.type === 'user-info') {
                    addContact(conn.peer, data.username);
                    
                    const message = {
                        id: 'user-join',
                        sender: 'system',
                        username: data.username,
                        content: `${data.username} 加入了聊天`,
                        timestamp: new Date().toLocaleTimeString(),
                        isSystem: true
                    };
                    state.messageHistory.push(message);
                    displayMessages();
                }
                else if (data.type === 'file-start' || data.type === 'file-chunk') {
                    handleReceivedFile(data);
                }
                else if (data.type === 'message-action') {
                    handleMessageAction(data);
                }
            } catch (e) {
                console.error('处理数据错误:', e);
            }
        });

        conn.on('close', () => {
            addSystemMessage(`系统: 连接已断开`);
            if (state.activeConnections[conn.peer]) {
                delete state.activeConnections[conn.peer];
            }
            
            if (state.conn && state.conn.peer === conn.peer) {
                state.conn = null;
                updateUI();
                statusDiv.textContent = "状态: 已断开连接";
            }
        });

        conn.on('error', (err) => {
            console.error('连接错误:', err);
            addSystemMessage(`系统: 连接错误 - ${err.message}`);
        });
    }

    // 处理接收到的文件
    function handleReceivedFile(data) {
        if (data.type === 'file-start') {
            state.fileTransfer = {
                inProgress: true,
                fileName: data.fileName,
                fileType: data.fileType,
                chunks: new Array(data.totalChunks),
                receivedChunks: 0,
                totalChunks: data.totalChunks,
                sender: data.username
            };
            
            fileProgress.style.display = 'block';
            progressBar.style.width = '0%';
            addSystemMessage(`系统: 开始接收文件 ${data.fileName}`);
        } 
        else if (data.type === 'file-chunk' && state.fileTransfer.inProgress) {
            state.fileTransfer.chunks[data.chunkIndex] = data.chunkData;
            state.fileTransfer.receivedChunks++;
            
            const progress = Math.round((state.fileTransfer.receivedChunks / state.fileTransfer.totalChunks) * 100);
            progressBar.style.width = `${progress}%`;
            
            if (state.fileTransfer.receivedChunks === state.fileTransfer.totalChunks) {
                const fileData = state.fileTransfer.chunks.join('');
                
                let previewContent = '';
                if (state.fileTransfer.fileType.startsWith('image/')) {
                    previewContent = `<img src="${fileData}" class="file-preview" alt="${state.fileTransfer.fileName}">`;
                } else if (state.fileTransfer.fileType === 'application/pdf') {
                    previewContent = `
                        <div class="file-icon">📄</div>
                        <div>PDF文件: ${state.fileTransfer.fileName}</div>
                    `;
                } else if (state.fileTransfer.fileType === 'image/webp') {
                    previewContent = `<img src="${fileData}" class="file-preview" alt="${state.fileTransfer.fileName}">`;
                } else if (state.fileTransfer.fileType === 'application/octet-stream' && 
                           state.fileTransfer.fileName.endsWith('.ggb')) {
                    previewContent = `
                        <div class="geogebra-icon"></div>
                        <div>GeoGebra文件: ${state.fileTransfer.fileName}</div>
                    `;
                } else if (state.fileTransfer.fileType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
                           state.fileTransfer.fileName.endsWith('.docx')) {
                    previewContent = `
                        <div class="word-icon">📝</div>
                        <div>Word文档: ${state.fileTransfer.fileName}</div>
                    `;
                } else if (state.fileTransfer.fileType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
                           state.fileTransfer.fileName.endsWith('.xlsx')) {
                    previewContent = `
                        <div class="excel-icon">📊</div>
                        <div>Excel文件: ${state.fileTransfer.fileName}</div>
                    `;
                } else if (state.fileTransfer.fileType === 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ||
                           state.fileTransfer.fileName.endsWith('.pptx')) {
                    previewContent = `
                        <div class="ppt-icon">📽️</div>
                        <div>PowerPoint文件: ${state.fileTransfer.fileName}</div>
                    `;
                } else if (state.fileTransfer.fileType === 'text/plain' ||
                           state.fileTransfer.fileName.endsWith('.txt')) {
                    previewContent = `
                        <div class="text-icon">📝</div>
                        <div>文本文件: ${state.fileTransfer.fileName}</div>
                    `;
                } else if (state.fileTransfer.fileType === 'video/mp4' ||
                           state.fileTransfer.fileName.endsWith('.mp4')) {
                    previewContent = `
                        <div class="video-icon">🎬</div>
                        <div>视频文件: ${state.fileTransfer.fileName}</div>
                        <video controls>
                            <source src="${fileData}" type="video/mp4">
                            您的浏览器不支持视频播放
                        </video>
                    `;
                } else if (state.fileTransfer.fileType === 'audio/mp3' ||
                           state.fileTransfer.fileName.endsWith('.mp3')) {
                    previewContent = `
                        <div class="audio-icon">🎵</div>
                        <div>音频文件: ${state.fileTransfer.fileName}</div>
                        <audio controls>
                            <source src="${fileData}" type="audio/mpeg">
                            您的浏览器不支持音频播放
                        </audio>
                    `;
                } else {
                    previewContent = `
                        <div class="file-icon">📁</div>
                        <div>文件: ${state.fileTransfer.fileName}</div>
                    `;
                }
                
                const message = {
                    id: 'file-' + Date.now(),
                    sender: state.fileTransfer.sender,
                    username: state.fileTransfer.sender,
                    content: '发送了一个文件',
                    timestamp: new Date().toLocaleTimeString(),
                    isFile: true,
                    fileName: state.fileTransfer.fileName,
                    fileType: state.fileTransfer.fileType,
                    fileData: fileData,
                    previewContent: previewContent
                };
                
                state.messageHistory.push(message);
                displayMessages();
                
                fileProgress.style.display = 'none';
                
                state.fileTransfer = {
                    inProgress: false
                };
                
                if (state.soundEnabled && state.fileTransfer.sender !== state.username) {
                    playNotificationSound();
                }
                
                addSystemMessage(`系统: 文件 ${state.fileTransfer.fileName} 接收完成`);
            }
        }
    }

    // 处理消息操作
    function handleMessageAction(data) {
        if (data.action === 'recall') {
            const messageIndex = state.messageHistory.findIndex(msg => msg.id === data.messageId);
            if (messageIndex !== -1) {
                const message = state.messageHistory[messageIndex];
                message.isRecalled = true;
                message.recalledBy = data.username;
                displayMessages();
            }
        }
    }

    // 显示消息
    function displayMessages() {
        chatMessages.innerHTML = '';
        const visibleMessages = state.messageHistory.filter(msg => !msg.isDeleted);
        
        visibleMessages.forEach(msg => {
            const isMe = msg.sender === state.username;
            const div = document.createElement('div');
            div.className = `message ${isMe ? 'my-message' : 'other-message'}`;
            div.dataset.messageId = msg.id;
            
            if (msg.isSystem) {
                div.innerHTML = `
                    <div class="system-message">${msg.content}</div>
                `;
            } else if (msg.isRecalled) {
                div.innerHTML = `
                    <div class="message-username">${msg.username} ${isMe ? '(我)' : ''}</div>
                    <div class="message-content deleted-message">${isMe ? '你撤回了一条消息' : `${msg.username} 撤回了一条消息`}</div>
                    <div class="message-time">${msg.timestamp}</div>
                `;
            } else if (msg.isFile) {
                div.innerHTML = `
                    <div class="message-username">${msg.username} ${isMe ? '(我)' : ''}</div>
                    <div class="message-content">${msg.isSending ? '正在发送文件...' : msg.content}</div>
                    <div class="file-message">
                        ${msg.previewContent || ''}
                        <div class="file-download">
                            ${msg.fileData ? `<a href="${msg.fileData}" download="${msg.fileName}">下载文件</a>` : ''}
                        </div>
                    </div>
                    <div class="message-time">${msg.timestamp}</div>
                `;
            } else {
                div.innerHTML = `
                    <div class="message-username">${msg.username} ${isMe ? '(我)' : ''}</div>
                    <div class="message-content">${msg.content}</div>
                    <div class="message-time">${msg.timestamp}</div>
                `;
            }
            
            chatMessages.appendChild(div);
            
            if (!msg.isSystem) {
                div.addEventListener('contextmenu', (e) => {
                    showContextMenu(e, msg);
                });
            }
        });
        chatMessages.scrollTop = chatMessages.scrollHeight;
    }

    // 显示右键菜单
    function showContextMenu(e, message) {
        e.preventDefault();
        state.selectedMessage = message;
        
        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;
        
        let left = e.clientX;
        let top = e.clientY;
        
        contextMenu.style.display = 'block';
        const menuWidth = contextMenu.offsetWidth;
        const menuHeight = contextMenu.offsetHeight;
        
        if (left + menuWidth > viewportWidth) {
            left = viewportWidth - menuWidth - 5;
        }
        
        if (top + menuHeight > viewportHeight) {
            top = viewportHeight - menuHeight - 5;
        }
        
        contextMenu.style.left = `${left}px`;
        contextMenu.style.top = `${top}px`;
        
        const isMe = message.sender === state.username;
        recallMenuItem.style.display = isMe ? 'block' : 'none';
        deleteMenuItem.style.display = 'block';
        
        e.stopPropagation();
    }

    // 更新UI状态
    function updateUI() {
        const isConnected = state.conn !== null;
        messageInput.disabled = !isConnected;
        sendBtn.disabled = !isConnected;
        fileInput.disabled = !isConnected;
        fileInputLabel.style.opacity = isConnected ? '1' : '0.5';
    }

    // 连接到其他用户
    function connectToPeer() {
        const peerId = peerIdInput.value.trim();
        if (!peerId) {
            alert("请输入对方ID");
            return;
        }
        
        chatContainer.style.display = 'block';
        statusDiv.textContent = `状态: 正在连接...`;
        
        if (state.activeConnections[peerId]) {
            state.conn = state.activeConnections[peerId];
            updateUI();
            addSystemMessage(`系统: 已连接到已有会话`);
            statusDiv.textContent = `状态: 已连接`;
            return;
        }
        
        const conn = state.peer.connect(peerId, {
            reliable: true,
            serialization: 'json'
        });

        const timeout = setTimeout(() => {
            if (!conn.open) {
                conn.close();
                addSystemMessage("连接超时,请检查网络");
                statusDiv.textContent = "状态: 连接超时";
            }
        }, 10000);

        conn.on('open', () => {
            clearTimeout(timeout);
            state.conn = conn;
            state.activeConnections[peerId] = conn;
            updateUI();
            addSystemMessage(`系统: 已连接`);
            statusDiv.textContent = `状态: 已连接`;
            
            conn.send({
                type: 'user-info',
                username: state.username
            });
            
            addContact(peerId, "未知用户");
        });
        
        setupConnectionEvents(conn);
    }

    // 发送消息
    function sendMessage() {
        const text = messageInput.value.trim();
        if (!text || !state.conn) return;
        
        const message = {
            id: Date.now().toString(),
            sender: state.username,
            username: state.username,
            content: text,
            timestamp: new Date().toLocaleTimeString()
        };
        
        state.messageHistory.push(message);
        displayMessages();
        
        try {
            state.conn.send({
                type: 'chat',
                message: message
            });
        } catch (e) {
            console.error('发送消息失败:', e);
            addSystemMessage('系统: 发送消息失败,请检查连接');
        }
        
        messageInput.value = '';
    }

    // 处理文件选择
    fileInput.addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (!file || !state.conn) return;
        
        // 支持的文件类型
        const validTypes = [
            'image/jpeg', 
            'image/png', 
            'image/gif', 
            'image/webp',
            'application/pdf',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // .docx
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',      // .xlsx
            'application/vnd.openxmlformats-officedocument.presentationml.presentation', // .pptx
            'text/plain',                                                             // .txt
            'video/mp4',                                                              // .mp4
            'audio/mp3',                                                              // .mp3
            'application/octet-stream'                                               // 用于.ggb文件
        ];
        
        // 检查文件类型或扩展名
        const fileExt = file.name.split('.').pop().toLowerCase();
        if (!validTypes.includes(file.type) && 
            fileExt !== 'ggb' && 
            fileExt !== 'docx' && 
            fileExt !== 'xlsx' && 
            fileExt !== 'pptx' && 
            fileExt !== 'txt' &&
            fileExt !== 'mp4' &&
            fileExt !== 'mp3') {
            addSystemMessage("系统: 只支持JPG、PNG、GIF、WebP图片、PDF文件、Word(.docx)、Excel(.xlsx)、PowerPoint(.pptx)、文本(.txt)、视频(.mp4)、音频(.mp3)和GeoGebra(.ggb)文件");
            return;
        }

const MAX_FILE_SIZE = file.type.startsWith('video/') || file.type.startsWith('audio/') || file.name.endsWith('.mp4') || file.name.endsWith('.mp3') ? 900 * 1024 * 1024 // 900MB for video/audio : 15 * 1024 * 1024; // 15MB for others

if (file.size > MAX_FILE_SIZE) { const maxSize = MAX_FILE_SIZE === 900 * 1024 * 1024 ? '900MB' : '5MB'; addSystemMessage(系统: 文件 "${file.name}" 超过${maxSize}限制); return; }
sendFile(file); fileInput.value = ''; });

    // 发送文件
    function sendFile(file) {
        const reader = new FileReader();
        
        reader.onload = (e) => {
            const fileData = e.target.result;
            
            let previewContent = '';
            if (file.type.startsWith('image/')) {
                previewContent = `<img src="${fileData}" class="file-preview" alt="${file.name}">`;
            } else if (file.type === 'application/pdf') {
                previewContent = `
                    <div class="file-icon">📄</div>
                    <div>PDF文件: ${file.name}</div>
                `;
            } else if (file.name.endsWith('.ggb')) {
                previewContent = `
                    <div class="geogebra-icon"></div>
                    <div>GeoGebra文件: ${file.name}</div>
                `;
            } else if (file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
                       file.name.endsWith('.docx')) {
                previewContent = `
                    <div class="word-icon">📝</div>
                    <div>Word文档: ${file.name}</div>
                `;
            } else if (file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
                       file.name.endsWith('.xlsx')) {
                previewContent = `
                    <div class="excel-icon">📊</div>
                    <div>Excel文件: ${file.name}</div>
                `;
            } else if (file.type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation' ||
                       file.name.endsWith('.pptx')) {
                previewContent = `
                    <div class="ppt-icon">📽️</div>
                    <div>PowerPoint文件: ${file.name}</div>
                `;
            } else if (file.type === 'text/plain' ||
                       file.name.endsWith('.txt')) {
                previewContent = `
                    <div class="text-icon">📝</div>
                    <div>文本文件: ${file.name}</div>
                `;
            } else if (file.type === 'video/mp4' ||
                       file.name.endsWith('.mp4')) {
                previewContent = `
                    <div class="video-icon">🎬</div>
                    <div>视频文件: ${file.name}</div>
                    <video controls>
                        <source src="${fileData}" type="video/mp4">
                        您的浏览器不支持视频播放
                    </video>
                `;
            } else if (file.type === 'audio/mp3' ||
                       file.name.endsWith('.mp3')) {
                previewContent = `
                    <div class="audio-icon">🎵</div>
                    <div>音频文件: ${file.name}</div>
                    <audio controls>
                        <source src="${fileData}" type="audio/mpeg">
                        您的浏览器不支持音频播放
                    </audio>
                `;
            } else {
                previewContent = `
                    <div class="file-icon">📁</div>
                    <div>文件: ${file.name}</div>
                `;
            }
            
            const message = {
                id: 'file-' + Date.now(),
                sender: state.username,
                username: state.username,
                content: '正在发送文件...',
                timestamp: new Date().toLocaleTimeString(),
                isFile: true,
                fileName: file.name,
                fileType: file.type || 'application/octet-stream', // 为.ggb文件设置默认类型
                fileData: fileData,
                previewContent: previewContent,
                isSending: true
            };
            
            state.messageHistory.push(message);
            displayMessages();
            
            try {
                const chunkSize = 16000;
                const totalChunks = Math.ceil(fileData.length / chunkSize);
                
                fileProgress.style.display = 'block';
                progressBar.style.width = '0%';
                
                state.conn.send({
                    type: 'file-start',
                    username: state.username,
                    fileName: file.name,
                    fileType: file.type || 'application/octet-stream',
                    totalChunks: totalChunks
                });
                
                let chunksSent = 0;
                const sendNextChunk = (index) => {
                    if (index >= totalChunks) {
                        fileProgress.style.display = 'none';
                        
                        const msgIndex = state.messageHistory.findIndex(m => m.id === message.id);
                        if (msgIndex !== -1) {
                            state.messageHistory[msgIndex].content = '发送了一个文件';
                            state.messageHistory[msgIndex].isSending = false;
                            displayMessages();
                        }
                        
                        return;
                    }
                    
                    const chunk = fileData.substring(index * chunkSize, (index + 1) * chunkSize);
                    state.conn.send({
                        type: 'file-chunk',
                        chunkIndex: index,
                        chunkData: chunk
                    });
                    
                    chunksSent++;
                    const progress = Math.round((chunksSent / totalChunks) * 100);
                    progressBar.style.width = `${progress}%`;
                    
                    setTimeout(() => sendNextChunk(index + 1), 0);
                };
                
                sendNextChunk(0);
                
            } catch (e) {
                console.error('发送文件失败:', e);
                addSystemMessage('系统: 发送文件失败,请检查连接');
                fileProgress.style.display = 'none';
            }
        };
        
        reader.readAsDataURL(file);
    }

    // 断开连接
    function disconnect() {
        if (state.conn) {
            state.conn.close();
            if (state.activeConnections[state.conn.peer]) {
                delete state.activeConnections[state.conn.peer];
            }
            state.conn = null;
        }
        updateUI();
        addSystemMessage("系统: 已断开连接");
        statusDiv.textContent = "状态: 已断开连接";
    }

    // 设置用户名
    function setUsername() {
        const username = usernameInput.value.trim();
        if (!username) {
            alert("用户名不能为空");
            return;
        }
        
        state.username = username;
        usernameSetupDiv.style.display = 'none';
        connectBtn.disabled = false;
        statusDiv.textContent = `状态: 就绪`;
        addSystemMessage(`系统: 你的用户名已设置为 "${username}"`);
    }

    // 初始化应用
    function initApp() {
        initAudio();
        initContextMenu();
        
        generateBtn.addEventListener('click', initPeer);
        confirmUsernameBtn.addEventListener('click', setUsername);
        usernameInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') setUsername();
        });
        connectBtn.addEventListener('click', connectToPeer);
        sendBtn.addEventListener('click', sendMessage);
        messageInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') sendMessage();
        });
        disconnectBtn.addEventListener('click', disconnect);
        soundToggle.addEventListener('click', toggleSound);
        
        setTimeout(() => {
            if (state.soundEnabled) {
                playNotificationSound();
            }
        }, 1000);
    }

    // 启动应用
    initApp();
</script>