<?php
// ---------------------------------------------------------
// Просмотр исходного кода (параметр source в URL)
// ---------------------------------------------------------
if ($_GET["source"]=="view") {
    
show_source($_SERVER['SCRIPT_FILENAME']);
    exit;
}

session_start();

// ---------------------------------------------------------
// Сброс истории сообщений (параметр reset в URL)
// ---------------------------------------------------------
if (isset($_GET['reset'])) {
    
// Очищаем историю сообщений
    
$_SESSION['messages'] = [];
    
// Перенаправляем пользователя на эту же страницу, чтобы очистка произошла без параметров в URL
    
header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

// ---------------------------------------------------------
// Инициализируем историю сообщений, если ранее не была определена
// ---------------------------------------------------------
if (!isset($_SESSION['messages'])) {
    
$_SESSION['messages'] = [];
}

// ---------------------------------------------------------
// Настройки API (URL-адреса запросов)
// ---------------------------------------------------------
$IMAGE_API = 'https://image.pollinations.ai/prompt/'; // API генерации изображений (GET-запрос)
$TEXT_API  = 'https://text.pollinations.ai/';         // API генерации текста/распознавания изображений (POST-запрос)

// ---------------------------------------------------------
// ФУНКЦИИ
// ---------------------------------------------------------

/**
 * Отправка POST-запроса к текстовому API с заданным payload.
 * Возвращает ответ API или сообщение об ошибке.
 *
 * @param array $payload Данные запроса
 * @return string Ответ API или описание ошибки
 */
function postTextAPI($payload) {
    global 
$TEXT_API;

    
// Инициализируем cURL-сессию
    
$ch = curl_init($TEXT_API);
    
curl_setopt_array($ch, [
        
CURLOPT_RETURNTRANSFER => true,                           // Возвращать ответ вместо прямого вывода
        
CURLOPT_POST           => true,                           // Используем метод POST
        
CURLOPT_POSTFIELDS     => json_encode($payload),          // Кодируем данные в JSON
        
CURLOPT_HTTPHEADER     => ['Content-Type: application/json'], // Устанавливаем Content-Type: application/json
        
CURLOPT_TIMEOUT        => 60,                             // Таймаут запроса 60 секунд
        
CURLOPT_SSL_VERIFYPEER => false                           // Не проверять SSL-сертификат (можно изменить в продакшене)
    
]);

    
// Получаем ответ, ошибки и HTTP-код
    
$response = curl_exec($ch);
    
$error    = curl_error($ch);
    
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
curl_close($ch);

    
// Если HTTP-код не 200 или ответа нет – формируем сообщение об ошибке
    
if ($httpCode !== 200 || !$response) {
        return 
"Ошибка запроса: $error (HTTP $httpCode)";
    }
    return 
$response;
}

/**
 * Функция для анализа изображения по URL через текстовый API.
 *
 * @param string $url Ссылка на изображение
 * @param string $instruction Текст запроса для анализа изображения; по умолчанию "What's in this image?"
 * @return string Ответ API
 */
function analyzeImageByUrl($url, $instruction = "Что изображено на картинке?") {
    
$payload = [
        
"messages" => [
            [
                
"role"    => "user",
                
"content" => [
                    [
"type" => "text", "text" => $instruction],
                    [
"type" => "image_url", "image_url" => ["url" => $url]]
                ]
            ]
        ],
        
"model" => "openai"
    
];
    return 
postTextAPI($payload);
}

/**
 * Сохранение загруженного изображения в папку "upload" и получение URL сохранённого файла.
 *
 * @param string $tmpName Временное имя файла
 * @param string $originalName Оригинальное имя файла
 * @return mixed URL сохранённого файла или false при ошибке
 */
function saveImageFile($tmpName, $originalName) {
    
$uploadDir = __DIR__ . '/upload/';
    
// Если папка не существует, создаём её
    
if (!is_dir($uploadDir)) {
        
mkdir($uploadDir, 0777, true);
    }
    
// Генерируем уникальное имя файла
    
$newFilename = uniqid() . '_' . basename($originalName);
    
$destination = $uploadDir . $newFilename;
    if (
move_uploaded_file($tmpName, $destination)) {
        
// Формируем URL сохранённого файла
        
$protocol  = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
        
$host      = $_SERVER['HTTP_HOST'];
        
$scriptDir = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
        return 
$protocol . "://" . $host . $scriptDir . "/upload/" . $newFilename;
    }
    return 
false;
}

/**
 * Проверка файла на текстовый формат по первым 1024 байтам.
 *
 * @param string $filePath Путь к файлу
 * @return bool true, если файл похож на текстовый, и false если встречаются бинарные данные.
 */
function isTextFile($filePath) {
    
$content = file_get_contents($filePath, false, null, 0, 1024);
    
// Если в первых 1024 байтах найдены бинарные символы – возвращаем false
    
return !preg_match('/[\x00-\x08\x0B-\x0C\x0E-\x1F]/', $content);
}

/**
 * Определение наличия ключевых слов для генерации изображения в тексте.
 *
 * @param string $text Входной текст
 * @return bool true, если встречены ключевые слова, иначе false
 */
function containsImageKeywords($text) {
    return 
preg_match('/нарисуй|покажи|изображение|фото/i', $text);
}

/**
 * Функция для подсветки кода с использованием регулярных выражений.
 *
 * @param string $code Исходный код
 * @return string HTML-разметка с подсветкой синтаксиса
 */
function highlightCode($code) {
    
// Экранируем HTML-сущности, чтобы код не интерпретировался браузером
    
$code = htmlspecialchars($code);

    
// Подсветка комментариев (/* ... */, // и #) – делаем их серыми
    
$code = preg_replace(
        [
            
'/(\/\*[\s\S]*?\*\/)/',   // многострочные комментарии
            
'/(\/\/.*)/',             // однострочные комментарии, начинающиеся с //
            
'/(#.*)/'                 // однострочные комментарии, начинающиеся с #
        
],
        [
            
'<span style="color: grey;">$1</span>',
            
'<span style="color: grey;">$1</span>',
            
'<span style="color: grey;">$1</span>'
        
],
        
$code
    
);

    
// Подсветка текстовых литералов (одинарные и двойные кавычки) – зелёным цветом
    
$code = preg_replace(
        
'/((&quot;).*?(&quot;)|(&#039;).*?(&#039;))/',
        
'<span style="color: green;">$0</span>',
        
$code
    
);

    
// Подсветка чисел – красным цветом
    
$code = preg_replace('/\b\d+\b/', '<span style="color: red;">$0</span>', $code);

    
// Подсветка ключевых слов (например, function, echo, return и т.д.) – синим цветом и жирным шрифтом
    
$keywords = [
        
'function', 'echo', 'return', 'if', 'else', 'for', 'foreach',
        
'while', 'do', 'switch', 'case', 'break', 'continue', 'class',
        
'public', 'private', 'protected', 'static', 'var', 'new',
        
'try', 'catch', 'throw'
    
];
    
$pattern = '/\b(' . implode('|', array_map('preg_quote', $keywords)) . ')\b/';
    
$code = preg_replace($pattern, '<span style="color: blue; font-weight: bold;">$1</span>', $code);

    
// Подсветка HTML-тегов, если они присутствуют – фиолетовым цветом
    
$code = preg_replace('/(&lt;\/?[\w\s="\'-]+&gt;)/', '<span style="color: purple;">$1</span>', $code);

    
// Оборачиваем код в тег pre для сохранения форматирования
    
return "<pre style='white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word;'>$code</pre>";
}


// ---------------------------------------------------------
// ОБРАБОТКА POST-зАПРОСОВ (отправка сообщений и загрузка файлов)
// ---------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    
// Получаем введённый пользователем текст, удаляя лишние пробелы
    
$userText = trim($_POST['message'] ?? "");

    
// Если текст не пустой, сохраняем как сообщение пользователя
    
if (!empty($userText)) {
        
$_SESSION['messages'][] = ['role' => 'user', 'content' => $userText];
    }

    
// Если прикреплён файл, проверяем его наличие через временное имя
    
if (!empty($_FILES['file']['tmp_name'])) {
        
$fileName   = $_FILES['file']['name'];
        
$fileSize   = $_FILES['file']['size'];
        
$tmpName    = $_FILES['file']['tmp_name'];
        
$extension  = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));

        
// Список расширений для текстовых файлов
        
