feat(agents): make sources optional and drop the synthetic "Default" source

The sources list used to start with a fake "Default" entry that had no id
and, at run time, meant "no source, skip retrieval". The agent form
pre-selected it, snapped back to it when the last source was deselected,
and refused to publish without it, so a new agent always looked like it
had a knowledge base when it had none.

Backend
- /api/sources returns only ingested sources; no placeholder row.
- Publishing an agent no longer requires a source on create or update.
  The legacy "default" value is still accepted and maps to NULL.

Frontend
- The agent source picker starts empty, can be cleared, and shows a hint
  that a source-less agent answers from the model and its tools only.
- The picker groups sources into "Your sources" and "Shared with team"
  when any team-shared source exists, shows "N sources selected" for a
  multi-selection, and gets the same "Go to Sources" / "Upload new"
  footer as the chat picker. A source uploaded from the form is selected
  when it lands.
- Source selection serialisation and the picker id live in one helper
  shared with the chat picker; the four copies in the form are gone.
- The client no longer seeds a placeholder source in the store, and the
  dead auto-select of a "default" document is removed.
This commit is contained in:
Alex committed 2026-09-09 17:59:08 +01:00
1 parent fb15649a3f
commit 79d418f32a
23 files changed
+532 -365

No files matched your search

+17 -3
View File
@@ -80,7 +80,9 @@ class TestCombinedJson:
response = CombinedJson().get()
assert response.status_code == 401
def test_returns_default_plus_user_sources(self, app, pg_conn):
def test_returns_only_real_user_sources(self, app, pg_conn):
"""No synthetic "Default" row: every entry is an ingested source
with an id, so clients never have to special-case a placeholder."""
from docsgpt.api.user.sources.routes import CombinedJson
user = "u-list-sources"
@@ -93,8 +95,20 @@ class TestCombinedJson:
assert response.status_code == 200
names = [d["name"] for d in response.json]
assert "Default" in names
assert "doc1" in names
assert "Default" not in names
assert names == ["doc1"]
assert all(d.get("id") for d in response.json)
def test_returns_empty_list_when_user_has_no_sources(self, app, pg_conn):
from docsgpt.api.user.sources.routes import CombinedJson
with _patch_db(pg_conn), app.test_request_context("/api/sources"):
from flask import request
request.decoded_token = {"sub": "u-no-sources"}
response = CombinedJson().get()
assert response.status_code == 200
assert response.json == []
def test_db_error_returns_400(self, app):
from docsgpt.api.user.sources.routes import CombinedJson