From fcb6fc97c557939b8166958f7b12acb26a0dfab9 Mon Sep 17 00:00:00 2001 From: ossirytk Date: Fri, 3 Apr 2026 14:37:58 +0300 Subject: [PATCH 1/2] Web ui refinments --- README.md | 20 +- cards/Shodan-specV2.jpg | Bin 0 -> 81516 bytes core/conversation_manager.py | 11 + core/conversation_prompt_history_mixin.py | 12 + core/rag_manager.py | 30 ++ docs/future_work/COPILOT_COMPACT_REFERENCE.md | 30 +- docs/future_work/REFINEMENTS.md | 156 +++++- docs/future_work/UI_REFINEMENTS.md | 102 +++- scripts/rag/push_rag_data.py | 2 +- templates/diagnostics_panel.html | 81 ++- templates/index.html | 495 +++++++++++++++--- templates/rag/collections_list.html | 41 ++ templates/rag/files_list.html | 70 +++ templates/rag/layout.html | 4 +- templates/rag/upload_result.html | 16 + templates/sessions_search_results.html | 28 + web_app.py | 271 +++++++++- 17 files changed, 1254 insertions(+), 115 deletions(-) create mode 100644 cards/Shodan-specV2.jpg create mode 100644 templates/rag/upload_result.html create mode 100644 templates/sessions_search_results.html diff --git a/README.md b/README.md index 0ef8221..b80f940 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,11 @@ Character-focused local chatbot with RAG support (ChromaDB + LangChain), CLI and ## What It Includes - Local chat runtime backed by `llama-cpp-python` -- Character-card-driven prompting (`cards/*.json`) +- Character-card-driven prompting (`cards/*.json`) with avatar display - RAG retrieval from ChromaDB collections - Dynamic context budgeting and history management - GPU offload auto-layer calculation and KV cache quant support +- Web UI (FastAPI + Jinja2 + HTMX): chat, session management, RAG management, diagnostics - Scripted workflows for analyzing, pushing, and managing RAG data ## Current Runtime Entry Points @@ -75,9 +76,20 @@ Notes for web chat behavior: - Shows status updates (`Ready`, `Sending`, `Thinking`, `Streaming`, `Timed out`). - Applies a stream timeout and surfaces a `Retry` button on stream failure. -- Supports named session save + explicit session picker load in the sidebar. -- Shows both latest retrieval debug stats and per-turn retrieval trace history. -- Provides quick actions for copy/export and command-equivalent controls (`clear`, `reload`, `help`). +- Sidebar has three tabs: **Character** (avatar + card info), **Sessions** (save/load/search), **Debug** (per-turn retrieval trace + diagnostics). +- Named session save/load and full-text session search with character and date filters. +- Token budget bar in the Diagnostics tab shows real-time context-window allocation (system / history / RAG / examples / input / reserved / free). +- Per-turn stats: estimated prompt and completion tokens, context window fill %, RAG chunks used. +- Quick actions for copy/export (TXT, JSON, ZIP bundle) and command-equivalent controls (`clear`, `reload`, `help`). +- Saveable preset profiles for retrieval settings (MMR, rerank, multi-query, k values). + +RAG management UI at **`/rag`** (link in the chat sidebar): + +- Upload new source files (`.txt`) and create ChromaDB collections directly from the browser. +- View, lint, and run coverage analysis on `rag_data/` files. +- List, query, rebuild, and delete collections. +- Run fixture evaluations and view retrieval trend history. +- View embedding benchmark results. ## Setup diff --git a/cards/Shodan-specV2.jpg b/cards/Shodan-specV2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eb3c3b5f8e58900fca7d0ffadc3463d9438188c1 GIT binary patch literal 81516 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

