Files
Helpomatica/README.md
T
2026-08-14 11:06:58 +07:00

366 lines
17 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Helpomatica
Локальная веб-панель в духе Claude Projects: проекты, инструкции, файлы,
общие чаты, пользователи с ролями и ответы **Cursor Agent**. Интерфейс
на русском. Это не SPA-фреймворк и не облачный продукт — один процесс
FastAPI раздаёт статику и JSON API, данные лежат на диске сервера.
Ответы агента **не полностью офлайн**: нужен `CURSOR_API_KEY` с
[Cursor Dashboard → Integrations](https://cursor.com/dashboard/integrations).
Веб-панель, логин, файлы и предразбор логов работают и без ключа.
---
## Как запустить локально
Скопируйте `.env.example` в `.env` и вставьте ключ:
```env
CURSOR_API_KEY=crsr_your_key_here
CURSOR_MODEL=auto
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
```
Затем:
```powershell
.\run.ps1
```
Скрипт ставит зависимости в `.deps` (`pip install --target .deps`),
выставляет `PYTHONPATH` и поднимает uvicorn с `--reload` на
**`0.0.0.0:8010`**.
- На этой машине: http://127.0.0.1:8010
- С других ПК в LAN: `http://<IP-этой-машины>:8010`
- Вход по умолчанию: **`admin` / `admin`**
Если PowerShell блокирует локальные сценарии:
```powershell
powershell -ExecutionPolicy Bypass -File .\run.ps1
```
Если страница не открывается с другого ПК, разрешите входящий TCP 8010
в брандмауэре Windows:
```powershell
netsh advfirewall firewall add rule name="Helpomatica 8010" dir=in action=allow protocol=TCP localport=8010
```
Загрузки и агент выполняются **на машине сервера**, не в браузере клиента.
Файлы копируются во временный workspace из `data/uploads/`.
---
## Docker
Нужен файл `.env` (из `.env.example`). Данные не кладутся в образ:
том `./data:/app/data` хранит `db.json` и загрузки.
```bash
docker compose up -d --build
```
Откройте http://localhost:8010 и войдите. После пересборки контейнера
проекты, чаты и файлы остаются в `./data`.
Healthcheck бьёт в `GET /` (страница логина, без авторизации).
`GET /api/auth/me` для проверки не подходит — без cookie он отдаёт 401.
Подробности про Cursor Agent в Linux-контейнере — в разделе
[Cursor SDK и Docker](#cursor-sdk-и-docker).
---
## Что это такое
Панель для команды на одной машине / в LAN:
- **Проекты** — название, описание, инструкции, модель, иконка.
- **Инструкции** — общий контекст для всех чатов проекта.
- **Файлы** — материалы проекта (видны во всех чатах) или только этого чата.
- **Чаты** — общие на проект: любой вошедший пользователь читает и пишет
в тех же разговорах.
- **Пользователи** — роли `admin` и `user`. Админ видит имена авторов
сообщений; обычный пользователь видит «Вы» / «Коллега».
- **Ответы** — локальный Cursor Agent (`AsyncAgent`) со стримингом,
размышлениями, историей инструментов и кнопкой «Стоп».
Дополнительно в UI: поиск по чатам, экспорт чата в Markdown, чипы
контекстных файлов, копирование кода, цитаты строк лога, диаграммы
Mermaid, формулы KaTeX, светлая/тёмная тема, индикатор статуса агента.
---
## Архитектура
```
Браузер (static/index.html + app.js + style.css)
│ cookie helpomatica_session
FastAPI (main.py) ──► data/db.json
│ data/uploads/
│ %TEMP%/helpomatica-agent/hlp-* (в Docker: /tmp/...)
cursor-sdk: AsyncClient.launch_bridge → AsyncAgent.send → SSE
Cursor API (нужен CURSOR_API_KEY)
```
- **Бэкенд:** FastAPI, статика смонтирована на `/static`, корень `GET /`
отдаёт `static/index.html`. Фронтенд — ванильный JS, без React/Vue.
- **Хранение:** JSON-файл `data/db.json` (атомарная запись через `.tmp`)
и файловая система `data/uploads/`. Папка `data/` в Git не входит.
- **Сессии:** список в `db.json`, cookie HttpOnly `helpomatica_session`
(`SameSite=Lax`, без `Secure` — HTTP по IP в LAN), срок 30 дней,
не больше 200 сессий. Пароль: PBKDF2-HMAC-SHA256, 200000 итераций.
- **Доступ:** все авторизованные видят все проекты и общие чаты.
Отдельных ACL на проект нет. Файлы с `scope=chat` попадают в агент
только этого чата; `scope=project` — во все чаты проекта.
- **Параллельность:** глобального single-flight нет. У каждого `chat_id`
своя очередь: один активный запрос и максимум один в ожидании
(второй лишний → HTTP 409). Разные чаты запускают отдельные local bridge.
- **Слушает** `0.0.0.0:8010`.
`SESSION_SECRET` в `.env` — заготовка, **main.py его не читает**.
---
## Стек
Версии из текущего `pip install` в `.deps``requirements.txt`
пакеты **без пинов**):
| Слой | Что |
|------|-----|
| Язык | Python **3.12** (локально 3.12.10). `cursor-sdk` требует **≥ 3.10** |
| API | FastAPI **0.141.1**, Starlette, Pydantic v2 |
| Сервер | uvicorn[standard] **0.52.1** |
| Агент | cursor-sdk **1.0.27** (`AsyncAgent`, `AsyncClient.launch_bridge`, `LocalAgentOptions`) + httpx |
| Конфиг | python-dotenv **1.2.2** |
| Загрузки | python-multipart **0.0.32** |
| Предразбор логов | `log_preanalysis.py` (stdlib) |
| Фронтенд | `static/app.js`, `static/style.css` — без сборки |
| CDN | Mermaid 11, KaTeX 0.16.22, шрифты Google (Manrope, Playfair Display) |
| Данные | JSON + файлы на диске |
Локальный запуск (`run.ps1`) кладёт пакеты в `.deps` и добавляет каталог
в `sys.path`. Docker ставит те же пакеты в venv через `pip install -r requirements.txt`.
---
## Форма `data/db.json`
Корень — объект со списками. При пустом файле / первом старте создаётся
админ из `ADMIN_USERNAME` / `ADMIN_PASSWORD`.
```json
{
"users": [
{
"id": "hex",
"username": "admin",
"display_name": "Admin",
"password_hash": "salt$pbkdf2hex",
"role": "admin",
"created_at": "ISO-8601"
}
],
"sessions": [{ "id": "token", "user_id": "…", "created_at": "…" }],
"projects": [{
"id": "…", "name": "…", "description": "…", "instructions": "…",
"icon": "✦", "model": "auto", "created_by": "…",
"created_at": "…", "updated_at": "…"
}],
"chats": [{
"id": "…", "project_id": "…", "title": "…", "sort_order": 0,
"created_by": "…", "updated_by": "…",
"created_at": "…", "updated_at": "…"
}],
"messages": [{
"id": "…", "chat_id": "…", "role": "user|assistant", "content": "…",
"attachments": [{ "id": "…", "name": "…" }],
"user_id": "… или null", "author_name": "Admin|Помощник|…",
"thinking": "…", "thinking_duration_ms": 17519,
"activity": [{ "id": "a1", "type": "thinking|shell|…", "title": "…",
"detail": "…", "status": "running|completed" }],
"created_at": "…"
}],
"files": [{
"id": "…", "project_id": "…", "chat_id": "null или id чата",
"scope": "project|chat", "name": "line_u_codes.log.1",
"label": "", "stored_name": "<uuid>_оригинал",
"content_type": "…", "size": 123, "created_at": "…"
}],
"analysis_cache": [{
"key": "sha256", "file_sig": "sha256", "project_id": "…",
"job_id": "", "question": "нормализованный текст",
"answer": "…", "preanalysis": "markdown", "summary": "…",
"created_at": "…"
}]
}
```
Байты файлов лежат в `data/uploads/<stored_name>`, в UI показывается
оригинальное `name`.
---
## Как проходит запрос к агенту
1. Пользователь пишет сообщение. Фронт сохраняет его
`POST /api/chats/{id}/messages` (роль `user`, вложения, автор).
2. Сразу же `POST /api/chats/{id}/respond` — SSE-поток
(`text/event-stream`).
3. Сервер занимает слот чата (или ставит в очередь на один).
Событие `status: queued` / `starting`.
4. Собирается workspace: `prepare_agent_workspace` создаёт
`%TEMP%/helpomatica-agent/hlp-<project8>-*` (в Linux/Docker —
`/tmp/helpomatica-agent/hlp-…`). Туда hardlink/копия загрузок
(и в `./`, и в `./files/`), плюс подсказки `README.md`,
`PROJECT_FILES.md`, `AGENTS.md`, `.cursorignore`.
5. Если вопрос похож на разбор логов (`log_preanalysis.looks_like_log_job`)
и в скоупе есть `*.log*`:
- ищется `analysis_cache`;
- иначе поток-скан логов с SSE `progress` («Читаю лог… N%»);
- результат пишется в `./PREANALYSIS.md`.
6. Промпт: инструкции проекта + выдержки файлов + последние 30 сообщений
+ правило «cwd уже workspace, читай PREANALYSIS.md первым».
7. В **отдельном потоке** (на Windows — `WindowsProactorEventLoopPolicy`)
вызывается `AsyncClient.launch_bridge(workspace=cwd)` и
`AsyncAgent.create` / `agent.send`. События моста читаются в этом
потоке; основной event loop забирает их через `asyncio.to_thread(queue.get)`.
8. В браузер уходят SSE:
- `thinking` — дельты `thinking-delta`;
- `activity` — инструменты, shell, статус, шаги «Логи»;
- `delta` — токены ответа (`text-delta`);
- `thinking_done`, `done` / `cancelled` / `error`.
9. Готовый текст сохраняется как сообщение ассистента (`thinking`,
`activity`, `thinking_duration_ms`). При успехе пишется кэш разбора
(до 80 записей).
Кнопка «Стоп» — `POST /api/chats/{id}/cancel`.
---
## Workspace агента и файлы
- Каталог: `tempfile.gettempdir()/helpomatica-agent/hlp-…`.
- Копируются файлы **проекта** + файлы **этого чата** + вложения сообщений.
- Вложения, chat-scope и логи копируются всегда; остальные файлы проекта
режутся лимитами 40 МБ / файл и 80 МБ суммарно.
- Если hardlink недоступен — `shutil.copy2`.
- Агент работает только с этим cwd; исходники панели и диск клиента
ему не отдаются.
Цитаты в ответе вида `:15231:line_u_codes.log.1` или
`::log:line_u_codes.log.1:15231::` фронт превращает в ссылку на
`GET /api/files/{id}/excerpt?start=&end=` (фрагмент ±25 строк).
---
## Авторизация и API (кратко)
Публично: `GET /`, `/static/*`, `POST /api/auth/login`.
Остальные `/api/*` требуют cookie.
| Метод | Путь | Назначение |
|-------|------|------------|
| POST | `/api/auth/login` | вход, Set-Cookie |
| POST | `/api/auth/logout` | выход |
| GET | `/api/auth/me` | текущий пользователь |
| GET/POST | `/api/users` | список / создание (только admin) |
| GET | `/api/agent/status` | ключ, SDK, busy, очереди чатов |
| CRUD | `/api/projects`, `/api/chats` | проекты, чаты, порядок |
| GET | `/api/chats/{id}/export` | Markdown |
| POST | `/api/chats/{id}/messages` | сохранить сообщение |
| POST | `/api/chats/{id}/respond` | SSE-ответ агента |
| POST | `/api/chats/{id}/cancel` | остановить генерацию |
| GET | `/api/search?q=` | поиск по названиям и тексту (от 2 символов, до 30) |
| POST | `/api/projects/{id}/files` | загрузка (`scope` + `chat_id`) |
| POST | `/api/chats/{id}/files/bind` | привязать файлы к чату |
| GET | `/api/files/{id}` | скачать |
| GET | `/api/files/{id}/excerpt` | фрагмент лога по строкам |
Модель проекта выбирается в UI (`auto`, `composer-2.5`, `composer-2`,
`gpt-5.2`, `claude-4.6-sonnet`) и уходит в `AgentOptions.model`.
По умолчанию — `CURSOR_MODEL` из `.env`.
---
## Cursor SDK и Docker
На Windows wheel `cursor-sdk` кладёт в
`cursor_sdk/_vendor/bridge/bin/`:
- `cursor-sdk-bridge.cmd` → запускает **`node.exe`** (вендорный, ~89 МБ)
- `../dist/bin/cursor-sdk-bridge.js` — сам мост (Node, не отдельный .exe агента)
`resolve_bridge_path()` ищет: `CURSOR_SDK_BRIDGE_BIN`, затем bundled
`bin/cursor-sdk-bridge` (POSIX) / `cursor-sdk-bridge.cmd` (Windows),
затем PATH.
**Не копируйте `.deps` с Windows-хоста в Linux-образ** — там `node.exe`.
Dockerfile ставит пакет заново: Linux-wheel cursor-sdk (x64/arm64)
должен принести свой `node` + launcher `cursor-sdk-bridge`.
Если в контейнере мост не стартует (нет bundled node, ошибка glibc,
нет `cursor-sdk-bridge`):
- веб-UI, логин, файлы и **предразбор логов** всё равно работают;
- ответы Cursor Agent нужно снимать на Windows-хосте через `.\run.ps1`.
Образ не содержит `.env`. Ключ передаётся `env_file: .env`.
---
## Безопасность
- Сразу смените пароль админа (`admin`/`admin` только для первого старта).
- Не коммитьте `.env` (уже в `.gitignore`). Ключ Cursor — секрет.
- Панель слушает весь LAN: ограничьте доступ брандмауэром.
- Cookie без `Secure`: нормально для HTTP в LAN, не для публичного HTTPS
без доработки.
- Это не multi-tenant isolation: любой `user` видит все проекты и чаты.
- Генерации картинок в панели нет; агенту это явно запрещено в промпте.
---
## Файлы репозитория
| Путь | Роль |
|------|------|
| `main.py` | FastAPI-приложение |
| `log_preanalysis.py` | потоковый скан логов → `PREANALYSIS.md` |
| `static/` | HTML/CSS/JS |
| `run.ps1` | локальный запуск на Windows |
| `requirements.txt` | зависимости |
| `.env.example` | шаблон переменных (без секретов) |
| `Dockerfile`, `docker-compose.yml`, `.dockerignore` | контейнер |
| `test_preanalysis.py` | короткий тест предразбора логов |
| `data/` | БД и загрузки на диске сервера — **не в Git** (только `data/.gitkeep`) |
---
## Выгрузка в Gitea
В репозиторий кладите только исходники продукта: `main.py`, `log_preanalysis.py`, `static/`, `requirements.txt`, `run.ps1`, Docker-файлы, `.env.example`, `.gitignore`, `README.md`.
**Не коммитьте и не копируйте в Gitea:**
- `.env` — там `CURSOR_API_KEY` и пароль админа
- `data/` — чаты, загрузки, `db.json` (том Docker `./data` тоже только локальный)
- `.deps/` — локальный `pip install --target` (ставится заново из `requirements.txt`)
После клона:
```powershell
copy .env.example .env
# вставьте CURSOR_API_KEY и смените ADMIN_PASSWORD
docker compose up -d --build
# или локально на Windows:
.\run.ps1
```