SAVE: Backup before Google Drive integration
This commit is contained in:
parent
a28fcce19b
commit
f4ac98c6f4
9 changed files with 1671 additions and 1 deletions
45
DOCS/AI_SESSION_RESUME.md
Normal file
45
DOCS/AI_SESSION_RESUME.md
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# 🧠 ИНСТРУКЦИЯ ПО ВОССТАНОВЛЕНИЮ СЕССИИ (19.04.2026)
|
||||||
|
|
||||||
|
> **Для AI-агента:** Прочитай этот файл полностью. Здесь зафиксированы результаты многочасового исследования и разработки связки Domovoy Bot + NotebookLM.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 СУТЬ ПРОЕКТА
|
||||||
|
Создание "ИИ-Юриста" для жильцов дома.
|
||||||
|
1. **Источник**: Модуль `mutt` в `domovoy_bot` (выкачивает почту, конвертирует всё в PDF).
|
||||||
|
2. **База знаний**: Файл `domovoy_bot/services/email_auditor/exported_emails/TOTAL_ARCHIVE_2025-2026.pdf`.
|
||||||
|
3. **Мозги**: Google NotebookLM (Блокнот: "Разбор ответов строго по ЖК РФ").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 ВАЖНЫЕ ДАННЫЕ
|
||||||
|
- **Notebook ID**: `00ed7d92-1ab2-4159-ac72-1d0c4be21377`
|
||||||
|
- **Source Name**: `all_mail_gmail.pdf` (в блокноте) или `TOTAL_ARCHIVE_2025-2026.pdf` (локально).
|
||||||
|
- **Session ID (прошлой сессии)**: `6dd7a10a-c921-4558-ac9e-6834c8037455`.
|
||||||
|
- **Gitea**: `http://192.168.10.105:3001` (репозиторий `domovoy_bot`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ ЧТО УЖЕ СДЕЛАНО (ЭТАП 1)
|
||||||
|
1. **Sync Helper**: Создан скрипт `domovoy_bot/services/email_auditor/sync_helper.py` для пуша архива в ИИ.
|
||||||
|
2. **Web UI**: В `domovoy_bot/web/templates/emails.html` добавлена кнопка **«СИНХРОНИЗИРОВАТЬ С ИИ»**.
|
||||||
|
3. **Web API**: В `domovoy_bot/web/app.py` добавлен эндпоинт `/api/emails/sync_ai`.
|
||||||
|
4. **Telegram**: В `domovoy_bot/handlers/admin.py` добавлена команда `/legal_sync`.
|
||||||
|
5. **Proxy**: В `~/.gemini/settings.json` прописаны SOCKS5 прокси (10808) для NotebookLM MCP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❌ ТЕКУЩАЯ ПРОБЛЕМА
|
||||||
|
**Авторизация NotebookLM**: Куки, скопированные из браузера, отклоняются Google при запросе с VPS.
|
||||||
|
**Решение**: Нужно выполнить `nlm login` через SSH-тоннель или найти способ передать свежий CSRF-токен.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 ПЛАН ДАЛЬШЕ
|
||||||
|
1. **Этап 1 (финал)**: Добиться успешного `source_add` через MCP.
|
||||||
|
2. **Этап 2**: Команда `/legal <вопрос>` в боте для верифицированных жильцов.
|
||||||
|
3. **Этап 3**: Полная докеризация (единый `docker-compose.yml`).
|
||||||
|
4. **Этап 4**: Публикация на GitHub и статьи.
|
||||||
|
|
||||||
|
---
|
||||||
|
**Кладбище проектов**: Проект `automaticLM` признан неудачным и перемещен в `~/archive/FAILED_PROJECTS/`. Не использовать его.
|
||||||
1
DOCS/GITEA_SETUP.md
Normal file
1
DOCS/GITEA_SETUP.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# 💾 GITEA SETUP\n\n- **Host**: http://192.168.10.105:3001\n- **Repo**: domovoy_bot\n- **User**: matrixhasyou\n- **Push Command**: git push origin main\n\n**Заметка**: Всегда делай коммит перед важными изменениями!
|
||||||
1
DOCS/NOTEBOOKLM_COOKIES.txt
Normal file
1
DOCS/NOTEBOOKLM_COOKIES.txt
Normal file
File diff suppressed because one or more lines are too long
1
DOCS/PROJECT_ROADMAP.md
Normal file
1
DOCS/PROJECT_ROADMAP.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# 🚀 ROADMAP: DOMOVOY BOT + NOTEBOOKLM\n\n## 📝 ЭТАП 1: ИИ-ЮРИСТ (АДМИН-ПАНЕЛЬ)\n- [ ] Связка модуля mutt с NotebookLM.\n- [ ] Автоматическая замена источника 'all_mail_gmail.pdf' в блокноте 'Разбор ответов строго по ЖК РФ'.\n- [ ] Кнопка 'СИНХРОНИЗИРОВАТЬ С ИИ' в веб-панели.\n- [ ] Команда /legal_sync для админа.\n\n## ⚖️ ЭТАП 2: ИИ-ЮРИСТ ДЛЯ ЖИТЕЛЕЙ\n- [ ] Доступ для верифицированных жильцов.\n- [ ] Команда /legal <вопрос>.\n- [ ] Юридический анализ ответов через NotebookLM.\n\n## 🐳 ЭТАП 3: ДОКЕРИЗАЦИЯ\n- [ ] Создание единого docker-compose для деплоя.\n- [ ] Инструкции по 'борьбе' с УК для жителей.\n\n## ✍️ ЭТАП 4: ПУБЛИЧНОСТЬ\n- [ ] Статьи на Хабр/VC.\n- [ ] Open-source релиз на GitHub.\n\n--- \n## 🔐 ИНФРАСТРУКТУРА\n- **Gitea**: http://192.168.10.105:3001\n- **Notebook ID**: 00ed7d92-1ab2-4159-ac72-1d0c4be21377\n- **Source Name**: all_mail_gmail.pdf
|
||||||
1474
exports/chat_history_2026-04-20_03-00.json
Normal file
1474
exports/chat_history_2026-04-20_03-00.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -39,6 +39,38 @@ class BroadcastState(StatesGroup):
|
||||||
# АДМИН МЕНЮ
|
# АДМИН МЕНЮ
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
|
@router.message(Command('legal_sync'))
|
||||||
|
@admin_only
|
||||||
|
async def cmd_legal_sync(message: Message):
|
||||||
|
"""Синхронизация архива с NotebookLM"""
|
||||||
|
await message.answer("🤖 <b>Запускаю синхронизацию с NotebookLM...</b>", parse_mode='HTML')
|
||||||
|
|
||||||
|
import os, subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Путь к скрипту и venv
|
||||||
|
base_dir = Path(__file__).parent.parent
|
||||||
|
script_path = base_dir / "services" / "email_auditor" / "sync_helper.py"
|
||||||
|
venv_python = base_dir / "venv" / "bin" / "python3"
|
||||||
|
|
||||||
|
if not script_path.exists():
|
||||||
|
await message.answer("❌ Скрипт синхронизации не найден.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["PYTHONPATH"] = str(base_dir)
|
||||||
|
# Запускаем скрипт
|
||||||
|
result = subprocess.run([str(venv_python), str(script_path)], capture_output=True, text=True, env=env)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
await message.answer(f"✅ <b>Результат:</b>\n{result.stdout.strip()}", parse_mode='HTML')
|
||||||
|
else:
|
||||||
|
await message.answer(f"❌ <b>Ошибка:</b>\n{result.stderr}", parse_mode='HTML')
|
||||||
|
except Exception as e:
|
||||||
|
await message.answer(f"❌ Произошла ошибка: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command('admin'))
|
@router.message(Command('admin'))
|
||||||
@admin_only
|
@admin_only
|
||||||
async def cmd_admin(message: Message):
|
async def cmd_admin(message: Message):
|
||||||
|
|
|
||||||
76
services/email_auditor/sync_helper.py
Normal file
76
services/email_auditor/sync_helper.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Helper script to sync master PDF with NotebookLM.
|
||||||
|
Uses notebooklm-mcp (cli) internally.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Path setup
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent
|
||||||
|
PROJECT_ROOT = BASE_DIR.parent.parent
|
||||||
|
load_dotenv(PROJECT_ROOT / ".env")
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||||
|
handlers=[logging.FileHandler(BASE_DIR / "sync.log", encoding="utf-8"), logging.StreamHandler()]
|
||||||
|
)
|
||||||
|
log = logging.getLogger("SyncAI")
|
||||||
|
|
||||||
|
# Config
|
||||||
|
MASTER_PDF = BASE_DIR / "exported_emails" / "TOTAL_ARCHIVE_2025-2026.pdf"
|
||||||
|
NOTEBOOK_ID = "00ed7d92-1ab2-4159-ac72-1d0c4be21377"
|
||||||
|
SOURCE_NAME = "all_mail_gmail.pdf"
|
||||||
|
|
||||||
|
def run_sync():
|
||||||
|
log.info("Starting AI Synchronization...")
|
||||||
|
|
||||||
|
if not MASTER_PDF.exists():
|
||||||
|
log.error(f"File not found: {MASTER_PDF}")
|
||||||
|
return False, "Файл архива не найден."
|
||||||
|
|
||||||
|
log.info(f"Using Master PDF: {MASTER_PDF} ({MASTER_PDF.stat().st_size / 1024 / 1024:.2f} MB)")
|
||||||
|
|
||||||
|
# Check for proxy
|
||||||
|
env = os.environ.copy()
|
||||||
|
if os.getenv("USE_PROXY", "").lower() == "true":
|
||||||
|
proxy = f"socks5h://{os.getenv('PROXY_HOST', '127.0.0.1')}:{os.getenv('PROXY_PORT', '10808')}"
|
||||||
|
env["HTTPS_PROXY"] = proxy
|
||||||
|
env["HTTP_PROXY"] = proxy
|
||||||
|
log.info(f"Using proxy for sync: {proxy}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Step 1: Check if source exists and delete it (optional, to avoid duplicates)
|
||||||
|
log.info("Step 1: Checking for existing source in NotebookLM...")
|
||||||
|
# (This will be implemented when MCP auth is fixed)
|
||||||
|
|
||||||
|
# Step 2: Upload new version
|
||||||
|
log.info(f"Step 2: Uploading {MASTER_PDF} to notebook {NOTEBOOK_ID}...")
|
||||||
|
|
||||||
|
# For now, we simulate the call since auth is broken
|
||||||
|
# command = ["nlm", "source", "add", str(MASTER_PDF), "--notebook", NOTEBOOK_ID]
|
||||||
|
# result = subprocess.run(command, env=env, capture_output=True, text=True)
|
||||||
|
|
||||||
|
# if result.returncode == 0:
|
||||||
|
# log.info("Successfully synced with NotebookLM!")
|
||||||
|
# return True, "Синхронизация с ИИ успешно завершена."
|
||||||
|
# else:
|
||||||
|
# log.error(f"Sync failed: {result.stderr}")
|
||||||
|
# return False, f"Ошибка синхронизации: {result.stderr}"
|
||||||
|
|
||||||
|
log.warning("!!! SYNCHRONIZATION SIMULATED (WAITING FOR AUTH FIX) !!!")
|
||||||
|
return True, "Файл подготовлен. Ожидание восстановления авторизации NotebookLM."
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Unexpected error: {e}")
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
success, msg = run_sync()
|
||||||
|
print(msg)
|
||||||
17
web/app.py
17
web/app.py
|
|
@ -546,6 +546,23 @@ async def api_run_email_audit(username: str = Depends(get_current_admin)):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JSONResponse(status_code=500, content={"message": str(e)})
|
return JSONResponse(status_code=500, content={"message": str(e)})
|
||||||
|
|
||||||
|
@app.post("/api/emails/sync_ai")
|
||||||
|
async def api_sync_email_ai(username: str = Depends(get_current_admin)):
|
||||||
|
script_path = BASE_DIR / "services" / "email_auditor" / "sync_helper.py"
|
||||||
|
venv_python = BASE_DIR / "venv" / "bin" / "python3"
|
||||||
|
if not script_path.exists(): return JSONResponse(status_code=404, content={"message": "Sync script not found"})
|
||||||
|
try:
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["PYTHONPATH"] = str(BASE_DIR)
|
||||||
|
# Запускаем скрипт синхронизации
|
||||||
|
result = subprocess.run([str(venv_python), str(script_path)], capture_output=True, text=True, env=env)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return JSONResponse(content={"message": result.stdout.strip()})
|
||||||
|
else:
|
||||||
|
return JSONResponse(status_code=500, content={"message": f"Ошибка: {result.stderr}"})
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(status_code=500, content={"message": str(e)})
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@
|
||||||
<i class="bi bi-file-earmark-pdf-fill display-1 text-danger mb-3"></i>
|
<i class="bi bi-file-earmark-pdf-fill display-1 text-danger mb-3"></i>
|
||||||
<h5>TOTAL_ARCHIVE_2025-2026.pdf</h5>
|
<h5>TOTAL_ARCHIVE_2025-2026.pdf</h5>
|
||||||
<p class="small opacity-50">Обновлен: {{ fmt_local(last_update) }}</p>
|
<p class="small opacity-50">Обновлен: {{ fmt_local(last_update) }}</p>
|
||||||
<a href="/static/email_archive/TOTAL_ARCHIVE_2025-2026.pdf" class="btn btn-success w-100" download>СКАЧАТЬ АРХИВ</a>
|
<a href="/static/email_archive/TOTAL_ARCHIVE_2025-2026.pdf" class="btn btn-success w-100 mb-2" download>СКАЧАТЬ АРХИВ</a>
|
||||||
|
<button id="syncAiBtn" class="btn btn-matrix w-100">
|
||||||
|
<i class="bi bi-robot"></i> СИНХРОНИЗИРОВАТЬ С ИИ
|
||||||
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="bi bi-file-earmark-x display-1 opacity-20 mb-3"></i>
|
<i class="bi bi-file-earmark-x display-1 opacity-20 mb-3"></i>
|
||||||
<p>Архив еще не сформирован</p>
|
<p>Архив еще не сформирован</p>
|
||||||
|
|
@ -130,6 +133,26 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
document.getElementById('syncAiBtn').addEventListener('click', async function() {
|
||||||
|
const btn = this;
|
||||||
|
if (!confirm('Отправить архив в NotebookLM для анализа?')) return;
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
const oldHtml = btn.innerHTML;
|
||||||
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> СИНХРОНИЗИРУЮ...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/emails/sync_ai', { method: 'POST' });
|
||||||
|
const result = await response.json();
|
||||||
|
alert(result.message);
|
||||||
|
} catch (e) {
|
||||||
|
alert('Ошибка при синхронизации: ' + e);
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = oldHtml;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('runAuditBtn').addEventListener('click', async function() {
|
document.getElementById('runAuditBtn').addEventListener('click', async function() {
|
||||||
const btn = this;
|
const btn = this;
|
||||||
if (!confirm('Запустить процесс синхронизации с Gmail? Это может занять пару минут.')) return;
|
if (!confirm('Запустить процесс синхронизации с Gmail? Это может занять пару минут.')) return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue