feat(webui): upgrade settings and sidebar controls (#3906)
* feat(settings): expand settings api payload * feat(webui): build app-style settings center * feat(webui): add centered chat search dialog * fix(webui): shorten chat search label * fix(webui): center dialog entrance animation * fix(webui): simplify chat search results * fix(webui): tighten mobile settings navigation * feat(webui): persist sidebar state * feat(webui): add sidebar organization controls * refactor(webui): organize backend helpers * refactor(webui): remove utils compatibility shims * refactor(session): move shared webui helpers out of webui package * feat(webui): add image generation settings * style(webui): refine settings overview layout * fix(webui): localize settings zh-CN copy * style(webui): add settings status indicators * feat(webui): show sidebar run indicators * fix(webui): persist sidebar run indicators * fix(webui): highlight settings pending status * fix(webui): align settings test with provider update * fix(utils): preserve legacy webui helper imports
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import importlib
|
||||
|
||||
from nanobot.session import webui_turns
|
||||
from nanobot.webui import thread_disk, transcript
|
||||
|
||||
|
||||
def test_legacy_webui_utils_imports_resolve_to_new_modules() -> None:
|
||||
legacy_thread_disk = importlib.import_module("nanobot.utils.webui_thread_disk")
|
||||
legacy_transcript = importlib.import_module("nanobot.utils.webui_transcript")
|
||||
legacy_turn_helpers = importlib.import_module("nanobot.utils.webui_turn_helpers")
|
||||
|
||||
assert legacy_thread_disk.delete_webui_thread is thread_disk.delete_webui_thread
|
||||
assert legacy_transcript.append_transcript_object is transcript.append_transcript_object
|
||||
assert legacy_turn_helpers.mark_webui_session is webui_turns.mark_webui_session
|
||||
@@ -0,0 +1,73 @@
|
||||
import json
|
||||
|
||||
from nanobot.webui.sidebar_state import (
|
||||
default_webui_sidebar_state,
|
||||
read_webui_sidebar_state,
|
||||
webui_sidebar_state_path,
|
||||
write_webui_sidebar_state,
|
||||
)
|
||||
|
||||
|
||||
def test_sidebar_state_defaults_when_file_missing(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
|
||||
state = read_webui_sidebar_state()
|
||||
|
||||
assert state == default_webui_sidebar_state()
|
||||
assert webui_sidebar_state_path() == tmp_path / "webui" / "sidebar-state.json"
|
||||
|
||||
|
||||
def test_sidebar_state_normalizes_old_or_partial_payload(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
path = webui_sidebar_state_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"pinned_keys": ["websocket:a", "websocket:a", "", 123],
|
||||
"archived_keys": ["websocket:b"],
|
||||
"title_overrides": {"websocket:a": " Release notes ", "bad": ""},
|
||||
"tags_by_key": {"websocket:a": ["work", "work", ""]},
|
||||
"collapsed_groups": {"Earlier": 1},
|
||||
"view": {"density": "tiny", "show_archived": True, "sort": "nope"},
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
state = read_webui_sidebar_state()
|
||||
|
||||
assert state["schema_version"] == 1
|
||||
assert state["pinned_keys"] == ["websocket:a"]
|
||||
assert state["archived_keys"] == ["websocket:b"]
|
||||
assert state["title_overrides"] == {"websocket:a": "Release notes"}
|
||||
assert state["tags_by_key"] == {"websocket:a": ["work"]}
|
||||
assert state["collapsed_groups"] == {"Earlier": True}
|
||||
assert state["view"] == {
|
||||
"density": "comfortable",
|
||||
"show_previews": False,
|
||||
"show_timestamps": False,
|
||||
"show_archived": True,
|
||||
"sort": "updated_desc",
|
||||
}
|
||||
|
||||
|
||||
def test_sidebar_state_write_is_scoped_to_config_data_dir(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
|
||||
state = write_webui_sidebar_state(
|
||||
{
|
||||
"pinned_keys": ["websocket:a"],
|
||||
"archived_keys": ["websocket:b"],
|
||||
"title_overrides": {"websocket:a": "Release"},
|
||||
"view": {"density": "compact", "show_previews": True},
|
||||
}
|
||||
)
|
||||
|
||||
assert state["pinned_keys"] == ["websocket:a"]
|
||||
assert state["archived_keys"] == ["websocket:b"]
|
||||
assert state["title_overrides"] == {"websocket:a": "Release"}
|
||||
assert state["view"]["density"] == "compact"
|
||||
assert state["view"]["show_previews"] is True
|
||||
assert webui_sidebar_state_path().is_file()
|
||||
assert read_webui_sidebar_state()["pinned_keys"] == ["websocket:a"]
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from nanobot.utils.webui_thread_disk import delete_webui_thread, webui_thread_file_path
|
||||
from nanobot.utils.webui_transcript import append_transcript_object, webui_transcript_path
|
||||
from nanobot.webui.thread_disk import delete_webui_thread, webui_thread_file_path
|
||||
from nanobot.webui.transcript import append_transcript_object, webui_transcript_path
|
||||
|
||||
|
||||
def test_delete_webui_thread_removes_legacy_json_and_transcript(tmp_path, monkeypatch) -> None:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from nanobot.utils.webui_transcript import (
|
||||
from nanobot.webui.transcript import (
|
||||
WEBUI_TRANSCRIPT_SCHEMA_VERSION,
|
||||
append_transcript_object,
|
||||
read_transcript_lines,
|
||||
@@ -294,7 +294,7 @@ def test_replay_keeps_new_file_edit_after_reasoning_in_order(tmp_path, monkeypat
|
||||
|
||||
|
||||
def test_build_response_schema(monkeypatch, tmp_path) -> None:
|
||||
from nanobot.utils.webui_transcript import build_webui_thread_response
|
||||
from nanobot.webui.transcript import build_webui_thread_response
|
||||
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
key = "websocket:t3"
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import AsyncMock, MagicMock
|
||||
import pytest
|
||||
|
||||
from nanobot.bus.events import InboundMessage
|
||||
from nanobot.utils import webui_turn_helpers as wth
|
||||
from nanobot.session import webui_turns as wth
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
|
||||
Reference in New Issue
Block a user