$textFileExt = ['txt', 'md', 'html', 'csv', 'log', 'json', 'xml'];

        
// Если файл слишком большой (более 5 МБ) – добавляем сообщение об ошибке
        
if ($fileSize > 5 * 1024 * 1024) {
            
$_SESSION['messages'][] = [
                
'role'    => 'assistant',
                
'content' => 'Файл слишком большой (максимум 5 МБ)'
            
];
        }
        
// Обработка изображений
        
elseif (@getimagesize($tmpName) !== false || in_array($extension, ['jpg','jpeg','png','gif','webp'])) {
            
$url = saveImageFile($tmpName, $fileName);
            if (
$url) {
                
// Сохраняем сообщение с прикреплённым изображением пользователя
                
$_SESSION['messages'][] = [
                    
'role'    => 'user',
                    
'content' => $url,
                    
'type'    => 'attached_image'
                
];
                
// Если присутствует текст в поле – используем его как инструкцию, иначе стандартное значение
                
$instructionImage = !empty($userText) ? $userText : "Что изображено на картинке?";
                
// Отправляем запрос к API для анализа изображения
                
$resp = analyzeImageByUrl($url, $instructionImage);
                
$_SESSION['messages'][] = [
                    
'role'    => 'assistant',
                    
'content' => $resp
                
];
            } else {
                
$_SESSION['messages'][] = [
                    
'role'    => 'assistant',
                    
'content' => "Ошибка сохранения изображения"
                
];
            }
        }
        
