Compare commits

..

7 Commits

Author SHA1 Message Date
393e45ccb0 Edit api.php via Editor 2025-09-28 00:31:42 +00:00
3e0da2dc32 Edit api.php via Editor 2025-09-28 00:27:43 +00:00
Flatlogic Bot
fb3f1d33ed Revert to version c584dca 2025-09-28 00:21:32 +00:00
Flatlogic Bot
935f41f960 Revert to version 15280ba 2025-09-28 00:01:19 +00:00
Flatlogic Bot
96847809f7 3 2025-09-27 23:45:23 +00:00
Flatlogic Bot
fff16d2578 1 2025-09-27 23:37:23 +00:00
Flatlogic Bot
c584dcac4e 2 2025-09-27 23:20:14 +00:00
6 changed files with 424 additions and 126 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules/ node_modules/
*/node_modules/ */node_modules/
*/build/ */build/
.env

121
api.php Normal file
View File

@ -0,0 +1,121 @@
<?php
// api.php
header('Content-Type: application/json');
// --- Helper function to get environment variables ---
function get_env($key, $default = null) {
$path = __DIR__ . '/.env';
if (!file_exists($path)) {
return $default; // Если .env не найден, возвращаем дефолтное значение
}
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) {
continue; // Пропускаем строки с комментариями
}
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value);
if ($name === $key) {
return $value; // Нашли ключ в .env
}
}
return $default; // Ключ не найден в .env, возвращаем дефолт
}
// --- Main Logic ---
// 1. Получаем API-ключ (с дефолтным значением, если .env отсутствует или ключ не указан)
$apiKey = get_env('GEMINI_API_KEY', 'sk-Hml0aR9tSiqYqQFtjDaqX6RsUm2Vz8');
if (empty($apiKey)) { // Проверяем, что ключ не пустой
echo json_encode(['reply' => 'Ошибка: API-ключ не найден. Проверьте дефолтное значение в коде или добавьте ключ в .env.']);
exit;
}
// 2. Получаем и валидируем JSON-запрос пользователя
$input = json_decode(file_get_contents('php://input'), true);
if (json_last_error() !== JSON_ERROR_NONE) {
echo json_encode(['reply' => 'Ошибка: Некорректный формат JSON в запросе.']);
exit;
}
$userMessage = trim($input['message'] ?? '');
if (empty($userMessage)) {
echo json_encode(['reply' => 'Пожалуйста, введите сообщение.']);
exit;
}
// 3. Загружаем базу знаний
$knowledgeBasePath = __DIR__ . '/db/knowledge_base.txt';
if (!file_exists($knowledgeBasePath)) {
echo json_encode(['reply' => 'Ошибка: База знаний не найдена. Создайте файл db/knowledge_base.txt.']);
exit;
}
$knowledgeBase = file_get_contents($knowledgeBasePath);
// 4. Подготавливаем подсказку (prompt) для AI
$prompt = "Ты — ИИ-ассистент, специалист по внутренней системе управления складом под названием HUB. Твоя задача — отвечать на вопросы пользователя, основываясь ИСКЛЮЧИТЕЛЬНО на предоставленной базе знаний. Не придумывай ничего от себя. Если ответа в базе знаний нет, вежливо сообщи, что ты можешь отвечать только на вопросы, связанные с системой HUB.\n\nВот база знаний:\n---\n" . $knowledgeBase . "\n---\n\nВопрос пользователя: \"" . $userMessage . "\"";
// 5. Вызываем Gemini API с корректной структурой запроса
$url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=' . $apiKey;
$data = [
'model' => 'gemini-pro', // Указываем модель
'prompt' => ['text' => $prompt], // Ключевой часть prompt
'temperature' => 0.7 // Регулирует случайность ответа (0.0 — детерминированный, 1.0 — случайный)
];
$options = [
'http' => [
'header' => "Content-Type: application/json\r\n",
'method' => 'POST',
'content' => json_encode($data),
'ignore_errors' => true // Для извлечения ошибок API
]
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
$http_response_header = $http_response_header ?? [];
// Извлекаем HTTP-статус из заголовков ответа
$status_line = $http_response_header[0] ?? 'HTTP/1.1 500 Internal Server Error';
preg_match('/HTTP\/\S+\s(\d+)/', $status_line, $match);
$status = $match[1] ?? 500;
// Обрабатываем ошибки подключения (например, отсутствие интернета)
if ($response === FALSE) {
$error_msg = 'Не удалось связаться с API. ';
$last_error = error_get_last();
if ($last_error) {
$error_msg .= $last_error['message'];
}
error_log("Gemini API Error: Status $status, Error: $error_msg");
echo json_encode(['reply' => "Ошибка подключения к ИИ-сервису. Статус: $status. Подробности: $error_msg. Проверьте настройки сервера."]);
exit;
}
// Распарсываем ответ API и проверяем на JSON-ошибки
$result = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
error_log("Gemini API Error: Неверный JSON в ответе. Ответ: $response");
echo json_encode(['reply' => 'Ошибка: Не удалось разобрать ответ ИИ-сервиса как JSON.']);
exit;
}
// Обрабатываем ошибки API (например, некорректный ключ)
if (isset($result['error'])) {
$error_code = $result['error']['code'] ?? 'Неопределен';
$error_message = $result['error']['message'] ?? 'Неизвестная ошибка';
error_log("Gemini API Error: Код $error_code, Сообщение: $error_message");
echo json_encode(['reply' => "Ошибка ИИ-сервиса. Код: $error_code. Сообщение: $error_message."]);
exit;
}
// Извлекаем и выводим ответ AI
if (isset($result['candidates'][0]['content']['text'])) {
$reply = trim($result['candidates'][0]['content']['text']);
echo json_encode(['reply' => $reply]);
} else {
error_log("Gemini API: Непредвиденная структура ответа. Ответ: " . print_r($result, true));
echo json_encode(['reply' => 'ИИ-сервис вернул неожиданный ответ. Проверьте API-ключ и базу знаний.']);
}
?>