LmeELZYKVAyq(rZou08y~cNS^m#Uuwx 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 None: "mes": {"mode": "unknown", "returned": 0, "candidates": 0, "queries": 0, "rerank_applied": False}, "cleanup": {"main": 0, "mes": 0, "cross_removed": 0}, } + self.last_token_budget: dict[str, int] = { + "system_prompt_tokens": 0, + "history_tokens": 0, + "rag_tokens": 0, + "examples_tokens": 0, + "input_tokens": 0, + "total_estimated": 0, + "context_window": 0, + "available_for_context": 0, + "reserved_for_response": 0, + } self._vector_client: object | None = None self._vector_embedder: object | None = None self._cross_encoder: object | None = None diff --git a/core/conversation_prompt_history_mixin.py b/core/conversation_prompt_history_mixin.py index 8720927..6bbde69 100644 --- a/core/conversation_prompt_history_mixin.py +++ b/core/conversation_prompt_history_mixin.py @@ -186,6 +186,18 @@ def _prepare_dynamic_vector_context(self, message: str, mes_example: str) -> tup vector_context = str(allocation["allocated_context"]) allocated_history = str(allocation["allocated_history"]) + self.last_token_budget = { + "system_prompt_tokens": budget.system_prompt_tokens, + "history_tokens": int(allocation["history_tokens"]), + "rag_tokens": int(allocation["context_tokens"]), + "examples_tokens": int(allocation["examples_tokens"]), + "input_tokens": int(allocation["input_tokens"]), + "total_estimated": int(allocation["total_allocated"]) + budget.system_prompt_tokens, + "context_window": budget.total_context, + "available_for_context": budget.available_for_context, + "reserved_for_response": budget.reserved_for_response, + } + if self.runtime_config.debug_context: logger.debug(self.context_manager.get_context_info(budget, allocation)) diff --git a/core/rag_manager.py b/core/rag_manager.py index dc0be54..f208a76 100644 --- a/core/rag_manager.py +++ b/core/rag_manager.py @@ -204,6 +204,36 @@ def file_content(config: RagScriptConfig, filename: str) -> str | None: return candidate.read_text(encoding="utf-8") +def save_rag_file(config: RagScriptConfig, stem: str, content: bytes) -> dict[str, Any]: + """Save *content* as ``{stem}.txt`` in the rag_data directory. + + Raises ``ValueError`` if *stem* is invalid. + Returns a file-info dict matching the shape produced by :func:`list_rag_files`. + """ + if not is_valid_stem(stem): + msg = f"Invalid stem {stem!r}: only letters, digits, underscores, and hyphens are allowed." + raise ValueError(msg) + rag_dir = Path(config.documents_directory) + rag_dir.mkdir(parents=True, exist_ok=True) + dest = rag_dir / f"{stem}.txt" + dest.write_bytes(content) + return { + "name": dest.name, + "stem": stem, + "type": "message_examples" if stem.endswith("_message_examples") else "lore", + "size": len(content), + "has_metadata": (rag_dir / f"{stem}.json").exists(), + } + + +def list_rag_stems(config: RagScriptConfig) -> list[str]: + """Return a sorted list of stems for all .txt files in rag_data/.""" + rag_dir = Path(config.documents_directory) + if not rag_dir.exists(): + return [] + return sorted(p.stem for p in rag_dir.glob("*.txt")) + + # --------------------------------------------------------------------------- # Linting # --------------------------------------------------------------------------- diff --git a/docs/future_work/COPILOT_COMPACT_REFERENCE.md b/docs/future_work/COPILOT_COMPACT_REFERENCE.md index 8a8fbe7..83211f5 100644 --- a/docs/future_work/COPILOT_COMPACT_REFERENCE.md +++ b/docs/future_work/COPILOT_COMPACT_REFERENCE.md @@ -1,6 +1,6 @@ # Copilot Compact Reference — Implemented State -Last verified: 2026-03-29 +Last verified: 2026-04-03 Use this as the single compact reference for implemented work across conversation quality, RAG quality, and web app behavior. @@ -143,18 +143,46 @@ Primary files: - **Per-turn diagnostics panel**: collapsible sidebar panel showing Turn, Latency (s), Chars, Main chunks, MES chunks, Cross-removed, and Drift score (colour-coded at warning/fail thresholds) for the last 40 turns. Auto-refreshes after each stream. Route: `GET /chat/diagnostics`. - **Saveable preset profiles**: collapsible sidebar panel for saving/applying/deleting named snapshots of 7 retrieval settings (`use_mmr`, `rag_rerank_enabled`, `rag_sentence_compression_enabled`, `rag_multi_query_enabled`, `rag_k`, `rag_k_mes`, `debug_context`). Profiles persisted in `configs/profiles.json`; applied in-place to the live `ConversationRuntimeConfig` without restart. Routes: `GET/POST /settings/profiles/*`. - **One-click export bundle**: `GET /chat/export/bundle` downloads a ZIP containing `manifest.json`, `conversation.json` (full session), `retrieval_traces.json` (per-turn history), and `drift_history.json`. Button in composer quick-actions. +- **RAG Management UI** (`/rag`): Standalone dark-theme page with left nav. Sections: Collections (list, detail, delete, ad-hoc query, rebuild/push with async job, fingerprint backfill), Files (list, view, lint run/fix, coverage analysis), Evaluate (fixture pack selector, run evaluate-fixtures, results table, retrieval trend history), Benchmark (last-run model comparison table). Long-running ops (push, evaluate) use in-memory `JobStore` + HTMX polling (`every 2s`). Link from chat sidebar. +- **Session history search**: Collapsible "Search sessions" panel inside the Sessions sidebar panel. Searches all saved `logs/web_sessions/session_*.json` files by free text (matches session name and message content), character name filter, and optional date range. Returns matching sessions with inline message excerpts and a Load button. Route: `GET /sessions/search?q=&character=&from_date=&to_date=`. +- **Token budget visualization + per-turn stats** (`/chat/diagnostics`): A stacked colour-coded bar at the top of the Diagnostics panel shows the current context-window allocation split across System prompt, History, RAG context, Examples, User input, Reserved, and Free headroom (green/yellow/red by fill %). The per-turn table now shows estimated Prompt tokens, estimated Completion tokens (chars/4), Context window % fill (colour-coded), and RAG chunks retrieved. A session-totals row below the table shows cumulative prompt/completion tokens and average context %. Backend: `ConversationManager.last_token_budget` dict populated from `ContextBudget` + `allocate_content()` return values in `_prepare_dynamic_vector_context()`; stored per trace in `_record_retrieval_trace`. +- **Character avatar display + tabbed sidebar**: The chat sidebar is restructured into three tabs + — 🎭 Character, 💾 Sessions, 🔍 Debug — with a compact always-visible header showing a small + avatar and character name. The Character tab displays the full avatar image (if present) alongside + card metadata. Route: `GET /characters/avatar` returns the avatar as a `FileResponse`; + `_character_avatar_path()` searches `character_storage//avatar.{ext}` then `cards/.{ext}`. + `has_avatar` bool is passed to the index template context. +- **RAG file upload + create-collection from UI**: The RAG Files page now includes an "Upload + Source File" panel — file picker (`.txt`), auto-filled stem, optional collection name for + immediate ingest. Uploading without a collection name saves the file and refreshes the file list. + With a collection name it triggers a push job. Each lore file row has an "Ingest →" toggle that + reveals an inline form to build a collection from that file. The Collections page has a "Create + New Collection" section with a dropdown of existing file stems. New routes: + `POST /rag/files/upload` (multipart), `POST /rag/collections`. + New backend: `rag_manager.save_rag_file()`, `rag_manager.list_rag_stems()`. +- **Bug fix — creating new ChromaDB collections**: `push_to_collection()` in + `scripts/rag/push_rag_data.py` previously only caught `ValueError` when deleting a non-existent + collection before recreating it. ChromaDB raises `chromadb.errors.NotFoundError` for missing + collections; that exception was uncaught and crashed the entire push. Fixed by widening the + `except` clause to use the already-defined `MISSING_COLLECTION_ERRORS` tuple + (`ValueError | NotFoundError`). This was a latent bug exposed by the first UI-driven + collection creation. Primary files: - `web_app.py` - `main.py` - `core/preset_profiles.py` +- `core/rag_manager.py` (+ `save_rag_file`, `list_rag_stems`, `_character_avatar_path` helpers) +- `core/job_queue.py` +- `scripts/rag/push_rag_data.py` (bug fix: `MISSING_COLLECTION_ERRORS` in `push_to_collection`) - `templates/index.html` - `templates/chat_message_pair.html` - `templates/chat_messages.html` - `templates/chat_single_message.html` - `templates/diagnostics_panel.html` - `templates/presets_panel.html` +- `templates/rag/layout.html` (+ 13 RAG partial templates incl. `upload_result.html`) ## Current Defaults Snapshot diff --git a/docs/future_work/REFINEMENTS.md b/docs/future_work/REFINEMENTS.md index b0c6ff0..c2b06a6 100644 --- a/docs/future_work/REFINEMENTS.md +++ b/docs/future_work/REFINEMENTS.md @@ -17,10 +17,25 @@ Implemented state lives in `docs/future_work/COPILOT_COMPACT_REFERENCE.md`. - ✅ Integrate conversation quality command into a single quality-gate workflow with retrieval and RAG-data checks. (2026-03-26) - ✅ Add CI regression policy for conversation quality baselines (warn vs hard fail by severity). (2026-03-26) - Add docs for fixture authoring rules and baseline refresh workflow. +- **Character name mismatch on first turn (investigate).** On the first message of a session the + persona drift scorer may not match the character name correctly, producing an artificially high + drift score or misfire. Two candidate causes: (1) lazy initialisation — `character_name` may be + empty when the first `PersonaAnchor` is built, as card loading (`parse_prompt`) runs during + `__init__` before all attributes are set; (2) mes_example normalisation — the card linter + normalises `` / `` markers to plain `user:` / `assistant:` format, stripping the + original character name from example turns, which may confuse the heuristic name-match on turn 1. + Investigation steps: add a log line in `_record_retrieval_trace` printing `character_name` and + `drift_score` at turn 1; check whether `PersonaAnchor.character_name` is populated before the + first call; and compare drift scores with and without mes_example injection on turn 1. ### RAG Data Quality -*(All scoped items completed; implemented state is tracked in `docs/future_work/COPILOT_COMPACT_REFERENCE.md`.)* +- **Shodan lore coverage is low.** The `rag_data/` source files for Shodan are sparse relative to the + character's depth. Coverage analysis shows many lore topics unmapped. Work needed: expand lore files + with canonical game text (System Shock 1 & 2 dialogue, environment descriptions, terminal messages), + re-run lint and coverage checks, then rebuild the collection. + +*(Previously scoped items completed; implemented state is tracked in `docs/future_work/COPILOT_COMPACT_REFERENCE.md`.)* ### Retrieval Quality @@ -145,6 +160,134 @@ Each character maintains its own RAG collection and persona drift tracker. A tur **Large effort, Medium value.** Treat as a long-horizon milestone — do not start until §6–8 are stable. The only "Large" effort item in this backlog. +### 11. Character Card Import & Avatar Support + +Improve the character loading pipeline to support richer card formats and give each character a +visible identity in the UI. + +#### 11.1 Character Avatar / Icon Upload + +Each character should have an optional avatar image displayed in the chat UI next to assistant +messages and in the character selector. Implementation: + +- Store avatars in `character_storage//avatar.png` (or `.jpg`, `.webp`). +- Serve via `GET /characters/{name}/avatar` — returns the image, falls back to a generated + initial/monogram placeholder if no avatar is found. +- Web UI: display avatar thumbnail in the chat header and optionally next to each assistant message + bubble. Upload button on the character settings page (see `UI_REFINEMENTS.md §C`). +- Keep the image small (≤ 512 px, ≤ 200 KB) — resize on upload with Pillow. + +#### 11.2 Character Card V2 / V3 Import + +The project currently loads character data from plain JSON files in `cards/`. Extend this to +support importing from standard character card formats used by the wider AI chat ecosystem. + +**Character Card V2 (PNG `chara` tEXt chunk):** +- PNG files with a `chara` tEXt chunk containing base64-encoded JSON (TavernCardV2 format). +- Fields map directly to existing config: `name`, `description`, `scenario`, `mes_example`, + `first_mes`, `personality`, `system_prompt`, `post_history_instructions`, `character_book`. +- Already partially supported via `cards/leonardo_da_vinci.png` — formalise the import path. + +**Character Card V3 (CCv3 — the current community standard):** +- Spec: +- PNG/APNG: JSON embedded in `ccv3` tEXt chunk as UTF-8 → base64. If both `chara` and `ccv3` + chunks are present, prefer `ccv3`. +- CHARX: zip file with `card.json` at root. Assets (icons, backgrounds, emotion sprites) live in + `assets/{type}/` subdirectories and can be accessed via `embeded://path` URIs. +- JSON: plain `.json` file containing the CharacterCardV3 object directly. +- V3 adds: `assets[]` (icon, background, emotion images), `nickname`, `group_only_greetings`, + `creation_date`, `modification_date`, `source[]`, multilingual creator notes. +- **Implementation priority:** PNG V2 import is simplest and highest value (most cards in the wild + are V2 PNG). V3 PNG import is a small additional step. CHARX support can come later. +- Use the `pypng` or `Pillow` library to read tEXt chunks; no heavyweight dependency needed. +- On import: write a normalised JSON card to `cards/` and optionally extract the embedded avatar + to `character_storage//avatar.png`. + +**Suggested implementation order:** +1. Formalise V2 PNG import (read `chara` chunk → normalise → save JSON + avatar). +2. V3 PNG import (read `ccv3` chunk → normalise; fall back to `chara` if absent). +3. Avatar display in web UI (§11.1 + `UI_REFINEMENTS.md §C`). +4. CHARX import (zip extraction + asset handling). +5. In-app card editor (§11.3 below). + +#### 11.3 In-App Character Card Editor + +A web-based form editor for creating and editing character cards without leaving the application. +Several community implementations can be used as reference for field layout and PNG embedding: + +- [ZoltanAI/character-editor](https://github.com/ZoltanAI/character-editor) — lightweight + browser-side editor for V1/V2 cards; entirely static HTML/JS, good reference for field layout. +- SillyTavern's built-in editor supports V2 fields and lorebook editing. +- The [CCv3 spec](https://github.com/kwaroran/character-card-spec-v3/blob/main/SPEC_V3.md) + provides the canonical field reference for a V3-compatible editor. + +**Backend requirements:** + +- `GET /characters/{name}/edit` — load existing card fields into the edit form. +- `POST /characters/{name}/edit` — validate and save edited fields to `cards/.json`. +- `GET /characters/new` / `POST /characters/new` — create a new card from scratch. +- `POST /characters/{name}/export/png` — embed card JSON into a PNG tEXt chunk (`ccv3`) and + return the PNG for download. Uses the stored avatar as the base image. +- `POST /characters/{name}/avatar` — upload a new avatar image (resize to ≤ 512 px with Pillow, + save to `character_storage//avatar.png`). Replaces `UI_REFINEMENTS.md §C.2`. + +**Field coverage (minimum viable):** +`name`, `description`, `scenario`, `personality`, `first_mes`, `mes_example`, +`voice_instructions` (project-specific), `tags`, `creator`, `system_prompt`. +Lorebook / `character_book` editing is out of scope for the initial version. + +**PNG embedding:** +Read tEXt chunks with `struct` (stdlib) or `Pillow`; write `ccv3` chunk (base64-encoded UTF-8 +JSON). Also write a `chara` chunk for backward compatibility with V2 readers. No new heavy +dependencies needed — `Pillow` is already a likely dependency for image resizing. + +**Effort:** Medium. The form and routing are straightforward; the PNG round-trip (read → edit → +re-embed) is the only non-trivial part. Build after §11.1–11.2 so the parsing layer is shared. + +## §12 User-Facing Documentation Site + +A non-technical, friendly guide for people who want to use light-chat without programming knowledge, +hosted alongside the repository as a static site. + +### Motivation + +The web UI has grown substantially (chat, RAG management, diagnostics, session search, character +management). Many features have in-UI help text, but there is no cohesive end-user reference. +A dedicated documentation site lowers the barrier to entry and helps non-technical users +understand what the tool does and how to use it. + +### Hosting options (all compatible with GitHub Pages) + +| Option | Notes | +|--------|-------| +| **MkDocs Material** (recommended) | Python-based, clean modern theme, markdown source. Fits the project's Python tooling; `mkdocs gh-deploy` publishes to GitHub Pages. Add as a `uv` dev dependency. | +| **Docsify** | Single HTML file + plain markdown; zero build step, works directly from a `docs/` folder on GitHub Pages. Good for rapid publishing. | +| **Docusaurus** | Node.js/React, strong search and versioning. More setup overhead; worthwhile if the docs grow large. | + +**Recommended starting point:** MkDocs Material. One `mkdocs.yml` config, `uv add --dev mkdocs-material`, +and `uv run mkdocs gh-deploy` is all that's needed. The source already lives in `docs/`. + +### Content scope (minimum viable) + +| Page | Audience-level description | +|------|---------------------------| +| **Welcome / What is this?** | Plain-language intro: local AI chatbot, character cards, no data sent to cloud | +| **Getting started** | How to install, configure a model, and start the server | +| **Using the chat** | Sending messages, sessions (save/load/search), keyboard shortcuts, export | +| **Character cards** | What they are, where to put card files, adding an avatar image | +| **Knowledge base (RAG)** | Plain-language: what a "collection" is, how to add a new character's info, what "coverage" means | +| **Settings and profiles** | What each retrieval setting does in plain English; saving and applying profiles | +| **Diagnostics panel** | What the token bar and per-turn table show; how to read drift scores | +| **Troubleshooting** | Common errors, model not loading, no collections found, stream timeout | + +### Implementation notes + +- Place MkDocs source in `docs/` (already exists) with `mkdocs.yml` at the repository root. +- Separate developer/contributor documentation (current `docs/`) from user-guide pages + (`docs/user_guide/`) using MkDocs navigation sections. +- The in-UI help guides (chat sidebar and RAG page) can be reused or adapted as source material. +- A GitHub Actions workflow can automate `mkdocs gh-deploy` on every push to `main`. + *(Web UX and observability improvements are tracked in `docs/future_work/UI_REFINEMENTS.md`.)* ## Suggested Execution Order @@ -159,10 +302,13 @@ stable. The only "Large" effort item in this backlog. 8. ✅ Add retrieval trend rendering and debug export artifacts. (2026-03-26) 9. Iterate on higher-level UX and explainability improvements — see `docs/future_work/UI_REFINEMENTS.md`. 10. Add pressure-aware context compaction and per-turn token usage stats (§8). -11. Implement Tier 1 markdown persona memory (§6) — requires user identity scoping first. -12. Add conversation branching, character hot-reload, stop hooks, and skills macros (§7). -13. CLI quality-of-life pass: themes and keybindings (§9). -14. Multi-character conversation mode (§10) — long-horizon, after §6–8 are stable. +11. Implement V2/V3 card import and avatar upload pipeline (§11.1–11.2). +12. Build in-app character card editor with PNG export (§11.3 + `UI_REFINEMENTS.md §C.5`). +13. Implement Tier 1 markdown persona memory (§6) — requires user identity scoping first. +14. Add conversation branching, character hot-reload, stop hooks, and skills macros (§7). +15. CLI quality-of-life pass: themes and keybindings (§9). +16. Multi-character conversation mode (§10) — long-horizon, after §6–8 are stable. +17. Publish user-facing documentation site (§12) — MkDocs Material on GitHub Pages. ## Next Steps diff --git a/docs/future_work/UI_REFINEMENTS.md b/docs/future_work/UI_REFINEMENTS.md index f68193b..55d851a 100644 --- a/docs/future_work/UI_REFINEMENTS.md +++ b/docs/future_work/UI_REFINEMENTS.md @@ -90,15 +90,14 @@ the `scripts/rag/` CLI toolset, accessible from the browser without a terminal. | Area | Features | |------|---------| -| **RAG data files** | List, view, run linting, run coverage analysis | -| **Collections** | List, inspect, delete, rebuild, query test | +| **RAG data files** | List, view, upload new files, run linting, run coverage analysis | +| **Collections** | List, inspect, delete, rebuild, create from uploaded file, query test | | **Fixture evaluation** | Run evaluate-fixtures, view results, view trend history | | **Embedding benchmarking** | Trigger benchmark run, view results | | **Collection migration** | Re-embed to new model, backfill fingerprints | Out of scope for this plan (requires broader changes): - In-browser text editing of `rag_data/` source files -- File upload / new character creation - Real-time log streaming during long-running jobs (deferred to async job tracker) ### B.2 Routes @@ -109,12 +108,14 @@ imports (no subprocess); UI responses use HTMX partial renders consistent with e | Method | Path | Purpose | |--------|------|---------| | GET | `/rag` | RAG management root panel | -| GET | `/rag/files` | List `rag_data/` files with status badges | +| GET | `/rag/files` | List `rag_data/` files with status badges; upload panel | | GET | `/rag/files/{filename}` | View file content (read-only) | +| POST | `/rag/files/upload` | Upload a new `.txt` source file; optional immediate ingest | | POST | `/rag/lint` | Run message-example linting; return results table | | POST | `/rag/lint/fix` | Run linting with auto-fix; return diff summary | | POST | `/rag/coverage` | Run coverage analysis on a lore file; return score + report | | GET | `/rag/collections` | List ChromaDB collections with counts and fingerprints | +| POST | `/rag/collections` | Create a new collection from an existing source file | | GET | `/rag/collections/{name}` | Collection detail: model, dimensions, sample docs | | DELETE | `/rag/collections/{name}` | Delete collection (with confirmation step) | | POST | `/rag/collections/{name}/query` | Ad-hoc test query; return top-k chunks with scores | @@ -205,18 +206,97 @@ Or, given the project's existing pattern, call the CLI module functions directly ### B.8 Non-Goals (Deferred) - In-browser text editor for `rag_data/` source files (use VS Code or a dedicated CMS). -- File upload for new character data (filesystem write from web raises deployment concerns). - Real-time log streaming for long-running jobs (stdout pipe to WebSocket — separate effort). - Multi-user / authentication (single-user local tool assumption). --- +## C. Character Management UI + +UI surfaces for character card import, avatar upload, and character switching. Depends on +`REFINEMENTS.md §11` backend work for card parsing and avatar storage. + +### C.1 Character Avatar Display + +Show a character avatar image in the chat interface. + +- Display the avatar in the chat header next to the character name. +- Optionally show a small avatar thumbnail next to each assistant message bubble. +- Fall back to a coloured monogram/initial placeholder if no avatar is set. +- Source: `GET /characters/{name}/avatar` — served by the web app, returns the stored image or a + generated fallback. + +### C.2 Avatar Upload + +Allow uploading a custom avatar image for the active character. + +- Upload button in the character settings area (or a dedicated character management page). +- Accepts PNG, JPEG, or WebP; server resizes to ≤ 512 px and saves to + `character_storage//avatar.png`. +- Instant preview update after upload via HTMX partial replace. + +### C.3 Character Card Import + +A drag-and-drop or file-picker import flow for standard character card files. + +- Accepts: PNG (V2 `chara` chunk or V3 `ccv3` chunk), plain JSON (CCv2 or CCv3), and CHARX zip. +- On import: extracts card fields, normalises to the project's JSON format, saves to `cards/`, + and optionally extracts the embedded avatar. +- Shows a preview of extracted fields (name, description snippet, scenario snippet) before + confirming the import. +- After import, allows immediately switching to the new character without restarting. +- Route: `POST /characters/import` (multipart form upload). + +### C.4 Character Selector / Switcher + +A UI for browsing and switching the active character without restarting the server. + +- Lists all cards in `cards/` with avatar thumbnails, name, and a brief description snippet. +- "Switch" button triggers a hot-reload (see `REFINEMENTS.md §7` character hot-reload). +- Shows which character is currently active. +- Route: `GET /characters` (list), `POST /characters/{name}/activate`. + +### C.5 In-App Character Card Editor + +A form-based editor for creating and editing character cards within the web UI. Several +open-source implementations already exist and could be referenced or adapted: + +- [ZoltanAI/character-editor](https://github.com/ZoltanAI/character-editor) — standalone + browser-based editor for V1/V2 cards; good reference for field layout and PNG embedding. +- SillyTavern has a built-in card editor that supports V2 and lorebook editing. +- [character-card-spec-v3](https://github.com/kwaroran/character-card-spec-v3) provides the + canonical field reference for a V3-compatible editor. + +Scope for this project: + +- Edit core fields: name, description, scenario, personality, first message, mes_example, + voice instructions, tags. +- Avatar upload inline (replaces §C.2 standalone upload). +- Save as JSON to `cards/` and optionally export as CCv2/CCv3 PNG (embed in tEXt chunk). +- Route: `GET /characters/{name}/edit`, `POST /characters/{name}/edit`. +- Depends on `REFINEMENTS.md §11` card format backend and §C.3 import pipeline (shared parsing). + +**Note on avatar management without a full editor:** Until §C.5 is built, the simplest path +is to drop an image file (`avatar.png`, `.jpg`, or `.webp`) into +`character_storage//` — the web app serves it automatically via +`GET /characters/avatar`. The stem is the character name lowercased with spaces → underscores +(e.g., "SHODAN" → `character_storage/shodan/avatar.jpg`). + +--- + ## Suggested Execution Order (UI) -1. RAG Management UI (§B) as a self-contained milestone — implement §B.6 steps in order. -2. Token budget visualization (§A.1) and per-turn token stats panel (§A.2) — low-risk extensions +1. ✅ RAG Management UI (§B) as a self-contained milestone — implement §B.6 steps in order. +2. ✅ Token budget visualization (§A.1) and per-turn token stats panel (§A.2) — low-risk extensions to the existing diagnostics panel; depends on `REFINEMENTS.md §8` backend work. -3. Session history search (§A.4) — stateless read-only feature, no new backend state model needed. -4. Conversation branching controls (§A.3) — depends on `REFINEMENTS.md §7` session state changes. -5. Memory panel (§A.5) — depends on `REFINEMENTS.md §6` Tier 1 memory being implemented first. -6. Skills dropdown (§A.6) — depends on `REFINEMENTS.md §7` skills config backend. +3. ✅ Session history search (§A.4) — stateless read-only feature, no new backend state model needed. +4. ✅ Character avatar display + tab sidebar (§C.1) — avatar served from `character_storage//`. +5. ✅ RAG file upload + create collection from UI (§B.1/B.2 extension) — `POST /rag/files/upload`, + `POST /rag/collections`; "Ingest →" per-file action; upload-and-ingest combined flow. +6. Conversation branching controls (§A.3) — depends on `REFINEMENTS.md §7` session state changes. +7. Memory panel (§A.5) — depends on `REFINEMENTS.md §6` Tier 1 memory being implemented first. +8. Skills dropdown (§A.6) — depends on `REFINEMENTS.md §7` skills config backend. +9. Avatar upload UI (§C.2) — small addition; no major backend dependency. +10. Character card import UI (§C.3) — depends on `REFINEMENTS.md §11.2` import backend. +11. Character switcher (§C.4) — depends on `REFINEMENTS.md §7` character hot-reload. +12. In-app character card editor (§C.5) — largest UI item; depends on §C.3 + §C.4. diff --git a/scripts/rag/push_rag_data.py b/scripts/rag/push_rag_data.py index 2cc77bc..4265a89 100644 --- a/scripts/rag/push_rag_data.py +++ b/scripts/rag/push_rag_data.py @@ -236,7 +236,7 @@ def push_to_collection( try: context.client.delete_collection(collection_name) logger.info(f"Deleted existing collection: {collection_name}") - except ValueError: + except MISSING_COLLECTION_ERRORS: logger.debug(f"Collection {collection_name} doesn't exist, creating new") else: assert_collection_fingerprint_compatible(context.client, collection_name, expected_fingerprint) diff --git a/templates/diagnostics_panel.html b/templates/diagnostics_panel.html index d4d6b50..2183eeb 100644 --- a/templates/diagnostics_panel.html +++ b/templates/diagnostics_panel.html @@ -1,29 +1,69 @@

+{% if last_budget and last_budget.context_window > 0 %} +{% set cw = last_budget.context_window %} +{% set sys_pct = (last_budget.system_prompt_tokens / cw * 100) | round(1) %} +{% set hist_pct = (last_budget.history_tokens / cw * 100) | round(1) %} +{% set rag_pct = (last_budget.rag_tokens / cw * 100) | round(1) %} +{% set ex_pct = (last_budget.examples_tokens / cw * 100) | round(1) %} +{% set inp_pct = (last_budget.input_tokens / cw * 100) | round(1) %} +{% set res_pct = (last_budget.reserved_for_response / cw * 100) | round(1) %} +{% set used_pct = (last_budget.total_estimated / cw * 100) | round(1) %} +
+
+ Context window: {{ last_budget.total_estimated }} / {{ cw }} tokens + {{ used_pct }}% +
+
+
+
+
+
+
+
+
+
+
+ System + History + RAG + Examples + Input + Reserved + Free +
+
+{% endif %} {% if history %} - - - - - - - + + + + + + + {% for entry in history %} {% set r = entry.retrieval if entry.retrieval else {} %} - {% set cl = r.cleanup if r.cleanup else {} %} {% set drift = entry.persona.drift_score if entry.persona else none %} + {% set tb = entry.token_budget if entry.token_budget else {} %} + {% set cw = tb.context_window if tb.context_window else 0 %} + {% set used = tb.total_estimated if tb.total_estimated else 0 %} + {% set ctx_pct = (used / cw * 100) | round(0) | int if cw > 0 else none %} + {% set main_chunks = r.main.returned if r.main is defined else 0 %} - - - - + + + + {{ ctx_pct ~ "%" if ctx_pct is not none else "—" }} + + {{ "%.3f" | format(drift) if drift is not none else "—" }} @@ -31,6 +71,23 @@ {% endfor %}
TLatencyCharsMainMESCross−DriftTLatencyPrompt~Compl~Ctx%RAGDrift
{{ entry.turn }} {{ "%.2fs" | format(entry.latency_s) if entry.latency_s is not none else "—" }}{{ entry.chars_emitted if entry.chars_emitted is not none else "—" }}{{ cl.main if cl.main is defined else 0 }}{{ cl.mes if cl.mes is defined else 0 }}{{ cl.cross_removed if cl.cross_removed is defined else 0 }}{{ used if used > 0 else "—" }}{{ entry.estimated_completion_tokens if entry.estimated_completion_tokens is not none else "—" }}{{ main_chunks }}
+{% set ns = namespace(total_prompt=0, total_compl=0, count=0) %} +{% for entry in history %} + {% set tb = entry.token_budget if entry.token_budget else {} %} + {% if tb.total_estimated and tb.total_estimated > 0 %} + {% set ns.total_prompt = ns.total_prompt + tb.total_estimated %} + {% set ns.total_compl = ns.total_compl + (entry.estimated_completion_tokens or 0) %} + {% set ns.count = ns.count + 1 %} + {% endif %} +{% endfor %} +{% if ns.count > 0 %} +
+ Session totals — Prompt: {{ ns.total_prompt }} tok  ·  + Completion: {{ ns.total_compl }} tok  ·  + Avg ctx: {{ (ns.total_prompt / ns.count / (last_budget.context_window or 1) * 100) | round(1) if last_budget and last_budget.context_window else "—" }}% + ({{ ns.count }} turn{{ "s" if ns.count != 1 else "" }}) +
+{% endif %} {% else %} No turns recorded yet. {% endif %} diff --git a/templates/index.html b/templates/index.html index 4aa450b..235643c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -31,16 +31,121 @@ } .sidebar h1 { - margin: 0 0 0.5rem; + margin: 0; + font-size: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .sidebar-header { + display: flex; + align-items: center; + gap: 0.55rem; + margin-bottom: 0.75rem; + } + + .sidebar-avatar-sm { + width: 38px; + height: 38px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; + border: 2px solid #2d3743; + } + + .sidebar-avatar-sm-placeholder { + width: 38px; + height: 38px; + border-radius: 50%; + background: #1f2d3d; + border: 2px solid #2d3743; + display: flex; + align-items: center; + justify-content: center; font-size: 1.1rem; + font-weight: 700; + color: #7cb3e8; + flex-shrink: 0; } - .meta { - font-size: 0.9rem; + /* Sidebar tabs */ + .sidebar-tabs { + display: flex; + gap: 2px; + margin-bottom: 0; + } + + .sidebar-tab { + flex: 1; + padding: 0.35rem 0.2rem; + font-size: 0.73rem; + font-weight: 600; + background: #0f1317; + border: 1px solid #2d3743; + border-bottom: none; + border-radius: 5px 5px 0 0; + color: #8b949e; + cursor: pointer; + text-align: center; + transition: color 0.15s, background 0.15s; + } + + .sidebar-tab:hover { color: #c9d1d9; } + + .sidebar-tab.active { + background: #161b22; + color: #e6edf3; + border-color: #3d4751; + } + + .sidebar-pane { + display: none; + border: 1px solid #3d4751; + border-radius: 0 0 8px 8px; + padding: 0.75rem; + background: #0f1317; + } + + .sidebar-pane.active { display: block; } + + /* Character tab */ + .char-avatar-wrap { + display: flex; + justify-content: center; + margin-bottom: 0.75rem; + } + + .char-avatar { + width: 180px; + height: 180px; + border-radius: 12px; + object-fit: cover; + border: 2px solid #2d3743; + } + + .char-avatar-placeholder { + width: 180px; + height: 180px; + border-radius: 12px; + background: #1f2d3d; + border: 2px solid #2d3743; + display: flex; + align-items: center; + justify-content: center; + font-size: 4rem; + font-weight: 700; + color: #7cb3e8; + } + + .char-meta { + font-size: 0.82rem; color: #98a6b6; - line-height: 1.5; + line-height: 1.7; } + .char-meta strong { color: #c9d1d9; } + .sidebar-actions { display: grid; grid-template-columns: 1fr 1fr; @@ -317,6 +422,65 @@ color: #98a6b6; } + /* Token budget bar */ + .budget-section { + margin: 0.5rem 0 0.75rem; + } + .budget-label { + font-size: 0.75rem; + color: #c5cdd6; + margin-bottom: 4px; + display: flex; + align-items: center; + gap: 0.4rem; + } + .budget-pct { + font-size: 0.7rem; + padding: 1px 5px; + border-radius: 3px; + font-weight: 600; + } + .bpct-green { background: #1e3a2a; color: #a8d8a8; } + .bpct-yellow { background: #3a2e10; color: #ffd166; } + .bpct-red { background: #3a1a1a; color: #f8b4b4; } + .budget-bar { + display: flex; + height: 12px; + border-radius: 4px; + overflow: hidden; + background: #1a1e2e; + cursor: help; + } + .budget-seg { min-width: 0; transition: width 0.4s; } + .seg-system { background: #7c6fdc; } + .seg-history { background: #3a9bd5; } + .seg-rag { background: #48c774; } + .seg-examples { background: #f9a825; } + .seg-input { background: #ff7043; } + .seg-reserved { background: #667788; } + .seg-headroom { background: #252840; flex: 1; } + .budget-legend { + display: flex; + flex-wrap: wrap; + gap: 4px 8px; + margin-top: 4px; + } + .budget-legend .leg { + font-size: 0.65rem; + padding: 1px 5px; + border-radius: 2px; + color: #0a0a14; + font-weight: 600; + opacity: 0.9; + } + .diag-totals { + font-size: 0.72rem; + color: #98a6b6; + margin-top: 0.5rem; + padding-top: 0.4rem; + border-top: 1px solid #2a2e3e; + } + .profile-current-table { margin-top: 0.5rem; } @@ -433,11 +597,139 @@ color: #c9d1d9; } - .guide-item p { - margin: 0; - font-size: 0.77rem; + .search-result-item { + padding: 0.4rem 0.5rem; + border: 1px solid #2d3743; + border-radius: 6px; + background: #111418; + margin-top: 0.3rem; + } + + .search-result-header { + display: flex; + align-items: baseline; + gap: 0.3rem; + margin-bottom: 0.2rem; + flex-wrap: wrap; + } + + .search-result-name { + font-size: 0.78rem; + font-weight: 600; + color: #c9d1d9; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .badge-char { + font-size: 0.68rem; + background: #1a2b3b; + color: #79c0ff; + padding: 0.1rem 0.35rem; + border-radius: 8px; + white-space: nowrap; + } + + .search-result-date { + font-size: 0.7rem; color: #98a6b6; - line-height: 1.45; + white-space: nowrap; + } + + .search-snippet { + font-size: 0.73rem; + color: #98a6b6; + line-height: 1.35; + margin-bottom: 0.15rem; + word-break: break-word; + } + + .snip-role { + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + color: #4e8bc4; + margin-right: 0.25rem; + } + + .search-load-btn { + margin-top: 0.3rem; + padding: 0.22rem 0.45rem; + font-size: 0.74rem; + background: #1e242b; + border: 1px solid #2d3743; + border-radius: 5px; + color: #79c0ff; + cursor: pointer; + } + + .search-load-btn:hover { + background: #262d36; + } + + #session-search summary { + font-size: 0.8rem; + color: #98a6b6; + cursor: pointer; + list-style: none; + padding: 0.25rem 0; + margin-top: 0.35rem; + } + + #session-search summary::after { + content: " ▸"; + font-size: 0.7rem; + } + + #session-search[open] summary::after { + content: " ▾"; + } + + .search-form { + display: flex; + flex-direction: column; + gap: 0.3rem; + margin-top: 0.4rem; + } + + .search-form input[type="text"], + .search-form input[type="date"] { + border: 1px solid #2d3743; + background: #111418; + color: #e6edf3; + border-radius: 5px; + padding: 0.3rem 0.45rem; + font-size: 0.78rem; + width: 100%; + box-sizing: border-box; + } + + .search-form input[type="date"]::-webkit-calendar-picker-indicator { + filter: invert(0.65); + } + + .search-date-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0.3rem; + } + + .search-submit-btn { + padding: 0.3rem 0.5rem; + font-size: 0.78rem; + background: #1f6feb; + border: 1px solid #1f6feb; + border-radius: 5px; + color: white; + cursor: pointer; + } + + #session-search-results { + max-height: 360px; + overflow-y: auto; } @@ -445,12 +737,17 @@
@@ -559,6 +901,17 @@

Presets