// Обработка текстовых файлов
        
elseif (in_array($extension, $textFileExt) || isTextFile($tmpName)) {
            
$content = file_get_contents($tmpName);
            
// Ограничиваем размер содержимого файла (максимум 10 000 символов)
            
$content = mb_substr($content, 0, 10000);
            
// Сохраняем содержимое файла как сообщение пользователя с типом "attached_textfile"
            
$_SESSION['messages'][] = [
                
'role'    => 'user',
                
'content' => $content,
                
'type'    => 'attached_textfile'
            
];
            
// Формируем объединённый запрос: текст пользователя + содержимое файла
            
$mergedQuery = ($userText ? $userText . "\n" : "") . "Содержимое файла $fileName:\n" . $content;
            
            
// Формирование запроса с передачей истории диалога
            
$history = [];
            
// Добавляем системное сообщение в начале диалога
            
$history[] = [ "role" => "system", "content" => "You are a helpful assistant." ];
            
// Проходим по всей истории сообщений и добавляем те, которые можно отправить API.
            // Например, пропускаем сообщения с прикреплёнными изображениями, поскольку API ожидает текст.
            
foreach ($_SESSION['messages'] as $msg) {
                
// Если задан тип и он не "attached_textfile", пропускаем его
                
if (isset($msg['type']) && $msg['type'] !== 'attached_textfile') {
                    continue;
                }
                
$history[] = [ "role" => $msg['role'], "content" => $msg['content'] ];
            }
            
// Для запроса можно дополнительно указать объединённый текст запроса (если нужно)
            
$history[] = [ "role" => "user", "content" => $mergedQuery ];

            
$payload = [
                
"messages" => $history,
                
"model"   => "openai",
                
"seed"    => rand(1, 100000),
                
"private" => true
            
];

            
$resp = postTextAPI($payload);
            
$_SESSION['messages'][] = [
                
'role'    => 'assistant',
                
'content' => $resp
            
];
        }
        
