feat: 增强弱网与断线场景下的移动端交互反馈

This commit is contained in:
2026-03-06 06:54:51 +08:00
parent 5a817e6646
commit b309dca688
4 changed files with 40 additions and 13 deletions

View File

@@ -8,19 +8,19 @@ function formatTime(ts: number): string {
}
interface HistoryListProps {
sendJSON: (obj: Record<string, unknown>) => void;
sendPaste: (text: string) => void;
}
export function HistoryList({ sendJSON }: HistoryListProps) {
export function HistoryList({ sendPaste }: HistoryListProps) {
const history = useAppStore((s) => s.history);
const clearHistory = useAppStore((s) => s.clearHistory);
const handleItemClick = useCallback(
(text: string) => {
sendJSON({ type: "paste", text });
sendPaste(text);
toast.info("发送粘贴…");
},
[sendJSON],
[sendPaste],
);
return (

View File

@@ -8,9 +8,13 @@ interface MicButtonProps {
export function MicButton({ onStart, onStop }: MicButtonProps) {
const connected = useAppStore((s) => s.connectionStatus === "connected");
const micReady = useAppStore((s) => s.micReady);
const recording = useAppStore((s) => s.recording);
const pendingStart = useAppStore((s) => s.pendingStart);
const isActive = recording || pendingStart;
const stopping = useAppStore((s) => s.stopping);
const weakNetwork = useAppStore((s) => s.weakNetwork);
const isActive = recording || pendingStart || stopping;
const disabled = !connected || !micReady || stopping;
const handlePointerDown = useCallback(
(e: React.PointerEvent<HTMLButtonElement>) => {
@@ -46,6 +50,8 @@ export function MicButton({ onStart, onStop }: MicButtonProps) {
"cursor-not-allowed border-edge bg-linear-to-br from-surface-hover to-surface text-fg-secondary opacity-30 shadow-[0_2px_12px_rgba(0,0,0,0.3),inset_0_1px_0_rgba(255,255,255,0.04)]";
const activeClasses =
"animate-mic-breathe scale-[1.06] border-accent-hover bg-accent text-white shadow-[0_0_32px_rgba(99,102,241,0.35),0_0_80px_rgba(99,102,241,0.2)]";
const weakClasses =
"border-amber-400 bg-linear-to-br from-amber-400/15 to-surface text-amber-200 shadow-[0_0_22px_rgba(251,191,36,0.28)]";
const idleClasses =
"border-edge bg-linear-to-br from-surface-hover to-surface text-fg-secondary shadow-[0_2px_12px_rgba(0,0,0,0.3),inset_0_1px_0_rgba(255,255,255,0.04)]";
@@ -54,13 +60,15 @@ export function MicButton({ onStart, onStop }: MicButtonProps) {
<div className="relative flex touch-none items-center justify-center">
<button
type="button"
disabled={!connected}
disabled={disabled}
className={`relative z-1 flex size-24 cursor-pointer touch-none select-none items-center justify-center rounded-full border-2 transition-all duration-[250ms] ease-[cubic-bezier(0.4,0,0.2,1)] ${
!connected
disabled
? disabledClasses
: isActive
? activeClasses
: idleClasses
: weakNetwork
? weakClasses
: isActive
? activeClasses
: idleClasses
}`}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
@@ -98,7 +106,17 @@ export function MicButton({ onStart, onStop }: MicButtonProps) {
))}
</div>
</div>
<p className="font-medium text-fg-dim text-sm">{"按住说话"}</p>
<p className="font-medium text-fg-dim text-sm">
{!micReady
? "请先准备麦克风"
: !connected
? "连接中断,等待重连"
: stopping
? "收尾中…"
: weakNetwork
? "网络波动,已启用缓冲"
: "按住说话"}
</p>
</section>
);
}

View File

@@ -3,7 +3,11 @@ import { useAppStore } from "../stores/app-store";
export function PreviewBox() {
const text = useAppStore((s) => s.previewText);
const active = useAppStore((s) => s.previewActive);
const weakNetwork = useAppStore((s) => s.weakNetwork);
const recording = useAppStore((s) => s.recording);
const hasText = text.length > 0;
const placeholder =
weakNetwork && recording ? "网络波动中,音频缓冲后发送…" : "按住说话…";
return (
<section className="shrink-0 pb-3">
@@ -17,7 +21,7 @@ export function PreviewBox() {
<p
className={`break-words text-base leading-relaxed ${hasText ? "" : "text-fg-dim"}`}
>
{hasText ? text : "按住说话…"}
{hasText ? text : placeholder}
</p>
</div>
</section>

View File

@@ -20,7 +20,12 @@ const statusConfig = {
export function StatusBadge() {
const status = useAppStore((s) => s.connectionStatus);
const { text, dotClass, borderClass } = statusConfig[status];
const weakNetwork = useAppStore((s) => s.weakNetwork);
const { dotClass, borderClass } = statusConfig[status];
const text =
status === "connected" && weakNetwork
? "网络波动"
: statusConfig[status].text;
return (
<div