Skip to content

Commit 98feb73

Browse files
haasonsaasclaude
andcommitted
Add web UI with React frontend and Axum backend server
Full-stack web dashboard for DiffScope with: - React 19 + TypeScript + Vite + Tailwind CSS v4 frontend - Axum 0.8 backend with rust-embed for SPA asset serving - Dashboard with quick-action review buttons, score trends, severity stats - Diff viewer with inline comments, file sidebar, syntax highlighting - Review history with search, filtering, and pagination - Analytics page with score trends, severity breakdown, category charts - Settings page with model presets (OpenRouter), adapter config, review params - Doctor page for system diagnostics Frontend quality improvements: - Shared constants (SEV_COLORS, STATUS_STYLES, CHART_THEME, REFETCH intervals) - Shared score utilities (scoreColorClass, scoreRingClass) - Merged CommentCard + InlineComment into single variant component - ErrorBoundary for graceful crash recovery - Lightweight syntax highlighting for diff lines (keywords, strings, comments) - Model presets extracted to shared config - Exported HunkView/LineRow components from DiffViewer Backend improvements: - completed_at timestamp now set on all terminal review states - JSON file persistence (~/.local/share/diffscope/reviews.json) - 5-minute review timeout - Feedback action stored on comments - Timestamps changed to i64 - Pagination on list_reviews endpoint - OpenRouter API key env var support (OPENROUTER_API_KEY) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5166a6c commit 98feb73

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+10055
-91
lines changed

Cargo.lock

Lines changed: 448 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ categories = ["development-tools", "command-line-utilities"]
1111

1212
[dependencies]
1313
clap = { version = "4.4", features = ["derive"] }
14-
tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "fs", "time"] }
14+
tokio = { version = "1.35", features = ["macros", "rt-multi-thread", "fs", "time", "sync"] }
1515
serde = { version = "1.0", features = ["derive"] }
1616
serde_json = "1.0"
1717
serde_yaml = "0.9"
@@ -31,6 +31,11 @@ glob = "0.3"
3131
ignore = "0.4"
3232
shell-words = "1.1"
3333
url = "2.5"
34+
axum = "0.8"
35+
tower-http = { version = "0.6", features = ["cors", "fs"] }
36+
rust-embed = "8"
37+
uuid = { version = "1", features = ["v4"] }
38+
mime_guess = "2"
3439

3540
[dev-dependencies]
3641
tempfile = "3.8"

README.md

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,20 +221,96 @@ git diff | diffscope review \
221221

222222
#### Docker Compose (Ollama + DiffScope)
223223
```bash
224-
# Start Ollama and DiffScope together
224+
# Start Ollama (with GPU) and DiffScope together — model is auto-pulled
225225
docker compose up diffscope-local
226226
227-
# Pull a model first
228-
docker compose exec ollama ollama pull codellama
227+
# CPU-only mode (no NVIDIA GPU required)
228+
docker compose --profile cpu up ollama-cpu
229+
230+
# Pull a specific model manually
231+
docker compose exec ollama ollama pull deepseek-coder:6.7b-instruct
232+
```
233+
234+
#### Docker with GPU
235+
236+
Requires [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html):
237+
238+
```bash
239+
# Install NVIDIA Container Toolkit (Ubuntu/Debian)
240+
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
241+
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
242+
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
243+
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
244+
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
245+
sudo nvidia-ctk runtime configure --runtime=docker && sudo systemctl restart docker
246+
247+
# Run with GPU
248+
docker compose up diffscope-local
249+
```
250+
251+
If you don't have an NVIDIA GPU, use the CPU-only profile or run Ollama directly on the host.
252+
253+
#### Recommended Models
254+
255+
| RAM Available | Recommended Model | Command |
256+
|---|---|---|
257+
| 8 GB | Codellama 7B Q4 | `ollama pull codellama:7b-instruct-q4_0` |
258+
| 16 GB | Deepseek Coder 6.7B | `ollama pull deepseek-coder:6.7b-instruct` |
259+
| 16 GB | Codellama 13B Q4 | `ollama pull codellama:13b-instruct-q4_0` |
260+
| 32+ GB | Deepseek Coder 33B Q4 | `ollama pull deepseek-coder:33b-instruct-q4_0` |
261+
262+
For best results, use instruction-tuned (`-instruct`) variants of code-specialized models.
263+
264+
#### Performance Tuning
265+
266+
For local models, adjust these config values based on your model's context window:
267+
268+
```yaml
269+
# .diffscope.yml for 7B model on 16GB RAM
270+
context_window: 8192 # Model's actual context limit
271+
max_tokens: 2048 # Max response length
272+
max_diff_chars: 12000 # Truncate large diffs
273+
max_context_chars: 8000 # Limit surrounding code context
274+
context_max_chunks: 8 # Max context files to include
275+
temperature: 0.1 # Low temp for consistent reviews
229276
```
230277

278+
**Tips:**
279+
- `diffscope doctor` shows detected context window and tests inference speed
280+
- Quantized models (Q4, Q5) use ~50-60% less RAM with minimal quality loss
281+
- GPU inference is 5-10x faster than CPU-only
282+
- First request after model load is slower (loading into VRAM)
283+
231284
#### Check Your Setup
232285
```bash
233286
# Verify endpoint reachability, models, and recommendations
234287
diffscope doctor
235288
diffscope doctor --base-url http://localhost:11434
236289
```
237290

