A native macOS application for accessing all hosted LLM at NVIDIA via the integrate.api.nvidia.com/v1/chat/completions API.
- Features
- Interface Overview
- Requirements
- Build & Run
- Architecture
- Project Structure
- Design System
- Metrics
- Theme
- Health Check
- System Prompt
- API
- Error Handling
- Security
- Testing
- Distribution
- Dependencies
- FAQ
- Streaming — token-by-token responses via Server-Sent Events (SSE)
- Markdown — rendering via MarkdownUI with code highlighting
- Editing — message editing and regeneration
- Attachments — drag & drop images, paste from clipboard
- Multimodal — text, images (URL/base64), video (URL)
- Popover — quick access via menu bar icon
- Global Hotkey —
Cmd+Shift+Nto toggle popover - Quick Model Switcher — directly in the popover
- Token Counter —
prompt_tokens,completion_tokens,total_tokensfrom APIusage - Latency — Time-to-first-token (TTFT), total response time, tokens/sec
- Dashboard — daily charts, per-model breakdown, period summary
- Badges — compact indicator under each assistant response
- Context — context window fill indicator with overflow warning
- Export — CSV and JSON
- System / Light / Dark — toggle with icons (sun / moon / half-circle)
- Auto-follow — "System" automatically matches macOS appearance
- Persisted — preference saved between launches
- Model availability indicator — ✅ available / ❌ unavailable / 🔒 no key / 🔄 checking
- Auto-check — on model load and API key save
- Minimal request —
max_tokens: 1to conserve tokens
- Global default — set in Settings, applied to new conversations
- Per-conversation override — editable in chat via collapsible panel
- Persisted — in SwiftData,
systemPromptfield inConversation
- Web Search — research mode via Google Custom Search API
- History — SwiftData, search, export to Markdown/JSON/CSV
- Security — API keys stored in macOS Keychain
- All parameters — temperature, top_p, max_tokens, presence/frequency penalty, stop, seed, thinking_mode
The app uses Liquid Glass materials from macOS Tahoe: translucent panels, vibrancy on text, hierarchical SF Symbols.
| Component | Description |
|---|---|
| Main Window | NavigationSplitView: sidebar + message thread + input |
| Menu Bar Popover | Compact 360px chat with recent messages |
| Settings | 7 tabs: API, Models, Search, Behavior, History, Metrics, About |
| Metrics Dashboard | Summary + charts (Swift Charts) + model breakdown |
| Usage Badge | Glass capsule under response: tokens, time, tokens/sec |
| Context Indicator | Glass capsule: estimated tokens / context limit |
| Model Status | Glass capsule: ✅/❌/🔒/🔄 with colored icon |
| Theme Toggle | Toolbar icon: 🌓/☀️/🌙 |
| Component | Version |
|---|---|
| macOS | 15.0+ (Sequoia), 26+ (Tahoe) recommended for Liquid Glass |
| Xcode | 16.0+ |
| Swift | 6.0+ |
| Architecture | Apple Silicon (M1/M2/M3/M4) |
| NVIDIA API | Key from build.nvidia.com |
# 1. Open the project
open NvidiaLLM.xcodeproj
# 2. Build (Cmd+B)
# 3. Run (Cmd+R)
# 4. Tests (Cmd+U)# Debug build
xcodebuild -scheme NvidiaLLM -configuration Debug build
# Release build
xcodebuild -scheme NvidiaLLM -configuration Release build
# Run
open build/Debug/NvidiaLLM.app
# Tests
xcodebuild -scheme NvidiaLLM test- The app launches in the Menu Bar (CPU icon in the menu bar)
- Open Settings (
Cmd+,) → API tab → enter NVIDIA API Key - (Optional) Search tab → Google Custom Search API Key + CX
- Models tab → select default model + system prompt
- Click the model status indicator — it should turn ✅ green
- Start chatting in the window or via the popover
┌─────────────────────────────────────────────────────────┐
│ macOS App (SwiftUI) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Menu Bar │ │ Chat │ │ Settings │ │ Metrics │ │
│ │ Popover │ │ Window │ │ Window │ │ Dashboard│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴──────────────┴──────────────┴──────────────┴────┐ │
│ │ Services Layer │ │
│ │ NVIDIAAPI │ ModelsFetcher │ GoogleSearch │ Keychain │ │
│ │ MetricsStore │ TokenEstimator │ LatencyTracker │ │
│ │ HealthCheck │ StreamingParser │ │
│ └──────────────────────┬────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴────────────────────────────────┐ │
│ │ Storage (SwiftData + UserDefaults) │ │
│ │ Conversation │ Message │ UsageRecord │ AppSettings │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
- MVVM —
@ObservableViewModels for Chat, Popover, Metrics - Service Layer — separate services for API, metrics, keychain
- SwiftData — native persistence,
@Modelclasses - AsyncStream — streaming via
AsyncStream<StreamEvent> - Dependency Injection — services passed through
init
NvidiaLLM/
├── App/
│ ├── NvidiaLLMApp.swift # @main, ModelContainer, scenes
│ ├── ContentView.swift # TabView: Chat + Metrics + ThemeToggle
│ └── AppDelegate.swift # NSApplicationDelegate
│
├── Models/
│ ├── Conversation.swift # @Model: title, systemPrompt, messages
│ ├── Message.swift # @Model: role, content, tokens, latency
│ ├── UsageRecord.swift # @Model: per-request metrics
│ ├── NvidiaModel.swift # Model metadata + context_length
│ ├── SamplingParams.swift # temperature, top_p, max_tokens, etc.
│ ├── APIModels.swift # Request/Response DTOs
│ ├── SSEChunk.swift # SSE streaming chunk model
│ └── AppSettings.swift # @Observable UserDefaults wrapper
│
├── Services/
│ ├── NVIDIAAPIService.swift # Streaming + non-streaming API client
│ ├── StreamingParser.swift # SSE line parser → SSEChunk
│ ├── ModelsFetcher.swift # GET /v1/models
│ ├── GoogleSearchService.swift # Google Custom Search API
│ ├── KeychainManager.swift # SecItem wrapper
│ ├── MetricsStore.swift # SwiftData metrics CRUD + aggregation
│ ├── TokenEstimator.swift # Pre-send context estimation
│ ├── LatencyTracker.swift # TTFT + response time
│ ├── HealthCheckService.swift # Model availability check
│ └── APIError.swift # Error enum with LocalizedError
│
├── Chat/
│ ├── MainChatView.swift # NavigationSplitView
│ ├── ChatSidebar.swift # Conversation list + search
│ ├── MessageThreadView.swift # Scrollable message list
│ ├── MessageBubbleView.swift # Single message with glass bg
│ ├── ChatInputView.swift # Input + toolbar + sliders
│ ├── StreamingTextView.swift # Token-by-token with cursor
│ ├── UsageBadgeView.swift # Token/latency badge per message
│ ├── ContextIndicatorView.swift # Context window fill indicator
│ ├── ModelStatusIndicatorView.swift # Health check status badge
│ ├── ErrorStateView.swift # Error banner with retry button
│ └── ChatViewModel.swift # @Observable VM
│
├── MenuBar/
│ ├── MenuBarController.swift # MenuBarExtra scene
│ ├── PopoverView.swift # Quick chat popover
│ └── PopoverViewModel.swift # @Observable VM
│
├── Settings/
│ ├── SettingsView.swift # TabView scene
│ ├── APISettingsView.swift # API key (Keychain) + endpoint
│ ├── ModelSettingsView.swift # Default model + system prompt + params
│ ├── SearchSettingsView.swift # Google Custom Search config
│ ├── BehaviorSettingsView.swift # Theme + hotkey + launch + network
│ ├── HistorySettingsView.swift # Export + retention + clear
│ ├── MetricsSettingsView.swift # Retention + export + clear
│ └── AboutView.swift # Version + links
│
├── Metrics/
│ ├── MetricsView.swift # Dashboard with period selector
│ ├── MetricsSummaryView.swift # 6 metric cards (glass)
│ ├── TokenUsageChartView.swift # Swift Charts bar chart
│ ├── ModelBreakdownView.swift # Per-model horizontal bars
│ └── MetricsViewModel.swift # @Observable aggregation
│
├── DesignSystem/
│ ├── VisualEffectView.swift # NSVisualEffectView wrapper
│ ├── GlassWindowModifier.swift # .glassWindowBackground()
│ ├── GlassButtonStyle.swift # ButtonStyle with glass
│ ├── GlassTextFieldStyle.swift # TextFieldStyle
│ ├── GlassSliderStyle.swift # Slider + label + value
│ ├── ThemeManager.swift # AppearanceMode + ThemeManager
│ └── ThemeToggleView.swift # Sun/moon/system toggle
│
├── Extensions/
│ ├── View+Extensions.swift # glassBackground, glassCapsuleBackground
│ ├── Color+Extensions.swift # nvidiaGreen, userBubble, assistantBubble
│ └── Date+Extensions.swift # relativeTime, shortFormatted, daysAgo
│
├── Resources/
│ ├── Assets.xcassets/ # App icon, AccentColor
│ └── Info.plist # Bundle config, ATS, macOS 15+
│
├── Tests/
│ ├── StreamingParserTests.swift # SSE parsing (7 tests)
│ ├── TokenEstimatorTests.swift # Token estimation (8 tests)
│ ├── MetricsStoreTests.swift # Metrics CRUD + aggregation (5 tests)
│ ├── NVIDIAAPIServiceTests.swift # API error handling (3 tests)
│ ├── ModelsFetcherTests.swift # Model fetching (3 tests)
│ ├── ChatViewModelTests.swift # VM operations (5 tests)
│ └── HealthCheckServiceTests.swift # Health check + theme (9 tests)
│
├── Package.swift # SPM manifest
└── README.md # This file
Total: 62 files across 10 modules.
The app strictly follows the macOS Tahoe design code:
| Principle | Implementation |
|---|---|
| Liquid Glass | .glassEffect() on macOS 26+, fallback .ultraThinMaterial |
| Vibrancy | NSVisualEffectView with .hudWindow material |
| SF Symbols | All icons are system, .symbolRenderingMode(.hierarchical) |
| Fonts | System font (SF Pro), .design(.monospaced) for numbers |
| Colors | Semantic colors (.primary, .secondary, .tertiary), .accentColor |
| Corners | 10–14pt for cards, 6–8pt for buttons, capsule for badges |
| Animations | .spring(duration: 0.2) for presses, .easeOut for scroll |
| HIG | System button styles, proper keyboard navigation, accessibility |
// Glass background for panels
.glassBackground(cornerRadius: 14)
// Glass capsule for badges
.glassCapsuleBackground()
// Glass window background
.glassWindowBackground()
// Glass button
.buttonStyle(GlassButtonStyle(prominence: .prominent))All icons use hierarchical rendering mode for depth:
Image(systemName: "cpu")
.symbolRenderingMode(.hierarchical)
.foregroundStyle(.nvidiaGreen)| Metric | Source | When |
|---|---|---|
prompt_tokens |
API usage |
After each request |
completion_tokens |
API usage |
After each request |
total_tokens |
API usage |
After each request |
| TTFT (ms) | LatencyTracker |
Time to first token |
| Response time (ms) | LatencyTracker |
Total generation time |
| Tokens/sec | Calculated | completion_tokens / generation_time |
- In chat —
UsageBadgeViewunder each assistant response- Compact:
↑10 ↓20 · 1.2s - Expanded: input/output/total/model
- Compact:
- Dashboard — Metrics tab in the main window
- 6 cards: Requests, Total Tokens, Prompt, Completion, Avg TTFT, Avg Response
- Daily token usage chart (Swift Charts)
- Top models by tokens
- Latency statistics
- Export — Settings → Metrics → CSV/JSON
ContextIndicatorView in the input panel shows:
- Estimated tokens for the current context (pre-send)
- Context window fill (
context_lengthfrom model metadata) - Colored progress bar: 🟢 <60% / 🟡 <80% / 🔴 >80%
- Warning when approaching the limit
| Mode | Icon | Description |
|---|---|---|
| System | circle.lefthalf.filled |
Follows macOS |
| Light | sun.max.fill |
Light |
| Dark | moon.stars.fill |
Dark |
- Toolbar —
ThemeToggleViewbutton in the top-right corner- Click cycles: system → light → dark → system
- Right-click opens a menu for direct selection
- Settings → Behavior → Appearance — segmented picker
// ThemeManager — @Observable, persisted in UserDefaults
@Observable final class ThemeManager {
var appearance: AppearanceMode // system / light / dark
}
// Applied in App
.preferredColorScheme(theme.appearance.colorScheme)HealthCheckService sends a minimal request to the API:
{
"model": "nvidia/llama-3.1-nemotron-70b-instruct",
"messages": [{"role": "user", "content": "Hi"}],
"max_tokens": 1,
"stream": false
}| Status | Icon | Color | Condition |
|---|---|---|---|
| Unknown | questionmark.circle |
gray | Not checked |
| Checking | arrow.triangle.2.circlepath |
blue | In progress (rotating) |
| Available | checkmark.circle.fill |
green | HTTP 200 or 429 |
| Unavailable | xmark.circle.fill |
red | 401/403/404/5xx/network |
| No API key | lock.circle |
orange | Key not set |
ModelStatusIndicatorView — glass capsule next to the model selector in the input panel. Click triggers a re-check.
- Global default — Settings → Models → Default System Prompt
- Applied to all new conversations
- Stored in
AppSettings.defaultSystemPrompt
- Per-conversation — collapsible panel in the input area (
gearshapeicon)- Overrides the global default
- Stored in
Conversation.systemPrompt
The system prompt is sent as the first message with role: "system":
{
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello"}
]
}POST https://integrate.api.nvidia.com/v1/chat/completions
GET https://integrate.api.nvidia.com/v1/models
// Request with include_usage to get token counts in the final chunk
{
"model": "...",
"messages": [...],
"stream": true,
"stream_options": {"include_usage": true}
}
// The final SSE chunk contains usage:
data: {"choices":[],"usage":{"prompt_tokens":10,"completion_tokens":20,"total_tokens":30}}
data: [DONE]
| Parameter | Type | Default |
|---|---|---|
temperature |
Double | 0.7 |
top_p |
Double | 0.95 |
max_tokens |
Int | 1024 |
presence_penalty |
Double | 0 |
frequency_penalty |
Double | 0 |
stop |
[String] | nil |
seed |
Int | nil |
chat_template_kwargs.thinking_mode |
String | "off" |
- 3 attempts max with exponential backoff (1s → 2s → 4s)
- Retries for: 429 (rate limited), 5xx (server errors), timeout, network connection lost, no internet
- Respects
Retry-Afterheader from the server - Does not retry: 401/403 (auth), 404 (not found), 400 (bad request), decoding errors
Settings → Behavior → Network:
| Parameter | Default | Range | Meaning |
|---|---|---|---|
| Request timeout | 30s | 10–120s | Time without data before timeout |
| Resource timeout | 120s | 30–600s | Total time including retries |
| Max retries | 3 | 0–10 | Retry attempts for transient errors |
ErrorStateView — glass background with:
- Hierarchical SF Symbol
exclamationmark.triangle.fill - Retry button — re-sends the last request
- Dismiss button (X) — clears the error
| Error | What happens | Retry? |
|---|---|---|
| No API key | "Set API key in Settings" | ❌ |
| Invalid URL | "API URL is invalid" | ❌ |
| 401/403 Auth | "Auth failed" | ❌ |
| 404 Not found | "Model not found" | ❌ |
| 429 Rate limited | Waits Retry-After or backoff |
✅ 3 times |
| 5xx Server error | Exponential backoff | ✅ 3 times |
| Timeout | Exponential backoff | ✅ 3 times |
| Network lost | Exponential backoff | ✅ 3 times |
| No internet | Exponential backoff | ✅ 3 times |
| Decoding error | "Failed to decode" | ❌ |
| Stream error | "Streaming error: ..." | ❌ |
| Data | Storage |
|---|---|
| NVIDIA API Key | macOS Keychain (SecItem) |
| Google Search API Key | macOS Keychain |
| Google Search CX | macOS Keychain |
| Conversations | SwiftData (SQLite, local) |
| Usage Records | SwiftData (SQLite, local) |
| Settings | UserDefaults |
API keys are never stored in UserDefaults or plain text.
# Run all tests
xcodebuild -scheme NvidiaLLM test
# Or via Xcode: Cmd+U| Test File | Tests | What it Covers |
|---|---|---|
StreamingParserTests |
7 | SSE parsing: delta, usage, [DONE], errors |
TokenEstimatorTests |
8 | Token estimation: EN/Cyrillic, context, limits |
MetricsStoreTests |
5 | CRUD, aggregation, breakdown, CSV export |
NVIDIAAPIServiceTests |
3 | Missing key, invalid URL, stream error |
ModelsFetcherTests |
3 | Fetch, displayName |
ChatViewModelTests |
5 | CRUD conversations, send without key, context |
HealthCheckServiceTests |
9 | Health check statuses, theme cycle |
Total: 40 tests.
# Archive
xcodebuild -scheme NvidiaLLM -configuration Release archive \
-archivePath build/NvidiaLLM.xcarchive
# Export .app
xcodebuild -exportArchive \
-archivePath build/NvidiaLLM.xcarchive \
-exportPath build/ \
-exportOptionsPlist ExportOptions.plist# Codesign
codesign --deep --force --verify --verbose=4 \
--sign "Developer ID Application: Your Name (TEAM_ID)" \
build/NvidiaLLM.app
# Notarize
xcrun notarytool submit build/NvidiaLLM.zip \
--apple-id "you@email.com" \
--team-id "TEAM_ID" \
--password "app-specific-password" \
--wait
# Staple
xcrun stapler staple build/NvidiaLLM.app- GitHub Actions / Xcode Cloud — build + test on every PR
- Automatic archive + notarization for release tags
| Package | Version | Purpose |
|---|---|---|
| MarkdownUI | 2.4+ | Markdown rendering |
| HotKey | 0.2+ | Global keyboard shortcuts |
Minimal external dependencies — everything else is native (SwiftUI, SwiftData, Swift Charts, Security, AppKit).
On build.nvidia.com — log in with an NVIDIA Developer account, select a model, click "Get API Key".
NVIDIA NIM pricing changes over time and is not exposed via the API. We only show tokens and latency, which come directly from the API response and are guaranteed to be accurate.
No. The app targets Apple Silicon (M1+). Minimum macOS 15.
Yes — Settings → API → Endpoint. Any OpenAI-compatible endpoint works.
~/Library/Application Support/com.nvidia-llm.app/ — SwiftData SQLite database.
Settings → History → Export as Markdown / JSON.
Settings → Metrics → Export as CSV / JSON.
Private project. All rights reserved.