163
assets/css/custom.css Normal file
View File

@ -0,0 +1,163 @@
:root {
--bg-color: #121212;
--surface-color: #1E1E1E;
--primary-color: #00FF9B;
--secondary-color: #00C2FF;
--text-color: #E0E0E0;
--text-muted-color: #888;
--border-radius-md: 12px;
--border-radius-sm: 8px;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Roboto Mono', 'Consolas', 'Monaco', monospace;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
overflow: hidden;
}
.chat-container {
width: 100%;
max-width: 800px;
height: 100vh;
max-height: 100vh; /* For mobile browsers */
display: flex;
flex-direction: column;
background-color: var(--bg-color);
border-radius: 0;
box-shadow: 0 0 40px rgba(0, 255, 155, 0.1);
overflow: hidden;
}
.chat-header {
padding: 16px 24px;
background-color: var(--surface-color);
border-bottom: 1px solid #333;
text-align: center;
}
.chat-header h1 {
font-size: 1.5rem;
font-weight: 500;
margin: 0;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
}
.chat-header .text-muted {
font-size: 0.85rem;
color: var(--text-muted-color);
margin: 0;
}
.chat-messages {
flex-grow: 1;
padding: 24px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Custom scrollbar for webkit browsers */
.chat-messages::-webkit-scrollbar {
width: 6px;
}
.chat-messages::-webkit-scrollbar-track {
background: var(--bg-color);
}
.chat-messages::-webkit-scrollbar-thumb {
background-color: var(--surface-color);
border-radius: 6px;
}
.message {
display: flex;
max-width: 75%;
}
.message-content {
padding: 12px 16px;
border-radius: var(--border-radius-md);
line-height: 1.5;
}
.message-content p {
margin: 0;
}
.bot-message {
align-self: flex-start;
}
.bot-message .message-content {
background-color: var(--surface-color);
border-top-left-radius: 0;
}
.user-message {
align-self: flex-end;
}
.user-message .message-content {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
color: #000;
border-top-right-radius: 0;
}
.chat-input-area {
padding: 16px 24px;
background-color: var(--surface-color);
border-top: 1px solid #333;
}
#chat-form .form-control {
background-color: #333;
border: 1px solid #444;
color: var(--text-color);
border-radius: var(--border-radius-sm);
padding: 12px 16px;
height: 48px;
}
#chat-form .form-control:focus {
background-color: #333;
color: var(--text-color);
box-shadow: 0 0 0 2px var(--primary-color);
border-color: var(--primary-color);
}
#chat-form .form-control::placeholder {
color: var(--text-muted-color);
}
#chat-form .btn {
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border: none;
border-radius: var(--border-radius-sm);
margin-left: 12px;
width: 48px;
height: 48px;
display: flex;
justify-content: center;
align-items: center;
transition: transform 0.2s ease;
}
#chat-form .btn:hover {
transform: scale(1.05);
}
#chat-form .btn svg {
color: #000;
}

84
assets/js/main.js Normal file
View File

@ -0,0 +1,84 @@
document.addEventListener('DOMContentLoaded', () => {
const chatForm = document.getElementById('chat-form');
const messageInput = document.getElementById('message-input');
const chatMessages = document.getElementById('chat-messages');
chatForm.addEventListener('submit', (e) => {
e.preventDefault();
const messageText = messageInput.value.trim();
if (messageText) {
appendMessage(messageText, 'user');
messageInput.value = '';
showBotTyping();
// Send message to backend
fetch('api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ message: messageText })
})
.then(response => response.json())
.then(data => {
removeBotTyping();
appendMessage(data.reply, 'bot');
})
.catch(error => {
removeBotTyping();
console.error('Error:', error);
appendMessage('Извините, произошла ошибка. Попробуйте еще раз.', 'bot');
});
}
});
function appendMessage(text, sender) {
const messageWrapper = document.createElement('div');
messageWrapper.classList.add('message', `${sender}-message`);
const messageContent = document.createElement('div');
messageContent.classList.add('message-content');
const paragraph = document.createElement('p');
paragraph.innerHTML = text; // Use innerHTML to render potential HTML tags from response
messageContent.appendChild(paragraph);
messageWrapper.appendChild(messageContent);
chatMessages.appendChild(messageWrapper);
scrollToBottom();
}
function showBotTyping() {
const typingIndicator = document.createElement('div');
typingIndicator.id = 'typing-indicator';
typingIndicator.classList.add('message', 'bot-message');
const content = document.createElement('div');
content.classList.add('message-content');
const p = document.createElement('p');
p.textContent = '...';
content.appendChild(p);
typingIndicator.appendChild(content);
chatMessages.appendChild(typingIndicator);
scrollToBottom();
}
function removeBotTyping() {
const typingIndicator = document.getElementById('typing-indicator');
if (typingIndicator) {
typingIndicator.remove();
}
}
function scrollToBottom() {
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// Initial scroll to bottom if content is already there
scrollToBottom();
});

1
db/knowledge_base.txt Normal file
View File

