Support any OpenAI-compatible LLM endpoint for summarization - #59
Merged
Conversation
Collapse the per-provider summarization calls into a single config-driven call path. Groq, OpenAI, Gemini, and local servers (llama.cpp, Ollama, LM Studio, vLLM) all speak the same /chat/completions shape, so a provider is now a row in the PROVIDERS table with a base URL rather than a bespoke function. - Add Gemini as a built-in provider via its OpenAI-compatible endpoint. - Add a "Local / Custom" provider: user supplies a base URL and free-text model name; API key optional (Authorization header omitted when absent). - Update the default Groq model to Qwen3 32B (Groq is deprecating Llama 3.3 70B) and refresh the stored-model default to match. - Preferences UI renders a URL field and free-text model input for custom providers, and uses per-provider API-key hints. - Document the CORS requirement for browser-initiated requests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc
✅ Deploy Preview for savrdev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
The Submit screen's "Test Summary" path was not updated for the provider abstraction: it never passed the custom base URL (so the Local/Custom provider threw "No API endpoint configured") and hard-required an API key (blocking keyless local servers). Mirror ArticleScreen's provider handling there. Also include the underlying error message in the failure toast in both screens, so a deprecated model or a provider outage is visible to the user instead of a generic "Failed to generate summary". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc
The output cap was a fixed max_tokens: 1000, which truncated longer summaries — especially with reasoning models like Gemini 2.5, where part of the token budget is consumed by internal thinking before the answer is emitted. Derive the limit from the detail level instead (512–6144 tokens), and give the connection test some headroom too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc
Hardcoded model lists drift as providers add and retire models. Add fetchAvailableModels(), which derives the OpenAI-compatible /v1/models URL from the chat-completions endpoint and returns the provider's current model ids (stripping Gemini's "models/" prefix). A refresh button beside the model selector in Preferences populates the dropdown from that live list, falling back to the curated defaults when nothing has been fetched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc
Drop the curated per-provider model arrays entirely — they drift out of date as providers add and retire models. The model field is now free text by default (works offline and for local servers with no key), and the ↻ button upgrades it to a dropdown populated from the provider's live /v1/models endpoint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc
max_tokens was being used to shape summary length per detail level, but a token cap doesn't shorten output — it truncates it mid-sentence, which is a worse failure mode than running a bit long. Move length control into the prompt instead (a flat "keep it under 800 words" instruction alongside the existing per-level qualitative guidance), and replace the tiered token caps with a single flat MAX_OUTPUT_TOKENS = 4096 that exists only as a safety net against a model ignoring the prompt and generating without bound. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc
Reasoning models such as Qwen3 (on Groq) and DeepSeek-R1 emit their chain-of-thought inline in the message content wrapped in <think>...</think> tags. We were passing that through verbatim, so the reasoning leaked into the summary. Strip those blocks (and any stray unpaired tags) at the single content-extraction point so it applies to every provider and model. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc
Record which model/provider produced a summary and when, stored as a summaryMeta object on the article alongside the summary text. Cleared with the summary, and surfaced as a "Generated by <model> (<provider>) · <date>" caption in the summary drawer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refactored the summarization system to work with any OpenAI-compatible chat-completions endpoint, eliminating provider-specific code paths. This enables support for cloud services (Groq, OpenAI, Gemini), local model servers (llama.cpp, Ollama, LM Studio, vLLM), and custom endpoints through a single unified implementation.
Key Changes
callGroqApi()andcallOpenAiApi()functions with a singlecallChatApi()that works with any OpenAI-compatible endpointPROVIDERSarray with endpoint URL, API key requirements, and available modelsbaseUrlparameter tosummarizeText()andtestApiConnection()to support user-supplied endpoints for custom providersgetSummaryCustomBaseUrlFromCookie()andsetSummaryCustomBaseUrlInCookie()to persist custom server URLsImplementation Details
SummaryProvidertype is now a plain string alias rather than a closed union, allowing extensibilityresolveEndpoint()helper determines the correct chat-completions URL based on provider type and user inputgetProviderConfig()utility provides type-safe access to provider configurationhttps://claude.ai/code/session_01LeYafqjhHzqH1GCXwo1grc