Working chat: auth, connections, streaming, folders
LLeMbas now runs end to end. Register, add an OpenAI-compatible connection, and hold a real streaming conversation organised into folders. Verified against the local llama-swap instance. Streaming is the one genuinely tricky part. Sending a message returns two HTML fragments -- the user bubble and an empty assistant bubble carrying an sse-connect -- and that attribute is the ONLY thing that starts a generation. Rendering an incomplete assistant message as a streaming shell falls out of the same template, which means loading a page whose last reply never finished simply picks it up again. Details worth knowing about, each commented where it matters: - SSE payloads are split across several data: lines. A raw newline in one data: line truncates the event, which shows up the first time a model emits a code block. - Markdown is rendered server-side by the same helper for both the page and the final streamed frame, so the two cannot disagree. The fence renderer is replaced outright rather than using markdown-it's highlight option, which re-wraps output in a second <pre>. - escape_text is html.escape, not nh3.clean_text: it escapes character by character, so escaping stream chunks separately equals escaping the whole string. - The stream opens its own session via session_scope(); it outlives the request handler and the dependency-scoped session may be closed. - Deleting a folder keeps the chats inside it (FK is SET NULL). Losing a conversation to a mis-clicked folder delete is unforgivable. - Login failures use one message for "no such account" and "wrong password" so the form cannot enumerate registered addresses. Also adds deploy/ for the gamebox install at https://chat.lan: system unit, nginx vhost with buffering off (buffering on turns streaming into one lump at the end), and install/update scripts following the same service-user and /srv bind-mount conventions as llama-swap and comfyui. 70 tests, ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
-8
@@ -7,7 +7,7 @@ from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic import Field, model_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ class Settings(BaseSettings):
|
||||
)
|
||||
|
||||
secret_key: str = Field(default="")
|
||||
# Set when no LEMBAS_SECRET_KEY was supplied and one had to be invented.
|
||||
# main.py warns about it at startup; see the validator below.
|
||||
secret_key_is_ephemeral: bool = Field(default=False, exclude=True)
|
||||
|
||||
data_dir: Path = Path("./data")
|
||||
|
||||
host: str = "127.0.0.1"
|
||||
@@ -34,13 +38,15 @@ class Settings(BaseSettings):
|
||||
session_ttl: int = 60 * 60 * 24 * 30
|
||||
request_timeout: float = 300.0
|
||||
|
||||
@field_validator("secret_key")
|
||||
@classmethod
|
||||
def _generate_secret_if_absent(cls, v: str) -> str:
|
||||
# A generated key lets `lembas serve` work out of the box, but it changes
|
||||
# on every restart: sessions drop and stored API keys become unreadable.
|
||||
# main.py warns loudly about this. Never rely on it in production.
|
||||
return v or secrets.token_urlsafe(48)
|
||||
@model_validator(mode="after")
|
||||
def _generate_secret_if_absent(self) -> Settings:
|
||||
# A generated key lets `lembas serve` work with no configuration at all,
|
||||
# but it changes on every restart: sessions drop and stored API keys
|
||||
# become unreadable. Flagged so startup can warn. Never use in anger.
|
||||
if not self.secret_key:
|
||||
self.secret_key = secrets.token_urlsafe(48)
|
||||
self.secret_key_is_ephemeral = True
|
||||
return self
|
||||
|
||||
@property
|
||||
def db_path(self) -> Path:
|
||||
|
||||
Reference in New Issue
Block a user