Flush Dropdown:
- Flush button now opens a dropdown showing all loaded models
- Each model shows device, VRAM usage, and individual Unload button
- Backend endpoints: GET /model/loaded, POST /model/unload/{id}
- Bottom actions: Flush caches, Unload all + flush
Credentials Tab:
- New Settings > Credentials tab with HF_TOKEN and TRANSLATE_API_KEY
- Session-scoped via POST /system/set-env (no ElevenLabs — we ARE the alternative)
- Shows 'Set' / 'Not set' badge for HF token
Notification Panel:
- Moved from header dropdown to footer status bar (4th tab: Notifications)
- Bell icon in header dispatches event to open footer tab
- Click notification → navigates to relevant page (e.g., Settings for HF token)
- No inline inputs — notifications are purely informational + navigational
Whisper Model Selector:
- Capture widget now has quality preset picker: tiny → large-v3
- Persisted in localStorage; sent to backend as 'model' form field
- Backend passes chosen model to ASR backend
Reactive Transcriptions:
- Custom window event (omni:transcription-added) bridges CaptureButton → TranscriptionsPage
- Page updates in realtime when new dictation completes
56 lines
1.6 KiB
React
56 lines
1.6 KiB
React
/**
|
|
* NotificationPanel — bell icon in the header that opens the
|
|
* Notifications tab in the footer status bar.
|
|
*/
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { Bell } from 'lucide-react';
|
|
import { API } from '../api/client';
|
|
import './NotificationPanel.css';
|
|
|
|
export default function NotificationPanel() {
|
|
const [count, setCount] = useState(0);
|
|
const [hasErrors, setHasErrors] = useState(false);
|
|
const [hasWarns, setHasWarns] = useState(false);
|
|
|
|
const fetchCount = useCallback(async () => {
|
|
try {
|
|
const res = await fetch(`${API}/system/notifications`);
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
const notifs = data.notifications || [];
|
|
setCount(notifs.length);
|
|
setHasErrors(notifs.some(n => n.level === 'error'));
|
|
setHasWarns(notifs.some(n => n.level === 'warn'));
|
|
}
|
|
} catch {
|
|
// Backend not ready
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchCount();
|
|
const iv = setInterval(fetchCount, 30000);
|
|
return () => clearInterval(iv);
|
|
}, [fetchCount]);
|
|
|
|
const openNotifications = () => {
|
|
window.dispatchEvent(new CustomEvent('omni:open-notifications'));
|
|
};
|
|
|
|
return (
|
|
<button
|
|
className={`notif-trigger ${count > 0 ? 'notif-trigger--has-items' : ''}`}
|
|
onClick={openNotifications}
|
|
aria-label={`Notifications (${count})`}
|
|
title="Notifications"
|
|
>
|
|
<Bell size={14} />
|
|
{count > 0 && (
|
|
<span className={`notif-badge ${hasErrors ? '' : hasWarns ? 'notif-badge--warn' : ''}`}>
|
|
{count}
|
|
</span>
|
|
)}
|
|
</button>
|
|
);
|
|
}
|