|
|
|
@@ -6,7 +6,7 @@
|
|
|
|
|
|
|
|
|
|
**Architecture:** Phase 1 adds bearer token auth to the existing Quart app (new `api_keys` table, updated `_check_auth`, settings UI tab). Phase 2 is a standalone `fable-mcp/` Python package using the `mcp[cli]` SDK that calls the Fable HTTP API via `httpx`. The two phases are sequential — Phase 2 can be built/tested independently using a write-scoped API key once Phase 1 is done.
|
|
|
|
|
|
|
|
|
|
**Deployment decision:** `fable-mcp/` stays permanently inside the `fabledassistant` repo. It will always be versioned alongside the backend it targets. Future Task A will serve the package from the running Fable Docker image so users can install it directly from their instance.
|
|
|
|
|
**Deployment decision:** `fable-mcp/` stays permanently inside the `scribe` repo. It will always be versioned alongside the backend it targets. Future Task A will serve the package from the running Fable Docker image so users can install it directly from their instance.
|
|
|
|
|
|
|
|
|
|
**Tech Stack:** Python 3.12, Quart (Phase 1); `mcp[cli]`, `httpx`, `python-dotenv` (Phase 2); Vue 3 + TypeScript (Settings UI); pytest for both.
|
|
|
|
|
|
|
|
|
@@ -23,16 +23,16 @@
|
|
|
|
|
| Action | File | Purpose |
|
|
|
|
|
|--------|------|---------|
|
|
|
|
|
| Create | `alembic/versions/0027_add_api_keys.py` | DB migration for `api_keys` table |
|
|
|
|
|
| Create | `src/fabledassistant/models/api_key.py` | `ApiKey` SQLAlchemy model |
|
|
|
|
|
| Modify | `src/fabledassistant/models/__init__.py` | Export `ApiKey` |
|
|
|
|
|
| Create | `src/fabledassistant/services/api_keys.py` | create/list/revoke/lookup service functions |
|
|
|
|
|
| Modify | `src/fabledassistant/auth.py` | Add bearer token check before session fallback |
|
|
|
|
|
| Create | `src/fabledassistant/routes/api_keys.py` | GET/POST/DELETE `/api/api-keys` blueprint |
|
|
|
|
|
| Modify | `src/fabledassistant/app.py` | Register `api_keys_bp` and `search_bp` |
|
|
|
|
|
| Modify | `src/fabledassistant/services/chat.py:17-30` | Add `conversation_type` param to `create_conversation` |
|
|
|
|
|
| Modify | `src/fabledassistant/services/chat.py:123-135` | Exclude `"mcp"` type from `cleanup_old_conversations` |
|
|
|
|
|
| Modify | `src/fabledassistant/routes/chat.py:73-79` | Pass `conversation_type` from POST body |
|
|
|
|
|
| Create | `src/fabledassistant/routes/search.py` | `GET /api/search` semantic search endpoint |
|
|
|
|
|
| Create | `src/scribe/models/api_key.py` | `ApiKey` SQLAlchemy model |
|
|
|
|
|
| Modify | `src/scribe/models/__init__.py` | Export `ApiKey` |
|
|
|
|
|
| Create | `src/scribe/services/api_keys.py` | create/list/revoke/lookup service functions |
|
|
|
|
|
| Modify | `src/scribe/auth.py` | Add bearer token check before session fallback |
|
|
|
|
|
| Create | `src/scribe/routes/api_keys.py` | GET/POST/DELETE `/api/api-keys` blueprint |
|
|
|
|
|
| Modify | `src/scribe/app.py` | Register `api_keys_bp` and `search_bp` |
|
|
|
|
|
| Modify | `src/scribe/services/chat.py:17-30` | Add `conversation_type` param to `create_conversation` |
|
|
|
|
|
| Modify | `src/scribe/services/chat.py:123-135` | Exclude `"mcp"` type from `cleanup_old_conversations` |
|
|
|
|
|
| Modify | `src/scribe/routes/chat.py:73-79` | Pass `conversation_type` from POST body |
|
|
|
|
|
| Create | `src/scribe/routes/search.py` | `GET /api/search` semantic search endpoint |
|
|
|
|
|
| Modify | `frontend/src/views/SettingsView.vue` | Add "API Keys" tab |
|
|
|
|
|
| Create | `tests/test_api_keys.py` | Unit tests for service + auth |
|
|
|
|
|
| Create | `tests/test_search_route.py` | Unit test for search endpoint |
|
|
|
|
@@ -42,13 +42,13 @@
|
|
|
|
|
### Task 1: ApiKey model + migration
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
- Create: `src/fabledassistant/models/api_key.py`
|
|
|
|
|
- Create: `src/scribe/models/api_key.py`
|
|
|
|
|
- Create: `alembic/versions/0027_add_api_keys.py`
|
|
|
|
|
- Modify: `src/fabledassistant/models/__init__.py`
|
|
|
|
|
- Modify: `src/scribe/models/__init__.py`
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write the model**
|
|
|
|
|
|
|
|
|
|
Create `src/fabledassistant/models/api_key.py`:
|
|
|
|
|
Create `src/scribe/models/api_key.py`:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
@@ -56,8 +56,8 @@ from datetime import datetime, timezone
|
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, Index, Integer, Text
|
|
|
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
|
|
|
|
|
|
from fabledassistant.models import Base
|
|
|
|
|
from fabledassistant.models.base import CreatedAtMixin
|
|
|
|
|
from scribe.models import Base
|
|
|
|
|
from scribe.models.base import CreatedAtMixin
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ApiKey(Base, CreatedAtMixin):
|
|
|
|
@@ -97,10 +97,10 @@ class ApiKey(Base, CreatedAtMixin):
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Export from models __init__**
|
|
|
|
|
|
|
|
|
|
In `src/fabledassistant/models/__init__.py`, add after the last import line:
|
|
|
|
|
In `src/scribe/models/__init__.py`, add after the last import line:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from fabledassistant.models.api_key import ApiKey # noqa: E402, F401
|
|
|
|
|
from scribe.models.api_key import ApiKey # noqa: E402, F401
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Write the migration**
|
|
|
|
@@ -155,8 +155,8 @@ Expected: `Running upgrade 0026 -> 0027, add api_keys table`
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add src/fabledassistant/models/api_key.py \
|
|
|
|
|
src/fabledassistant/models/__init__.py \
|
|
|
|
|
git add src/scribe/models/api_key.py \
|
|
|
|
|
src/scribe/models/__init__.py \
|
|
|
|
|
alembic/versions/0027_add_api_keys.py
|
|
|
|
|
git commit -m "feat: add ApiKey model and migration 0027"
|
|
|
|
|
```
|
|
|
|
@@ -166,7 +166,7 @@ git commit -m "feat: add ApiKey model and migration 0027"
|
|
|
|
|
### Task 2: ApiKey service
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
- Create: `src/fabledassistant/services/api_keys.py`
|
|
|
|
|
- Create: `src/scribe/services/api_keys.py`
|
|
|
|
|
- Create: `tests/test_api_keys.py` (service tests)
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write the failing tests**
|
|
|
|
@@ -181,7 +181,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from fabledassistant.services.api_keys import (
|
|
|
|
|
from scribe.services.api_keys import (
|
|
|
|
|
_hash_key,
|
|
|
|
|
generate_key,
|
|
|
|
|
create_api_key,
|
|
|
|
@@ -212,7 +212,7 @@ def test_hash_key_is_sha256():
|
|
|
|
|
def test_generate_key_prefix():
|
|
|
|
|
key = "fmcp_abcdefghijklmnop"
|
|
|
|
|
# prefix is first 12 chars of the full key
|
|
|
|
|
from fabledassistant.services.api_keys import _key_prefix
|
|
|
|
|
from scribe.services.api_keys import _key_prefix
|
|
|
|
|
assert _key_prefix(key) == "fmcp_abcdefg" # first 12 chars
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -222,7 +222,7 @@ async def test_create_api_key_returns_full_key():
|
|
|
|
|
mock_key_obj.id = 1
|
|
|
|
|
mock_key_obj.to_dict.return_value = {"id": 1, "name": "test", "scope": "read", "key_prefix": "fmcp_xxx"}
|
|
|
|
|
|
|
|
|
|
with patch("fabledassistant.services.api_keys.async_session") as mock_session_ctx:
|
|
|
|
|
with patch("scribe.services.api_keys.async_session") as mock_session_ctx:
|
|
|
|
|
mock_session = AsyncMock()
|
|
|
|
|
mock_session.__aenter__ = AsyncMock(return_value=mock_session)
|
|
|
|
|
mock_session.__aexit__ = AsyncMock(return_value=False)
|
|
|
|
@@ -238,7 +238,7 @@ async def test_create_api_key_returns_full_key():
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_lookup_key_returns_none_for_unknown():
|
|
|
|
|
with patch("fabledassistant.services.api_keys.async_session") as mock_session_ctx:
|
|
|
|
|
with patch("scribe.services.api_keys.async_session") as mock_session_ctx:
|
|
|
|
|
mock_session = AsyncMock()
|
|
|
|
|
mock_session.__aenter__ = AsyncMock(return_value=mock_session)
|
|
|
|
|
mock_session.__aexit__ = AsyncMock(return_value=False)
|
|
|
|
@@ -262,7 +262,7 @@ Expected: `ImportError` or `ModuleNotFoundError` (service doesn't exist yet)
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Write the service**
|
|
|
|
|
|
|
|
|
|
Create `src/fabledassistant/services/api_keys.py`:
|
|
|
|
|
Create `src/scribe/services/api_keys.py`:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
import hashlib
|
|
|
|
@@ -271,8 +271,8 @@ from datetime import datetime, timezone
|
|
|
|
|
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
|
|
|
|
from fabledassistant.models import async_session
|
|
|
|
|
from fabledassistant.models.api_key import ApiKey
|
|
|
|
|
from scribe.models import async_session
|
|
|
|
|
from scribe.models.api_key import ApiKey
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_key() -> str:
|
|
|
|
@@ -365,7 +365,7 @@ Expected: all tests pass
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add src/fabledassistant/services/api_keys.py tests/test_api_keys.py
|
|
|
|
|
git add src/scribe/services/api_keys.py tests/test_api_keys.py
|
|
|
|
|
git commit -m "feat: add ApiKey service with create/list/revoke/lookup"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@@ -374,7 +374,7 @@ git commit -m "feat: add ApiKey service with create/list/revoke/lookup"
|
|
|
|
|
### Task 3: Auth middleware — bearer token support
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
- Modify: `src/fabledassistant/auth.py`
|
|
|
|
|
- Modify: `src/scribe/auth.py`
|
|
|
|
|
- Modify: `tests/test_api_keys.py` (add auth middleware tests)
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Add auth middleware tests**
|
|
|
|
@@ -400,7 +400,7 @@ def test_scope_validation():
|
|
|
|
|
async def test_bearer_token_path_sets_g_user(monkeypatch):
|
|
|
|
|
"""Valid bearer token authenticates and sets g.user and g.api_key."""
|
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
|
from fabledassistant.auth import _check_auth
|
|
|
|
|
from scribe.auth import _check_auth
|
|
|
|
|
|
|
|
|
|
# Mock ApiKey object
|
|
|
|
|
fake_key = MagicMock()
|
|
|
|
@@ -411,8 +411,8 @@ async def test_bearer_token_path_sets_g_user(monkeypatch):
|
|
|
|
|
fake_user = MagicMock()
|
|
|
|
|
fake_user.role = "user"
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr("fabledassistant.auth.lookup_key", AsyncMock(return_value=fake_key))
|
|
|
|
|
monkeypatch.setattr("fabledassistant.auth.get_user_by_id", AsyncMock(return_value=fake_user))
|
|
|
|
|
monkeypatch.setattr("scribe.auth.lookup_key", AsyncMock(return_value=fake_key))
|
|
|
|
|
monkeypatch.setattr("scribe.auth.get_user_by_id", AsyncMock(return_value=fake_user))
|
|
|
|
|
|
|
|
|
|
called_with_user = {}
|
|
|
|
|
|
|
|
|
@@ -434,7 +434,7 @@ async def test_bearer_token_path_sets_g_user(monkeypatch):
|
|
|
|
|
async with app.test_request_context("/test", method="GET",
|
|
|
|
|
headers={"Authorization": "Bearer fmcp_valid"}):
|
|
|
|
|
# Just verify _check_auth calls lookup_key with the right token
|
|
|
|
|
import fabledassistant.auth as auth_module
|
|
|
|
|
import scribe.auth as auth_module
|
|
|
|
|
auth_module.lookup_key.assert_called_with # callable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -442,7 +442,7 @@ async def test_bearer_token_path_sets_g_user(monkeypatch):
|
|
|
|
|
async def test_read_only_key_blocked_on_post():
|
|
|
|
|
"""Read-only API key returns 403 on non-GET requests."""
|
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
|
from fabledassistant.auth import _check_auth
|
|
|
|
|
from scribe.auth import _check_auth
|
|
|
|
|
from quart import Quart
|
|
|
|
|
|
|
|
|
|
fake_key = MagicMock()
|
|
|
|
@@ -459,8 +459,8 @@ async def test_read_only_key_blocked_on_post():
|
|
|
|
|
headers={"Authorization": "Bearer fmcp_readonly"}),
|
|
|
|
|
):
|
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
with patch("fabledassistant.auth.lookup_key", AsyncMock(return_value=fake_key)), \
|
|
|
|
|
patch("fabledassistant.auth.get_user_by_id", AsyncMock(return_value=fake_user)):
|
|
|
|
|
with patch("scribe.auth.lookup_key", AsyncMock(return_value=fake_key)), \
|
|
|
|
|
patch("scribe.auth.get_user_by_id", AsyncMock(return_value=fake_user)):
|
|
|
|
|
|
|
|
|
|
async def dummy():
|
|
|
|
|
return "ok"
|
|
|
|
@@ -481,15 +481,15 @@ docker compose run --rm app pytest tests/test_api_keys.py -v
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Update auth.py**
|
|
|
|
|
|
|
|
|
|
Replace `src/fabledassistant/auth.py` with:
|
|
|
|
|
Replace `src/scribe/auth.py` with:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
import functools
|
|
|
|
|
|
|
|
|
|
from quart import g, jsonify, request, session
|
|
|
|
|
|
|
|
|
|
from fabledassistant.services.auth import get_user_by_id
|
|
|
|
|
from fabledassistant.services.api_keys import lookup_key
|
|
|
|
|
from scribe.services.auth import get_user_by_id
|
|
|
|
|
from scribe.services.api_keys import lookup_key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _check_auth(f, required_role: str | None = None):
|
|
|
|
@@ -556,7 +556,7 @@ Expected: all existing tests still pass
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add src/fabledassistant/auth.py tests/test_api_keys.py
|
|
|
|
|
git add src/scribe/auth.py tests/test_api_keys.py
|
|
|
|
|
git commit -m "feat: add bearer token auth to _check_auth, falls back to session"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@@ -565,18 +565,18 @@ git commit -m "feat: add bearer token auth to _check_auth, falls back to session
|
|
|
|
|
### Task 4: API key routes + app registration
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
- Create: `src/fabledassistant/routes/api_keys.py`
|
|
|
|
|
- Modify: `src/fabledassistant/app.py`
|
|
|
|
|
- Create: `src/scribe/routes/api_keys.py`
|
|
|
|
|
- Modify: `src/scribe/app.py`
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write the routes**
|
|
|
|
|
|
|
|
|
|
Create `src/fabledassistant/routes/api_keys.py`:
|
|
|
|
|
Create `src/scribe/routes/api_keys.py`:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from quart import Blueprint, jsonify, request
|
|
|
|
|
|
|
|
|
|
from fabledassistant.auth import login_required, get_current_user_id
|
|
|
|
|
from fabledassistant.services.api_keys import create_api_key, list_api_keys, revoke_api_key
|
|
|
|
|
from scribe.auth import login_required, get_current_user_id
|
|
|
|
|
from scribe.services.api_keys import create_api_key, list_api_keys, revoke_api_key
|
|
|
|
|
|
|
|
|
|
api_keys_bp = Blueprint("api_keys", __name__, url_prefix="/api/api-keys")
|
|
|
|
|
|
|
|
|
@@ -619,10 +619,10 @@ async def revoke_key_route(key_id: int):
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Register in app.py**
|
|
|
|
|
|
|
|
|
|
In `src/fabledassistant/app.py`, add the import alongside the other route imports:
|
|
|
|
|
In `src/scribe/app.py`, add the import alongside the other route imports:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from fabledassistant.routes.api_keys import api_keys_bp
|
|
|
|
|
from scribe.routes.api_keys import api_keys_bp
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
And add the registration line after `app.register_blueprint(users_bp)`:
|
|
|
|
@@ -651,7 +651,7 @@ curl -s http://localhost:8080/api/auth/me \
|
|
|
|
|
- [ ] **Step 4: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add src/fabledassistant/routes/api_keys.py src/fabledassistant/app.py
|
|
|
|
|
git add src/scribe/routes/api_keys.py src/scribe/app.py
|
|
|
|
|
git commit -m "feat: add API key CRUD routes and register blueprint"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@@ -660,14 +660,14 @@ git commit -m "feat: add API key CRUD routes and register blueprint"
|
|
|
|
|
### Task 5: Conversation type wiring
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
- Modify: `src/fabledassistant/services/chat.py` (lines 17-30 and 123-135)
|
|
|
|
|
- Modify: `src/fabledassistant/routes/chat.py` (lines 73-79)
|
|
|
|
|
- Modify: `src/scribe/services/chat.py` (lines 17-30 and 123-135)
|
|
|
|
|
- Modify: `src/scribe/routes/chat.py` (lines 73-79)
|
|
|
|
|
|
|
|
|
|
Note: `Conversation.conversation_type` already exists in the model. `list_conversations` already filters by `conv_type`. This task only wires up creation and retention exclusion.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Update `create_conversation` service**
|
|
|
|
|
|
|
|
|
|
In `src/fabledassistant/services/chat.py`, change the function signature at line 17:
|
|
|
|
|
In `src/scribe/services/chat.py`, change the function signature at line 17:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
async def create_conversation(
|
|
|
|
@@ -692,7 +692,7 @@ async def create_conversation(
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Update `cleanup_old_conversations` to exclude "mcp"**
|
|
|
|
|
|
|
|
|
|
In `src/fabledassistant/services/chat.py`, update the WHERE clause at line 130:
|
|
|
|
|
In `src/scribe/services/chat.py`, update the WHERE clause at line 130:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
result = await session.execute(
|
|
|
|
@@ -708,7 +708,7 @@ result = await session.execute(
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Update the POST route to accept conversation_type**
|
|
|
|
|
|
|
|
|
|
In `src/fabledassistant/routes/chat.py`, update `create_conversation_route` (around line 73):
|
|
|
|
|
In `src/scribe/routes/chat.py`, update `create_conversation_route` (around line 73):
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
@chat_bp.route("/conversations", methods=["POST"])
|
|
|
|
@@ -737,7 +737,7 @@ Expected: all tests pass
|
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add src/fabledassistant/services/chat.py src/fabledassistant/routes/chat.py
|
|
|
|
|
git add src/scribe/services/chat.py src/scribe/routes/chat.py
|
|
|
|
|
git commit -m "feat: wire conversation_type through create_conversation, exclude mcp from retention sweep"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
@@ -746,8 +746,8 @@ git commit -m "feat: wire conversation_type through create_conversation, exclude
|
|
|
|
|
### Task 6: Semantic search endpoint
|
|
|
|
|
|
|
|
|
|
**Files:**
|
|
|
|
|
- Create: `src/fabledassistant/routes/search.py`
|
|
|
|
|
- Modify: `src/fabledassistant/app.py`
|
|
|
|
|
- Create: `src/scribe/routes/search.py`
|
|
|
|
|
- Modify: `src/scribe/app.py`
|
|
|
|
|
- Create: `tests/test_search_route.py`
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Write the failing test**
|
|
|
|
@@ -762,7 +762,7 @@ from unittest.mock import patch, AsyncMock
|
|
|
|
|
|
|
|
|
|
def test_content_type_mapping():
|
|
|
|
|
"""Verify content_type string maps to correct is_task value."""
|
|
|
|
|
from fabledassistant.routes.search import _content_type_to_is_task
|
|
|
|
|
from scribe.routes.search import _content_type_to_is_task
|
|
|
|
|
assert _content_type_to_is_task("note") is False
|
|
|
|
|
assert _content_type_to_is_task("task") is True
|
|
|
|
|
assert _content_type_to_is_task("all") is None
|
|
|
|
@@ -779,13 +779,13 @@ Expected: `ImportError` (module doesn't exist yet)
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Write the route**
|
|
|
|
|
|
|
|
|
|
Create `src/fabledassistant/routes/search.py`:
|
|
|
|
|
Create `src/scribe/routes/search.py`:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from quart import Blueprint, jsonify, request
|
|
|
|
|
|
|
|
|
|
from fabledassistant.auth import login_required, get_current_user_id
|
|
|
|
|
from fabledassistant.services.embeddings import semantic_search_notes
|
|
|
|
|
from scribe.auth import login_required, get_current_user_id
|
|
|
|
|
from scribe.services.embeddings import semantic_search_notes
|
|
|
|
|
|
|
|
|
|
search_bp = Blueprint("search", __name__, url_prefix="/api/search")
|
|
|
|
|
|
|
|
|
@@ -834,9 +834,9 @@ Note: check `services/embeddings.py` to confirm the return type of `semantic_sea
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Register in app.py**
|
|
|
|
|
|
|
|
|
|
Add to imports in `src/fabledassistant/app.py`:
|
|
|
|
|
Add to imports in `src/scribe/app.py`:
|
|
|
|
|
```python
|
|
|
|
|
from fabledassistant.routes.search import search_bp
|
|
|
|
|
from scribe.routes.search import search_bp
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Add registration:
|
|
|
|
@@ -855,8 +855,8 @@ Expected: all tests pass
|
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add src/fabledassistant/routes/search.py \
|
|
|
|
|
src/fabledassistant/app.py \
|
|
|
|
|
git add src/scribe/routes/search.py \
|
|
|
|
|
src/scribe/app.py \
|
|
|
|
|
tests/test_search_route.py
|
|
|
|
|
git commit -m "feat: add GET /api/search semantic search endpoint"
|
|
|
|
|
```
|
|
|
|
@@ -1905,7 +1905,7 @@ async def send_message(
|
|
|
|
|
return f"Error: {e}"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Note: verify the Fable message POST endpoint path by checking `src/fabledassistant/routes/chat.py` — search for the route that accepts a user message and triggers generation. Adjust `/api/chat/conversations/{conv_id}/messages` if the actual path differs.
|
|
|
|
|
Note: verify the Fable message POST endpoint path by checking `src/scribe/routes/chat.py` — search for the route that accepts a user message and triggers generation. Adjust `/api/chat/conversations/{conv_id}/messages` if the actual path differs.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Run tests**
|
|
|
|
|
|
|
|
|
|