refactor: 工作空间改为单一终端,移除多 Tab 机制
- 移除新建终端按钮和 Tab 栏,只保留唯一默认终端 - AI 命令始终发往唯一终端,避免多终端时目标不确定 - 删除 createTerminalSession/closeTerminalTab/renderTerminalTabs 等多 Tab 相关代码
This commit is contained in:
@@ -31,8 +31,7 @@ interface FileNode {
|
||||
|
||||
let panelVisible = true; // 常驻显示,不可关闭
|
||||
let activeTab: 'terminal' | 'files' = 'terminal';
|
||||
let terminalSessions: TerminalSession[] = [];
|
||||
let activeTerminalId = '';
|
||||
let terminal: TerminalSession | null = null; // 唯一终端
|
||||
let workspaceDir = '';
|
||||
let currentFileDir = '';
|
||||
let fileTree: FileNode[] = [];
|
||||
@@ -196,8 +195,14 @@ export async function initWorkspacePanel(): Promise<void> {
|
||||
// 绑定 UI 事件
|
||||
bindEvents();
|
||||
|
||||
// 创建默认终端
|
||||
createTerminalSession('终端 1');
|
||||
// 创建唯一终端
|
||||
terminal = {
|
||||
id: genId(),
|
||||
title: '终端',
|
||||
lines: [],
|
||||
running: false,
|
||||
autoScroll: true
|
||||
};
|
||||
|
||||
logInfo('工作空间面板已初始化');
|
||||
}
|
||||
@@ -213,12 +218,6 @@ function bindEvents(): void {
|
||||
// 清空按钮
|
||||
document.querySelector('#wsClearBtn')?.addEventListener('click', clearTerminal);
|
||||
|
||||
// 新建终端 Tab
|
||||
document.querySelector('#wsNewTabBtn')?.addEventListener('click', () => {
|
||||
const num = terminalSessions.length + 1;
|
||||
createTerminalSession(`终端 ${num}`);
|
||||
});
|
||||
|
||||
// 文件刷新
|
||||
document.querySelector('#wsFileRefreshBtn')?.addEventListener('click', () => {
|
||||
loadFiles(currentFileDir || workspaceDir);
|
||||
@@ -250,22 +249,8 @@ function switchTab(tab: 'terminal' | 'files'): void {
|
||||
}
|
||||
|
||||
// ── 终端管理 ──
|
||||
function createTerminalSession(title: string): string {
|
||||
const session: TerminalSession = {
|
||||
id: genId(),
|
||||
title,
|
||||
lines: [],
|
||||
running: false,
|
||||
autoScroll: true
|
||||
};
|
||||
terminalSessions.push(session);
|
||||
activeTerminalId = session.id;
|
||||
renderPanel();
|
||||
return session.id;
|
||||
}
|
||||
|
||||
function getActiveSession(): TerminalSession | undefined {
|
||||
return terminalSessions.find(s => s.id === activeTerminalId);
|
||||
return terminal ?? undefined;
|
||||
}
|
||||
|
||||
/** AI Tool Calling 实时输出 → 流式追加到活跃终端 */
|
||||
@@ -318,11 +303,11 @@ function handleToolDone(command: string, exitCode: number | null): void {
|
||||
}
|
||||
|
||||
function appendTerminalOutput(sessionId: string, type: 'stdout' | 'stderr', data: string): void {
|
||||
const session = terminalSessions.find(s => s.id === sessionId);
|
||||
if (!session) {
|
||||
logDebug('工作空间输出丢失', `未找到会话: ${sessionId}`);
|
||||
if (!terminal || terminal.id !== sessionId) {
|
||||
logDebug('工作空间输出丢失', `未匹配终端: ${sessionId}`);
|
||||
return;
|
||||
}
|
||||
const session = terminal;
|
||||
|
||||
// 按行分割,处理 \r 进度更新
|
||||
const lines = data.split('\n');
|
||||
@@ -354,11 +339,11 @@ function appendTerminalOutput(sessionId: string, type: 'stdout' | 'stderr', data
|
||||
}
|
||||
|
||||
function handleProcessExit(sessionId: string, code: number | null): void {
|
||||
const session = terminalSessions.find(s => s.id === sessionId);
|
||||
if (!session) {
|
||||
logDebug('工作空间退出信号丢失', `未找到会话: ${sessionId}`);
|
||||
if (!terminal || terminal.id !== sessionId) {
|
||||
logDebug('工作空间退出信号丢失', `未匹配终端: ${sessionId}`);
|
||||
return;
|
||||
}
|
||||
const session = terminal;
|
||||
|
||||
session.running = false;
|
||||
const exitMsg = code === 0 ? '✓ 进程正常退出' : `✗ 进程退出 (code: ${code})`;
|
||||
@@ -408,21 +393,6 @@ function clearTerminal(): void {
|
||||
renderTerminal();
|
||||
}
|
||||
|
||||
function closeTerminalTab(sessionId: string): void {
|
||||
if (terminalSessions.length <= 1) return; // 至少保留一个
|
||||
|
||||
const session = terminalSessions.find(s => s.id === sessionId);
|
||||
if (session?.running) {
|
||||
window.metonaDesktop?.workspace.kill(sessionId);
|
||||
}
|
||||
|
||||
terminalSessions = terminalSessions.filter(s => s.id !== sessionId);
|
||||
if (activeTerminalId === sessionId) {
|
||||
activeTerminalId = terminalSessions[0].id;
|
||||
}
|
||||
renderPanel();
|
||||
}
|
||||
|
||||
// ── 文件浏览器 ──
|
||||
async function loadFiles(dirPath: string): Promise<void> {
|
||||
const bridge = window.metonaDesktop;
|
||||
@@ -489,7 +459,6 @@ export function renderPanel(): void {
|
||||
|
||||
if (activeTab === 'terminal') {
|
||||
renderTerminal();
|
||||
renderTerminalTabs();
|
||||
} else {
|
||||
renderFileList();
|
||||
}
|
||||
@@ -532,41 +501,6 @@ function renderTerminal(): void {
|
||||
};
|
||||
}
|
||||
|
||||
function renderTerminalTabs(): void {
|
||||
const tabBar = document.querySelector('#wsTermTabBar') as HTMLElement;
|
||||
if (!tabBar) return;
|
||||
|
||||
tabBar.innerHTML = terminalSessions.map(s => {
|
||||
const active = s.id === activeTerminalId;
|
||||
const running = s.running ? ' ●' : '';
|
||||
const closeBtn = terminalSessions.length > 1
|
||||
? `<button class="ws-tab-close" data-close="${s.id}" title="关闭">✕</button>`
|
||||
: '';
|
||||
return `<div class="ws-tab ${active ? 'active' : ''}" data-session="${s.id}">
|
||||
<span class="ws-tab-title">${escapeHtml(s.title)}${running}</span>
|
||||
${closeBtn}
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// 绑定 tab 点击事件
|
||||
tabBar.querySelectorAll('.ws-tab').forEach(tab => {
|
||||
tab.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const closeBtn = target.closest('.ws-tab-close') as HTMLElement;
|
||||
if (closeBtn) {
|
||||
e.stopPropagation();
|
||||
closeTerminalTab(closeBtn.dataset.close!);
|
||||
return;
|
||||
}
|
||||
const sessionId = (tab as HTMLElement).dataset.session;
|
||||
if (sessionId) {
|
||||
activeTerminalId = sessionId;
|
||||
renderPanel();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderFileList(): void {
|
||||
const container = document.querySelector('#wsFileList') as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
@@ -179,7 +179,6 @@
|
||||
|
||||
<!-- 终端 Tab -->
|
||||
<div class="ws-content" id="wsTerminalContent">
|
||||
<div class="ws-term-tab-bar" id="wsTermTabBar"></div>
|
||||
<div class="ws-term-output" id="wsTermOutput"></div>
|
||||
<div class="ws-term-toolbar">
|
||||
<span class="ws-term-hint" id="wsTermHint">等待 AI 执行命令...</span>
|
||||
@@ -195,11 +194,6 @@
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="ws-toolbar-btn" id="wsNewTabBtn" title="新建终端">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user