feat(server): capability framework, AudioMuse sonic routing & PsyLab Connections (#1033)

* feat(server): probe AudioMuse via OpenSubsonic and add PsyLab Connections tab

Navidrome ≥0.62: detect sonicSimilarity extension for reliable plugin signal;
older servers keep the legacy getSimilarSongs probe. PsyLab gets a Connections
tab with session, endpoint, and active-server capability details.

* feat(psylab): polish Connections tab, admin role probe, and tab bar layout

Status badges and Navidrome admin/user validation in Connections; prevent
PsyLab tab row from vertically collapsing under the Logs flex layout.

* docs: add CHANGELOG and credits for PR #1032

* feat(settings): auto-enable AudioMuse on Navidrome 0.62+ with status indicator

Replace the per-server manual toggle with a probe-driven badge when
sonicSimilarity is available; pre-0.62 Navidrome keeps the legacy toggle.

* feat(server): capability framework with AudioMuse sonic routing

Add a declarative server-capability catalog (src/serverCapabilities/) that
picks a feature strategy per server generation, runs only the needed probes,
and routes API calls. AudioMuse Instant Mix now prefers the OpenSubsonic
sonicSimilarity endpoint (getSonicSimilarTracks) on Navidrome 0.62+ and
falls back to legacy getSimilarSongs.

- catalog/context/resolve: eligibility, detection, activation, call routing
- storeView: read facade over the existing per-server probe maps
- getSonicSimilarTracks API client + fetchSimilarTracksRouted router
- route Instant Mix and Lucky Mix through the resolver
- ServersTab + PsyLab Connections read the resolver (auto status vs toggle)
- tests: resolve, storeView, router

* docs: update CHANGELOG and credits for PR #1033

Renamed branch supersedes PR #1032: point changelog/credits at #1033 and
document the server-capability framework, auto-managed AudioMuse indicator,
and sonic Instant Mix routing.

* refactor(server): address PR #1033 review — idempotent probe, drop dead code

- Make scheduleInstantMixProbeForServer idempotent: skip when a definitive
  result is cached; re-probe only on force (add/edit/test server), a prior
  error, or a server version/type change (invalidated in setSubsonicServerIdentity).
  Removes the steady-state 120 s re-probe, the present→probing→present flicker,
  and the momentary legacy-fallback routing window.
- Remove now-dead identity helpers (showAudiomuseNavidromeServerSetting,
  isAudiomusePluginAutoManaged, isNavidromeSonicSimilarityEligible,
  resolveAudiomusePluginProbeUiStatus + type) and the superseded
  probeAudiomusePluginWithCredentials; the catalog is the single source of truth.
- Drop the never-emitted 'unsupported' AudiomusePluginProbeResult variant.
- Fill audiomuseStatus* keys in all 8 non-en locales.
- Tests: probe idempotency + version-change invalidation; retarget
  OpenSubsonic test to fetchOpenSubsonicExtensionsWithCredentials.
This commit is contained in:
cucadmuh
2026-06-09 00:16:29 +03:00
committed by GitHub
parent 0878cbf308
commit 6118b3940f
43 changed files with 1867 additions and 74 deletions
+66 -35
View File
@@ -24,7 +24,10 @@ import {
} from '../../utils/server/serverUrlRemigration';
import { useConfirmModalStore } from '../../store/confirmModalStore';
import { showToast } from '../../utils/ui/toast';
import { showAudiomuseNavidromeServerSetting } from '../../utils/server/subsonicServerIdentity';
import PerfProbeStatusBadge, { type PerfProbeBadgeTone } from '../sidebar/perfProbe/PerfProbeStatusBadge';
import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS } from '../../serverCapabilities/catalog';
import { resolveFeatureForServer } from '../../serverCapabilities/storeView';
import type { CapabilityStatus, ResolvedCapability } from '../../serverCapabilities/types';
import { serverListDisplayLabel } from '../../utils/server/serverDisplayName';
import { serverIndexKeyForProfile } from '../../utils/server/serverIndexKey';
import { switchActiveServer } from '../../utils/server/switchActiveServer';
@@ -33,6 +36,24 @@ import { ServerGripHandle } from './ServerGripHandle';
const AUDIOMUSE_NV_PLUGIN_URL = 'https://github.com/NeptuneHub/AudioMuse-AI-NV-plugin';
function audiomuseProbeBadge(
status: CapabilityStatus,
t: (key: string) => string,
): { tone: PerfProbeBadgeTone; label: string } {
switch (status) {
case 'present': return { tone: 'ok', label: t('settings.audiomuseStatusActive') };
case 'absent': return { tone: 'muted', label: t('settings.audiomuseStatusNotDetected') };
case 'error': return { tone: 'error', label: t('settings.audiomuseStatusProbeFailed') };
default: return { tone: 'warn', label: t('settings.audiomuseStatusChecking') };
}
}
/** Row visibility: hide only when a manual (legacy) strategy proves the feature absent. */
function showAudiomuseRow(resolved: ResolvedCapability | null): boolean {
if (!resolved || resolved.strategyId === null || resolved.status === 'ineligible') return false;
return !(resolved.activation === 'manual' && resolved.status === 'absent');
}
type ServerDropTarget = { idx: number; before: boolean } | null;
export function ServersTab({
@@ -135,7 +156,7 @@ export function ServersTab({
openSubsonic: probe.ping.openSubsonic,
};
auth.setSubsonicServerIdentity(server.id, identity);
scheduleInstantMixProbeForServer(server.id, probe.baseUrl, server.username, server.password, identity);
scheduleInstantMixProbeForServer(server.id, probe.baseUrl, server.username, server.password, identity, true);
}
setConnStatus(s => ({ ...s, [server.id]: probe.ok ? 'ok' : 'error' }));
} catch {
@@ -232,7 +253,7 @@ export function ServersTab({
openSubsonic: ping.openSubsonic,
};
auth.setSubsonicServerIdentity(id, identity);
scheduleInstantMixProbeForServer(id, data.url, data.username, data.password, identity);
scheduleInstantMixProbeForServer(id, data.url, data.username, data.password, identity, true);
setConnStatus(s => ({ ...s, [id]: 'ok' }));
const added = useAuthStore.getState().servers.find(s => s.id === id);
if (added) void bootstrapIndexedServer(added);
@@ -324,7 +345,7 @@ export function ServersTab({
openSubsonic: ping.openSubsonic,
};
auth.setSubsonicServerIdentity(id, identity);
scheduleInstantMixProbeForServer(id, data.url, data.username, data.password, identity);
scheduleInstantMixProbeForServer(id, data.url, data.username, data.password, identity, true);
}
setConnStatus(s => ({ ...s, [id]: ping.ok ? 'ok' : 'error' }));
} catch {
@@ -483,10 +504,13 @@ export function ServersTab({
onVerify={() => void librarySync.runServerAction(serverIndexKeyForProfile(srv), 'verify')}
onCancel={() => void librarySync.handleCancel()}
/>
{showAudiomuseNavidromeServerSetting(
auth.subsonicServerIdentityByServer[srv.id],
auth.instantMixProbeByServer[srv.id],
) && (
{(() => {
const resolved = resolveFeatureForServer(srv.id, FEATURE_AUDIOMUSE_SIMILAR_TRACKS);
if (!showAudiomuseRow(resolved) || !resolved) return null;
const autoManaged = resolved.activation === 'auto';
const probeBadge = audiomuseProbeBadge(resolved.status, t);
const audiomuseActive = !!auth.audiomuseNavidromeByServer[srv.id];
return (
<div
className="settings-toggle-row"
data-settings-search={t('settings.audiomuseTitle')}
@@ -497,7 +521,7 @@ export function ServersTab({
<div>
<div style={{ fontWeight: 500, display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
{t('settings.audiomuseTitle')}
{!!auth.audiomuseNavidromeByServer[srv.id] && auth.audiomuseNavidromeIssueByServer[srv.id] && (
{audiomuseActive && auth.audiomuseNavidromeIssueByServer[srv.id] && (
<AlertTriangle
size={16}
style={{ color: 'var(--warning, #f59e0b)', flexShrink: 0 }}
@@ -506,35 +530,42 @@ export function ServersTab({
/>
)}
</div>
<div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.45 }}>
<Trans
i18nKey="settings.audiomuseDesc"
components={{
pluginLink: (
<a
href={AUDIOMUSE_NV_PLUGIN_URL}
onClick={e => {
e.preventDefault();
void openUrl(AUDIOMUSE_NV_PLUGIN_URL);
}}
style={{ color: 'var(--accent)', textDecoration: 'underline' }}
/>
),
}}
/>
</div>
{!autoManaged && (
<div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.45 }}>
<Trans
i18nKey="settings.audiomuseDesc"
components={{
pluginLink: (
<a
href={AUDIOMUSE_NV_PLUGIN_URL}
onClick={e => {
e.preventDefault();
void openUrl(AUDIOMUSE_NV_PLUGIN_URL);
}}
style={{ color: 'var(--accent)', textDecoration: 'underline' }}
/>
),
}}
/>
</div>
)}
</div>
</div>
<label className="toggle-switch" aria-label={t('settings.audiomuseTitle')}>
<input
type="checkbox"
checked={!!auth.audiomuseNavidromeByServer[srv.id]}
onChange={e => auth.setAudiomuseNavidromeEnabled(srv.id, e.target.checked)}
/>
<span className="toggle-track" />
</label>
{autoManaged ? (
<PerfProbeStatusBadge tone={probeBadge.tone}>{probeBadge.label}</PerfProbeStatusBadge>
) : (
<label className="toggle-switch" aria-label={t('settings.audiomuseTitle')}>
<input
type="checkbox"
checked={audiomuseActive}
onChange={e => auth.setAudiomuseNavidromeEnabled(srv.id, e.target.checked)}
/>
<span className="toggle-track" />
</label>
)}
</div>
)}
);
})()}
</div>
);
})}
@@ -1,16 +1,17 @@
import { useState } from 'react';
import { Activity, ScrollText, SlidersHorizontal, Wrench, X } from 'lucide-react';
import { Activity, Network, ScrollText, SlidersHorizontal, Wrench, X } from 'lucide-react';
import { createPortal } from 'react-dom';
import SidebarPerfProbeMonitorTab from './perfProbe/SidebarPerfProbeMonitorTab';
import SidebarPerfProbeTogglesTab from './perfProbe/SidebarPerfProbeTogglesTab';
import SidebarPerfProbeTuningTab from './perfProbe/SidebarPerfProbeTuningTab';
import SidebarPerfProbeLogsTab from './perfProbe/SidebarPerfProbeLogsTab';
import SidebarPerfProbeConnectionsTab from './perfProbe/SidebarPerfProbeConnectionsTab';
import { resetPerfProbeFlags, type PerfProbeFlags } from '../../utils/perf/perfFlags';
import { clearPerfLiveOverlayPins } from '../../utils/perf/perfOverlayPins';
import { resetPerfOverlayAppearance } from '../../utils/perf/perfOverlayAppearance';
import { resetPerfOverlayMode } from '../../utils/perf/perfOverlayMode';
type TabId = 'monitor' | 'toggles' | 'tuning' | 'logs';
type TabId = 'monitor' | 'connections' | 'toggles' | 'tuning' | 'logs';
interface Props {
open: boolean;
@@ -65,7 +66,7 @@ export default function SidebarPerfProbeModal({
<header className="sidebar-perf-modal__header">
<h3 id="psylab-title" className="modal-title">PsyLab</h3>
<p className="sidebar-perf-modal__hint">
Live metrics with optional on-screen overlays, runtime tuning, and diagnostic disable toggles.
Live metrics, server connections, runtime tuning, and diagnostic disable toggles.
</p>
</header>
@@ -80,6 +81,16 @@ export default function SidebarPerfProbeModal({
<Activity size={15} />
Monitor
</button>
<button
type="button"
role="tab"
aria-selected={tab === 'connections'}
className={`sidebar-perf-modal__tab${tab === 'connections' ? ' sidebar-perf-modal__tab--active' : ''}`}
onClick={() => setTab('connections')}
>
<Network size={15} />
Connections
</button>
<button
type="button"
role="tab"
@@ -114,6 +125,7 @@ export default function SidebarPerfProbeModal({
<div className={`sidebar-perf-modal__body${tab === 'logs' ? ' sidebar-perf-modal__body--logs' : ''}`}>
{tab === 'monitor' && <SidebarPerfProbeMonitorTab />}
{tab === 'connections' && <SidebarPerfProbeConnectionsTab />}
{tab === 'tuning' && <SidebarPerfProbeTuningTab />}
{tab === 'toggles' && (
<SidebarPerfProbeTogglesTab
@@ -0,0 +1,23 @@
import type { ReactNode } from 'react';
export interface PerfProbeDetailRow {
label: string;
value: ReactNode;
}
interface Props {
rows: PerfProbeDetailRow[];
}
export default function PerfProbeDetailList({ rows }: Props) {
return (
<dl className="perf-server-dl">
{rows.map(row => (
<div key={row.label} className="perf-server-dl__row">
<dt>{row.label}</dt>
<dd>{row.value}</dd>
</div>
))}
</dl>
);
}
@@ -71,6 +71,7 @@ interface SectionProps {
hint?: string;
children: ReactNode;
defaultOpen?: boolean;
layout?: 'grid' | 'stack';
onOpenChange?: (open: boolean) => void;
}
@@ -79,6 +80,7 @@ export function PerfProbeMetricSection({
hint,
children,
defaultOpen = true,
layout = 'grid',
onOpenChange,
}: SectionProps) {
return (
@@ -92,7 +94,9 @@ export function PerfProbeMetricSection({
<span>{title}</span>
{hint && <span className="perf-metric-section__hint">{hint}</span>}
</summary>
<div className="perf-metric-section__grid">{children}</div>
<div className={layout === 'stack' ? 'perf-metric-section__body' : 'perf-metric-section__grid'}>
{children}
</div>
</details>
);
}
@@ -0,0 +1,16 @@
import type { ReactNode } from 'react';
export type PerfProbeBadgeTone = 'ok' | 'warn' | 'error' | 'neutral' | 'muted';
interface Props {
tone: PerfProbeBadgeTone;
children: ReactNode;
}
export default function PerfProbeStatusBadge({ tone, children }: Props) {
return (
<span className={`perf-status-badge perf-status-badge--${tone}`}>
{children}
</span>
);
}
@@ -0,0 +1,129 @@
import { useAuthStore } from '../../../store/authStore';
import { usePlayerStore } from '../../../store/playerStore';
import { useConnectionStatus } from '../../../hooks/useConnectionStatus';
import { useNavidromeAdminRole } from '../../../hooks/useNavidromeAdminRole';
import { serverListDisplayLabel } from '../../../utils/server/serverDisplayName';
import { findServerByIdOrIndexKey } from '../../../utils/server/serverLookup';
import { PerfProbeMetricSection } from './PerfProbeMetricCard';
import PerfProbeDetailList from './PerfProbeDetailList';
import PerfProbeStatusBadge, { type PerfProbeBadgeTone } from './PerfProbeStatusBadge';
import SidebarPerfProbeServerSection from './SidebarPerfProbeServerSection';
function connectionStatusBadge(status: string): { tone: PerfProbeBadgeTone; label: string } {
switch (status) {
case 'connected': return { tone: 'ok', label: 'Connected' };
case 'disconnected': return { tone: 'error', label: 'Disconnected' };
case 'checking': return { tone: 'warn', label: 'Checking…' };
default: return { tone: 'muted', label: status };
}
}
function sessionBadge(loggedIn: boolean): { tone: PerfProbeBadgeTone; label: string } {
return loggedIn
? { tone: 'ok', label: 'Logged in' }
: { tone: 'muted', label: 'Not logged in' };
}
function adminRoleBadge(role: ReturnType<typeof useNavidromeAdminRole>): { tone: PerfProbeBadgeTone; label: string } {
switch (role) {
case 'admin': return { tone: 'ok', label: 'Admin' };
case 'user': return { tone: 'neutral', label: 'Standard user' };
case 'checking':
case 'idle': return { tone: 'warn', label: 'Checking…' };
case 'error': return { tone: 'error', label: 'Could not verify' };
case 'na':
default: return { tone: 'muted', label: 'N/A (not Navidrome)' };
}
}
function endpointBadge(isLan: boolean): { tone: PerfProbeBadgeTone; label: string } {
return isLan
? { tone: 'ok', label: 'LAN' }
: { tone: 'neutral', label: 'Public / remote' };
}
export default function SidebarPerfProbeConnectionsTab() {
const { status, isLan, serverName } = useConnectionStatus();
const isLoggedIn = useAuthStore(s => s.isLoggedIn);
const activeServerId = useAuthStore(s => s.activeServerId);
const servers = useAuthStore(s => s.servers);
const connectUrl = useAuthStore(s => s.getBaseUrl());
const queueServerId = usePlayerStore(s => s.queueServerId);
const adminRole = useNavidromeAdminRole();
const queueServer = queueServerId ? findServerByIdOrIndexKey(queueServerId) : undefined;
const queueDiffersFromActive = Boolean(
queueServerId && activeServerId && queueServerId !== activeServerId,
);
const connBadge = connectionStatusBadge(status);
const sessBadge = sessionBadge(isLoggedIn);
const roleBadge = adminRoleBadge(adminRole);
return (
<div className="perf-monitor">
<div className="perf-conn-summary" aria-label="Connection overview">
<div className="perf-conn-summary__item">
<span className="perf-conn-summary__label">Link</span>
<PerfProbeStatusBadge tone={connBadge.tone}>{connBadge.label}</PerfProbeStatusBadge>
</div>
<div className="perf-conn-summary__item">
<span className="perf-conn-summary__label">Session</span>
<PerfProbeStatusBadge tone={sessBadge.tone}>{sessBadge.label}</PerfProbeStatusBadge>
</div>
{isLoggedIn && (
<div className="perf-conn-summary__item">
<span className="perf-conn-summary__label">Role</span>
<PerfProbeStatusBadge tone={roleBadge.tone}>{roleBadge.label}</PerfProbeStatusBadge>
</div>
)}
</div>
<PerfProbeMetricSection title="Connection" defaultOpen layout="stack">
<PerfProbeDetailList
rows={[
{
label: 'Status',
value: <PerfProbeStatusBadge tone={connBadge.tone}>{connBadge.label}</PerfProbeStatusBadge>,
},
{
label: 'Session',
value: <PerfProbeStatusBadge tone={sessBadge.tone}>{sessBadge.label}</PerfProbeStatusBadge>,
},
...(isLoggedIn
? [{
label: 'Navidrome role',
value: <PerfProbeStatusBadge tone={roleBadge.tone}>{roleBadge.label}</PerfProbeStatusBadge>,
}]
: []),
...(serverName ? [{ label: 'Browse label', value: serverName }] : []),
...(connectUrl ? [{ label: 'Connect URL', value: <code className="perf-server-dl__code">{connectUrl}</code> }] : []),
...(status === 'connected'
? [{
label: 'Endpoint',
value: (
<PerfProbeStatusBadge tone={endpointBadge(isLan).tone}>
{endpointBadge(isLan).label}
</PerfProbeStatusBadge>
),
}]
: []),
]}
/>
</PerfProbeMetricSection>
<SidebarPerfProbeServerSection adminRole={adminRole} />
{queueDiffersFromActive && queueServer && (
<PerfProbeMetricSection title="Queue playback server" defaultOpen={false} layout="stack">
<PerfProbeDetailList
rows={[
{ label: 'Name', value: serverListDisplayLabel(queueServer, servers) },
{ label: 'Scope key', value: <code className="perf-server-dl__code">{queueServerId}</code> },
]}
/>
</PerfProbeMetricSection>
)}
</div>
);
}
@@ -0,0 +1,155 @@
import { useAuthStore } from '../../../store/authStore';
import type { NavidromeAdminRole } from '../../../hooks/useNavidromeAdminRole';
import { isNavidromeServer } from '../../../utils/server/subsonicServerIdentity';
import { FEATURE_AUDIOMUSE_SIMILAR_TRACKS, OP_SIMILAR_TRACKS } from '../../../serverCapabilities/catalog';
import {
isFeatureActiveForServer,
resolveCallRoutesForServer,
resolveFeatureForServer,
} from '../../../serverCapabilities/storeView';
import type { CapabilityStatus, FeatureTrust, ResolvedCapability } from '../../../serverCapabilities/types';
import { PerfProbeMetricSection } from './PerfProbeMetricCard';
import PerfProbeDetailList, { type PerfProbeDetailRow } from './PerfProbeDetailList';
import PerfProbeStatusBadge, { type PerfProbeBadgeTone } from './PerfProbeStatusBadge';
function formatServerType(identity: { type?: string; serverVersion?: string; openSubsonic?: boolean } | undefined): string {
if (!identity?.type?.trim()) return 'Unknown';
const version = identity.serverVersion?.trim();
return version ? `${identity.type} ${version}` : identity.type;
}
function detectionBadge(status: CapabilityStatus): { tone: PerfProbeBadgeTone; label: string } {
switch (status) {
case 'present': return { tone: 'ok', label: 'Detected' };
case 'absent': return { tone: 'muted', label: 'Not detected' };
case 'probing': return { tone: 'warn', label: 'Checking…' };
case 'error': return { tone: 'error', label: 'Probe failed' };
case 'unknown': return { tone: 'muted', label: 'Not probed yet' };
default: return { tone: 'muted', label: 'N/A' };
}
}
function strategyLabel(strategyId: string | null): string {
switch (strategyId) {
case 'opensubsonic.sonicSimilarity': return 'sonicSimilarity (OpenSubsonic)';
case 'subsonic.getSimilarSongs': return 'getSimilarSongs (legacy)';
default: return '—';
}
}
function trustBadge(trust: FeatureTrust | null): { tone: PerfProbeBadgeTone; label: string } {
switch (trust) {
case 'high': return { tone: 'ok', label: 'high' };
case 'low': return { tone: 'warn', label: 'heuristic' };
default: return { tone: 'muted', label: '—' };
}
}
function adminRoleBadge(role: NavidromeAdminRole): { tone: PerfProbeBadgeTone; label: string } {
switch (role) {
case 'admin': return { tone: 'ok', label: 'Admin' };
case 'user': return { tone: 'neutral', label: 'Standard user' };
case 'checking':
case 'idle': return { tone: 'warn', label: 'Checking…' };
case 'error': return { tone: 'error', label: 'Could not verify' };
case 'na':
default: return { tone: 'muted', label: 'N/A' };
}
}
interface Props {
adminRole?: NavidromeAdminRole;
}
export default function SidebarPerfProbeServerSection({ adminRole = 'na' }: Props) {
const activeServerId = useAuthStore(s => s.activeServerId);
const server = useAuthStore(s => s.servers.find(srv => srv.id === s.activeServerId));
const identity = useAuthStore(s =>
activeServerId ? s.subsonicServerIdentityByServer[activeServerId] : undefined,
);
// Subscribe to the probe maps so the resolver-derived rows re-render on probe updates.
useAuthStore(s => (activeServerId ? s.audiomusePluginProbeByServer[activeServerId] : undefined));
useAuthStore(s => (activeServerId ? s.instantMixProbeByServer[activeServerId] : undefined));
useAuthStore(s => (activeServerId ? s.audiomuseNavidromeByServer[activeServerId] : undefined));
if (!server) {
return (
<PerfProbeMetricSection title="Active server" defaultOpen layout="stack">
<div className="perf-monitor-empty perf-monitor-empty--inline">
No server configured.
</div>
</PerfProbeMetricSection>
);
}
const navidrome = isNavidromeServer(identity);
const role = adminRoleBadge(adminRole);
const resolved: ResolvedCapability | null = activeServerId
? resolveFeatureForServer(activeServerId, FEATURE_AUDIOMUSE_SIMILAR_TRACKS)
: null;
const audiomuseActive = activeServerId
? isFeatureActiveForServer(activeServerId, FEATURE_AUDIOMUSE_SIMILAR_TRACKS)
: false;
const routes = activeServerId
? resolveCallRoutesForServer(activeServerId, FEATURE_AUDIOMUSE_SIMILAR_TRACKS, OP_SIMILAR_TRACKS)
: [];
const rows: PerfProbeDetailRow[] = [
{ label: 'Name', value: server.name || server.url },
{ label: 'Profile URL', value: <code className="perf-server-dl__code">{server.url}</code> },
{ label: 'Subsonic server', value: formatServerType(identity) },
{
label: 'OpenSubsonic',
value: identity?.openSubsonic
? <PerfProbeStatusBadge tone="ok">yes</PerfProbeStatusBadge>
: identity
? <PerfProbeStatusBadge tone="muted">no</PerfProbeStatusBadge>
: '—',
},
];
if (navidrome) {
rows.push({
label: 'Navidrome role',
value: <PerfProbeStatusBadge tone={role.tone}>{role.label}</PerfProbeStatusBadge>,
});
}
if (resolved && resolved.strategyId !== null && resolved.status !== 'ineligible') {
const detect = detectionBadge(resolved.status);
const trust = trustBadge(resolved.trust);
rows.push(
{
label: 'AudioMuse Instant Mix',
value: <PerfProbeStatusBadge tone={detect.tone}>{detect.label}</PerfProbeStatusBadge>,
},
{ label: 'Provider', value: <code className="perf-server-dl__code">{strategyLabel(resolved.strategyId)}</code> },
{
label: 'Detection trust',
value: <PerfProbeStatusBadge tone={trust.tone}>{trust.label}</PerfProbeStatusBadge>,
},
{
label: 'Mode',
value: audiomuseActive
? (
<PerfProbeStatusBadge tone="ok">
{resolved.activation === 'auto' ? 'active (auto)' : 'enabled in Settings'}
</PerfProbeStatusBadge>
)
: <PerfProbeStatusBadge tone="muted">off</PerfProbeStatusBadge>,
},
);
if (routes.length > 0) {
rows.push({
label: 'Call route',
value: <code className="perf-server-dl__code">{routes.map(r => r.endpoint).join(' → ')}</code>,
});
}
}
return (
<PerfProbeMetricSection title="Active server" defaultOpen layout="stack">
<PerfProbeDetailList rows={rows} />
</PerfProbeMetricSection>
);
}