mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-09-14 18:56:52 +00:00
AI models — live catalog with one-click refresh
This commit is contained in:
@@ -341,6 +341,16 @@ export function NotificationSettings() {
|
||||
const [aiTestResult, setAiTestResult] = useState<{ success: boolean; message: string; model?: string } | null>(null)
|
||||
const [providerModels, setProviderModels] = useState<string[]>([])
|
||||
const [loadingProviderModels, setLoadingProviderModels] = useState(false)
|
||||
// Metadata returned by the backend after the catalog refresh — used
|
||||
// to render the "Last update" line and toast the diff.
|
||||
const [catalogMeta, setCatalogMeta] = useState<{
|
||||
success: boolean
|
||||
message: string
|
||||
changed: boolean
|
||||
previous_updated: string | null
|
||||
new_updated: string | null
|
||||
diff: Record<string, { added: string[]; removed: string[] }>
|
||||
} | null>(null)
|
||||
const [showCustomPromptInfo, setShowCustomPromptInfo] = useState(false)
|
||||
const [editingCustomPrompt, setEditingCustomPrompt] = useState(false)
|
||||
const [customPromptDraft, setCustomPromptDraft] = useState("")
|
||||
@@ -993,16 +1003,38 @@ export function NotificationSettings() {
|
||||
|
||||
setLoadingProviderModels(true)
|
||||
try {
|
||||
const data = await fetchApi<{ success: boolean; models: string[]; recommended: string; message: string }>("/api/notifications/provider-models", {
|
||||
const data = await fetchApi<{
|
||||
success: boolean
|
||||
models: string[]
|
||||
recommended: string
|
||||
message: string
|
||||
catalog_meta?: {
|
||||
success: boolean
|
||||
message: string
|
||||
changed: boolean
|
||||
previous_updated: string | null
|
||||
new_updated: string | null
|
||||
diff: Record<string, { added: string[]; removed: string[] }>
|
||||
} | null
|
||||
}>("/api/notifications/provider-models", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
body: JSON.stringify({
|
||||
provider,
|
||||
api_key: apiKey,
|
||||
ollama_url: config.ai_ollama_url,
|
||||
openai_base_url: config.ai_openai_base_url,
|
||||
// Every click pulls the latest verified catalog from GitHub
|
||||
// before the intersection runs, so a user with an outdated
|
||||
// AppImage still sees today's model list. The backend keeps
|
||||
// the local file when the fetch fails, so an offline node
|
||||
// simply doesn't refresh but still resolves models.
|
||||
refresh_catalog: true,
|
||||
}),
|
||||
})
|
||||
if (data.catalog_meta) {
|
||||
setCatalogMeta(data.catalog_meta)
|
||||
}
|
||||
if (data.success && data.models && data.models.length > 0) {
|
||||
setProviderModels(data.models)
|
||||
// Auto-select recommended model if current selection is empty or not in the list
|
||||
@@ -2409,7 +2441,7 @@ export function NotificationSettings() {
|
||||
className="h-9 px-3 shrink-0"
|
||||
onClick={() => fetchProviderModels()}
|
||||
disabled={
|
||||
loadingProviderModels ||
|
||||
loadingProviderModels ||
|
||||
(config.ai_provider === 'ollama' && !config.ai_ollama_url) ||
|
||||
(config.ai_provider !== 'ollama' && config.ai_provider !== 'anthropic' && !config.ai_api_keys?.[config.ai_provider])
|
||||
}
|
||||
@@ -2419,7 +2451,12 @@ export function NotificationSettings() {
|
||||
) : (
|
||||
<>
|
||||
<RefreshCw className="h-4 w-4 mr-1" />
|
||||
{t("settings.notifications.ai.load")}
|
||||
{/* Label swaps once we have models loaded — "Load" reads
|
||||
right on the empty state, "Update" reads right after
|
||||
the first pull. Same action underneath either way. */}
|
||||
{t(providerModels.length === 0
|
||||
? "settings.notifications.ai.load"
|
||||
: "settings.notifications.ai.updateModels")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -2427,6 +2464,32 @@ export function NotificationSettings() {
|
||||
{providerModels.length > 0 && (
|
||||
<p className="text-xs text-green-500">{t("settings.notifications.ai.modelsAvailable", { count: providerModels.length })}</p>
|
||||
)}
|
||||
{/* Catalog freshness line — surfaces the `_updated` date of the
|
||||
verified_ai_models.json currently in use, plus a compact diff
|
||||
summary from the most recent GitHub pull so the user knows
|
||||
what just happened without opening a modal. */}
|
||||
{catalogMeta && (
|
||||
<div className="text-[11px] text-muted-foreground space-y-0.5">
|
||||
<div>
|
||||
{t("settings.notifications.ai.catalogLastUpdated", {
|
||||
date: catalogMeta.new_updated || catalogMeta.previous_updated || "—",
|
||||
})}
|
||||
</div>
|
||||
{catalogMeta.changed && catalogMeta.success && Object.keys(catalogMeta.diff).length > 0 && (
|
||||
<div className="text-blue-400">
|
||||
{t("settings.notifications.ai.catalogChanged", {
|
||||
added: Object.values(catalogMeta.diff).reduce((n, d) => n + d.added.length, 0),
|
||||
removed: Object.values(catalogMeta.diff).reduce((n, d) => n + d.removed.length, 0),
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{!catalogMeta.success && catalogMeta.message && (
|
||||
<div className="text-amber-400">
|
||||
{t("settings.notifications.ai.catalogFetchFailed", { reason: catalogMeta.message })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Prompt Mode section */}
|
||||
|
||||
Reference in New Issue
Block a user