291+
#### Troubleshooting
292+
293+
**Model is slow (>30 seconds per review)**
294+
- Check tokens/sec with `diffscope doctor`
295+
- Try a quantized model: `ollama pull codellama:7b-instruct-q4_0`
296+
- Reduce context: set `max_diff_chars: 8000` and `context_window: 4096`
297+
298+
**Out of memory errors**
299+
- Use a smaller model (7B instead of 13B)
300+
- Use heavier quantization (Q4 instead of Q8)
301+
- Set `context_window` lower in config (e.g., 4096)
302+
- Monitor with `nvidia-smi` (GPU) or `htop` (RAM)
303+
304+
**Empty or garbage reviews**
305+
- Run `diffscope doctor` to test model responsiveness
306+
- Try a code-specialized model (deepseek-coder, codellama)
307+
- Avoid models smaller than 3B for code review
308+
309+
**"Endpoint unreachable" error**
310+
- Verify server is running: `curl http://localhost:11434/api/tags`
311+
- Check the port matches your `base_url` config
312+
- For Docker: ensure services are on the same network
313+
238314
#### Environment Variables
239315
| Variable | Description |
240316
|----------|-------------|

docker-compose.yml

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ services:
1616
build: .
1717
image: diffscope:latest
1818
depends_on:
19-
ollama:
20-
condition: service_healthy
19+
ollama-pull:
20+
condition: service_completed_successfully
2121
environment:
2222
- DIFFSCOPE_BASE_URL=http://ollama:11434
2323
- DIFFSCOPE_MODEL=${DIFFSCOPE_MODEL:-ollama:codellama}
@@ -32,12 +32,45 @@ services:
3232
- "11434:11434"
3333
volumes:
3434
- ollama_data:/root/.ollama
35+
deploy:
36+
resources:
37+
reservations:
38+
devices:
39+
- driver: nvidia
40+
count: all
41+
capabilities: [gpu]
42+
healthcheck:
43+
test: ["CMD-SHELL", "curl -sf http://localhost:11434/api/tags || exit 1"]
44+
interval: 10s
45+
timeout: 5s
46+
retries: 5
47+
start_period: 30s
48+
49+
# CPU-only variant (use instead of 'ollama' when no GPU is available)
50+
ollama-cpu:
51+
image: ollama/ollama:latest
52+
ports:
53+
- "11434:11434"
54+
volumes:
55+
- ollama_data:/root/.ollama
56+
profiles:
57+
- cpu
3558
healthcheck:
3659
test: ["CMD-SHELL", "curl -sf http://localhost:11434/api/tags || exit 1"]
3760
interval: 10s
3861
timeout: 5s
3962
retries: 5
4063
start_period: 30s
4164

65+
ollama-pull:
66+
image: ollama/ollama:latest
67+
depends_on:
68+
ollama:
69+
condition: service_healthy
70+
entrypoint: ["ollama", "pull", "${DIFFSCOPE_OLLAMA_MODEL:-codellama:7b}"]
71+
environment:
72+
- OLLAMA_HOST=http://ollama:11434
73+
restart: "no"
74+
4275
volumes:
4376
ollama_data:

src/adapters/llm.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub struct ModelConfig {
1717
impl Default for ModelConfig {
1818
fn default() -> Self {
1919
Self {
20-
model_name: "gpt-4o".to_string(),
20+
model_name: "anthropic/claude-sonnet-4.6".to_string(),
2121
api_key: None,
2222
base_url: None,
2323
temperature: 0.2,
@@ -64,10 +64,27 @@ pub fn create_adapter(config: &ModelConfig) -> Result<Box<dyn LLMAdapter>> {
6464
return match adapter.as_str() {
6565
"anthropic" => Ok(Box::new(crate::adapters::AnthropicAdapter::new(config)?)),
6666
"ollama" => Ok(Box::new(crate::adapters::OllamaAdapter::new(config)?)),
67+
"openrouter" => {
68+
// OpenRouter uses OpenAI-compatible API
69+
let mut or_config = config.clone();
70+
if or_config.base_url.is_none() {
71+
or_config.base_url = Some("https://openrouter.ai/api/v1".to_string());
72+
}
73+
Ok(Box::new(crate::adapters::OpenAIAdapter::new(or_config)?))
74+
}
6775
_ => Ok(Box::new(crate::adapters::OpenAIAdapter::new(config)?)),
6876
};
6977
}
7078

79+
// OpenRouter-style model IDs (vendor/model)
80+
if config.model_name.contains('/') {
81+
let mut or_config = config;
82+
if or_config.base_url.is_none() {
83+
or_config.base_url = Some("https://openrouter.ai/api/v1".to_string());
84+
}
85+
return Ok(Box::new(crate::adapters::OpenAIAdapter::new(or_config)?));
86+
}
87+
7188
// Model-name heuristic
7289
match config.model_name.as_str() {
7390
// Anthropic Claude models (all versions)
@@ -236,7 +253,7 @@ mod tests {
236253
#[test]
237254
fn test_model_config_default() {
238255
let config = ModelConfig::default();
239-
assert_eq!(config.model_name, "gpt-4o");
256+
assert_eq!(config.model_name, "anthropic/claude-sonnet-4.6");
240257
assert!(config.api_key.is_none());
241258
assert!(config.base_url.is_none());
242259
assert!((config.temperature - 0.2).abs() < f32::EPSILON);

0 commit comments

Comments
 (0)