User: Напиши простой сервер на express

Assistant: <p>Конечно! Ниже приведен простой пример сервера на Express. Для начала работы убедитесь, что у вас установлен Node.js и пакетный менеджер npm.</p>
<ol>
<li><p>Создайте новый каталог для вашего проекта и перейдите в него:</p>
<pre><code class="language-bash">mkdir my-express-server
cd my-express-server
</code></pre>
</li>
<li><p>Инициализируйте новый проект Node.js:</p>
<pre><code class="language-bash">npm init -y
</code></pre>
</li>
<li><p>Установите Express:</p>
<pre><code class="language-bash">npm install express
</code></pre>
</li>
<li><p>Создайте файл <code>server.js</code> и добавьте в него следующий код:</p>
<pre><code class="language-javascript">const express = require(&#39;express&#39;);
const app = express();
const port = 3000;

// Маршрут для главной страницы
app.get(&#39;/&#39;, (req, res) =&gt; {
  res.send(&#39;Hello, World!&#39;);
});

// Запуск сервера
app.listen(port, () =&gt; {
  console.log(`Сервер запущен по адресу http://localhost:${port}`);
});
</code></pre>
</li>
<li><p>Запустите сервер:</p>
<pre><code class="language-bash">node server.js
</code></pre>
</li>
</ol>
<p>Теперь ваш сервер работает и доступен по адресу <code>http://localhost:3000</code>. При переходе на этот адрес в браузере вы увидите сообщение &quot;Hello, World!&quot;. Вы можете добавить больше маршрутов и логики по необходимости.</p>

User: <!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GPT SSE Client</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
            max-width: 1400px;
            margin: auto;
            background-color: #212121;
            color: #ececec;
            display: flex;
            flex-direction: column;
            height: 100vh;
            box-sizing: border-box;
        }

        #messages {
            display: flex;
            flex-direction: column;
            border: 2px solid #2f2f2f;
            padding: 10px;
            flex-grow: 1;
            overflow-y: auto;
            margin-bottom: 20px;
            border-radius: 5px;
        }

        .message-container {
            width: 100%;
            display: flex;
            margin-bottom: 10px;
        }

        .user-container {
            justify-content: flex-end;
        }

        .user-message {
            background-color: #2f2f2f;
            padding: 10px;
            border-radius: 5px;
            max-width: 80%;
            width: fit-content;
        }

        .assistant-message {
            background-color: #3f3f3f;
            padding: 10px;
            border-radius: 5px;
            max-width: 80%;
            width: fit-content;
        }

        .input-container {
            width: 100%;
            max-width: 800px;
            margin: 0 auto;
        }

        .input-wrapper {
            position: relative;
            margin-bottom: 10px;
        }

        textarea {
            width: 100%;
            background-color: #2f2f2f;
            color: #ececec;
            border: none;
            padding: 15px 60px 15px 15px;
            font-size: 16px;
            border-radius: 5px;
            resize: none;
            height: 50px;
            box-sizing: border-box;
        }

        .send-button {
            position: absolute;
            right: 10px;
            top: 47%;
            transform: translateY(-50%);
            background-color: #3f3f3f;
            color: white;
            border: none;
            padding: 8px;
            font-size: 16px;
            border-radius: 50%;
            cursor: pointer;
            transition: background-color 0.3s;
        }

        .send-button:hover {
            background-color: black;
        }

        .model-select-container {
            margin-top: 10px;
            display: flex;
            align-items: center;
        }

        .model-select-label {
            margin-right: 10px;
            font-size: 14px;
            color: #a0a0a0;
        }

        select {
            appearance: none;
            color: #ffffff;
            background-color: #3a3a3a;
            border: none;
            padding: 5px 30px 5px 10px;
            border-radius: 5px;
            font-size: 14px;
            cursor: pointer;
        }

        .model-select-wrapper {
            position: relative;
        }

        .model-select-wrapper::after {
            content: '▼';
            font-size: 12px;
            color: #ffffff;
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
            pointer-events: none;
        }

        textarea:focus {
            outline: none;
        }

        textarea::placeholder {
            color: #808080;
        }

        pre {
            background-color: black;
            padding: 10px;
            border-radius: 4px;
            overflow-x: auto;
        }

        code {
            font-family: 'Courier New', Courier, monospace;
        }

        #modeToggle {
            margin-bottom: 10px;
        }

        .generated-image {
            max-width: 600px;
            height: auto;
            margin-top: 10px;
        }
    </style>
