Add Network Quality Indicator component and integrate into Assistant and Mobile Call pages

- Introduce a new `NetworkQualityIndicator` component to visually represent network quality status during calls.
- Integrate the `NetworkQualityIndicator` into `AssistantPage` and `MobileCallPage`, enhancing user awareness of connection quality.
- Update `use-voice-preview` hook to manage network quality metrics, improving overall call experience and performance monitoring.
This commit is contained in:
Xin Wang
2026-07-10 15:25:26 +08:00
parent 2a002697aa
commit 5705726cfb
5 changed files with 270 additions and 4 deletions

View File

@@ -0,0 +1,70 @@
"use client";
import { Wifi, WifiOff } from "lucide-react";
import type {
NetworkQuality,
VoicePreviewStatus,
} from "@/hooks/use-voice-preview";
const QUALITY_VIEW: Record<
NetworkQuality,
{ label: string; color: string; darkColor: string }
> = {
unknown: {
label: "检测中",
color: "text-muted-foreground",
darkColor: "text-white/55",
},
good: {
label: "网络良好",
color: "text-emerald-600",
darkColor: "text-emerald-300",
},
fair: {
label: "网络一般",
color: "text-amber-600",
darkColor: "text-amber-300",
},
poor: {
label: "网络较差",
color: "text-red-600",
darkColor: "text-red-300",
},
};
export function NetworkQualityIndicator({
quality,
status,
dark = false,
className = "",
}: {
quality: NetworkQuality;
status: VoicePreviewStatus;
dark?: boolean;
className?: string;
}) {
const connected = status === "connected";
const qualityView = QUALITY_VIEW[quality];
const view = connected
? {
label: qualityView.label,
color: dark ? qualityView.darkColor : qualityView.color,
}
: {
label: status === "connecting" ? "检测中" : "未连接",
color: dark ? "text-white/55" : "text-muted-foreground",
};
const Icon = connected || status === "connecting" ? Wifi : WifiOff;
return (
<span
className={`inline-flex items-center gap-1.5 whitespace-nowrap text-xs ${view.color} ${className}`}
title={view.label}
aria-label={view.label}
>
<Icon className="size-3.5" aria-hidden="true" />
<span>{view.label}</span>
</span>
);
}

View File

@@ -40,6 +40,7 @@ import {
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { NetworkQualityIndicator } from "@/components/network-quality-indicator";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch";
@@ -2037,6 +2038,10 @@ function DebugDrawer({
<div className="shrink-0 text-sm font-medium text-foreground">
</div>
<NetworkQualityIndicator
quality={preview.networkQuality}
status={preview.status}
/>
<DebugConnectionStatus
status={preview.status}
micWarning={preview.micWarning}

View File

@@ -12,6 +12,7 @@ import {
Video,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { NetworkQualityIndicator } from "@/components/network-quality-indicator";
import {
DropdownMenu,
DropdownMenuContent,
@@ -247,6 +248,7 @@ export function MobileCallPage({ assistantId }: { assistantId: string }) {
selectOutputDevice,
supportsOutputSelection,
messages,
networkQuality,
connect,
replaceVideoStream,
disconnect,
@@ -325,6 +327,13 @@ export function MobileCallPage({ assistantId }: { assistantId: string }) {
{view === "chat" && <MobileCallTranscript messages={messages} />}
<NetworkQualityIndicator
quality={networkQuality}
status={status}
dark
className="absolute left-4 top-[max(1rem,env(safe-area-inset-top))] z-10 rounded-full border border-white/10 bg-black/30 px-3 py-1.5 backdrop-blur-md"
/>
<div className="absolute left-1/2 top-[max(1rem,env(safe-area-inset-top))] z-10 flex -translate-x-1/2 items-center gap-2 rounded-full border border-white/10 bg-black/30 px-3 py-1.5 text-xs text-white/85 backdrop-blur-md">
<span
className={[