Skip to content

Port EvanBacon/chat-template to SwiftUI and adopt it as the Mference Mac app UI - #6

Draft
NeelM0906 wants to merge 10 commits into
mainfrom
claude/chat-template-swift-desktop-3b0dc7
Draft

Port EvanBacon/chat-template to SwiftUI and adopt it as the Mference Mac app UI#6
NeelM0906 wants to merge 10 commits into
mainfrom
claude/chat-template-swift-desktop-3b0dc7

Conversation

@NeelM0906

Copy link
Copy Markdown
Owner

What changed

Three commits, building on each other:

  1. ChatTemplateMac — a SwiftUI desktop port of EvanBacon/chat-template (new, additive)

    • ChatTemplateCore library: chat/store models with the template's throttled ~30 fps streaming-store pattern, the template's mock word-by-word streaming backend, an OpenAI-compatible SSE streaming backend (defaults to the local MferenceServer at http://127.0.0.1:8080/v1), an SSE line parser, and a block-level markdown parser (fenced code, GFM tables, headings, lists, quotes; tolerates unclosed fences mid-stream).
    • ChatTemplateMac app: split-view chat UI with grouped sidebar (starred/recency, star/rename/delete), streaming markdown messages with shimmer loading and scroll-to-bottom, glass prompt composer, model-picker menu, Settings (mock mode by default; API key kept in the login keychain).
    • Run it: swift run ChatTemplateMac
  2. Wire the template UI into the Mference Mac app

    • New ChatTemplateUI library target shares the template's SwiftUI views (message bubbles, markdown renderer with code-copy, shimmer) between both apps.
    • OutputPaneView: the NSTextView incremental transcript is replaced with the template conversation. Streaming still drains GenerationTranscriptMailbox on the same 0.1 s cadence, and live/committed response resolution (mailbox while running, outputText when terminal) matches the previous transcript exactly. Placeholder, copy actions, and the context menu are unchanged.
    • PromptComposerView: glass (ultraThinMaterial) composer with auto-growing input and Return-to-send; attachments, prompt tips, clear, and the GenerateControl (live tok/s + stop) are preserved.
  3. Reskin the Mference app shell to the template design

    • RootView becomes a NavigationSplitView with a native toolbar: model status menu (name + status dot, load/reload/cancel/unload actions), New Chat, and an inspector toggle; the runtime-controls panel now presents via SwiftUI .inspector.
    • TemplateSidebarView: native source list grouped by recency with rename/delete context menus and the same guarded selection semantics; keeps the local-chats footer and appearance picker.
    • Empty state uses the template copy; the status HUD strip and example cards no longer show.

Why

Requested: implement chat-template as a Swift desktop app, then wire it into the Mference Mac app as its working chat UI.

Notes for reviewers

  • No inference-path changes. All edits are presentation-layer; AppModel, the decode service, and the mailbox protocol are untouched.
  • StatusHUDView, ChatSidebarView, and PromptExamplesView remain in-tree but unreferenced, to keep reverting or blending the old design cheap. Happy to delete them instead if preferred.
  • The HUD's live metric strip (tok/s / tokens / memory) is no longer always visible; tok/s still shows in the Generate control while running, and full diagnostics remain in the inspector.
  • New tests: ChatTemplateCoreTests (22 tests — SSE parsing, markdown blocks, streaming lifecycle incl. stop/partial-commit/error/delete-cancels). Full run: 269 tests in the affected suites pass via Scripts/test.sh.
  • A code-review pass drove several fixes already: streaming text can't render into the wrong chat (generatingChatID), no silent mock fallback on a bad base URL, server error bodies survive into the error banner, API key stored in the keychain rather than UserDefaults.

Test plan

  • Scripts/test.sh --filter "ChatTemplateCoreTests|MferenceMacPresentationTests|MferenceAppCoreTests" — 269 tests pass
  • swift build (all targets, debug + release) clean
  • MferenceMac launches with the new shell; model-load placeholder, sidebar, toolbar, and composer verified
  • Real end-to-end generation (Load Model → send → streamed markdown) — app is pointed at the installed Qwen 3.6 checkpoint; needs a human click since the session lacked screen-control approval

…emplate

New additive targets (no changes to existing Mference code):
- ChatTemplateCore: chat/store models with throttled ~30fps streaming,
  mock word-by-word backend (template's MOCK_RESPONSES), OpenAI-compatible
  SSE streaming backend defaulting to the local MferenceServer at
  http://127.0.0.1:8080/v1, SSE line parser, block-level markdown parser
  (fenced code, GFM tables, headings, lists, quotes; tolerates unclosed
  fences while streaming).
- ChatTemplateMac: NavigationSplitView chat UI with grouped sidebar
  (starred/recency buckets, star/rename/delete), streaming markdown
  messages with shimmer loading and scroll-to-bottom, glass prompt
  composer with send/stop, model picker menu with extended-thinking
  toggle, settings (mock mode default; API key kept in login keychain).
- ChatTemplateCoreTests: 22 tests covering SSE parsing, markdown blocks,
  and the streaming lifecycle (send/stop/error/partial-commit/delete).
- New ChatTemplateUI library target: shared SwiftUI chat views
  (MessageView, MarkdownView, CodeBlockView, TableBlockView, shimmer)
  used by both ChatTemplateMac and MferenceMac.
- MferenceMac OutputPaneView: replace the NSTextView incremental
  transcript with the template conversation — user bubbles, per-message
  markdown blocks with code-copy, shimmer during prefill, scroll-to-
  bottom button. Streaming still drains GenerationTranscriptMailbox on
  the same 0.1s cadence; live response resolution (mailbox while
  running, committed outputText when terminal) matches the previous
  transcript exactly. Placeholder, copy actions, and context menu are
  unchanged.
- MferenceMac PromptComposerView: glass (ultraThinMaterial) composer
  with auto-growing input and Return-to-send; attachments, prompt tips,
  clear, and the GenerateControl (tok/s + stop) are preserved.
- RootView: NavigationSplitView with a native toolbar replacing the
  fixed HStack layout and the status HUD strip. Toolbar carries the
  model status menu (principal), New Chat, and an inspector toggle;
  the inspector panel now uses SwiftUI's .inspector presentation.
- TemplateSidebarView: source-list sidebar grouped by recency
  (Today / 7 days / 30 days / Older) with rename/delete context menus
  and the same guarded selection semantics as before; keeps the
  local-chats footer and appearance picker.
- ModelStatusMenu: model name + status dot in the toolbar, with
  load/reload/cancel/unload actions and the presentation state line.
- Empty state now uses the template copy; prompt example cards no
  longer show (PromptExamplesView, StatusHUDView, and ChatSidebarView
  remain in-tree for easy revert but are no longer referenced).
- Window uses the standard title bar so the toolbar renders natively.
Fixes the scattered-folders model UX:

- ModelLibrary (AppCore): one dedicated, auto-detected place for models.
  Scans prioritized roots (Mference.libraryRoot override, the package
  checkout's scratch/ in dev trees, ~/Library/Application Support/
  Mference, and the parent of the last remembered directory) for
  .gturbo directories, identifying each by the family its own manifest
  declares — so dsv4.gturbo is recognized as DeepSeek-V4-Flash even
  though the name is not canonical. Downloads always target the first
  library root; no folder choice required.
- AppModel: on launch (persisted app only), when the remembered
  directory holds no complete install, the first installed library
  model is adopted automatically instead of showing 'Model required'
  for a phantom path. New selectModel(family:) switches between
  shipped models — installed ones activate at their detected location;
  missing ones route to the install screen targeted at the library
  root. The installer client is now swapped per family via an
  injectable factory. Catalog refreshes on path changes and after
  installs.
- ModelStatusMenu: the toolbar picker now lists the whole model family.
  Installed models activate directly (checkmark on the active one);
  missing models show a Download row with size, quant type, and
  parameter markers (e.g. 'Gemma 4 26B-A4B IT 4-bit · 14.6 GB').
- Tests: ModelLibraryTests cover per-family entries, manifest-based
  (not name-based) detection, root priority, incomplete-install
  skipping, and candidate-root ordering/dedup.
Merges claude/inkling-small-mlx-quant-fae643 (clean, no conflicts) so
the runtime on this branch can run Inkling-Small, and adds the
inklingSmall descriptor to ModelLibrary.shippedDescriptors and the
family-preference mapping. The toolbar picker now lists all four
models; an installed inklingsmall.gturbo is auto-detected by its
manifest family like the others.
Install receipts bind to the directory a model was verified at, so a
symlinked library entry (scratch/inklingsmall.gturbo ->
~/Downloads/inklingsmall.gturbo) failed the probe's directory-binding
check and showed as downloadable. The library scan now resolves
symlinks before probing, so verification and activation both use the
model's real path.
Restores the HUD's tok/s / tokens / memory readouts, dropped in the
shell reskin, as the top of the inspector — replacing the Path,
Choose Model Folder, Unload Model, and State rows the model library
and toolbar menu made redundant. Live token rate and count stream
during decode (last-run values when idle), and inference memory is
resampled every second via a TimelineView tick. Install size rows
still appear while a download is required.
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