@ -0,0 +1 @@
Это БАЗА знаний 1. Введение в систему HUB Цель: Понять назначение HUB и его роль в управлении складскими данными. HUB — это система для управления складскими предметами (грузами, контейнерами, коробками и т.д.) и процессами их перемещения, хранения и инвентаризации. Она позволяет отслеживать состояние предметов, их историю, взаимодействие с контейнерами и логистическими точками, а также проводить пересчеты и настраивать складскую инфраструктуру. Ключевые понятия: Предмет: Объект хранения (например, ТЯ, коробка, отправление). Логистический контейнер (ЛК): Емкость для хранения/перемещения предметов (статусы: "Формируется", "Сформирован" и др.). Инвентаризация/пересчет: Проверка фактического наличия предметов на складе против учетных данных. КМО: Внутренний термин, предположительно связанный с документацией о материальных ошибках/недостачах (например, "Книга Материальных Остатков"). ЦМН: Цель доставки (место назначения предмета). Тайм-слот: Временной интервал для доставки. 2. Основы интерфейса HUB и карточка предмета Цель: Знакомство с интерфейсом HUB и структурой карточки предмета. HUB содержит главное меню, схему склада и таблицу со списком предметов. Основная работа ведется через карточку предмета — детализированное окно с информацией о конкретном объекте. 2.1 Структура карточки предмета Карточка доступна при клике на ID предмета в таблице "Предметы". Она включает следующие вкладки: Вкладка Описание Основные данные «О предмете» Основная информация о предмете. Тип, статус, ID, номенклатура, принадлежность, стоимость, адрес хранения. «Состав» Текущий и исторический состав контейнера (для предметов, являющихся контейнерами). Список предметов внутри, даты добавления/удаления, статусы элементов состава. «История (перемещения)» История перемещений предмета между ячейками, контейнерами и логистическими точками. Тип события (принято, перемещено, вошло/вышло из контейнера), дата, источник/цель. «Отгрузки» Запись о выдаче предмета (отгрузке) со склада. Дата отгрузки, получате

180
index.php
View File

@ -1,131 +1,59 @@
<?php <!DOCTYPE html>
declare(strict_types=1); <html lang="ru">
@ini_set('display_errors', '1');
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
?>
<!doctype html>
<html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>New Style</title> <title>Your Logistic Koresh</title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <meta name="description" content="AI Assistant for HUB System">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <meta name="robots" content="noindex, nofollow">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style> <!-- Open Graph -->
:root { <meta property="og:title" content="Your Logistic Koresh">
--bg-color-start: #6a11cb; <meta property="og:description" content="AI Assistant for HUB System">
--bg-color-end: #2575fc; <meta property="og:type" content="website">
--text-color: #ffffff; <meta property="og:url" content=""> <!-- Will be the current URL -->
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1); <!-- Fonts -->
} <link rel="preconnect" href="https://fonts.googleapis.com">
body { <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
margin: 0; <link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet">
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); <!-- Bootstrap CSS -->
color: var(--text-color); <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
display: flex;
justify-content: center; <!-- Custom CSS -->
align-items: center; <link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% { background-position: 0% 0%; }
100% { background-position: 100% 100%; }
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
}
.loader {
margin: 1.25rem auto 1.25rem;
width: 48px;
height: 48px;
border: 3px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.hint {
opacity: 0.9;
}
.sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; border: 0;
}
h1 {
font-size: 3rem;
font-weight: 700;
margin: 0 0 1rem;
letter-spacing: -1px;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
code {
background: rgba(0,0,0,0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
footer {
position: absolute;
bottom: 1rem;
font-size: 0.8rem;
opacity: 0.7;
}
</style>
</head> </head>
<body> <body>
<main>
<div class="card"> <div class="chat-container">
<h1>Analyzing your requirements and generating your website…</h1> <header class="chat-header">
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes"> <h1>Your Logistic Koresh</h1>
<span class="sr-only">Loading…</span> <p class="text-muted">AI Assistant for HUB System</p>
</div> </header>
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWiZZy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will update automatically as the plan is implemented.</p> <main class="chat-messages" id="chat-messages">
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p> <!-- Messages will be appended here by JS -->
<div class="message bot-message">
<div class="message-content">
<p>Привет. Я твой логистический кореш. Я здесь, чтобы помочь тебе с вопросами по системе HUB. Спрашивай, не стесняйся.</p>
</div>
</div>
</main>
<footer class="chat-input-area">
<form id="chat-form" class="d-flex">
<input type="text" id="message-input" class="form-control" placeholder="Введите ваш вопрос..." autocomplete="off" required>
<button type="submit" class="btn btn-primary" id="send-button">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-send"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
</button>
</form>
</footer>
</div> </div>
</main>
<footer> <!-- Bootstrap JS -->
Page updated: <?= htmlspecialchars($now) ?> (UTC) <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</footer> <!-- Custom JS -->
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
</body> </body>
</html> </html>