Files
Dumitru Picalau 657dc07831 feat(ai): assistant overhaul: tool registry, server-kept chats, search index, security, page tools, web search, saved chats, images, permissions, derivation NOT (schema 10), OpenAI Responses API (#64)
Phases 2 to 8 of the AI assistant overhaul plus fixes from real-model chats: one tool registry with native tool calls, server-kept chat logs with prompt caching, an FTS5 library index with opt-in meaning search, keys bound to their address and a guard on every AI route, page tools and opt-in web search, saved chats with streaming, Try again, a context meter and compaction, chat images, delete/category/tag permissions with Undo, constraint and test cards, card fixes and previews, questions with answers to click, the derivation NOT toggle (schema v10, community catalog 0021), request fixes for the newest Claude models, and OpenAI's Responses API. Review fixes: Try again clears the old turn's fixes and answers, category Undo removes only what it made, a failed delete rolls back in order, not_matches patterns are checked, busy chats refuse a second request, and chat search ignores stored JSON.
2026-10-14 14:56:12 +02:00

65 lines
2.2 KiB
Python

"""Migration 021: pinned AI chats."""
from __future__ import annotations
import importlib
import sqlite3
from engine.db.migrations import migrate
from engine.db.repositories import MAX_AI_CHATS, ChatRepository
def _conn(_tmp_path):
conn = sqlite3.connect(":memory:")
conn.row_factory = sqlite3.Row
migrate(conn)
return conn
def test_column_exists_with_default(tmp_path):
conn = _conn(tmp_path)
cols = {r[1]: r for r in conn.execute("PRAGMA table_info(ai_chats);").fetchall()}
assert "pinned" in cols
chat = ChatRepository(conn).create(title="t")
assert chat["pinned"] is False
def test_rerun_is_idempotent(tmp_path):
conn = _conn(tmp_path)
mod = importlib.import_module("engine.db.migrations_sql.021_ai_chat_pins")
mod.up(conn)
mod.up(conn)
def test_list_rename_pin_and_prune_keeps_pins(tmp_path):
conn = _conn(tmp_path)
repo = ChatRepository(conn)
first = repo.create(title="Hats")
repo.append(first["id"], [{"kind": "user", "text": "make hats", "sent": "make hats"}],
meta={})
before = repo.get(first["id"])["updated_at"]
repo.update(first["id"], title="Hat ideas", pinned=True)
got = repo.get(first["id"])
assert got["title"] == "Hat ideas" and got["pinned"] is True
# Renaming and pinning aren't activity.
assert got["updated_at"] == before
for i in range(MAX_AI_CHATS + 3):
repo.create(title=f"c{i}")
rows = repo.list()
assert rows[0]["id"] == first["id"] and rows[0]["turns"] == 1
assert len(rows) == MAX_AI_CHATS + 1
assert [r["id"] for r in repo.list("make hat")] == [first["id"]]
assert repo.list("100%") == []
def test_truncate_drops_the_tail_only(tmp_path):
conn = _conn(tmp_path)
repo = ChatRepository(conn)
chat = repo.create(title="t")
repo.append(chat["id"], [{"kind": "user", "text": str(i)} for i in range(5)], meta={})
repo.truncate(chat["id"], 3, meta={"x": 1})
assert [e["text"] for e in repo.messages(chat["id"])] == ["0", "1", "2"]
repo.truncate(chat["id"], 10, meta={})
assert len(repo.messages(chat["id"])) == 3
repo.append(chat["id"], [{"kind": "user", "text": "new"}], meta={})
assert repo.messages(chat["id"])[-1]["text"] == "new"