style: 样式优化

This commit is contained in:
2026-06-14 16:31:23 +08:00
parent fe053f94dc
commit 7a0443ebcc
8 changed files with 902 additions and 164 deletions

View File

@@ -1,6 +1,7 @@
// ============================================================
// ChatPanel — 消息展示面板
// 职责:渲染对话消息列表、流式光标、元数据、自动滚动、文本输入
// 增强:空状态场景卡片、语音输入按钮
// ============================================================
import { useEffect, useRef, useState } from "react";
@@ -12,10 +13,33 @@ interface ChatPanelProps {
messages: ChatMessage[];
currentReply?: string;
connectionStatus: ConnectionStatus;
isMicOn?: boolean;
isSpeaking?: boolean;
onSendText?: (text: string) => void;
onToggleMic?: () => void;
onSceneCard?: (prompt: string) => void;
}
export function ChatPanel({ messages, currentReply, connectionStatus, onSendText }: ChatPanelProps) {
/** 场景卡片数据 */
function getSceneCards(t: (key: string) => string) {
return [
{ icon: "👁", titleKey: "scene.describe", descKey: "scene.describe.desc", prompt: t("scene.describe") },
{ icon: "🔤", titleKey: "scene.text", descKey: "scene.text.desc", prompt: t("scene.text") },
{ icon: "📦", titleKey: "scene.object", descKey: "scene.object.desc", prompt: t("scene.object") },
{ icon: "💡", titleKey: "scene.suggest", descKey: "scene.suggest.desc", prompt: t("scene.suggest") },
];
}
export function ChatPanel({
messages,
currentReply,
connectionStatus,
isMicOn,
isSpeaking,
onSendText,
onToggleMic,
onSceneCard,
}: ChatPanelProps) {
const bottomRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const isAutoScroll = useRef(true);
@@ -23,6 +47,8 @@ export function ChatPanel({ messages, currentReply, connectionStatus, onSendText
const { t } = useI18n();
const isConnected = connectionStatus === "connected";
const isEmpty = messages.length === 0 && !currentReply;
const sceneCards = getSceneCards(t);
// 用户上滚时暂停自动滚动,滚到底部时恢复
useEffect(() => {
@@ -53,15 +79,41 @@ export function ChatPanel({ messages, currentReply, connectionStatus, onSendText
setInputText("");
};
// 点击场景卡片
const handleSceneCard = (prompt: string) => {
if (onSceneCard) {
onSceneCard(prompt);
} else if (onSendText) {
onSendText(prompt);
}
};
return (
<div className="chat-panel">
<div className="chat-panel__messages" ref={containerRef}>
{/* 空状态提示 */}
{messages.length === 0 && !currentReply && (
{/* 空状态:场景卡片 */}
{isEmpty && (
<div className="chat-panel__welcome">
<span className="chat-panel__welcome-icon">💬</span>
<p>{isConnected ? t("chat.welcome.prompt") : t("chat.empty.prompt")}</p>
<span className="chat-panel__welcome-hint">{isConnected ? t("chat.welcome.hint") : t("chat.empty.hint")}</span>
{/* 场景快捷卡片 */}
<div className="scene-cards">
{sceneCards.map((card) => (
<button
key={card.titleKey}
className="scene-card"
onClick={() => handleSceneCard(card.prompt)}
>
<span className="scene-card__icon">{card.icon}</span>
<div className="scene-card__text">
<span className="scene-card__title">{t(card.titleKey)}</span>
<span className="scene-card__desc">{t(card.descKey)}</span>
</div>
</button>
))}
</div>
</div>
)}
@@ -106,6 +158,17 @@ export function ChatPanel({ messages, currentReply, connectionStatus, onSendText
onChange={(e) => setInputText(e.target.value)}
disabled={connectionStatus === "connecting"}
/>
{/* 语音输入按钮 */}
{onToggleMic && (
<button
type="button"
className={`chat-input__voice ${isMicOn ? "chat-input__voice--active" : ""} ${isSpeaking ? "btn--speaking" : ""}`}
onClick={onToggleMic}
title={t("chat.input.voice")}
>
🎤
</button>
)}
<button
type="submit"
className="chat-input__send"