fix: stop Ollama word-duplication by using its HTTP API instead of the CLI - #195
fix: stop Ollama word-duplication by using its HTTP API instead of the CLI#195tilhammer wants to merge 2 commits into
Conversation
…e CLI Fixes erictli#143. Scratch's ollama integration shelled out to `ollama run <model>`, piping the prompt via stdin and capturing stdout as plain text. Ollama's CLI re-enables its terminal word-wrap renderer by default even when piped (a flag-precedence quirk in cmd/cmd.go: the --nowordwrap flag default unconditionally overrides the correct non-interactive detection). That renderer erases a partially-printed word with ANSI cursor-back + erase-in-line, then reprints it whole on the next line - real terminals render this as clean wrapping, but our regex-based ANSI stripping only deleted the escape codes, not the characters already printed before them, leaving literal duplicated text ("founda" immediately followed by "foundational"). Switch ai_execute_ollama to POST http://localhost:11434/api/generate directly (stream: false, think: true) instead of spawning a subprocess. Ollama returns "response" and "thinking" as separate JSON fields, so enabling thinking no longer risks it leaking into note content - only "response" is ever written to the file. This also drops the separate `ollama show` pre-check subprocess (a 404 from /api/generate now drives the "model not found" message directly) and the CLI-stderr string matching in favor of real HTTP status codes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe Ollama integration now uses the local ChangesOllama HTTP integration
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant ai_execute_ollama
participant Ollama_HTTP_API
participant Canonical_File
ai_execute_ollama->>Ollama_HTTP_API: POST /api/generate with model and editing prompt
Ollama_HTTP_API-->>ai_execute_ollama: Generated markdown and thinking fields
ai_execute_ollama->>Canonical_File: Write validated markdown
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
src-tauri/src/lib.rs (2)
3409-3409: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHonor
OLLAMA_HOSTinstead of hardcoding the endpoint.Ollama users can move the server to another host or port through the
OLLAMA_HOSTenvironment variable. This call always targetshttp://localhost:11434, so those setups fail with the "Could not connect" message. Read the variable and fall back to the current default.Note that a remote or
https://host also requires a TLS feature onreqwest, whichsrc-tauri/Cargo.tomlcurrently disables.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/lib.rs` at line 3409, Update the Ollama request in the surrounding generation flow to read the OLLAMA_HOST environment variable and fall back to http://localhost:11434 when it is unset, then use that value for the POST endpoint. Enable the reqwest TLS feature in src-tauri/Cargo.toml so HTTPS Ollama hosts are supported.
3488-3499: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winStrip code fences before overwriting the note.
The prompt asks the model not to wrap output in fences, but that instruction is advisory. Small local models often return
```markdownwrappers anyway. This code writes the response directly to the canonical file, so a fenced response replaces the user's note with corrupted content and the original is lost.Add a guard that removes a leading and trailing fence before the write.
♻️ Proposed guard
- let edited_content = body.response.trim().to_string(); + let mut edited_content = body.response.trim().to_string(); + // Models sometimes ignore the "no code fences" instruction. + if edited_content.starts_with("```") && edited_content.ends_with("```") { + if let Some(rest) = edited_content.split_once('\n').map(|(_, rest)| rest) { + edited_content = rest + .trim_end() + .trim_end_matches("```") + .trim_end() + .to_string(); + } + } if edited_content.is_empty() {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/lib.rs` around lines 3488 - 3499, Before the empty-output check and write in the edited-content flow, detect a response wrapped in leading and trailing triple-backtick fences, remove the optional language line and closing fence, then trim the result. Keep the guard scoped to fenced responses and continue rejecting content that becomes empty before writing through the existing canonical-file path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/src/lib.rs`:
- Around line 3407-3419: Update the Ollama request in the client flow around
reqwest::Client::new and the POST to /api/generate to apply a generous request
timeout before send(). Handle timeout errors separately from other transport
failures, reporting a clear timeout-specific error while preserving the existing
handling for non-timeout failures.
- Around line 3410-3417: Remove the explicit "think": true option from the
Ollama request constructed in the visible .json payload, allowing each
configured model to use its default thinking behavior. Preserve the existing
model, prompt, and non-streaming request fields so edited Markdown is not
discarded for models that do not support thinking.
- Around line 3472-3486: The OllamaGenerateResponse parsing must accept a null
thinking field and convert JSON parse failures into the existing failed
AiExecutionResult shape. Change OllamaGenerateResponse.thinking to an optional
string, preserve the discarded-output logging only when a value is present, and
replace the response.json() error propagation with the same success: false
result used by other Ollama failure paths.
---
Nitpick comments:
In `@src-tauri/src/lib.rs`:
- Line 3409: Update the Ollama request in the surrounding generation flow to
read the OLLAMA_HOST environment variable and fall back to
http://localhost:11434 when it is unset, then use that value for the POST
endpoint. Enable the reqwest TLS feature in src-tauri/Cargo.toml so HTTPS Ollama
hosts are supported.
- Around line 3488-3499: Before the empty-output check and write in the
edited-content flow, detect a response wrapped in leading and trailing
triple-backtick fences, remove the optional language line and closing fence,
then trim the result. Keep the guard scoped to fenced responses and continue
rejecting content that becomes empty before writing through the existing
canonical-file path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1397fcc8-3153-4d11-8e60-ae3968c9c669
⛔ Files ignored due to path filters (1)
src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (2)
src-tauri/Cargo.tomlsrc-tauri/src/lib.rs
Addresses CodeRabbit review on erictli#195: - reqwest::Client::new() applied no timeout, so a stalled Ollama request (e.g. slow model load) would hang the Tauri command forever with no way to recover. Build the client with a 5-minute timeout, matching the timeout the other AI providers already use via execute_ai_cli, and report timeouts with a distinct message. - OllamaGenerateResponse.thinking was a plain String with #[serde(default)], which only covers a missing key - an explicit `"thinking": null` (which some models can send) would still fail to deserialize and reject the whole Tauri invoke instead of surfacing a normal AiExecutionResult failure. Made it Option<String> and handled the response-parse failure the same way every other failure in this function is handled (Ok(AiExecutionResult { success: false, .. })). Not applied: the suggestion to drop `think: true` entirely. Ollama silently no-ops the think parameter for models that don't support it (confirmed via upstream issue reports linked in the review) and an empty "thinking" string was already handled gracefully before this commit - the only real gap was the null case above, which is now fixed directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixes #143.
Root cause
ai_execute_ollamashelled out toollama run <model>, piping the prompt via stdin and capturing stdout as plain text.Ollama's own CLI (
cmd/cmd.go) has a flag-precedence quirk: it correctly detects piped/non-interactive stdin and setsopts.WordWrap = false, but a few lines later unconditionally re-derivesopts.WordWrap = !nowrapfrom the--nowordwrapflag's default value, silently re-enabling word-wrap rendering even when piped.With word-wrap on, Ollama's
displayResponserenderer prints a word's characters as they stream in, and if the word would cross the (assumed 80-column) line boundary, it erases the partial word with an ANSI cursor-back + erase-in-line sequence and reprints it whole on the next line. On a real terminal this looks like clean wrapping. Scratch only stripped well-formed ANSI escape codes via regex — which deletes the control bytes but not the characters already printed before them — so the "erased" partial word survives in the captured text, immediately followed by the full word:founda+foundational, exactly matching the reports in this issue.Fix
Switch
ai_execute_ollamatoPOST http://localhost:11434/api/generatedirectly (stream: false,think: true) instead of spawning a subprocess. No terminal rendering is involved at all, so there's nothing to strip or repair.As a side benefit, Ollama returns
responseandthinkingas separate JSON fields (confirmed against itsGenerateResponsestruct), so enabling thinking no longer risks it leaking into note content — onlyresponseis ever written to the file. This also let us drop the separateollama showpre-check subprocess (a 404 from/api/generatenow drives the "model not found" message directly) and the CLI-stderr string matching, in favor of real HTTP status codes and Ollama's structured{"error": "..."}body.Testing
cargo checkandcargo clippy -- -D warningspass clean.think: true) no longer mix<think>content into the saved note.Summary by CodeRabbit
New Features
Bug Fixes