diff --git a/agents/init.py b/agents/init.py
index 650f6bd3c843c0285c937ae7ba40f5b79645371c..3b0b7d0b45d3636b50154d553359dabd95e04312 100644
--- a/agents/init.py
+++ b/agents/init.py
@@ -101,7 +101,7 @@ def init_config_table(storage, config):
def ensure_directories():
for folder in ["logs", "scripts"]:
- full_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", folder))
+ full_path = os.path.abspath(os.path.join(os.path.dirname(__file__), folder))
if not os.path.exists(full_path):
os.makedirs(full_path)
print(f"[+] Создан каталог: {full_path}")
diff --git a/agents/notebook/templates/messages.html b/agents/notebook/templates/messages.html
index b6ba764292e7ba72b64207941e1faa65ebd21505..0ead591e0871db8bce4fbbe6acedab805e4f9747 100644
--- a/agents/notebook/templates/messages.html
+++ b/agents/notebook/templates/messages.html
@@ -10,6 +10,12 @@
.source-system { background-color: #fff3e0; }
.private::after { content: " 🔒"; }
.from-self::before { content: "🧍 "; }
+ pre.code-block {
+ background-color: #f0f0f0;
+ padding: 10px;
+ border-radius: 6px;
+ overflow-x: auto;
+ }
@@ -23,8 +29,10 @@
Сообщения
-
@@ -50,6 +58,22 @@
{{ msg.text|safe }}
+
+ {% if msg.code %}
+
+ Код:
+ {{ msg.code }}
+ {% endif %}
+
+ {% if msg.files %}
+
+ Файлы:
+
+ {% for file in msg.files %}
+ - {{ file.name }} ({{ file.size }} байт)
+ {% endfor %}
+
+ {% endif %}
{% endfor %}
diff --git a/agents/notebook/views.py b/agents/notebook/views.py
index 1757af34596db894229de40b4671ad19743a9535..1a797fb493906649c8c5c06b3bb1f4d0131aa797 100644
--- a/agents/notebook/views.py
+++ b/agents/notebook/views.py
@@ -2,11 +2,13 @@
import re
import bleach
+import uuid
-from fastapi import APIRouter, Request, Form
+from fastapi import APIRouter, Request, Form, UploadFile, File
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.templating import Jinja2Templates
from starlette.status import HTTP_303_SEE_OTHER
+from typing import List
from tools.storage import Storage
router = APIRouter()
@@ -76,25 +78,43 @@ def show_messages(request: Request, only_personal: bool = False):
})
@router.post("/messages")
-def post_message(
+async def post_message(
request: Request,
text: str = Form(...),
- hidden: str = Form(default="false")
+ code: str = Form(None),
+ hidden: str = Form(default="false"),
+ binary_files: List[UploadFile] = File(default=[])
):
did = request.session.get("did", "anon")
is_hidden = 1 if hidden.lower() == "true" else 0
- # Проверка на бан
if storage.is_banned(did):
return HTMLResponse(content="Вы забанены и не можете отправлять сообщения.", status_code=403)
- if text.strip():
- storage.write_note(
- content=sanitize_html(text.strip()),
+ if text.strip() or code or binary_files:
+ # Очистка текста
+ safe_text = sanitize_html(text.strip()) if text else ""
+
+ # Сохраняем сообщение и получаем message_id
+ message_id = storage.write_note_returning_id(
+ content=safe_text,
user_did=did,
source="user",
- hidden=is_hidden
+ hidden=is_hidden,
+ code=code.strip() if code else None
)
+
+ # Сохраняем файлы
+ for upload in binary_files:
+ data = await upload.read()
+ if data:
+ storage.save_attachment(
+ message_id=message_id,
+ filename=upload.filename,
+ mime_type=upload.content_type,
+ content=data
+ )
+
return RedirectResponse(url="/messages", status_code=303)
@router.get("/login")
diff --git a/agents/tools/db_structure.sql b/agents/tools/db_structure.sql
index c0d330c06e2b85e8a6d93ebf34c6476cc5fd172b..5d9a57aac072034328f97b22957aedfb40eb6cde 100644
--- a/agents/tools/db_structure.sql
+++ b/agents/tools/db_structure.sql
@@ -62,6 +62,7 @@ CREATE TABLE IF NOT EXISTS attachments (
message_id INTEGER NOT NULL, -- Связь с notes.id
filename TEXT, -- Имя файла
mime_type TEXT, -- Тип (например, image/png, application/zip)
+ size INTEGER, -- Размер файла
binary BLOB NOT NULL, -- Сами данные
FOREIGN KEY (message_id) REFERENCES notes(id) ON DELETE CASCADE
);
diff --git a/agents/tools/storage.py b/agents/tools/storage.py
index e21e261807fa9f5c43099792cef5014bba8eb51e..488ec54c76229ac3bffe7ccfc869baca85dba882 100644
--- a/agents/tools/storage.py
+++ b/agents/tools/storage.py
@@ -5,6 +5,7 @@ import sqlite3
import os
import json
import uuid
+import time
from datetime import datetime, timedelta, UTC
from werkzeug.security import generate_password_hash, check_password_hash
@@ -714,6 +715,23 @@ class Storage:
""", (content, user_did, source, timestamp, hidden))
self.conn.commit()
+ def write_note_returning_id(self, content, user_did, source="user", hidden=False, code=None):
+ cursor = self.conn.cursor()
+ cursor.execute("""
+ INSERT INTO notes (timestamp, text, user_did, source, hidden, code)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """, (time.time(), content, user_did, source, int(hidden), code))
+ self.conn.commit()
+ return cursor.lastrowid
+
+ def save_attachment(self, message_id, filename, mime_type, content):
+ cursor = self.conn.cursor()
+ cursor.execute("""
+ INSERT INTO attachments (message_id, filename, mime_type, size, binary)
+ VALUES (?, ?, ?, ?, ?)
+ """, (message_id, filename, mime_type, len(content), content))
+ self.conn.commit()
+
def get_notes(self, limit=50, user_did="anon", only_personal=False):
cursor = self.conn.cursor()
@@ -743,6 +761,18 @@ class Storage:
return [dict(row) for row in cursor.fetchall()]
+ for note in result:
+ note["attachments"] = self.get_attachments_for_note(note["id"])
+ return result
+
+ def get_attachments_for_note(self, message_id):
+ cursor = self.conn.cursor()
+ cursor.execute("""
+ SELECT id, filename, mime_type, size FROM attachments
+ WHERE message_id = ?
+ """, (message_id,))
+ return [dict(row) for row in cursor.fetchall()]
+
# Пользователи
def register_user(self, username: str, mail: str, password: str) -> bool:
mail = mail.lower()
diff --git a/hf_repo/agents/tools/db_structure.sql b/hf_repo/agents/tools/db_structure.sql
index 1541b92ecce06930792cb91b8f47eeeb791db957..5d9a57aac072034328f97b22957aedfb40eb6cde 100644
--- a/hf_repo/agents/tools/db_structure.sql
+++ b/hf_repo/agents/tools/db_structure.sql
@@ -42,17 +42,29 @@ CREATE TABLE IF NOT EXISTS diary_graph_index (
-- Заметки, подсказки, сообщения пользователя и LLM
CREATE TABLE IF NOT EXISTS notes (
- id INTEGER PRIMARY KEY AUTOINCREMENT, -- Уникальный идентификатор заметки
- text TEXT NOT NULL, -- Текст заметки
- tags TEXT, -- Теги (например: "idea", "instruction")
- user_did TEXT DEFAULT 'ALL', -- DID пользователя (или 'ALL' — для всех)
- source TEXT DEFAULT 'user', -- Источник заметки: user | cli | llm | system
- links TEXT DEFAULT '', -- Ссылки или связи с другими объектами
- read INTEGER DEFAULT 0, -- Статус прочтения LLM: 0 = нет, 1 = да
- hidden INTEGER DEFAULT 0, -- Скрыта ли от пользователя: 0 = нет, 1 = да
- priority INTEGER DEFAULT 0, -- Приоритет заметки
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ text TEXT NOT NULL, -- Основной текст заметки/сообщения
+ code TEXT, -- Прикреплённый код (Python, JS и т.п.)
+ tags TEXT, -- Теги (устанавливаются агентом, например: "idea", "instruction")
+ user_did TEXT DEFAULT 'ALL', -- Идентификатор пользователя (или 'ALL')
+ source TEXT DEFAULT 'user', -- Источник: user | cli | llm | system
+ links TEXT DEFAULT '', -- Ссылки на другие объекты (например, JSON со связями)
+ read INTEGER DEFAULT 0, -- Агент прочитал: 0 = нет, 1 = да
+ hidden INTEGER DEFAULT 0, -- Скрыто от UI (например, технические записи)
+ priority INTEGER DEFAULT 0, -- Приоритет обработки (>0: срочность/важность, задается вручную или агентом)
timestamp TEXT DEFAULT CURRENT_TIMESTAMP, -- Время создания
- llm_id TEXT -- Идентификатор LLM
+ llm_id TEXT -- Идентификатор агента, добавившего сообщение
+);
+
+-- Вложения (может быть несколько к одной заметке)
+CREATE TABLE IF NOT EXISTS attachments (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ message_id INTEGER NOT NULL, -- Связь с notes.id
+ filename TEXT, -- Имя файла
+ mime_type TEXT, -- Тип (например, image/png, application/zip)
+ size INTEGER, -- Размер файла
+ binary BLOB NOT NULL, -- Сами данные
+ FOREIGN KEY (message_id) REFERENCES notes(id) ON DELETE CASCADE
);
-- Лог процессов: задачи, ошибки, события
diff --git a/hf_repo/hf_repo/agents/add_message.py b/hf_repo/hf_repo/agents/add_message.py
index 6948a46b2b26b8beb814773504b9bf6a5d11f51a..ddcebdc259679e707bc6a9207b59e9701394f0ed 100644
--- a/hf_repo/hf_repo/agents/add_message.py
+++ b/hf_repo/hf_repo/agents/add_message.py
@@ -5,11 +5,12 @@ from tools.storage import Storage
storage = Storage()
-def add_message(content, source="cli", user_did="anon"):
+def add_message(content, source="cli", user_did="anon", hidden=1):
storage.write_note(
content,
source=source,
- user_did=user_did
+ user_did=user_did,
+ hidden=hidden
)
print(f"[+] Сообщение от {source} ({user_did}) добавлено: {content}")
@@ -19,6 +20,7 @@ if __name__ == "__main__":
parser.add_argument("--content", required=True)
parser.add_argument("--source", default="cli")
parser.add_argument("--user_did", default="anon")
+ parser.add_argument("--hidden", default=1)
args = parser.parse_args()
- add_message(args.content, args.source, args.user_did)
+ add_message(args.content, args.source, args.user_did, args.hidden)
diff --git a/hf_repo/hf_repo/agents/tools/storage.py b/hf_repo/hf_repo/agents/tools/storage.py
index abc788acd749ff19c248fcdafda15c563860b24e..e21e261807fa9f5c43099792cef5014bba8eb51e 100644
--- a/hf_repo/hf_repo/agents/tools/storage.py
+++ b/hf_repo/hf_repo/agents/tools/storage.py
@@ -735,7 +735,7 @@ class Storage:
FROM notes n
LEFT JOIN users u ON n.user_did = u.did
WHERE n.user_did = ?
- OR ((n.source = 'user' OR n.source = 'llm') AND n.hidden = 0)
+ OR ((n.source = 'user' OR n.source = 'llm' OR n.source = 'cli') AND n.hidden = 0)
ORDER BY n.timestamp DESC
LIMIT ?
"""
diff --git a/hf_repo/hf_repo/hf_repo/agents/notebook/views.py b/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
index 442d9bd7bc73217f9ab8a1e60dbe7f63a9120737..1757af34596db894229de40b4671ad19743a9535 100644
--- a/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
+++ b/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
@@ -1,5 +1,6 @@
# agents/notebook/views.py
+import re
import bleach
from fastapi import APIRouter, Request, Form
@@ -12,13 +13,20 @@ router = APIRouter()
templates = Jinja2Templates(directory="notebook/templates")
storage = Storage()
-allowed_tags = ['b', 'i', 's', 'u', 'a', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'table', 'caption', 'tr', 'th', 'td']
+allowed_tags = ['b', 'i', 's', 'u', 'a', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'table', 'caption', 'tr', 'th', 'td', 'code', 'pre', 'blockquote', 'br', 'hr']
allowed_attributes = {
'a': ['href', 'title']
}
-def sanitize_html(text):
- return bleach.clean(text, tags=allowed_tags, attributes=allowed_attributes, strip=True)
+# Очистка сообщений
+def sanitize_html(text: str) -> str:
+ # 1. Сначала очищаем HTML
+ cleaned = bleach.clean(text, tags=allowed_tags, attributes=allowed_attributes, strip=True)
+
+ # 2. Заменяем 3 и более
подряд на ровно два
+ cleaned = re.sub(r'(
\s*){3,}', '
', cleaned, flags=re.IGNORECASE)
+
+ return cleaned
@router.get("/chat")
def chat_page(request: Request):
diff --git a/hf_repo/hf_repo/hf_repo/agents/tools/__init__.py b/hf_repo/hf_repo/hf_repo/agents/tools/__init__.py
index 49652b8f9694110cfa87f3d1903c256af66e9be1..c1623bfec92d28925597e271faa5e7d700376cc7 100644
--- a/hf_repo/hf_repo/hf_repo/agents/tools/__init__.py
+++ b/hf_repo/hf_repo/hf_repo/agents/tools/__init__.py
@@ -1 +1 @@
-from agents.tools.storage import Storage
+from tools.storage import Storage
diff --git a/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/templates/messages.html b/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/templates/messages.html
index 3df3b2d777024accdd96f1d91091cc41e7609143..b6ba764292e7ba72b64207941e1faa65ebd21505 100644
--- a/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/templates/messages.html
+++ b/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/templates/messages.html
@@ -25,8 +25,8 @@
diff --git a/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/views.py b/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
index 7779f71798add544d2953b5df5bc4e7c41aa9c60..442d9bd7bc73217f9ab8a1e60dbe7f63a9120737 100644
--- a/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
+++ b/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
@@ -12,7 +12,7 @@ router = APIRouter()
templates = Jinja2Templates(directory="notebook/templates")
storage = Storage()
-allowed_tags = ['b', 'i', 'a', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'table', 'caption', 'tr', 'th', 'td']
+allowed_tags = ['b', 'i', 's', 'u', 'a', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'table', 'caption', 'tr', 'th', 'td']
allowed_attributes = {
'a': ['href', 'title']
}
diff --git a/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/views.py b/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
index 2976e44700e87ab3a4f7b0de4303cbfc54ee6fa5..7779f71798add544d2953b5df5bc4e7c41aa9c60 100644
--- a/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
+++ b/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/views.py
@@ -1,5 +1,7 @@
# agents/notebook/views.py
+import bleach
+
from fastapi import APIRouter, Request, Form
from fastapi.responses import RedirectResponse, HTMLResponse
from fastapi.templating import Jinja2Templates
@@ -10,6 +12,14 @@ router = APIRouter()
templates = Jinja2Templates(directory="notebook/templates")
storage = Storage()
+allowed_tags = ['b', 'i', 'a', 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'table', 'caption', 'tr', 'th', 'td']
+allowed_attributes = {
+ 'a': ['href', 'title']
+}
+
+def sanitize_html(text):
+ return bleach.clean(text, tags=allowed_tags, attributes=allowed_attributes, strip=True)
+
@router.get("/chat")
def chat_page(request: Request):
did = request.session.get("did")
@@ -72,7 +82,7 @@ def post_message(
if text.strip():
storage.write_note(
- content=text.strip(),
+ content=sanitize_html(text.strip()),
user_did=did,
source="user",
hidden=is_hidden
diff --git a/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/requirements.txt b/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/requirements.txt
index a63403550b2c5653989211469ee0a08a48065867..c515e968c47a5ffa70ce17857ce182ab4c5295e8 100644
--- a/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/requirements.txt
+++ b/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/requirements.txt
@@ -12,4 +12,5 @@ jinja2
python-multipart
passlib[bcrypt]
werkzeug
-itsdangerous
\ No newline at end of file
+itsdangerous
+bleach
\ No newline at end of file
diff --git a/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/templates/messages.html b/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/templates/messages.html
index 8b6c076dad77c38f5846dd71252100836e2c4379..3df3b2d777024accdd96f1d91091cc41e7609143 100644
--- a/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/templates/messages.html
+++ b/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/hf_repo/agents/notebook/templates/messages.html
@@ -46,10 +46,10 @@
Источник: {{ msg.source }} — {{ msg.timestamp[:19].replace('T', ' ') }}
- {{ msg.badges }}Пользователь: {% if msg.username %}{{ msg.username }}{% endif %} {% if msg.user_did !="" %}({{ msg.user_did }}){% endif %}
+ {% if msg.badges %}{{ msg.badges }}{% endif %}Пользователь: {% if msg.username %}{{ msg.username }}{% endif %} {% if msg.user_did %}({{ msg.user_did }}){% endif %}
- {{ msg.text }}
+ {{ msg.text|safe }}
{% endfor %}