feat: analysis providers, settings UI, song search, WAV duration fix
- Multi-provider AI analysis (Anthropic, OpenAI, Ollama, Algorithmic) - server-only guards on all provider files; client bundle fix - /settings page with provider status, Ollama model picker, preferences - Song search box on /analyze replacing raw MBID input (debounced, keyboard nav) - Auto-register song via MusicBrainz on POST /api/tracks (no more 404) - Fix WAV duration bug: last section songEnd was double-counting elapsed time - Registry sync comment updated for self-hosted HTTPS git servers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
79
components/settings/PreferencesPanel.tsx
Normal file
79
components/settings/PreferencesPanel.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
const PROVIDER_KEY = "clicktrack_analysis_provider";
|
||||
const MODEL_KEY = "clicktrack_ollama_model";
|
||||
|
||||
export default function PreferencesPanel() {
|
||||
const [rememberProvider, setRememberProvider] = useState(true);
|
||||
const [rememberModel, setRememberModel] = useState(true);
|
||||
const [cleared, setCleared] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Reflect current state: if the keys exist, persistence is active
|
||||
const hasProvider = localStorage.getItem(PROVIDER_KEY) !== null;
|
||||
const hasModel = localStorage.getItem(MODEL_KEY) !== null;
|
||||
setRememberProvider(hasProvider);
|
||||
setRememberModel(hasModel);
|
||||
}, []);
|
||||
|
||||
function handleToggleProvider(on: boolean) {
|
||||
setRememberProvider(on);
|
||||
if (!on) {
|
||||
localStorage.removeItem(PROVIDER_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
function handleToggleModel(on: boolean) {
|
||||
setRememberModel(on);
|
||||
if (!on) {
|
||||
localStorage.removeItem(MODEL_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
localStorage.removeItem(PROVIDER_KEY);
|
||||
localStorage.removeItem(MODEL_KEY);
|
||||
setRememberProvider(false);
|
||||
setRememberModel(false);
|
||||
setCleared(true);
|
||||
setTimeout(() => setCleared(false), 3000);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rememberProvider}
|
||||
onChange={(e) => handleToggleProvider(e.target.checked)}
|
||||
className="accent-green-500 w-4 h-4"
|
||||
/>
|
||||
<span className="text-sm text-zinc-300">Remember my last provider selection</span>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-3 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rememberModel}
|
||||
onChange={(e) => handleToggleModel(e.target.checked)}
|
||||
className="accent-green-500 w-4 h-4"
|
||||
/>
|
||||
<span className="text-sm text-zinc-300">Remember my last Ollama model</span>
|
||||
</label>
|
||||
|
||||
<div className="pt-1">
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="rounded-lg border border-zinc-700 px-4 py-2 text-sm text-zinc-400 hover:border-zinc-500 hover:text-zinc-200 transition-colors"
|
||||
>
|
||||
Clear saved preferences
|
||||
</button>
|
||||
{cleared && (
|
||||
<span className="ml-3 text-sm text-green-400">Preferences cleared.</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user