- JavaScript 46.4%
- Python 29.3%
- CSS 12.2%
- PHP 5.9%
- HTML 5.8%
- Other 0.4%
| app | ||
| frontend | ||
| nginx | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| default.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
| requirements.txt | ||
RAG-MD
Локальный поиск по нормативной и технической документации с генерацией ответов и ссылками на найденные фрагменты.
Приложение загружает PDF, DjVu, TXT и Markdown, извлекает текст, при необходимости использует OCR, сохраняет эмбеддинги в Qdrant и формирует ответы через Ollama.
Состав проекта
app/— FastAPI, обработка документов, OCR, индексация и генерация ответов.frontend/— интерфейс на PHP, jQuery и CSS.nginx/— маршрутизация запросов и проверка авторизации через Tinyauth.docker-compose.yml— Nginx, PHP-FPM, Tinyauth, RAG, Qdrant и Ollama.Dockerfile,requirements.txt— окружение Python для сервиса RAG..env.example— шаблон настроек без паролей и реальных учётных данных.
Подготовка и запуск
Нужны Docker с поддержкой Compose и Linux-контейнеров, свободное место для моделей и доступ к сети для первой загрузки образов, зависимостей и моделей.
-
Скопируйте
.env.exampleв.env. В PowerShell:Copy-Item .env.example .envВ Linux:
cp .env.example .env -
Заполните
TINYAUTH_USERSпользователями и bcrypt-хешами в формате Tinyauth v5. Оставляйте значение в одинарных кавычках, чтобы символы$в хешах не интерпретировались как подстановка переменных. Пустое значение не позволит Compose запустить сервис авторизации. -
В
nginx/default.confнастроены именаrag.example.testиauth.example.test. Для локального запуска добавьте их в файлhostsна компьютере, где открыт браузер:127.0.0.1 rag.example.test auth.example.testЕсли контейнеры работают на другом компьютере, используйте его IP. Для других доменов измените оба
server_nameв Nginx иTINYAUTH_APP_URLв.env. В текущем Compose внешний HTTP-порт —8000, поэтому локальный адрес авторизации —http://auth.example.test:8000. Запросы к неизвестному имени хоста Nginx отклоняет. -
Создайте каталоги данных, если они ещё не существуют:
New-Item -ItemType Directory -Force -Path docs, processed, qdrant_storageВ Linux:
mkdir -p docs processed qdrant_storage -
Проверьте конфигурацию и запустите контейнеры:
docker compose config --quiet docker compose up -d --build -
Загрузите модель Ollama. Команда ниже соответствует
LLM_MODELиз шаблона; при смене модели укажите её имя:docker compose exec ollama ollama pull qwen3.5:9b-q4_K_M -
Откройте
http://rag.example.test:8000, выполните вход, загрузите документы через интерфейс. Документы, скопированные вручную вdocs/, нужно проиндексировать через интерфейс. Первый запуск RAG включает загрузку модели эмбеддингов и может занять время.
Для просмотра журналов:
docker compose logs --tail=100 rag nginx tinyauth
Текущая конфигурация обслуживает HTTP. Для внешнего развёртывания настройте HTTPS и согласуйте адрес авторизации и TINYAUTH_SECURE_COOKIE с используемой схемой доступа.
Фронтэнд
Интерфейс использует локальную полную сборку jQuery 4.0.0, включая Ajax. Библиотека хранится в frontend/assets/js/jquery-4.0.0.min.js и загружается перед app.js; CDN, Node.js и сборка для запуска не нужны. DOM, события и запросы к API обрабатываются через jQuery.
Основная точка входа — frontend/index.php через Nginx и PHP-FPM. Страница app/static/index.html, которую отдаёт FastAPI при прямом обращении, использует те же CSS и JS из frontend/assets/; при изменении разметки обновляйте обе страницы. При работе через Compose изменения в frontend/ видны через подключённый каталог. Для обновления ресурсов внутри образа FastAPI пересоберите сервис: docker compose up -d --build rag.
Для браузерной проверки нужны Node.js и Chromium, Chrome или Edge. Запустите node tests/frontend-smoke.cjs; при необходимости задайте путь к браузеру через CHROME_PATH. Проверка поднимает временный локальный сервер с тестовым API и проверяет поиск, Markdown, сноски, загрузку файлов и обработку ошибок. В PowerShell команда $env:FRONTEND_ENTRY='php'; node tests/frontend-smoke.cjs проверяет разметку PHP-страницы без выполнения PHP. Эти проверки не запускают OCR, Qdrant или Ollama.
Данные и настройки
Диалоги и контекст
Интерфейс использует компоновку с историей слева, перепиской в центре и панелью источников и файлов справа. Каждый ответ сохраняет собственные сноски и таблицу источников. Панель релевантности переключается кнопкой «Проверить источники»; таблица и диаграмма сортируются по убыванию исходного score поиска. Значение сходства не является вероятностью правильного ответа.
«Релевантность источников» свёрнута в пустом диалоге и раскрывается при отправке запроса или выборе готового ответа. Её можно свернуть и раскрыть вручную. «Загрузка файлов» свёрнута по умолчанию и открывается нажатием на заголовок; база файлов остаётся раскрытой.
Полные диалоги с ответами и метаданными источников хранятся в IndexedDB этого браузера: до 50 диалогов, срок хранения — 30 дней с последнего сообщения. Есть удаление одного диалога и очистка истории. localStorage хранит только идентификатор открытого диалога. Тексты извлечённых фрагментов документов в историю не записываются. При недоступном хранилище переписка работает в памяти с видимым уведомлением. История привязана к адресу приложения и профилю браузера; синхронизации устройств и серверной истории по учётным записям в этой версии нет.
POST /ask сохраняет совместимость с одиночным вопросом и принимает дополнительный массив history из завершённых пар {role: "user", content: "…"}, {role: "assistant", content: "…"}. Системную роль, незавершённые пары и выход за ограничения API отклоняет. Неуспешные ответы не передаются как контекст следующего вопроса.
Сервер восстанавливает самостоятельный поисковый запрос из уточнения и истории, заново ищет документы и отправляет историю вместе с новыми фрагментами в Ollama /api/chat. На уточнение требуется два обращения к модели: восстановление темы и ответ. Старые номера сносок из контекста удаляются, чтобы не смешивать источники разных ответов.
Весь диалог остаётся доступен в интерфейсе, но память модели ограничена: браузер передаёт до 50 последних завершённых пар, сервер выбирает до 12 последних сообщений в пределах 5000 байт UTF-8 и сокращает каждое до 1800 байт. Для большого текущего вопроса бюджет истории уменьшается. Если часть истории не поместилась, интерфейс показывает уведомление. Это окно последних сообщений, без автоматического резюмирования старой переписки.
LLM_CONTEXT_SIZE задаёт окно Ollama, по умолчанию 16384 токена, минимум 8192. Увеличение окна требует больше памяти. Ответ ограничен 1536 токенами; бюджет входа консервативно рассчитывается по UTF-8, место для текущего вопроса, источников и системных правил резервируется отдельно.
Модель по умолчанию — qwen3.5:9b-q4_K_M (около 6,6 ГБ для загрузки; для работы нужна дополнительная память). Генерация ответа и восстановление поискового запроса используют think: false, чтобы рассуждения не расходовали бюджет ответа. Для Qwen3.5 используются параметры режима без рассуждений: temperature 0.7, top_p 0.8, top_k 20, min_p 0, presence_penalty 1.5 и repeat_penalty 1. Требуется версия Ollama с поддержкой Qwen3.5. При смене LLM_MODEL загрузите модель и пересоздайте RAG: docker compose up -d --build rag. Смена генеративной модели не требует переиндексации документов.
Индексация работает как фоновая задача и отображается в правой панели. «Обновить индекс базы» запускает /api/indexing/start, загрузка — /api/indexing/upload. /api/indexing/status возвращает состояние, обработанные файлы, текущую страницу OCR или пакет эмбеддингов, активное время и примерное время до окончания. Индикатор показывает долю завершённых файлов. Уже обработанные неизменённые документы пропускаются.
«Пауза» вызывает /api/indexing/{id}/pause, «Продолжить» — /api/indexing/{id}/resume. Пауза кооперативная: текущий вызов OCR, вычисление пакета или запись в Qdrant завершается, после чего поток ждёт продолжения. До подтверждения интерфейс показывает «Приостанавливаем…». Контрольные точки есть между страницами PDF/OCR, чтениями при хешировании, пакетами эмбеддингов и перед записью. Уже вычисленные пакеты не вычисляются заново после продолжения. Пауза останавливает учёт активного времени.
Оценка оставшегося времени приблизительная: учитывает активное время, размеры файлов и пройденные этапы. До появления первого измерения OCR, эмбеддингов или завершённого файла показано «Оцениваем время…». При разной сложности документов оценка пересчитывается; это не обещанное время завершения. На паузе она сохраняется и относится ко времени после продолжения.
Задача продолжает работать при закрытии или перезагрузке страницы, после открытия интерфейс восстанавливает её состояние. Одновременно разрешена одна задача индексации общей базы; конфликт возвращает HTTP 409, без перезаписи обрабатываемого файла. Совместимые /upload, /reindex и /reindex-file/… используют ту же очередь и ждут итогового результата. Контроллер рассчитан на один процесс Uvicorn, как в текущем Dockerfile. Состояние задач хранится в памяти сервера: после перезапуска контейнера незавершённую индексацию нужно запустить снова, ранее записанные документы будут пропущены.
Иконки интерфейса — общий набор контурных SVG в frontend/assets/icons.svg, соответствующий выбранному макету №2; отдельные цвета документов и папок заданы в CSS.
В блоке индексации постоянно показывается «проиндексировано / всего документов». /api/indexing/summary считает уникальные пути документов, представленные в текущей коллекции Qdrant, и сопоставляет их с существующими исходными файлами. Несколько фрагментов одного документа считаются одним документом; удалённые файлы и временные результаты OCR не учитываются. Сводка обновляется раз в 10 секунд и после завершения задачи. При недоступном Qdrant показывается неизвестное значение, а не ноль.
Список файлов, общая индексация и сводка используют один обход исходных документов: каталоги _ocr_*, временные загрузки и TXT, созданные DjVu OCR рядом с PPM, исключены. Кэш OCR сохраняется и продолжает использоваться при индексации исходного документа.
Высота списка файлов меняется нижней ручкой: перетаскиванием мышью/касанием либо стрелками вверх и вниз при фокусе на ручке. Размер сохраняется в localStorage и восстанавливается после перезагрузки; поиск, сворачивание и автоматическое обновление списка его не сбрасывают. Home возвращает стандартную высоту.
После обновления исходников пересоберите RAG, чтобы сервер принял новый формат контекста:
docker compose up -d --build rag
Проверки диалогов
node tests/frontend-smoke.cjs проверяет интерфейс в Chromium: форматирование, безопасность вывода, загрузку, обновление индекса, продолжение диалога, восстановление после перезагрузки, изоляцию разных диалогов и сноски старых ответов. FRONTEND_ENTRY=php выбирает разметку PHP без исполнения PHP. UI_SCREENSHOTS=1 дополнительно сохраняет настольный и мобильный скриншоты в output/interface-concepts/implemented/ и проверяет отсутствие горизонтального переполнения страницы.
Для тестов API установите небольшое отдельное окружение: python -m pip install -r tests/requirements.txt, затем python tests/test_dialogue.py и python tests/test_indexing.py. Можно установить зависимости через --target .test-deps; этот каталог исключён из Git. Проверяются реальные обработчики FastAPI, контракт сообщений Ollama, остановка и продолжение фонового потока, исключение времени паузы из ETA, защита от параллельной записи и контрольные точки настоящего конвейера индексации. Тяжёлые эмбеддинги, Qdrant, OCR и транспорт модели подменяются; эти проверки не заменяют запуск с вашей моделью и документами. Браузерные проверки дополнительно проверяют SVG, паузу, продолжение и отображение ETA; при UI_SCREENSHOTS=1 сохраняются состояния indexing-running.png и indexing-paused.png.
Эти файлы и каталоги остаются локально и исключены из Git:
| Путь | Содержимое |
|---|---|
.env |
Настройки конкретного сервера и учётные данные |
docs/ |
Загруженные документы и промежуточные файлы OCR |
processed/ |
Кэш извлечённого текста |
qdrant_storage/ |
Векторная база |
backup/ |
Локальные резервные копии |
dist/ |
Архивы исходников для передачи |
Модели Ollama, кэш Hugging Face и база Tinyauth хранятся в именованных Docker-томах. Они также не входят в репозиторий. На новом сервере модели нужно загрузить заново, документы перенести отдельно и проиндексировать. GitHub хранит исходный код; запуск контейнеров требует отдельного сервера.
Загрузка на GitHub
Папка подготовлена для ветки main. Перед первым коммитом укажите свои имя и email в Git, если они ещё не настроены:
git config user.name "YOUR_NAME"
git config user.email "YOUR_GITHUB_EMAIL"
git add .
git diff --cached --stat
git commit -m "Initial import of RAG-MD"
Создайте пустой репозиторий на GitHub, скопируйте его HTTPS-адрес и подставьте вместо YOUR_REPOSITORY_URL:
git remote add origin YOUR_REPOSITORY_URL
git push -u origin main
Для загрузки через веб-интерфейс можно распаковать dist/rag-md-github.zip и загрузить содержимое архива в репозиторий. Сам ZIP не заменяет размещение исходных файлов. Архив не содержит .git, локальных документов, базы и .env.