agent/static/demo_compact_layout.html

608 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>紧凑输入区 Demo</title>
<style>
:root {
--bg: #f1efe7;
--panel: rgba(255, 255, 255, 0.9);
--sidebar: rgba(255, 255, 255, 0.72);
--border: rgba(118, 103, 84, 0.35);
--text: #3d3929;
--muted: #8c8374;
--accent: #d37250;
--accent-strong: #b65937;
--shadow: 0 15px 40px rgba(61, 57, 41, 0.18);
--token-bg: rgba(255, 255, 255, 0.95);
--tool-bg: rgba(230, 224, 207, 0.4);
--success: #5e9f6d;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "PingFang SC", "SF Pro Display", "Iowan Old Style", ui-serif, Georgia, serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
display: flex;
}
.demo-app {
display: grid;
grid-template-columns: 260px 1fr 300px;
width: 100%;
min-height: 100vh;
overflow: hidden;
}
aside {
padding: 24px 20px;
border-right: 1px solid var(--border);
background: var(--sidebar);
backdrop-filter: blur(18px);
}
.conversation-panel {
border-right: 1px solid var(--border);
}
.conversation-panel h2,
.workspace-panel h2 {
margin-bottom: 16px;
font-size: 17px;
}
.conversation-item {
padding: 10px 12px;
border: 1px solid transparent;
border-radius: 10px;
margin-bottom: 8px;
cursor: pointer;
transition: border 0.2s ease, background 0.2s ease;
}
.conversation-item.active {
border-color: var(--accent);
background: rgba(211, 114, 80, 0.08);
}
.chat-shell {
position: relative;
display: flex;
flex-direction: column;
padding: 32px 48px 24px;
overflow: hidden;
}
.status-beacon {
position: absolute;
top: 18px;
left: 32px;
width: 240px;
z-index: 2;
}
.status-card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 18px;
padding: 12px 16px;
box-shadow: var(--shadow);
}
.status-title {
font-size: 15px;
font-weight: 600;
margin-bottom: 4px;
}
.status-meta {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 13px;
color: var(--muted);
}
.status-meta span {
display: flex;
align-items: center;
gap: 6px;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--success);
box-shadow: 0 0 10px rgba(94, 159, 109, 0.6);
}
.chat-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 18px;
padding-top: 96px;
}
.token-shelf {
width: min(820px, 85%);
margin: 0 auto;
position: relative;
transition: transform 0.35s ease;
}
.token-panel {
background: var(--token-bg);
border: 1px solid var(--border);
border-radius: 20px;
padding: 12px 20px;
box-shadow: var(--shadow);
transition: opacity 0.35s ease, transform 0.35s ease;
}
.token-shelf[data-collapsed="true"] .token-panel {
transform: translateY(-110%);
opacity: 0;
pointer-events: none;
}
.token-stats {
display: flex;
justify-content: space-between;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted);
}
.token-value {
font-size: 20px;
font-weight: 600;
color: var(--text);
margin-top: 6px;
}
.token-toggle {
position: absolute;
top: -12px;
left: 50%;
transform: translate(-50%, -100%);
width: 64px;
height: 32px;
border-radius: 999px 999px 0 0;
border: 1px solid var(--border);
border-bottom: none;
background: var(--panel);
cursor: pointer;
box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.08);
}
.token-toggle span {
display: inline-block;
transition: transform 0.3s ease;
}
.token-shelf[data-collapsed="true"] .token-toggle span {
transform: rotate(180deg);
}
.messages {
flex: 1;
min-height: 0;
border-radius: 24px;
padding: 28px 32px;
background: var(--panel);
border: 1px solid var(--border);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 18px;
}
.message {
padding: 16px 18px;
border-radius: 18px;
line-height: 1.6;
background: rgba(255, 255, 255, 0.8);
}
.message.user {
align-self: flex-end;
background: rgba(211, 114, 80, 0.15);
}
.message.assistant {
align-self: flex-start;
}
.tool-card {
background: var(--tool-bg);
border-radius: 14px;
padding: 12px 16px;
margin-top: 10px;
font-size: 13px;
}
.input-region {
padding-bottom: 12px;
}
.input-shell {
position: relative;
width: min(840px, 92%);
margin: 0 auto;
border: 1px solid var(--border);
border-radius: 999px;
background: rgba(255, 255, 255, 0.96);
box-shadow: var(--shadow);
padding: 8px 56px;
transition: border-radius 0.25s ease, padding 0.25s ease, min-height 0.25s ease;
display: flex;
align-items: center;
}
.input-shell.expanded {
border-radius: 34px;
padding-top: 18px;
padding-bottom: 18px;
align-items: flex-end;
}
.icon-btn {
position: absolute;
border: none;
background: transparent;
font-size: 22px;
cursor: pointer;
color: var(--text);
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s ease, transform 0.2s ease;
}
.icon-btn.plus {
left: 8px;
bottom: 10px;
}
.icon-btn.send {
right: 8px;
bottom: 10px;
background: var(--accent);
color: #fff;
box-shadow: inset 0 -2px 0 rgba(0, 0, 0, 0.15);
}
.input-shell.expanded .icon-btn.plus,
.input-shell.expanded .icon-btn.send {
bottom: 14px;
}
.icon-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
.icon-btn.send:hover {
background: var(--accent-strong);
}
.input-field {
width: 100%;
border: none;
resize: none;
font-size: 15px;
line-height: 1.5;
background: transparent;
color: var(--text);
padding: 4px 0;
outline: none;
max-height: calc(1.5em * 4);
transition: height 0.25s ease;
}
.primary-menu {
position: absolute;
left: 0;
bottom: calc(100% + 12px);
display: flex;
flex-direction: column;
gap: 6px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 18px;
box-shadow: var(--shadow);
padding: 10px;
width: 180px;
opacity: 0;
transform: translateY(10px);
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.primary-menu[data-open="true"] {
opacity: 1;
transform: translateY(0);
pointer-events: all;
}
.menu-entry {
background: transparent;
border: none;
padding: 8px 12px;
border-radius: 12px;
text-align: left;
font-size: 14px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
color: var(--text);
}
.menu-entry:hover {
background: rgba(0, 0, 0, 0.05);
}
.submenu-panel {
position: absolute;
bottom: calc(100% + 12px);
left: 190px;
width: 160px;
padding: 10px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 16px;
box-shadow: var(--shadow);
opacity: 0;
transform: translateY(10px);
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
}
.submenu-panel[data-open="true"] {
opacity: 1;
transform: translateY(0);
pointer-events: all;
}
.submenu-panel button {
width: 100%;
border: none;
background: transparent;
padding: 8px;
text-align: left;
border-radius: 10px;
cursor: pointer;
font-size: 13px;
}
.submenu-panel button:hover {
background: rgba(0, 0, 0, 0.05);
}
.workspace-panel {
border-right: none;
border-left: 1px solid var(--border);
}
.workspace-panel .focus-card {
background: rgba(255, 255, 255, 0.85);
border-radius: 14px;
padding: 12px;
margin-bottom: 12px;
border: 1px solid rgba(61, 57, 41, 0.08);
}
@media (max-width: 1200px) {
.demo-app {
grid-template-columns: 220px 1fr;
}
.workspace-panel {
display: none;
}
}
@media (max-width: 960px) {
.demo-app {
grid-template-columns: 1fr;
}
aside {
display: none;
}
.chat-shell {
padding: 32px 20px;
}
.input-shell {
width: 100%;
}
}
</style>
</head>
<body>
<div class="demo-app">
<aside class="conversation-panel">
<h2>🗂️ 对话记录</h2>
<div class="conversation-item active">
最近:修复 token 面板交互
</div>
<div class="conversation-item">
设计新输入栏
</div>
<div class="conversation-item">
子智能体联调
</div>
</aside>
<div class="chat-shell">
<div class="status-beacon">
<div class="status-card">
<div class="status-title">🤖 AI Agent</div>
<div class="status-meta">
<span>版本号 · v0.9.5</span>
<span>模式 · 思考模式</span>
<span><span class="status-dot"></span> 已连接</span>
</div>
</div>
</div>
<div class="chat-content">
<div class="token-shelf" data-collapsed="false">
<div class="token-panel">
<div class="token-stats">
<div>
<span>当前上下文</span>
<div class="token-value">4,320</div>
</div>
<div>
<span>累计输入</span>
<div class="token-value">18,955</div>
</div>
<div>
<span>累计输出</span>
<div class="token-value">12,204</div>
</div>
</div>
</div>
<button class="token-toggle" type="button">
<span></span>
</button>
</div>
<div class="messages">
<article class="message user">
我想让对话区域更高一些,顶部的状态栏太占地方了。
</article>
<article class="message assistant">
我们可以把顶部信息收纳在侧边栏上方,只留下 token 面板和消息流,视觉上能腾出大量空间。下面是一个示例工具调用卡片:
<div class="tool-card">
<strong>🛠️ read_file</strong>
<div>在 static/app.js 中定位输入框逻辑。</div>
</div>
</article>
<article class="message user">
输入栏需要合并工具按钮,保持 ChatGPT 的简洁风格。
</article>
<article class="message assistant">
已为你准备体育场式输入控件,并附带浮动菜单(上传文件、切换模式、工具禁用、设置等)。
</article>
</div>
<div class="input-region">
<div class="input-shell" data-state="single">
<button class="icon-btn plus" type="button" aria-label="展开菜单" data-role="menu-toggle">+</button>
<textarea class="input-field" rows="1" placeholder="输入内容,按 Enter 发送Shift + Enter 换行"></textarea>
<button class="icon-btn send" type="button" aria-label="发送消息"></button>
<div class="primary-menu" data-open="false">
<button class="menu-entry" type="button">上传文件</button>
<button class="menu-entry with-sub" type="button" data-menu-target="tools">工具禁用 <span></span></button>
<button class="menu-entry" type="button">快速 / 思考切换</button>
<button class="menu-entry with-sub" type="button" data-menu-target="settings">设置 <span></span></button>
</div>
<div class="submenu-panel" id="tools-submenu" data-open="false">
<button type="button">read_file</button>
<button type="button">run_command</button>
<button type="button">web_search</button>
</div>
<div class="submenu-panel" id="settings-submenu" data-open="false">
<button type="button">实时终端</button>
<button type="button">聚焦面板</button>
<button type="button">压缩对话</button>
</div>
</div>
</div>
</div>
</div>
<aside class="workspace-panel">
<h2>📁 聚焦与终端</h2>
<div class="focus-card">
<strong>focus_file · main.py</strong>
<p>状态:已锁定</p>
</div>
<div class="focus-card">
<strong>终端 #1</strong>
<p>pip install -r requirements.txt</p>
</div>
</aside>
</div>
<script>
const tokenShelf = document.querySelector('.token-shelf');
const tokenToggle = document.querySelector('.token-toggle');
const inputShell = document.querySelector('.input-shell');
const textarea = document.querySelector('.input-field');
const primaryMenu = document.querySelector('.primary-menu');
const plusButton = document.querySelector('[data-role="menu-toggle"]');
const submenuPanels = {
tools: document.getElementById('tools-submenu'),
settings: document.getElementById('settings-submenu')
};
tokenToggle.addEventListener('click', () => {
const collapsed = tokenShelf.getAttribute('data-collapsed') === 'true';
tokenShelf.setAttribute('data-collapsed', String(!collapsed));
});
const singleLineHeight = 28;
function autoResize() {
textarea.style.height = 'auto';
const maxHeight = parseFloat(getComputedStyle(textarea).lineHeight) * 4;
const newHeight = Math.min(textarea.scrollHeight, maxHeight);
textarea.style.height = newHeight + 'px';
if (newHeight > singleLineHeight + 4) {
inputShell.classList.add('expanded');
} else {
inputShell.classList.remove('expanded');
}
}
textarea.addEventListener('input', autoResize);
autoResize();
plusButton.addEventListener('click', (event) => {
event.stopPropagation();
const open = primaryMenu.getAttribute('data-open') === 'true';
primaryMenu.setAttribute('data-open', String(!open));
if (open) {
Object.values(submenuPanels).forEach(panel => panel.setAttribute('data-open', 'false'));
}
});
document.querySelectorAll('.menu-entry.with-sub').forEach(button => {
button.addEventListener('click', (event) => {
event.stopPropagation();
const target = button.getAttribute('data-menu-target');
const panel = submenuPanels[target];
const state = panel.getAttribute('data-open') === 'true';
Object.entries(submenuPanels).forEach(([key, node]) => {
node.setAttribute('data-open', key === target && !state ? 'true' : 'false');
});
});
});
document.addEventListener('click', (event) => {
if (!inputShell.contains(event.target)) {
primaryMenu.setAttribute('data-open', 'false');
Object.values(submenuPanels).forEach(panel => panel.setAttribute('data-open', 'false'));
}
});
</script>
</body>
</html>