Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad930da685 | ||
|
|
ace24fa9d2 |
40
PROJECT_OVERVIEW.md
Normal file
40
PROJECT_OVERVIEW.md
Normal 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`
|
||||
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import base64
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@ -8,10 +9,12 @@ import subprocess
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import zipfile
|
||||
from datetime import datetime, timezone
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
_LOG_LEVELS = {"debug": 10, "info": 20, "error": 40, "quiet": 100}
|
||||
@ -585,20 +588,172 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self.end_headers()
|
||||
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
|
||||
if self.path not in ("/", "/health"):
|
||||
self._send_json(404, {"ok": False, "error": "not_found"})
|
||||
parsed = urlparse(self.path)
|
||||
path = parsed.path or "/"
|
||||
|
||||
if path == "/":
|
||||
# Keep root JSON-compatible for extension and existing integrations.
|
||||
self._send_json(200, self._health_payload())
|
||||
return
|
||||
self._send_json(
|
||||
200,
|
||||
{
|
||||
"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]
|
||||
},
|
||||
)
|
||||
|
||||
if path in ("/guide", "/docs"):
|
||||
self._send_html(200, self._home_html())
|
||||
return
|
||||
|
||||
if path in ("/download/chrome-extension.zip", "/chrome_screenshot_ext.zip"):
|
||||
zip_bytes, err = self._build_extension_zip()
|
||||
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
|
||||
self.send_response(204)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user