Skip to content

Add a minimal web chat UI served by ds4-server - #718

Open
phierru wants to merge 3 commits into
antirez:mainfrom
phierru:chat-ui
Open

Add a minimal web chat UI served by ds4-server#718
phierru wants to merge 3 commits into
antirez:mainfrom
phierru:chat-ui

Conversation

@phierru

@phierru phierru commented Aug 6, 2026

Copy link
Copy Markdown

I added a minimal web chat interface, served directly by ds4-server at /, so a
model can be used from a browser without any extra tooling. There is no build step
and nothing to install: the page is embedded in the binary as a string literal, and
chat-ui.html is the same page as a standalone file if you prefer to open it from
disk.

Trying it

./ds4-server -m ./ds4flash.gguf --ctx 32768

Then open http://127.0.0.1:8000 — the UI is served by ds4-server itself on its
usual port, not by a separate process.

Two prompts that exercise the rendering:

  1. Show me the Maxwell equations
  2. Explain briefly how these equations imply electromagnetic waves

The first comes back with headings, bold text and display equations; the follow-up
uses inline math inside bullet lists.

What it does

  • Streams responses over the existing /v1/chat/completions SSE endpoint.
  • Renders Markdown: headings, nested lists, tables, blockquotes, links, horizontal
    rules, fenced and inline code.
  • Renders LaTeX with KaTeX: $$..$$ and \[..\] as display math, $..$ and
    \(..\) inline. Inline $..$ requires a non-space character next to both
    delimiters, so ordinary prose like "costs $5 and $7" is not swallowed as math.
  • A thinking selector in the header sends reasoning_effort per request, off by
    default. GLM 5.2 emits visible reasoning otherwise, which is tedious to read at
    streaming speeds.
  • A copy button per message, which copies the raw Markdown rather than the rendered
    HTML, and a stop button (also bound to Esc) that aborts generation while keeping
    whatever text already arrived.

Notes, and things you may want changed

  • KaTeX is loaded from a CDN (jsDelivr). It cannot reasonably be inlined because
    of the fonts. If it fails to load, equations degrade to showing the raw LaTeX
    instead of breaking, and re-render if it arrives late. I am happy to drop math
    rendering altogether if you would rather the UI carry no external dependency.
  • The copy embedded in ds4_server.c is generated from chat-ui.html rather than
    escaped by hand, since the LaTeX regexes are backslash-heavy and one missed escape
    yields a page that compiles but renders wrongly. tools/gen_chat_ui.py regenerates
    it; tools/gen_chat_ui.py --check reports staleness without writing, if you want
    it in a pre-release check.
  • tools/test_chat_ui.js covers the renderer (Markdown blocks, HTML escaping, math
    delimiters). It takes an optional path, so the same suite can be run against a page
    fetched from a running ds4-server — that is what verifies the C escaping survived
    the round trip, which compiling cannot tell you. Plain node, no dependencies.
  • Tested on macOS / Metal against DeepSeek V4 Flash (resident) and GLM 5.2 (SSD
    streaming). The UI is model-agnostic and reads the model id from /v1/models.
  • Purely additive: no existing file behaviour changes, and the new route only
    answers GET /, which was previously unhandled.

phierru and others added 3 commits August 3, 2026 14:18
Co-authored-by: Francesco Lardieri <flardi@gmx.net>
Co-authored-by: DwarfStar AI Agent <agent@dwarfstar.ai>
The UI previously rendered only bold/italic/code and showed raw Markdown
and LaTeX for everything else, which is unreadable for models that emit
headings, tables and equations.

- Markdown: headings, nested lists, tables, blockquotes, links, rules,
  fenced and inline code. Code and math are pulled out before escaping so
  their contents are never mangled by the inline rules.
- Math: KaTeX renders $$..$$ and \[..\] as display, $..$ and \(..\) inline.
  Inline $..$ requires non-space next to both delimiters so prose like
  "costs $5 and $7" is not swallowed. If KaTeX cannot load the source
  LaTeX is shown instead of being lost, and messages re-render on load.
- Thinking toggle in the header sends reasoning_effort per request
  (off by default, persisted in localStorage). GLM emits visible reasoning
  otherwise, which is costly at streaming speeds.
- Copy button per message copies the raw Markdown, not rendered HTML.
- Stop button (and Esc) aborts generation, keeping text received so far as
  ordinary history; an empty response bubble is removed instead.

The embedded copy in ds4_server.c is generated from chat-ui.html rather
than hand-escaped, since the LaTeX regexes are backslash-heavy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The chat UI lives in two places: chat-ui.html, which is editable and can be
opened from disk, and a string literal in ds4_server.c so the binary serves
the page without external assets. Keeping those in sync by hand is a trap,
because the Markdown and LaTeX regexes are backslash-heavy and a single
missed escape yields a page that compiles but renders wrongly.

- tools/gen_chat_ui.py regenerates the embedded copy from chat-ui.html.
  --check reports staleness without writing, for use before a release.
- tools/test_chat_ui.js covers the renderer: Markdown blocks, HTML escaping,
  and the math delimiters. It accepts a path, so the same suite can run
  against a page fetched from a running ds4-server; that is what proves the
  C escaping survived the round trip, which compiling cannot tell you.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant