style: 优化聊天面板和日志布局
- 聊天面板高度铺满整个可用窗口空间 - 日志面板改为覆盖式悬浮,不占用聊天区域 - 侧边栏支持收起/展开功能(64px 窄条模式) - 日志面板高度增加到 50vh,最大 80vh - 添加日志数量徽章显示 - 优化按钮文字和提示文案 Thanks @jospalmbier
This commit is contained in:
+43
-8
@@ -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"
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user