style: 优化聊天面板和日志布局

- 聊天面板高度铺满整个可用窗口空间
- 日志面板改为覆盖式悬浮,不占用聊天区域
- 侧边栏支持收起/展开功能(64px 窄条模式)
- 日志面板高度增加到 50vh,最大 80vh
- 添加日志数量徽章显示
- 优化按钮文字和提示文案

Thanks @jospalmbier
This commit is contained in:
风雨火
2026-03-24 15:47:12 +08:00
parent 5c21642262
commit ee814b56ef
5 changed files with 124 additions and 25 deletions
+43 -8
View File
@@ -10,6 +10,7 @@ function App() {
const [showTestPage, setShowTestPage] = useState(false);
const [showSimpleChat, setShowSimpleChat] = useState(false);
const [showLogs, setShowLogs] = useState(true);
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const {
composer,
setComposer,
@@ -108,10 +109,12 @@ function App() {
onPatchSession={patchSession}
onDeleteSession={deleteSession}
onSwitchSession={handleSwitchSession}
collapsed={sidebarCollapsed}
onToggleCollapse={() => setSidebarCollapsed(!sidebarCollapsed)}
/>
)}
<main className="flex-1 flex flex-col min-w-0 p-4 gap-4 h-[calc(100vh-80px)]">
<main className="flex-1 flex flex-col min-w-0 p-4 h-[calc(100vh-40px)] relative">
<ChatPanel
message={composer}
onMessageChange={setComposer}
@@ -122,7 +125,45 @@ function App() {
chatFocusMode={chatFocusMode}
onToggleFocusMode={toggleChatFocusMode}
/>
{showLogs && <LogPanel logs={logs} onClear={clearLogs} />}
{/* 日志面板 - 绝对定位覆盖式 */}
<div
className={`absolute bottom-4 left-4 right-4 transition-all duration-300 z-50 ${
showLogs
? 'opacity-100 translate-y-0 visible'
: 'opacity-0 translate-y-4 invisible pointer-events-none'
}`}
>
<LogPanel logs={logs} onClear={clearLogs} />
</div>
{/* 日志切换按钮 - 放在聊天面板内底部 */}
<div className="absolute bottom-4 right-6 z-[60]">
<button
onClick={() => setShowLogs(!showLogs)}
className={`flex items-center gap-2 px-4 py-2.5 rounded-xl font-semibold text-sm transition-all shadow-lg ${
showLogs
? 'bg-slate-900 text-white hover:bg-slate-800'
: 'bg-white/90 text-slate-700 hover:bg-white border border-slate-300'
}`}
title={showLogs ? '收起日志' : '查看日志'}
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d={showLogs ? "M19 9l-7 7-7-7" : "M5 15l7-7 7 7"}
/>
</svg>
<span className="hidden sm:inline">{showLogs ? '收起' : '日志'}</span>
{logs.length > 0 && (
<span className="ml-1 py-0.5 px-1.5 bg-cyan-500 text-white text-[10px] font-bold rounded-full">
{logs.length}
</span>
)}
</button>
</div>
</main>
<div className="absolute top-4 right-4 z-50 flex gap-2">
@@ -132,12 +173,6 @@ function App() {
>
</button>
<button
onClick={() => setShowLogs(!showLogs)}
className="px-4 py-2 bg-slate-900 text-white text-sm font-semibold rounded-lg hover:bg-slate-800 transition-colors shadow-lg"
>
{showLogs ? '隐藏日志' : '显示日志'}
</button>
<button
onClick={() => setShowTestPage(true)}
className="px-4 py-2 bg-slate-900 text-white text-sm font-semibold rounded-lg hover:bg-slate-800 transition-colors shadow-lg"
+2 -2
View File
@@ -76,7 +76,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
}, [messages]);
return (
<section className={`flex-1 flex flex-col min-h-0 bg-gradient-to-b from-white to-slate-50 border border-slate-200 rounded-[28px] overflow-hidden shadow-[0_30px_80px rgba(15,23,42,0.08)] transition-all duration-300 ${chatFocusMode ? 'chat-focus-mode-active' : ''}`}>
<section className={`flex flex-col h-full bg-gradient-to-b from-white to-slate-50 border border-slate-200 rounded-[28px] overflow-hidden shadow-[0_30px_80px rgba(15,23,42,0.08)] transition-all duration-300 ${chatFocusMode ? 'chat-focus-mode-active' : ''}`}>
<header className="shrink-0 px-4 py-2.5 border-b border-slate-200/60 bg-gradient-to-b from-white to-slate-100 flex items-center justify-between">
<h2 className="text-sm font-bold text-slate-700"></h2>
{onToggleFocusMode && (
@@ -100,7 +100,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
<div
ref={messagesRef}
className="flex-1 overflow-y-auto px-4 py-3 bg-gradient-to-b from-slate-50 to-white space-y-3"
className="flex-1 overflow-y-auto px-4 py-3 space-y-3"
>
{/* 最新系统消息提示 */}
{renderedMessages.latestSystemMessage && (
+71 -13
View File
@@ -23,6 +23,8 @@ interface ConnectionSidebarProps {
onPatchSession: (key: string, patch: { label?: string | null }) => void;
onDeleteSession: (key: string) => Promise<boolean>;
onSwitchSession: (key: string) => void;
collapsed?: boolean;
onToggleCollapse?: () => void;
}
export const ConnectionSidebar: React.FC<ConnectionSidebarProps> = ({
@@ -45,22 +47,78 @@ export const ConnectionSidebar: React.FC<ConnectionSidebarProps> = ({
onPatchSession,
onDeleteSession,
onSwitchSession,
collapsed = false,
onToggleCollapse,
}) => {
const isConnecting = status === "connecting";
const isConnected = status === "connected";
if (collapsed) {
return (
<aside className="w-16 flex-shrink-0 p-3 bg-gradient-to-b from-slate-100 to-slate-50 border-r border-slate-200 flex flex-col items-center gap-4">
<button
onClick={onToggleCollapse}
className="p-2.5 rounded-xl bg-white border border-slate-200 hover:bg-slate-50 transition-colors"
title="展开侧边栏"
>
<svg className="w-5 h-5 text-slate-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
</svg>
</button>
<div className="flex-1 flex flex-col items-center gap-3 mt-8">
<div className={`w-2 h-2 rounded-full ${
status === "connected" ? "bg-emerald-500" :
status === "connecting" ? "bg-blue-500" :
status === "error" ? "bg-red-500" : "bg-slate-400"
}`} title={statusText} />
</div>
<button
onClick={onConnect}
disabled={isConnecting}
className="p-2.5 rounded-xl bg-slate-900 text-white hover:bg-slate-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="连接网关"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</button>
<button
onClick={onDisconnect}
disabled={status !== "connected"}
className="p-2.5 rounded-xl bg-white border border-slate-200 text-slate-700 hover:bg-slate-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
title="断开连接"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M18.36 6.64a9 9 0 11-12.73 0" />
</svg>
</button>
</aside>
);
}
return (
<aside className="w-80 flex-shrink-0 p-6 bg-gradient-to-b from-slate-50 to-white border-r border-slate-200 overflow-y-auto">
<div className="mb-6">
<p className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-2">
Gateway Console
</p>
<h1 className="text-2xl font-bold text-slate-900 mb-2">
OpenClaw Chat Demo
</h1>
<p className="text-sm text-slate-600 leading-relaxed">
</p>
<div className="flex items-center justify-between mb-6">
<div className="min-w-0">
<p className="text-xs font-bold text-slate-500 uppercase tracking-widest mb-2">
Gateway Console
</p>
<h1 className="text-xl font-bold text-slate-900 truncate">
OpenClaw Chat Demo
</h1>
</div>
<button
onClick={onToggleCollapse}
className="p-2 rounded-lg hover:bg-slate-200/60 transition-colors"
title="收起侧边栏"
>
<svg className="w-4 h-4 text-slate-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 5l7 7-7 7M5 5l7 7-7 7" />
</svg>
</button>
</div>
<div className="bg-white rounded-2xl p-4 border border-slate-200 shadow-lg shadow-slate-900/5 mb-4">
@@ -145,7 +203,7 @@ export const ConnectionSidebar: React.FC<ConnectionSidebarProps> = ({
</button>
</div>
<section className="bg-white rounded-2xl p-4 border border-slate-200 shadow-lg shadow-slate-900/5">
<div className="bg-white rounded-xl p-3 border border-slate-200 shadow-sm mb-4">
<div className="flex items-center justify-between mb-3">
<span className="text-sm text-slate-500"></span>
<span
@@ -165,10 +223,10 @@ export const ConnectionSidebar: React.FC<ConnectionSidebarProps> = ({
<div className="flex items-start flex-col gap-1">
<span className="text-xs text-slate-500"></span>
<span className="font-semibold text-slate-900 text-sm break-all">
{sessionKey}
{sessionKey || "—"}
</span>
</div>
</section>
</div>
{isConnected && (
<SessionList
+4 -1
View File
@@ -89,7 +89,7 @@ export const LogPanel: React.FC<LogPanelProps> = ({ logs, onClear }) => {
}, [filteredLogs, autoScroll]);
return (
<section className="flex flex-col h-[30vh] min-h-[280px] bg-gradient-to-b from-slate-900 to-slate-800 border border-slate-700/50 rounded-[28px] overflow-hidden shadow-[0_28px_80px rgba(15,23,42,0.16)]">
<section className="flex flex-col h-[50vh] min-h-[350px] max-h-[80vh] bg-gradient-to-b from-slate-900 to-slate-800 border border-slate-700/50 rounded-[28px] overflow-hidden shadow-[0_28px_80px rgba(15,23,42,0.16)]">
<header className="flex flex-wrap items-start justify-between gap-4 p-4 border-b border-slate-700/50 bg-slate-900/70">
<div className="min-w-0">
<p className="text-[11px] font-bold text-slate-400 uppercase tracking-widest mb-1.5">
@@ -110,12 +110,14 @@ export const LogPanel: React.FC<LogPanelProps> = ({ logs, onClear }) => {
<button
onClick={() => setWrapLines(!wrapLines)}
className="py-2 px-3.5 bg-slate-800/80 text-slate-200 font-semibold text-sm rounded-xl border border-slate-600/30 hover:-translate-y-0.5 hover:border-cyan-400/40 hover:bg-slate-700/80 transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:translate-y-0"
title={wrapLines ? "点击使用单行滚动" : "点击启用自动换行"}
>
{wrapLines ? "自动换行" : "单行滚动"}
</button>
<button
onClick={() => setAutoScroll(!autoScroll)}
className="py-2 px-3.5 bg-slate-800/80 text-slate-200 font-semibold text-sm rounded-xl border border-slate-600/30 hover:-translate-y-0.5 hover:border-cyan-400/40 hover:bg-slate-700/80 transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:translate-y-0"
title={autoScroll ? "点击暂停自动滚动" : "点击启用自动滚动"}
>
{autoScroll ? "跟随最新" : "暂停跟随"}
</button>
@@ -123,6 +125,7 @@ export const LogPanel: React.FC<LogPanelProps> = ({ logs, onClear }) => {
onClick={handleCopy}
disabled={filteredLogs.length === 0}
className="py-2 px-3.5 bg-slate-800/80 text-slate-200 font-semibold text-sm rounded-xl border border-slate-600/30 hover:-translate-y-0.5 hover:border-cyan-400/40 hover:bg-slate-700/80 transition-all disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:translate-y-0"
title="复制当前筛选下的所有日志"
>
{copiedLabel}
</button>
+4 -1
View File
@@ -124,6 +124,10 @@ export function useGatewayChat() {
}>({
gatewayUrl: "ws://127.0.0.1:18789",
token: "628567e2508a81fb41e976b2c16fc2abe33de269a0dcd26b",
// gatewayUrl: "ws://192.168.137.30:31071/",
// token: "fdb6f92fe86039d01bc980769530d125c745059f1092877ea49d85b7e4de8552",
password: "",
sessionKey: "agent:clawagent:test",
});
@@ -1311,7 +1315,6 @@ export function useGatewayChat() {
safeSend,
waitResponse,
pushLog,
addSystemMessage,
addErrorMessage,
]);