<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat Interface</title>
    <style>
        body {
            font-family: 'Helvetica Neue', Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f7f9fc;
            color: #333;
        }
        .container {
            width: 98%;
            height: 98vh;
            background-color: #fff;
            border-radius: 12px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            display: flex;
            flex-direction: row;
            overflow: hidden;
        }
        .sidebar {
            width: 20%;
            min-width: 200px;
            border-right: 1px solid #e0e0e0;
            overflow-y: auto;
            padding: 20px;
            background-color: #f0f4f8;
        }
        .sidebar ul {
            list-style-type: none;
            padding: 0;
        }
        .sidebar li {
            font-size: 18px;
            padding: 10px 10px 10px 0;
            cursor: pointer;
            white-space: nowrap;
            overflow: visible;
            position: relative;
            display: flex;
        }
        .sidebar li .history-span {
            flex: 1;
            display: inline-block;
            overflow: hidden;
            text-overflow: ellipsis;
            max-width: calc(100% - 40px);
        }
        .sidebar li.selected {
            padding: 8px 12px;
            background-color: #e6e6e6;
            color: #333;
            border: 1px solid #ccc;
            border-radius: 12px;
        }
        .sidebar li:hover {
            padding: 8px 12px;
            background-color: #f1f1f1;
            color: #333;
            border: 1px solid #ccc;
            border-radius: 12px;
        }
        .sidebar li .menu-button {
            display: none;
            position: absolute;
            right: 10px;
            top: 10px;
            background: none;
            border: none;
            cursor: pointer;
            font-size: 18px;
            float: 'right';
        }
        .sidebar li:hover .menu-button {
            display: inline-block;
        }
        .dropdown-menu {
            display: none;
            position: absolute;
            right: 10px;
            top: 30px;
            background-color: #fff;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            z-index: 1000;
        }
        .dropdown-menu a {
            display: block;
            padding: 10px;
            text-decoration: none;
            color: #333;
            border-bottom: 1px solid #e0e0e0;
        }
        .dropdown-menu a:hover {
            background-color: #f0f0f0;
        }
        .dropdown-menu a:last-child {
            border-bottom: none;
        }
        .main-content {
            flex: 1;
            display: flex;
            flex-direction: column;
            background-color: #fff;
            max-width: 100%;
            overflow: hidden;
        }
        .chat-window {
            flex: 1;
            padding: 20px;
            overflow-x: auto;
            overflow-y: auto;
            border-bottom: 1px solid #e0e0e0;
        }
        .input-section {
            display: flex;
            flex-wrap: nowrap;
            padding: 10px;
            background-color: #f7f9fc;
            border-top: 1px solid #e0e0e0;
            justify-content: space-between;
        }
        .input-section textarea {
            flex: 1;
            max-width: calc(100% - 95px);
            padding: 10px;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            margin-right: 10px;
            font-size: 16px;
            color: #333;
            background-color: #fff;
            box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
            resize: none;
            overflow-y: auto;
            min-height: 40px;
            max-height: 200px;
        }
        .input-section button {
            padding: 10px 15px;
            border: none;
            border-radius: 8px;
            background-color: #007bff;
            color: #fff;
            cursor: pointer;
            font-size: 16px;
            transition: background-color 0.3s ease;
            min-width: 40px;
            margin-left: 5px;
        }
        .input-section button:hover {
            background-color: #0056b3;
        }
        .input-section button:last-child {
            margin-right: 0;
        }
        .message {
            display: flex;
            align-items: flex-start;
            margin-bottom: 10px;
        }
        .message.USER {
            justify-content: flex-start;
        }
        .message.ASSISTANT {
            justify-content: flex-end;
        }
        .message .avatar {
            width: 40px;
            height: 40px;
            border-radius: 50%;
            margin-right: 10px;
        }
        .message.ASSISTANT .avatar {
            margin-left: 10px;
            margin-right: 0;
        }
        .message .bubble {
            max-width: 70%;
            padding: 10px;
            border-radius: 12px;
            background-color: #f1f1f1;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
            overflow-wrap: break-word;
            word-wrap: break-word;
            word-break: break-all;
            font-size: 18px;
        }
        .message.USER .bubble {
            background-color: #007bff;
            color: #fff;
        }
    </style>

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css" integrity="sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww" crossorigin="anonymous">
    <!-- The loading of KaTeX is deferred to speed up page rendering -->
    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js" integrity="sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd" crossorigin="anonymous"></script>
    <!-- To automatically render math in text elements, include the auto-render extension: -->
    <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/contrib/auto-render.min.js" integrity="sha384-43gviWU0YVjaDtb/GhzOouOXtZMP/7XUzwPTstBeZFe/+rCMvRwr4yROQP43s0Xk" crossorigin="anonymous"
        onload="renderMathInElement(document.body);"></script>
    <script src="https://cdn.jsdelivr.net/npm/marked@13.0.3/lib/marked.umd.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/marked-highlight@2.1.3/lib/index.umd.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/default.min.css">
    <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="sidebar" id="history-bar">
            <button id="new-chat-button">➕</button>
            <ul id="history-list">
                <!-- History records will be listed here -->
            </ul>
        </div>
        <div class="main-content">
            <div class="chat-window" id="chat-window">
                <!-- Chat messages will be displayed here -->
            </div>
            <div class="input-section">
                <textarea id="text-input" placeholder="Type a message... (Shift+Enter for new line)"></textarea>
                <button id="interrupt-button" style="display: none;">⏹️</button>
                <button id="audio-button">🎤</button>
                <button id="image-button">📷</button>
                <input type="file" id="image-input" style="display: none;">
            </div>
        </div>
    </div>

    <script>
        let isRecording = false;
        let mediaRecorder;
        let audioChunks = [];
        let isInterrupted = false;
        let isUpdating = false;  // State variable indicating whether readResponse is in a data update state.
        
        const { Marked } = window.marked;
        const { markedHighlight } = window.markedHighlight;
        const marked = new Marked(
            markedHighlight({
                langPrefix: 'hljs language-',
                highlight(code, lang, info) {
                    const language = hljs.getLanguage(lang) ? lang : 'plaintext';
                    return hljs.highlight(code, { language }).value;
                }
            })
        );
        
        function convertToProxyURL(href) {
            if (href.startsWith("/proxy?href=")) {
                return href
            }
            else if (href.startsWith("blob:")) {
                return href
            }
            else {
                const encodedURL = encodeURIComponent(href);
                return `/proxy?href=${encodedURL}`;
            }
        }

        function getContentType(url) {
            try {
                const extension = url.split('.').pop().toLowerCase();
                if (['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac', 'aiff', 'wma', 'opus', 'ra', 'mid'].includes(extension)) {
                    return `audio/${extension}`;
                } else if (['mp4', 'webm', 'ogg', 'wmv', 'mov', 'avi', 'flv', 'mkv', 'mpeg', 'vob', 'rm', '3gp', 'ogv', 'm4v', 'h264', 'ts', 'm2ts', 'divx'].includes(extension)) {
                    return `video/${extension}`;
                } else if (['jpg', 'jpeg', 'bmp', 'png', 'gif', 'tiff', 'tif', 'webp', 'svg', 'heif', 'heic', 'raw'].includes(extension)) {
                    return 'image/jpeg';
                }
                
                const xhr = new XMLHttpRequest();
                xhr.open("HEAD", url, false);
                xhr.send();
                if (xhr.status === 200) {
                    return xhr.getResponseHeader("Content-Type");
                } else {
                    throw new Error("Failed to fetch Content-Type.");
                }
            } catch (error) {
                console.error(error);
                return "";
            }
        }

        function mathsExpression(expr, force) {
            if (expr.match(/^\$\$[\s\S]*\$\$$/)) {
                expr = expr.substr(2, expr.length - 4);
                return [katex.renderToString(expr, { displayMode: true }), true];
            } else if (expr.match(/^\$[\s\S]*\$$/)) {
                expr = expr.substr(1, expr.length - 2);
                return [katex.renderToString(expr, { displayMode: false }), true];
            } else if (force) {
                return [katex.renderToString(expr, { displayMode: true }), true];
            }
            else {
                return [expr, false]
            }
        }
        
        const renderer = new marked.Renderer();
        const rendererCode = renderer.code;
        const rendererCodespan = renderer.codespan;

        renderer.image = function(href, title, text) {
                let mediaType = "";
                if (href.startsWith("blob:")) {
                    mediaType = text;
                }
                else {
                    mediaType = getContentType(convertToProxyURL(href));
                }
                
                if (mediaType.startsWith('image')) {
                    return `<img src="${convertToProxyURL(href)}" alt="${text}" title="${title}" style="max-width: 100%; height: auto; display: block;">`;
                }
                else if (mediaType.startsWith('audio')) {
                    return `<audio controls><source src="${convertToProxyURL(href)}" type="${mediaType}">Your browser does not support the audio element.</audio>`;
                }
                else if (mediaType.startsWith('video')) {
                    return `<video controls><source src="${convertToProxyURL(href)}" type="${mediaType}">Your browser does not support the video element.</video>`;
                }
                else
                {
                    return `![${text}](${href}) ${title}`;
                }
            };

        renderer.code = function(code, lang, escaped) {
                if (!lang) {
                    const r = mathsExpression(code, false);
                    if (r[1] === true) {
                        return r[0];
                    }
                }
                else if ("latex" === lang) {
                    return mathsExpression(code, true)[0];
                }
                return rendererCode({text: code, lang: lang, escaped: escaped});
            };

        renderer.codespan = function(text) {
                const math = mathsExpression(text, false);
                if (math[1]) {
                    return math[0];
                }
                return rendererCodespan({text: text});
            }

        marked.use({ renderer });

        function renderMarkdown(message) {
            return marked.parse(message);
        }

        function disableInput() {
            document.getElementById('text-input').disabled = true;
            document.getElementById('audio-button').disabled = true;
            document.getElementById('image-button').disabled = true;
            document.getElementById('new-chat-button').disabled = true;
            var menubtns = document.getElementsByClassName('menu-button');
            for (var i=0; i<menubtns.length; i++) {
                menubtns[i].disabled = true;
            }
        }
        
        function enableInput() {
            document.getElementById('text-input').disabled = false;
            document.getElementById('audio-button').disabled = false;
            document.getElementById('image-button').disabled = false;
            document.getElementById('new-chat-button').disabled = false;
            var menubtns = document.getElementsByClassName('menu-button');
            for (var i=0; i<menubtns.length; i++) {
                menubtns[i].disabled = false;
            }
        }

        function readResponse(reader) {
            const decoder = new TextDecoder();
            let inputDisabled = false;  // Flag variable used to track whether the input field has been disabled.
            let content = '';
            let lastBotMessageElement = null;
            
            function read() {
                reader.read().then(({ done, value }) => {
                    if (done) {
                        enableInput();
                        document.getElementById('interrupt-button').style.display = 'none';
                        isUpdating = false;  // End of data updating.
                        return;
                    }
                    if (!inputDisabled) {
                        disableInput();
                        document.getElementById('interrupt-button').style.display = 'inline-block';
                        inputDisabled = true;  // Setting the flag variable to true indicates that the input field has been disabled.
                        isUpdating = true;  // Start data updating.
                    }
                    const text = decoder.decode(value, { stream: true });
                    const lines = text.split('\n');
                    
                    lines.forEach(line => {
                        if (line.startsWith('data: ')) {
                            const jsonString = line.replace('data: ', '');
                            try {
                                const responseObject = JSON.parse(jsonString);
                                const responseMessage = responseObject.message;
                                setTimeout(() => {
                                        lastBotMessageElement = chatWindow.querySelector('.message.ASSISTANT:last-child .bubble');
                                    }, 0);
                                if (lastBotMessageElement) {
                                    content += responseMessage;
                                    lastBotMessageElement.innerHTML = renderMarkdown(content);
                                } else {
                                    content = responseMessage;
                                    displayMsg("ASSISTANT", content);
                                }
                                const chatWindow = document.getElementById('chat-window');
                                chatWindow.scrollTop = chatWindow.scrollHeight;
                            } catch (e) {
                                console.error("Error parsing JSON:", e);
                            }
                        }
                    });
                    read();
                });
            }
            read();
        }

        function interrupt() {
            isInterrupted = true;
            document.getElementById('text-input').disabled = false;
            document.getElementById('interrupt-button').textContent = 'Send';
            fetch('/interrupt', {
                method: 'POST'
            }).catch(error => {
                console.error('Error:', error);
            });
        }

        function sendmsg(msg) {
            fetch('/sendmsg', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ message: msg })
            }).then(response => {
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                return response.json();
            }).then(data => {
                displayMsg("USER", msg);
                isInterrupted = false;
                if (isUpdating) {
                    document.getElementById('text-input').disabled = true;
                }
                document.getElementById('interrupt-button').textContent = '⏹️';
            }).catch(error => {
                console.error('Error:', error);
            });
        }

        async function newchat() {
            return fetch('/new_chat')
                    .then(response => response.json())
                    .then(data => {
                        const historyList = document.getElementById('history-list');
                        const historyItem = createHistoryItem('New Chat', data.sessionName);
                        historyList.insertBefore(historyItem, historyList.firstChild);
                        
                        const allItems = document.querySelectorAll('#history-list li');
                        allItems.forEach(item => item.classList.remove('selected'));
                        historyItem.classList.add('selected');
                        
                        const chatWindow = document.getElementById('chat-window');
                        chatWindow.innerHTML = '';
                    }).catch(error => {
                        console.error('Error:', error);
                    });
        }

        async function ensureChat() {
            const selectedItem = document.querySelector('#history-list .selected');
            let newChatPromise = Promise.resolve();

            if (selectedItem == null) {
                newChatPromise = newchat();
            }
            return newChatPromise;
        }
        
        function displayMsg(role, message) {
            const roleMap = {
                "USER": "/static/User.JPG",
                "ASSISTANT": "/static/AIlice.png",
            };
            const chatWindow = document.getElementById('chat-window');
            chatWindow.innerHTML += `
                <div class="message ${role}">
                    <img src=${roleMap[role] || "/static/AIlice.png"} class="avatar">
                    <div class="bubble">${renderMarkdown(message)}</div>
                </div>`;
            chatWindow.scrollTop = chatWindow.scrollHeight;
        }

        document.getElementById('interrupt-button').addEventListener('click', function() {
            if (isInterrupted) {
                const message = document.getElementById('text-input').value;
                document.getElementById('text-input').value = '';
                sendmsg(message);
            } else {
                interrupt();
            }
        });

        document.getElementById('text-input').addEventListener('keydown', function(event) {
            if (event.key === 'Enter' && !event.shiftKey) {
                event.preventDefault();
                const message = this.value.trim();
                this.value = '';
                this.style.height = 'auto';
                
                if (message) {
                    if (isInterrupted) {
                        sendmsg(message);
                    } else {
                        let newChatPromise = ensureChat();
                        
                        newChatPromise.then(() => {
                            displayMsg("USER", message);
                            
                            const currentHistory = document.querySelector('#history-list .selected');
                            if (currentHistory.querySelector('span').textContent === "New Chat") {
                                currentHistory.querySelector('span').textContent = message;
                            }
                            
                            return fetch('/chat', {
                                method: 'POST',
                                headers: {
                                    'Content-Type': 'application/json'
                                },
                                body: JSON.stringify({ message: message })
                            })
                        })
                        .then(response => {
                            if (!response.ok) {
                                throw new Error('Network response was not ok');
                            }
                            return response.body.getReader();
                        })
                        .then(reader => {
                            readResponse(reader);
                        })
                        .catch(error => {
                            console.error('Error:', error);
                        });
                    }
                }
            }
        });

        // Add this function to automatically resize the textarea
        document.getElementById('text-input').addEventListener('input', function() {
            this.style.height = 'auto';
            this.style.height = (this.scrollHeight) + 'px';
        });

        document.getElementById('audio-button').addEventListener('click', function() {
            console.log('Audio button clicked');
            if (isRecording) {
                mediaRecorder.stop();
                isRecording = false;
                this.textContent = '🎤';
            } else {
                navigator.mediaDevices.getUserMedia({ audio: true })
                    .then(stream => {
                        mediaRecorder = new MediaRecorder(stream);
                        mediaRecorder.start();
                        isRecording = true;
                        this.textContent = '⏹️';

                        mediaRecorder.addEventListener('dataavailable', event => {
                            audioChunks.push(event.data);
                        });

                        mediaRecorder.addEventListener('stop', () => {
                            const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
                            const formData = new FormData();
                            formData.append('audio', audioBlob, 'recording.wav');

                            let newChatPromise = ensureChat();
                            newChatPromise.then(() => {
                                // Update the chat window to display the audio uploaded by the USER.
                                const audioUrl = URL.createObjectURL(audioBlob);
                                displayMsg("USER", `![audio/wav](${audioUrl})`);

                                fetch('/upload_audio', {
                                    method: 'POST',
                                    body: formData
                                })
                                .then(response => {
                                    if (!response.ok) {
                                        throw new Error('Network response was not ok');
                                    }
                                    return response.body.getReader();
                                })
                                .then(reader => {
                                    readResponse(reader);
                                })
                                .catch(error => {
                                    console.error('Error:', error);
                                });  
                            })

                            audioChunks = [];
                        });
                    })
                    .catch(error => {
                        console.error('Error accessing microphone:', error);
                    });
            }
        });

        document.getElementById('image-button').addEventListener('click', function() {
            console.log('Image button clicked');
            document.getElementById('image-input').click();
        });

        document.getElementById('image-input').addEventListener('change', function() {
            console.log('Image input changed');
            const file = this.files[0];
            const formData = new FormData();
            formData.append('image', file);

            let newChatPromise = ensureChat();
            newChatPromise.then(() => {
                // Update the chat window to display the image uploaded by the USER.
                const imageUrl = URL.createObjectURL(file);
                displayMsg("USER", `![image](${imageUrl})`);

                fetch('/upload_image', {
                    method: 'POST',
                    body: formData
                })
                .then(response => {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.body.getReader();
                })
                .then(reader => {
                    readResponse(reader);
                })
                .catch(error => {
                    console.error('Error:', error);
                });
            })
        });
        
        document.getElementById('new-chat-button').addEventListener('click', function() {
            console.log('New chat button clicked');
            newchat()
        });
        
        // Load history list on page load
        window.onload = function() {
            fetch('/list_histories')
                .then(response => response.json())
                .then(data => {
                    const historyList = document.getElementById('history-list');
                    data.forEach(history => {
                        const historyItem = createHistoryItem(history[1], history[0]);
                        historyList.appendChild(historyItem);
                    });
                }).catch(error => {
                        console.error('Error:', error);
                    });
        };
        
        // Load selected history
        function loadHistory(sessionName, historyItem) {
            if (isUpdating) {
                return;
            }
            const allItems = document.querySelectorAll('#history-list li');
            allItems.forEach(item => item.classList.remove('selected'));
            historyItem.classList.add('selected');
            
            fetch(`/load_history?name=${sessionName}`)
                .then(response => response.json())
                .then(data => {
                    const chatWindow = document.getElementById('chat-window');
                    chatWindow.innerHTML = '';
                    data.forEach(([sender, message]) => {
                        displayMsg(sender, message);
                    });
                })
                .catch(error => {
                    console.error(error);
                });
        }

        function createHistoryItem(txt, sessionName) {
            const historyItem = document.createElement('li');

            const textSpan = document.createElement('span');
            textSpan.textContent = txt;
            textSpan.classList.add('history-span');
            
            const menuButton = document.createElement('button');
            menuButton.textContent = '⋮';
            menuButton.classList.add('menu-button');
            
            const dropdownMenu = document.createElement('div');
            dropdownMenu.classList.add('dropdown-menu');
            const deleteOption = document.createElement('a');
            deleteOption.textContent = 'Delete';
            deleteOption.href = '#';
            deleteOption.addEventListener('click', (event) => {
                event.stopPropagation();
                event.preventDefault();
                deleteHistory(sessionName, historyItem);
            });
            dropdownMenu.appendChild(deleteOption);

            historyItem.appendChild(textSpan);
            historyItem.appendChild(menuButton);
            historyItem.appendChild(dropdownMenu);
            historyItem.classList.add('history-item');
            historyItem.addEventListener('click', () => {loadHistory(sessionName, historyItem)});
            
            menuButton.addEventListener('click', (event) => {
                event.stopPropagation();
                dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
            });

            document.addEventListener('click', (event) => {
                if (!historyItem.contains(event.target)) {
                    dropdownMenu.style.display = 'none';
                }
            });
            
            return historyItem;
        }

        function deleteHistory(sessionName, historyItem) {
            fetch(`/delete_history/${sessionName}`, {
                method: 'DELETE'
            }).then(response => {
                if (response.ok) {
                    disableInput();
                    if (historyItem.classList.contains('selected')) {
                        const chatWindow = document.getElementById('chat-window');
                        chatWindow.innerHTML = '';
                    }
                    historyItem.remove();
                    enableInput();
                } else {
                    console.error('Failed to delete history item');
                }
            }).catch(error => {
                        console.error('Error:', error);
                    });
        }
    </script>
</body>
</html>
