jbot: fix voice button visibility - chat-panel height overflow

.chat-panel was using height:100% which consumed all right-col space,
pushing VoiceButton out of view (clipped by overflow:hidden).

Fix: switch .chat-panel to flex:1 + min-height:0 so it participates
in flex layout and leaves room for the VoiceButton below it.

Also moved VoiceButton component outside App() to avoid React
component-identity churn on every render.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-17 14:13:37 +10:00
co-authored by Copilot
parent cdb7efd61d
commit 1efcebfd9b
4 changed files with 37 additions and 38 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -272,7 +272,8 @@ kbd {
.chat-panel {
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
min-height: 0;
padding: 14px 18px;
gap: 10px;
overflow: hidden;
+31 -33
View File
@@ -45,41 +45,39 @@ function speakText(text) {
});
}
// ── Hold-to-talk voice button (defined outside App to keep stable identity) ──
function VoiceButton({ phase, onStart, onEnd }) {
const busy = phase === 'thinking' || phase === 'speaking';
let label = '🎙 按住说话';
let mod = '';
if (phase === 'acquiring') { label = '⏳ 准备中…'; mod = ' vbtn--acquiring'; }
else if (phase === 'listening') { label = '🔴 松开发送'; mod = ' vbtn--recording'; }
else if (phase === 'thinking') { label = '⚙ 思考中…'; mod = ' vbtn--busy'; }
else if (phase === 'speaking') { label = '🔊 播放中…'; mod = ' vbtn--busy'; }
const startEvt = (e) => { e.preventDefault(); if (!busy) onStart(); };
const endEvt = (e) => { e.preventDefault(); onEnd(); };
return (
<div className="vbtn-wrap">
<button
className={`vbtn${mod}`}
disabled={busy}
onMouseDown={startEvt}
onMouseUp={endEvt}
onMouseLeave={endEvt}
onTouchStart={startEvt}
onTouchEnd={endEvt}
onTouchCancel={endEvt}
>
{label}
</button>
</div>
);
}
export default function App() {
const navigate = useNavigate();
// ── VoiceButton: WeChat-style hold-to-record ──────────────
// Placed here so it shares App's handleSpaceDown/handleSpaceUp via closure.
function VoiceButton({ phase, onStart, onEnd }) {
const busy = phase === 'thinking' || phase === 'speaking';
let label = '🎙 按住说话';
let mod = '';
if (phase === 'acquiring') { label = '⏳ 准备中…'; mod = ' vbtn--acquiring'; }
else if (phase === 'listening') { label = '🔴 松开发送'; mod = ' vbtn--recording'; }
else if (phase === 'thinking') { label = '⚙ 思考中…'; mod = ' vbtn--busy'; }
else if (phase === 'speaking') { label = '🔊 播放中…'; mod = ' vbtn--busy'; }
const startEvt = (e) => { e.preventDefault(); if (!busy) onStart(); };
const endEvt = (e) => { e.preventDefault(); onEnd(); };
return (
<div className="vbtn-wrap">
<button
className={`vbtn${mod}`}
disabled={busy}
onMouseDown={startEvt}
onMouseUp={endEvt}
onMouseLeave={endEvt}
onTouchStart={startEvt}
onTouchEnd={endEvt}
onTouchCancel={endEvt}
>
{label}
</button>
</div>
);
}
// ── Expression / auto-rotate state ───────────────────────────
const [exprId, setExprId] = useState(ALL_AUTO_IDS[0]);
const [autoRotate, setAutoRotate] = useState(true);