fix: compact list pagination and refine mobile call UX

Show ellipsis page controls when lists grow large, and make mobile call start manual with clearer hangup layout and assistant name.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Eric Wang
2026-08-04 16:53:25 +08:00
co-authored by Cursor
parent 731a372df9
commit d1b05f16c7
2 changed files with 108 additions and 51 deletions
@@ -37,6 +37,7 @@ import {
useVoicePreview,
type ChatMessage,
} from "@/hooks/use-voice-preview";
import { assistantsApi } from "@/lib/api";
type CallView = "video" | "chat";
@@ -452,9 +453,25 @@ export function MobileCallPage({ assistantId }: { assistantId: string }) {
selectCamera: changeCamera,
} = camera;
const [view, setView] = useState<CallView>("chat");
const [assistantName, setAssistantName] = useState("");
const connecting = status === "connecting";
const inCall = !callEnded && (connecting || status === "connected");
useEffect(() => {
let cancelled = false;
void assistantsApi
.get(assistantId)
.then((assistant) => {
if (!cancelled) setAssistantName(assistant.name.trim());
})
.catch(() => {
if (!cancelled) setAssistantName("");
});
return () => {
cancelled = true;
};
}, [assistantId]);
const startCall = useCallback(async () => {
await connect({
visionEnabled: true,
@@ -474,11 +491,6 @@ export function MobileCallPage({ assistantId }: { assistantId: string }) {
[changeCamera, selectTransportCamera],
);
useEffect(() => {
void startCall();
// 首次打开页面时自动发起通话;hooks 会在卸载时各自释放媒体资源。
}, [startCall]);
const error = previewError || cameraError || photoCapture.error;
return (
@@ -524,12 +536,20 @@ export function MobileCallPage({ assistantId }: { assistantId: string }) {
? "通话中"
: connecting
? "正在连接…"
: "通话已结束"}
: status === "failed"
? "连接失败"
: "等待开始"}
</div>
{!inCall && (
<div className="absolute inset-0 z-[2] flex items-center justify-center px-8">
<div className="flex max-w-sm flex-col items-center gap-4 text-center">
<div className="flex max-w-sm flex-col items-center gap-5 text-center">
<div className="space-y-2">
<p className="text-xs tracking-wide text-white/45">AI 助手</p>
<h1 className="text-2xl font-medium leading-tight text-white">
{assistantName || "助手"}
</h1>
</div>
<Button
type="button"
onClick={() => {
@@ -539,7 +559,7 @@ export function MobileCallPage({ assistantId }: { assistantId: string }) {
className="h-12 gap-2 rounded-full bg-white px-6 text-[#07101a] shadow-2xl hover:bg-white/90"
>
<Phone size={19} />
{status === "failed" ? "重新开始通话" : "开始通话"}
{status === "failed" || callEnded ? "重新开始通话" : "开始通话"}
</Button>
{error && <p className="text-xs leading-5 text-white/65">{error}</p>}
</div>
@@ -547,26 +567,7 @@ export function MobileCallPage({ assistantId }: { assistantId: string }) {
)}
{inCall && (
<div className="absolute bottom-[max(1.25rem,env(safe-area-inset-bottom))] left-1/2 z-10 flex -translate-x-1/2 items-center gap-3 min-[380px]:gap-5 landscape:bottom-[max(1rem,env(safe-area-inset-bottom))]">
<CallAudioDeviceSelect
micValue={selectedDeviceId}
micDevices={audioInputs}
onMicSelect={selectDevice}
outputValue={selectedOutputDeviceId}
outputDevices={audioOutputs}
onOutputSelect={selectOutputDevice}
outputSupported={supportsOutputSelection}
disabled={connecting}
/>
<button
type="button"
onClick={endCall}
aria-label="挂断电话"
title="挂断电话"
className="flex size-14 items-center justify-center rounded-full bg-red-500 text-white shadow-2xl transition-transform hover:scale-105 hover:bg-red-600 active:scale-95 min-[380px]:size-16"
>
<PhoneOff className="size-6 min-[380px]:size-7" />
</button>
<div className="absolute bottom-[max(1.25rem,env(safe-area-inset-bottom))] left-1/2 z-10 flex -translate-x-1/2 flex-col items-center gap-3 landscape:bottom-[max(1rem,env(safe-area-inset-bottom))]">
{photoCapture.visible && (
<button
type="button"
@@ -583,12 +584,37 @@ export function MobileCallPage({ assistantId }: { assistantId: string }) {
)}
</button>
)}
<CallCameraDeviceSelect
value={cameraDeviceId}
devices={cameraDevices}
onSelect={selectCamera}
disabled={connecting}
/>
<div className="grid grid-cols-[1fr_auto_1fr] items-center gap-3 min-[380px]:gap-5">
<div className="flex justify-end">
<CallAudioDeviceSelect
micValue={selectedDeviceId}
micDevices={audioInputs}
onMicSelect={selectDevice}
outputValue={selectedOutputDeviceId}
outputDevices={audioOutputs}
onOutputSelect={selectOutputDevice}
outputSupported={supportsOutputSelection}
disabled={connecting}
/>
</div>
<button
type="button"
onClick={endCall}
aria-label="挂断电话"
title="挂断电话"
className="flex size-14 items-center justify-center rounded-full bg-red-500 text-white shadow-2xl transition-transform hover:scale-105 hover:bg-red-600 active:scale-95 min-[380px]:size-16"
>
<PhoneOff className="size-6 min-[380px]:size-7" />
</button>
<div className="flex justify-start">
<CallCameraDeviceSelect
value={cameraDeviceId}
devices={cameraDevices}
onSelect={selectCamera}
disabled={connecting}
/>
</div>
</div>
</div>
)}
</div>