</head>

<body>
    <h1>🧠 NeuroSpanAi</h1>
    <div id="modeToggle">
        <input type="radio" id="chatMode" name="mode" value="chat" checked>
        <label for="chatMode">💭Chat Mode</label>
        <input type="radio" id="imageMode" name="mode" value="image">
        <label for="imageMode">🖼️Image Generation Mode</label>
    </div>
    <div id="messages"></div>
    <form id="messageForm" class="input-container">
        <div class="input-wrapper">
            <textarea class="textarea" id="userMessage" rows="1" placeholder="Reply to AI..."></textarea>
            <button class="send-button" type="submit">✉️</button>
        </div>
        <div id="chatModelSelect" class="model-select-container">
            <label for="modelSelect" class="model-select-label">Select AI model:</label>
            <div class="model-select-wrapper">
                <select id="modelSelect">
                    <option value="gpt-4o">GPT-4O</option>
                    <option value="gemini-pro">Gemini Pro</option>
                    <option value="llama-3.1">Llama 3.1</option>
                    <option value="qwen">Qwen</option>
                </select>
            </div>
        </div>
        <div id="imageModelSelectContainer" class="model-select-container" style="display: none;">
            <label for="imageModelSelect" class="model-select-label">Select Image model:</label>
            <div class="model-select-wrapper">
                <select id="imageModelSelect">
                    <option value="dalle2">DALL-E 2</option>
                    <option value="stablediffusion-xl">Stable Diffusion XL</option>
                    <option value="midjourney">Midjourney</option>
                </select>
            </div>
        </div>
    </form>
    <script>
        const messagesDiv = document.getElementById('messages');
        const chatModelSelect = document.getElementById('modelSelect');
        const imageModelSelect = document.getElementById('imageModelSelect');
        const chatModelSelectDiv = document.getElementById('chatModelSelect');
        const imageModelSelectDiv = document.getElementById('imageModelSelectContainer');
        let currentMessageElement = null;
        let messages = JSON.parse(localStorage.getItem('chatHistory') || '[]');
        let isImageMode = false;

        // Display existing messages on load
        messages.forEach(msg => displayMessage(msg.content, msg.role));

        function updateUIForMode() {
            console.log('Updating UI for mode:', isImageMode ? 'Image' : 'Chat');
            if (isImageMode) {
                messages = [];
                messagesDiv.innerHTML = '';
                chatModelSelectDiv.style.display = 'none';
                imageModelSelectDiv.style.display = 'flex';
            } else {
                messages = JSON.parse(localStorage.getItem('chatHistory') || '[]');
                messagesDiv.innerHTML = '';
                messages.forEach(msg => displayMessage(msg.content, msg.role));
                chatModelSelectDiv.style.display = 'flex';
                imageModelSelectDiv.style.display = 'none';
            }
            console.log('Chat model select visibility:', chatModelSelectDiv.style.display);
            console.log('Image model select visibility:', imageModelSelectDiv.style.display);
        }

        document.getElementById('modeToggle').addEventListener('change', (event) => {
            isImageMode = event.target.value === 'image';
            console.log('Mode changed to:', isImageMode ? 'Image' : 'Chat');
            updateUIForMode();
        });

        document.getElementById('messageForm').addEventListener('submit', async (event) => {
            event.preventDefault();
            const userMessage = document.getElementById('userMessage').value;
            if (!userMessage.trim()) return;

            if (!isImageMode) {
                messages.push({ role: "user", content: userMessage });
            }
            displayMessage(userMessage, 'user');
            document.getElementById('userMessage').value = '';

            currentMessageElement = document.createElement('div');
            currentMessageElement.classList.add('message', 'assistant-message');
            messagesDiv.appendChild(currentMessageElement);

            try {
                let selectedModel;
                if (isImageMode && imageModelSelect) {
                    selectedModel = imageModelSelect.value;
                    console.log('Image model select element:', imageModelSelect);
                    console.log('Image model options:', Array.from(imageModelSelect.options).map(o => o.value));
                } else if (!isImageMode && chatModelSelect) {
                    selectedModel = chatModelSelect.value;
                    console.log('Chat model select element:', chatModelSelect);
                    console.log('Chat model options:', Array.from(chatModelSelect.options).map(o => o.value));
                } else {
                    throw new Error('Model select element not found');
                }
                console.log('Selected model:', selectedModel);

                const response = await fetch('/api/gpt', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        messages: isImageMode ? [{ role: "user", content: userMessage }] : messages,
                        stream: !isImageMode,
                        markdown: true,
                        model: selectedModel
                    })
                });

                if (isImageMode) {
                    try {
                        const data = await response.json();

                        let jsonData;
                        if (typeof data === 'string') {
                            // Удаляем все начальные подчеркивания
                            const trimmedData = data.replace(/^_+/, '');
                            jsonData = JSON.parse(trimmedData);
                        } else {
                            // Если данные уже являются объектом JSON
                            jsonData = data;
                        }

                        if (jsonData.images && jsonData.images.length > 0) {
                            displayImage(jsonData.images[0], jsonData.prompt);
                        } else {
                            updateMessage('Error: No image generated.');
                        }
                    } catch (error) {
                        console.error('Error processing response:', error);
                        updateMessage('Error: Failed to process the response.');
                    }
                } else {
                    const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
                    let buffer = '';
                    let assistantMessage = '';

                    while (true) {
                        const { value, done } = await reader.read();
                        if (done) break;

                        buffer += value;
                        const lines = buffer.split('\n');

                        for (let i = 0; i < lines.length - 1; i++) {
                            const line = lines[i].trim();
                            if (line.startsWith('data: ')) {
                                const jsonStr = line.slice(6);
                                if (jsonStr === '[DONE]') {
                                    break;
                                }
                                try {
                                    const data = JSON.parse(jsonStr);
                                    if (data.message !== null) {
                                        assistantMessage = data.message;
                                        updateMessage(assistantMessage);
                                    }
                                } catch (e) {
                                    console.error('Error parsing JSON:', e);
                                }
                            }
                        }

                        buffer = lines[lines.length - 1];
                    }

                    // Add assistant's message to history if not in image mode
                    messages.push({ role: "assistant", content: assistantMessage });
                    // Save updated history to localStorage
                    localStorage.setItem('chatHistory', JSON.stringify(messages));
                }

            } catch (error) {
                console.error('Error:', error);
                updateMessage('Error occurred while fetching the response.');
            }

            currentMessageElement = null;
        });

        function displayMessage(content, role) {
            const messageContainer = document.createElement('div');
            messageContainer.classList.add('message-container', `${role}-container`);

            const messageElement = document.createElement('div');
            messageElement.classList.add('message', `${role}-message`);
            messageElement.innerHTML = role === 'assistant' ? marked.parse(content) : content;

            messageContainer.appendChild(messageElement);
            messagesDiv.appendChild(messageContainer);
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }

        function updateMessage(message) {
            if (currentMessageElement) {
                currentMessageElement.innerHTML = marked.parse(message);
                messagesDiv.scrollTop = messagesDiv.scrollHeight;
            }
        }

        function displayImage(imageUrl, prompt) {
            if (currentMessageElement) {
                const imageElement = document.createElement('img');
                imageElement.src = imageUrl;
                imageElement.alt = prompt;
                imageElement.classList.add('generated-image');

                currentMessageElement.innerHTML = `<p>Generated image for prompt: "${prompt}"</p>`;
                currentMessageElement.appendChild(imageElement);
                messagesDiv.scrollTop = messagesDiv.scrollHeight;
            }
        }

        // Initialize UI based on current mode
        updateUIForMode();
    </script>
</body>

</html>

можешь тут добавить чтобы внизу input можно было очищать историю чата

Assistant: