feat: 终端空闲状态优化 — ASCII Logo + 工作空间信息 + 轮转贴士
This commit is contained in:
@@ -47,6 +47,22 @@ let fileTree: FileNode[] = [];
|
|||||||
let previewFile: { name: string; content: string; path: string } | null = null;
|
let previewFile: { name: string; content: string; path: string } | null = null;
|
||||||
let _counter = 0;
|
let _counter = 0;
|
||||||
|
|
||||||
|
// ── 空闲状态轮转贴士 ──
|
||||||
|
const IDLE_TIPS = [
|
||||||
|
'💡 可以让我读取文件、搜索代码、执行命令',
|
||||||
|
'💡 试试说"帮我看看这个目录里有什么"',
|
||||||
|
'💡 我可以联网搜索并抓取网页内容',
|
||||||
|
'💡 支持 Git 操作:状态查看、提交、分支管理',
|
||||||
|
'💡 我能记住你说过的重要信息,跨会话保留',
|
||||||
|
'💡 上传图片让我分析,支持 Vision 模型',
|
||||||
|
'💡 说"搜索..."我会自动联网查找最新信息',
|
||||||
|
'💡 我会从成功经验中学习,越用越聪明',
|
||||||
|
'💡 可以让我对比文件差异、批量替换文本',
|
||||||
|
'💡 支持浏览器控制,帮你操作网页',
|
||||||
|
];
|
||||||
|
let tipIndex = Math.floor(Math.random() * IDLE_TIPS.length);
|
||||||
|
let tipTimer: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
/** 工具调用卡片列表 */
|
/** 工具调用卡片列表 */
|
||||||
let toolCards: ToolCallRecord[] = [];
|
let toolCards: ToolCallRecord[] = [];
|
||||||
let toolAutoScroll = true;
|
let toolAutoScroll = true;
|
||||||
@@ -491,8 +507,17 @@ function renderTerminal(): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 空闲状态:显示 Logo + 信息 + 贴士
|
||||||
|
if (session.lines.length === 0 && !session.running) {
|
||||||
|
renderIdleState(container);
|
||||||
|
startTipRotation(container);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有内容时停止贴士轮转
|
||||||
|
stopTipRotation();
|
||||||
|
|
||||||
// 使用 DocumentFragment 优化渲染
|
// 使用 DocumentFragment 优化渲染
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
const wrapper = document.createElement('div');
|
const wrapper = document.createElement('div');
|
||||||
|
|
||||||
for (const line of session.lines) {
|
for (const line of session.lines) {
|
||||||
@@ -516,6 +541,74 @@ function renderTerminal(): void {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 渲染空闲状态 */
|
||||||
|
function renderIdleState(container: HTMLElement): void {
|
||||||
|
const tip = IDLE_TIPS[tipIndex];
|
||||||
|
const dirDisplay = workspaceDir
|
||||||
|
? workspaceDir.replace(/.*[/\\]/, '~/.')
|
||||||
|
: '未设置';
|
||||||
|
|
||||||
|
// 截取目录最后一段
|
||||||
|
const shortDir = workspaceDir
|
||||||
|
? '...' + workspaceDir.slice(-30)
|
||||||
|
: '未设置';
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="ws-idle-state">
|
||||||
|
<div class="ws-idle-logo">
|
||||||
|
<pre class="ws-idle-ascii"> ╭───────╮
|
||||||
|
│ ◕ ◕ │
|
||||||
|
│ ▽ │
|
||||||
|
╰─┬───┬─╯
|
||||||
|
╱│ │╲
|
||||||
|
╱ │ │ ╲
|
||||||
|
🦙 Metona</pre>
|
||||||
|
</div>
|
||||||
|
<div class="ws-idle-info">
|
||||||
|
<div class="ws-idle-info-row">
|
||||||
|
<span class="ws-idle-label">工作空间</span>
|
||||||
|
<span class="ws-idle-value" title="${escapeHtml(workspaceDir)}">${escapeHtml(shortDir)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ws-idle-divider"></div>
|
||||||
|
<div class="ws-idle-tip" id="wsIdleTip">${escapeHtml(tip)}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 重置滚动
|
||||||
|
container.scrollTop = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 启动贴士轮转 */
|
||||||
|
function startTipRotation(container: HTMLElement): void {
|
||||||
|
if (tipTimer) return;
|
||||||
|
tipTimer = setInterval(() => {
|
||||||
|
// 只在空闲状态时轮转
|
||||||
|
const session = getActiveSession();
|
||||||
|
if (!session || session.lines.length > 0 || session.running) {
|
||||||
|
stopTipRotation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tipIndex = (tipIndex + 1) % IDLE_TIPS.length;
|
||||||
|
const tipEl = container.querySelector('#wsIdleTip');
|
||||||
|
if (tipEl) {
|
||||||
|
tipEl.classList.add('fading');
|
||||||
|
setTimeout(() => {
|
||||||
|
tipEl.textContent = IDLE_TIPS[tipIndex];
|
||||||
|
tipEl.classList.remove('fading');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}, 6000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 停止贴士轮转 */
|
||||||
|
function stopTipRotation(): void {
|
||||||
|
if (tipTimer) {
|
||||||
|
clearInterval(tipTimer);
|
||||||
|
tipTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderFileList(): void {
|
function renderFileList(): void {
|
||||||
const container = document.querySelector('#wsFileList') as HTMLElement;
|
const container = document.querySelector('#wsFileList') as HTMLElement;
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|||||||
@@ -2351,6 +2351,89 @@ html, body {
|
|||||||
font-family: var(--font);
|
font-family: var(--font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 终端空闲状态 ── */
|
||||||
|
.ws-idle-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
padding: 20px 16px;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-logo {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-ascii {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: rgba(232, 115, 74, 0.65);
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
text-shadow: 0 0 20px rgba(232, 115, 74, 0.15);
|
||||||
|
animation: idle-glow 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes idle-glow {
|
||||||
|
0%, 100% { text-shadow: 0 0 20px rgba(232, 115, 74, 0.1); }
|
||||||
|
50% { text-shadow: 0 0 25px rgba(232, 115, 74, 0.25); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-label {
|
||||||
|
color: rgba(245, 240, 232, 0.35);
|
||||||
|
font-family: var(--font);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-value {
|
||||||
|
color: rgba(245, 240, 232, 0.6);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
text-align: right;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-divider {
|
||||||
|
width: 60px;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(232, 115, 74, 0.3), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-tip {
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(245, 240, 232, 0.45);
|
||||||
|
font-family: var(--font);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 240px;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ws-idle-tip.fading {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 工具调用 Tab */
|
/* 工具调用 Tab */
|
||||||
.ws-tools-output {
|
.ws-tools-output {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user