Compare commits

...

2 Commits

Author SHA1 Message Date
Flatlogic Bot
ad930da685 project_overview.md 2026-03-19 16:56:24 +00:00
Flatlogic Bot
ace24fa9d2 Autosave: 20260319-162804 2026-03-19 16:28:04 +00:00
2 changed files with 207 additions and 12 deletions

40
PROJECT_OVERVIEW.md Normal file
View File

@ -0,0 +1,40 @@
# Проект: Screenshot AI Bridge
Этот проект представляет собой систему для автоматизации сбора, обработки и анализа снимков экрана с использованием ИИ. Система разработана для работы в среде виртуальной машины Flatlogic и обеспечивает интеграцию между браузерным расширением и бэкенд-обработкой.
## Основные компоненты
1. **`tools/local_screenshot_bridge.py`**:
- Основной сервис (бэкенд), написанный на Python.
- Запускается как процесс PM2 на порту 3001.
- Обеспечивает следующие функции:
- **API эндпоинты**:
- `/`: Основной эндпоинт для данных (health check и JSON-ответы).
- `/health`: Проверка состояния системы.
- `/guide`: Человекочитаемая страница с инструкциями.
- `/download/chrome-extension.zip`: Динамическая генерация и загрузка архива с расширением для Chrome.
- **ИИ интеграция**:
- Автоматический сбор и анализ контента со скриншотов.
- Конфигурируемые параметры ИИ (напр. `--ai-max-output-tokens 2500`).
- Автоматическое сохранение результатов в директории `screenshots/` (PNG, JSON, .ai.json).
2. **`chrome_screenshot_ext/`**:
- Расширение для браузера Chrome.
- Используется для захвата содержимого экрана и отправки его в `local_screenshot_bridge` для последующей обработки.
3. **`screenshots/`**:
- Директория для хранения результатов обработки (снимки экрана, метаданные, контент, результаты анализа ИИ).
4. **`scripts/`**:
- Вспомогательные скрипты для подготовки ответов и обработки событий (напр. `on_screenshot.sh`).
## Развертывание и эксплуатация
- **Порт**: Приложение слушает порт 3001.
- **Прокси**: Доступ снаружи осуществляется через Apache (reverse proxy), который перенаправляет запросы на локальный порт 3001.
- **PM2**: Управление процессом `screenshot-bridge` осуществляется через PM2.
- **Конфигурация**: Параметры работы ИИ (такие как `ai-max-output-tokens`) задаются при запуске процесса через аргументы командной строки.
## Как использовать
Для получения расширения используйте эндпоинт: `https://<ваша-доменная-зона>/download/chrome-extension.zip`
Документация и статус системы доступны по адресу: `https://<ваша-доменная-зона>/guide`

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import base64 import base64
import io
import json import json
import os import os
import re import re
@ -8,10 +9,12 @@ import subprocess
import sys import sys
import time import time
import traceback import traceback
import zipfile
from datetime import datetime, timezone from datetime import datetime, timezone
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from urllib.parse import urlparse
_LOG_LEVELS = {"debug": 10, "info": 20, "error": 40, "quiet": 100} _LOG_LEVELS = {"debug": 10, "info": 20, "error": 40, "quiet": 100}
@ -585,20 +588,172 @@ class Handler(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
self.wfile.write(body) self.wfile.write(body)
def _send_html(self, status: int, html: str):
body = html.encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
self.wfile.write(body)
def _send_bytes(self, status: int, body: bytes, content_type: str, content_disposition: Optional[str] = None):
self.send_response(status)
self.send_header("Content-Type", content_type)
self.send_header("Content-Length", str(len(body)))
self.send_header("Access-Control-Allow-Origin", "*")
if content_disposition:
self.send_header("Content-Disposition", content_disposition)
self.end_headers()
self.wfile.write(body)
def _build_extension_zip(self) -> tuple[Optional[bytes], Optional[str]]:
project_root: Path = self.server.project_root # type: ignore[attr-defined]
ext_dir = project_root / "chrome_screenshot_ext"
if not ext_dir.exists() or not ext_dir.is_dir():
return None, f"missing extension directory: {ext_dir}"
buf = io.BytesIO()
with zipfile.ZipFile(buf, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
for p in sorted(ext_dir.rglob("*")):
if p.is_file():
zf.write(p, arcname=p.relative_to(project_root))
return buf.getvalue(), None
def _health_payload(self) -> dict:
return {
"ok": True,
"service": "local_screenshot_bridge",
"out_dir": str(self.server.out_dir), # type: ignore[attr-defined]
"has_run_cmd": bool(getattr(self.server, "run_cmd", None)), # type: ignore[attr-defined]
"ai_enabled": bool(getattr(self.server, "ai_enabled", False)), # type: ignore[attr-defined]
}
def _home_html(self) -> str:
health = self._health_payload()
ai_text = "Enabled" if health["ai_enabled"] else "Disabled"
run_cmd_text = "Configured" if health["has_run_cmd"] else "Not configured"
return f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Local Screenshot Bridge</title>
<style>
:root {{
--bg: #f8fafc;
--card: #ffffff;
--text: #0f172a;
--muted: #475569;
--line: #e2e8f0;
--accent: #2563eb;
--good: #16a34a;
}}
* {{ box-sizing: border-box; }}
body {{
margin: 0;
background: linear-gradient(180deg, #f8fafc, #eef2ff);
color: var(--text);
font: 16px/1.5 Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
padding: 36px 16px;
}}
.wrap {{ max-width: 820px; margin: 0 auto; }}
.card {{
background: var(--card);
border: 1px solid var(--line);
border-radius: 16px;
padding: 26px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.06);
}}
h1 {{ margin: 0 0 8px; font-size: 1.8rem; }}
p {{ margin: 0 0 14px; color: var(--muted); }}
.status {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
gap: 10px;
margin: 18px 0 8px;
}}
.pill {{
border: 1px solid var(--line);
border-radius: 12px;
padding: 10px 12px;
background: #fff;
}}
.pill strong {{ color: var(--good); }}
ol {{
margin: 12px 0 0;
padding-left: 20px;
}}
code {{
background: #f1f5f9;
border: 1px solid var(--line);
border-radius: 8px;
padding: 2px 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: .92em;
}}
a {{ color: var(--accent); text-decoration: none; }}
a:hover {{ text-decoration: underline; }}
</style>
</head>
<body>
<main class="wrap">
<section class="card">
<h1> Screenshot Bridge is running</h1>
<p>This server accepts screenshot payloads from the Chrome extension and can generate AI response suggestions.</p>
<div class="status">
<div class="pill">Server: <strong>Online</strong></div>
<div class="pill">AI: <strong>{ai_text}</strong></div>
<div class="pill">Run command: <strong>{run_cmd_text}</strong></div>
</div>
<h2>How to use</h2>
<ol>
<li>Download the extension zip from <a href="/download/chrome-extension.zip"><code>/download/chrome-extension.zip</code></a> (or copy <code>chrome_screenshot_ext</code> manually).</li>
<li>Open <code>chrome://extensions</code> in Chrome and enable <strong>Developer mode</strong>.</li>
<li>Click <strong>Load unpacked</strong> and choose the <code>chrome_screenshot_ext</code> folder.</li>
<li>Open any page, run the extension, and send a screenshot to this server.</li>
</ol>
<p style="margin-top:16px;">Need JSON health output? Use <a href="/health"><code>/health</code></a>.</p>
</section>
</main>
</body>
</html>"""
def do_GET(self): # noqa: N802 def do_GET(self): # noqa: N802
if self.path not in ("/", "/health"): parsed = urlparse(self.path)
self._send_json(404, {"ok": False, "error": "not_found"}) path = parsed.path or "/"
if path == "/":
# Keep root JSON-compatible for extension and existing integrations.
self._send_json(200, self._health_payload())
return return
self._send_json(
200, if path in ("/guide", "/docs"):
{ self._send_html(200, self._home_html())
"ok": True, return
"service": "local_screenshot_bridge",
"out_dir": str(self.server.out_dir), # type: ignore[attr-defined] if path in ("/download/chrome-extension.zip", "/chrome_screenshot_ext.zip"):
"has_run_cmd": bool(getattr(self.server, "run_cmd", None)), # type: ignore[attr-defined] zip_bytes, err = self._build_extension_zip()
"ai_enabled": bool(getattr(self.server, "ai_enabled", False)), # type: ignore[attr-defined] if err:
}, _log(self.server, "error", f"Extension zip failed: {err}") # type: ignore[arg-type]
) self._send_json(500, {"ok": False, "error": "extension_zip_failed", "detail": err})
return
self._send_bytes(
200,
zip_bytes or b"",
"application/zip",
'attachment; filename="chrome_screenshot_ext.zip"',
)
return
if path == "/health":
self._send_json(200, self._health_payload())
return
self._send_json(404, {"ok": False, "error": "not_found"})
def do_OPTIONS(self): # noqa: N802 def do_OPTIONS(self): # noqa: N802
self.send_response(204) self.send_response(204)