Skip to content

Chat with local models through Ollama and LM Studio, and Markdown in answers - #156

Merged
Louis-CFM merged 6 commits into
mainfrom
local-models-markdown
Oct 2, 2026
Merged

Louis-CFM merged 6 commits into
mainfrom
local-models-markdown

Conversation

@Louis-CFM

Copy link
Copy Markdown
Owner

Summary

  • Chat with local models, no API key: connect Ollama or LM Studio in Settings → Chat → Local models. Coucou only talks to a local server after you click Connect, and Disconnect forgets it.
  • Answers from local models stream in as they are written. Reasoning blocks from thinking models stay hidden, and a dropped text file is sent along (first 24,000 characters).
  • Markdown in chat answers: headings, bold, numbered and nested lists, quotes, and code blocks with a copy button. Links open only when they are plain web links.
  • Ollama and LM Studio pills, a wrapping provider row in the model picker, and updated README, support, privacy and integration docs.

Tests

  • New CI step, scripts/test-chat-parsing.sh: Markdown and stream parsing, the thinking filter, and end-to-end runs against a fake local server (model list, streamed answer, unknown model, server down).

Louis-CFM and others added 6 commits October 2, 2026 23:07
- Add ollama/lmstudio cases to ChatProvider with isLocal, pillID, init?(pillID:)
- Add LocalChat helpers: normaliseURL, parseSSEDelta, filterThinkingBlocks
- Stream local responses token-by-token via URLSession.bytes
- Add ai_ollama (#FACC15) and ai_lmstudio (#A3E635) pills to PillCatalog
- Add server URL settings in Settings → Chat (Local models GroupBox)
- NSAllowsLocalNetworking in both Info.plist targets

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ChatMarkdown block parser (Foundation only) and ChatMarkdownView renderer
- ChatBubble uses ChatMarkdownView for assistant messages
- Update system prompt to allow markdown formatting
- Add ChatMarkdown/LocalChat test suite with compile-and-run script
- Add test-chat-parsing step to CI build workflow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… e2e tests

- Add LocalChatError typed enum (serverUnreachable, modelNotFound, serverError)
- Add LocalChat.fetchModels with embedding model filter (nomic-embed, bge, etc.)
- Add LocalChat.streamChat: full SSE streaming with typed error handling
- Add LocalChat.progressiveFilter for partial think-block suppression
- ClaudeService delegates local streaming entirely to LocalChat.streamChat
- Add tests/fake_local_llm.py: stdlib Python server with think blocks, null-content
  deltas, model filter, unknown model 404, and Markdown streaming
- ChatParsingTests: unit tests + 4 end-to-end tests against fake server
- test-chat-parsing.sh starts/stops fake server with port-file handoff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lish

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Louis-CFM
Louis-CFM merged commit 2566843 into main Oct 2, 2026
1 check passed
@Louis-CFM
Louis-CFM deleted the local-models-markdown branch October 2, 2026 23:21
JhoanG956 pushed a commit to JhoanG956/coucou that referenced this pull request Oct 7, 2026
… server

As the Mac does since 0.1.3 (Louis-CFM#156, LocalChat.swift), plus a server of the
user's own that speaks the OpenAI API (vLLM, llama.cpp…), with an optional
key kept in the credential store (openai-compatible-key).

- local_connect checks that a server answers and lists its chat models
  (embedding and rerank models are left out). An empty address means the
  usual one on this computer; Ollama's OLLAMA_HOST is honoured.
- Addresses are validated and cleaned: http or https only, no
  user:password@, trailing /v1 and /api dropped, and the loopback names
  pinned to 127.0.0.1 (on Windows "localhost" can resolve to ::1 first while
  Ollama listens on IPv4 only). Requests to this computer skip any proxy.
- The answer streams: each step goes to the island as a chat-delta event
  with the text visible so far, at most 15 times a second. <think> blocks
  stay hidden while open and are dropped from the finished answer;
  reasoning-only deltas are ignored and an error event ends the turn.
- Every read is bounded: model lists and error bodies, one event line
  (1 MB) and the whole streamed answer (4 MB).
- A dropped text file goes along inline, 24 000 characters at most, read
  without loading more of the file than that; images and PDFs by name only.
- Settings: ollamaUrl, lmstudioUrl, customUrl (empty: not connected).

Based on Louis-CFM#173 by Justin Minkmar (local_chat.rs), re-applied on main
without the branches it was stacked on; ideas from Louis-CFM#262 by bagpotato94
(loopback pinning, no proxy for loopback, OLLAMA_HOST).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JhoanG956 pushed a commit to JhoanG956/coucou that referenced this pull request Oct 7, 2026
As on the Mac since Louis-CFM#156 (ChatMarkdown.swift, ChatMarkdownView.swift):
headings, paragraphs, bullet and numbered lists, quotes, rules, and fenced
code blocks with a copy button; bold, italic, inline code and links inside
the text. Every provider's answer goes through it.

- Built as DOM nodes from text only, never as HTML: nothing a model writes
  can inject markup. The tests run the renderer on a fake DOM whose
  innerHTML throws.
- A link opens only when it is http or https, and through Rust's open_url,
  which checks the scheme again; any other target stays plain text.
- Inline syntax is parsed into a tree first (parseInline), so it can be
  tested without a webview. No lookbehind in the regex: older WebKitGTK
  builds reject it, and one bad literal would take the whole island down.
  snake_case words are not italics.

Based on Louis-CFM#173 by Justin Minkmar (markdown.ts and its styles).

Co-Authored-By: Claude Opus 5.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