// Если тип файла не поддерживается, выводим сообщение об ошибке
        
else {
            
$_SESSION['messages'][] = [
                
'role'    => 'assistant',
                
'content' => 'Неподдерживаемый тип файла'
            
];
        }
    }
    
// Если нет прикреплённого файла – обрабатываем только текстовое сообщение
    
else {
        if (!empty(
$userText)) {
            
// Разбиваем введённый текст на строки для проверки, является ли первая строка URL изображения
            
$lines     = preg_split("/\r\n|\n|\r/", $userText);
            
$firstLine = trim($lines[0]);

            
// Если первая строка является корректным URL и имеет расширение изображения
            
if (filter_var($firstLine, FILTER_VALIDATE_URL) && preg_match('/\.(jpg|jpeg|png|gif|webp)$/i', $firstLine)) {
                
// Остальные строки используем как инструкцию для анализа изображения
                
$instructionImage = count($lines) > 1 ? trim(implode("\n", array_slice($lines, 1))) : "";
                if (empty(
$instructionImage)) {
                    
$instructionImage = "What's in this image?";
                }
                
// Сохраняем сообщение с прикреплённым изображением (URL)
                
$_SESSION['messages'][] = [
                    
'role'    => 'user',
                    
'content' => $firstLine,
                    
'type'    => 'attached_image'
                
];
                
// Отправка запроса на анализ изображения
                
$resp = analyzeImageByUrl($firstLine, $instructionImage);
                
$_SESSION['messages'][] = [
                    
'role'    => 'assistant',
                    
'content' => $resp
                
];
            }
            
// Если текст содержит ключевые слова для генерации изображения
            
elseif (containsImageKeywords($userText)) {
                
// Генерируем случайное число для параметра seed
                
$seed = rand(1, 100000);
                
// Формируем URL для генерации изображения
                
$generatedUrl = $IMAGE_API . urlencode($userText) . "?width=1280&height=720&seed=$seed&nologo=true&model=flux";
                
// Пытаемся скачать изображение
                
$imageData = @file_get_contents($generatedUrl);
                if (
$imageData === false) {
                    
$_SESSION['messages'][] = [
                        
'role'    => 'assistant',
                        
'content' => 'Ошибка загрузки сгенерированного изображения.'
                    
];
                } else {
                    
// Сохраняем сгенерированное изображение в папку upload
                    
$uploadDir = __DIR__ . '/upload/';
                    if (!
is_dir($uploadDir)) {
                        
mkdir($uploadDir, 0777, true);
                    }
                    
$localFilename = uniqid() . '.jpg';
                    
$localFilePath = $uploadDir . $localFilename;
                    if (
file_put_contents($localFilePath, $imageData)) {
                        
$protocol  = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https" : "http";
                        
$host      = $_SERVER['HTTP_HOST'];
                        
$scriptDir = rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
                        
$localUrl  = $protocol . "://" . $host . $scriptDir . "/upload/" . $localFilename;
                        
// Добавляем сообщение с ссылкой на сгенерированное изображение
                        
$_SESSION['messages'][] = [
                            
'role'    => 'assistant',
                            
'content' => $localUrl,
                            
'type'    => 'image'
                        
];
                    } else {
                        
$_SESSION['messages'][] = [
                            
'role'    => 'assistant',
                            
'content' => 'Ошибка сохранения сгенерированного изображения.'
                        
];
                    }
                }
            }
            
// Если текстовое сообщение не соответствует предыдущим условиям – отправляем запрос на генерацию текста,
            // при этом передаём всю историю диалога.
            
else {
                
// Собираем историю диалога для передачи в API
                
$history = [];
                
// Добавляем системное сообщение (основной контекст)
                
$history[] = [ "role" => "system", "content" => "You are a helpful assistant." ];
                
// Перебираем сохранённые сообщения из сессии.
                // Если сообщение содержит поле 'type', то включаем в историю только те, которые предназначены для текстового API.
                
foreach ($_SESSION['messages'] as $msg) {
                    
// Если сообщение имеет тип и это не текстовый файл, пропускаем его
                    
if (isset($msg['type']) && $msg['type'] !== 'attached_textfile') {
                        continue;
                    }
                    
$history[] = [ "role" => $msg['role'], "content" => $msg['content'] ];
                }
                
// Добавляем текущее сообщение пользователя, если нужно (хотя оно уже сохранено выше)
                // $history[] = [ "role" => "user", "content" => $userText ];

                
$payload = [
                    
"messages" => $history,
                    
"model"   => "openai",
                    
"seed"    => rand(1, 100000),
                    
"private" => true
                
];
                
$resp = postTextAPI($payload);
                
$_SESSION['messages'][] = [
                    
'role'    => 'assistant',
                    
'content' => $resp
                
];
            }
        }
    }
}
?>

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>NoJSчат</title>
    <style>
        /* Основной стиль контейнера чата */
        .chat-container { max-width: 800px; margin: 0 auto; padding: 20px; }
        /* Стили сообщений: от пользователя зеленый фон, от ассистента – голубой */
        .message { margin: 10px 0; padding: 10px; border-radius: 5px; }
        .user { background: #90EE90; }
        .assistant { background: #BFEDF8; }
        /* Стили для изображений внутри чата */
        img { max-width: 90%; height: auto; }
    </style>
</head>
<body>
    <div class="chat-container">
        <!-- Ссылка для сброса / начала заново -->
        <a href="?reset=1" style="float:right; color:red; text-decoration:none;">[Начать заново]</a>
        <h1>NoJSчат</h1>
        
        <?php 
        
// Определяем индекс последнего сообщения для якоря прокрутки
        
$lastIndex = count($_SESSION['messages']) - 1;    
        foreach (
$_SESSION['messages'] as $index => $msg): 
            
// Если это последнее сообщение, добавляем якорь "last"
            
echo ($index == $lastIndex) ? '<a name="last"></a>' : '';
            
// Определяем стиль блока в зависимости от отправителя (user или assistant)
            
$class = ($msg['role'] == "user") ? "user" : "assistant";
        
?>
            <div class="message <?= $class ?>">
                <b><?= ($msg['role'] == "user") ? "--- user:" : "--- AI:" ?></b>
                
                <?php if (isset($msg['type'])): ?>
                    <?php if ($msg['type'] === 'image'): ?>
                        <!-- Отображение сгенерированного изображения -->
                        <img src="<?= htmlspecialchars($msg['content']) ?>" alt="Сгенерированное изображение">
                    <?php elseif ($msg['type'] === 'attached_image'): ?>
                        <!-- Отображение прикреплённого изображения -->
                        <div>Прикреплённое изображение:<br>
                            <img src="<?= htmlspecialchars($msg['content']) ?>" alt="Прикреплённое изображение">
                        </div>
                    <?php elseif ($msg['type'] === 'attached_textfile'): ?>
                        <!-- Отображение содержимого текстового файла -->
                        <div>Содержимое файла:<br>
                            <pre><?= highlightCode($msg['content']) ?></pre>
                        </div>
                    <?php else: ?>
                        <!-- Отображение сообщения с подсветкой кода -->
                        <?= highlightCode($msg['content']) ?>
                    <?php endif; ?>
                <?php else: ?>
                    <!-- Если тип не задан, выводим сообщение с подсветкой -->
                    <?= highlightCode($msg['content']) ?>
                <?php endif; ?>
            </div>
        <?php endforeach; ?>

        <!-- Форма ввода сообщения и загрузки файла -->
        <form method="post" enctype="multipart/form-data" action="#last">
            <textarea name="message" rows="3" placeholder="Введите сообщение..." style="width:100%;"></textarea>
            <input type="file" name="file">
            <button type="submit">Отправить</button>
        </form>
    </div>
</body>
</html>