fix: improve terminal history rendering
This commit is contained in:
parent
21f206559e
commit
9a83f92dd9
@ -7,7 +7,7 @@ TERMINAL_TIMEOUT = 300
|
||||
TERMINAL_OUTPUT_WAIT = 5
|
||||
TERMINAL_SNAPSHOT_DEFAULT_LINES = 50
|
||||
TERMINAL_SNAPSHOT_MAX_LINES = 200
|
||||
TERMINAL_SNAPSHOT_MAX_CHARS = 60000
|
||||
TERMINAL_SNAPSHOT_MAX_CHARS = 6000000
|
||||
TERMINAL_INPUT_MAX_CHARS = 20000
|
||||
|
||||
__all__ = [
|
||||
|
||||
@ -536,33 +536,46 @@ class PersistentTerminal:
|
||||
output = f"[输出已截断,显示最后{self.display_size}字符]\n{output}"
|
||||
return output
|
||||
|
||||
def get_snapshot(self, last_n_lines: int, max_chars: int) -> Dict:
|
||||
def get_snapshot(self, last_n_lines: Optional[int], max_chars: Optional[int]) -> Dict:
|
||||
"""获取终端快照,包含按顺序排列的输入/输出"""
|
||||
if last_n_lines <= 0:
|
||||
last_n_lines = 1
|
||||
include_all_lines = last_n_lines is None or last_n_lines <= 0
|
||||
if not include_all_lines:
|
||||
last_n_lines = max(1, last_n_lines)
|
||||
|
||||
combined_lines: List[str] = []
|
||||
segments: List[str] = []
|
||||
reset = "\x1b[0m"
|
||||
command_color = "\x1b[1;32m"
|
||||
for event_type, _, data in self.io_history:
|
||||
if event_type == 'input':
|
||||
# 显示输入命令,保持与终端监控一致
|
||||
combined_lines.append(f"➜ {data.rstrip()}" if data.strip() else "➜")
|
||||
# 显示输入命令并加上ANSI颜色,保持与实时终端一致
|
||||
input_text = data.rstrip('\n')
|
||||
decorated = f"{command_color}➜ {input_text}{reset}" if input_text.strip() else f"{command_color}➜{reset}"
|
||||
if not decorated.endswith('\n'):
|
||||
decorated += '\n'
|
||||
segments.append(decorated)
|
||||
else:
|
||||
cleaned = data.replace('\r', '')
|
||||
# 按行拆分输出,保留空行
|
||||
segments = cleaned.splitlines()
|
||||
if cleaned.endswith('\n'):
|
||||
segments.append('')
|
||||
combined_lines.extend(segments if segments else [''])
|
||||
segments.append(cleaned)
|
||||
|
||||
if combined_lines:
|
||||
selected_lines = combined_lines[-last_n_lines:]
|
||||
output_text = '\n'.join(selected_lines)
|
||||
full_text = ''.join(segments)
|
||||
if full_text:
|
||||
line_chunks = full_text.splitlines(keepends=True)
|
||||
total_lines = len(line_chunks)
|
||||
if include_all_lines:
|
||||
selected_lines = line_chunks
|
||||
lines_requested = total_lines
|
||||
else:
|
||||
count = min(last_n_lines, total_lines)
|
||||
selected_lines = line_chunks[-count:] if count else []
|
||||
lines_requested = last_n_lines
|
||||
output_text = ''.join(selected_lines)
|
||||
else:
|
||||
selected_lines = []
|
||||
output_text = ''
|
||||
lines_requested = 0
|
||||
total_lines = 0
|
||||
|
||||
truncated = False
|
||||
if len(output_text) > max_chars:
|
||||
if max_chars is not None and max_chars > 0 and len(output_text) > max_chars:
|
||||
output_text = output_text[-max_chars:]
|
||||
truncated = True
|
||||
|
||||
@ -572,11 +585,14 @@ class PersistentTerminal:
|
||||
else:
|
||||
lines_returned = 0
|
||||
|
||||
if include_all_lines:
|
||||
lines_requested = total_lines
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"session": self.session_name,
|
||||
"output": output_text,
|
||||
"lines_requested": last_n_lines,
|
||||
"lines_requested": lines_requested,
|
||||
"lines_returned": lines_returned,
|
||||
"truncated": truncated,
|
||||
"is_interactive": self.is_interactive,
|
||||
|
||||
@ -416,11 +416,27 @@ class TerminalManager:
|
||||
}
|
||||
|
||||
terminal = self.terminals[target_session]
|
||||
requested_lines = last_n_lines
|
||||
if requested_lines is None:
|
||||
snapshot_lines = self.default_snapshot_lines
|
||||
elif requested_lines <= 0:
|
||||
snapshot_lines = None
|
||||
else:
|
||||
snapshot_lines = min(requested_lines, self.max_snapshot_lines)
|
||||
|
||||
snapshot_char_limit = self.max_snapshot_chars if snapshot_lines is not None else None
|
||||
snapshot = terminal.get_snapshot(
|
||||
last_n_lines or self.default_snapshot_lines,
|
||||
self.max_snapshot_chars
|
||||
snapshot_lines,
|
||||
snapshot_char_limit
|
||||
)
|
||||
output = snapshot.get("output") if snapshot.get("success") else terminal.get_output(last_n_lines)
|
||||
|
||||
fallback_lines = requested_lines
|
||||
if fallback_lines is None:
|
||||
fallback_lines = self.default_snapshot_lines
|
||||
elif fallback_lines <= 0:
|
||||
fallback_lines = 0
|
||||
|
||||
output = snapshot.get("output") if snapshot.get("success") else terminal.get_output(fallback_lines)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
@ -466,11 +482,16 @@ class TerminalManager:
|
||||
"existing_sessions": list(self.terminals.keys())
|
||||
}
|
||||
|
||||
line_limit = lines if lines is not None else self.default_snapshot_lines
|
||||
line_limit = max(1, min(line_limit, self.max_snapshot_lines))
|
||||
if lines is None:
|
||||
line_limit = self.default_snapshot_lines
|
||||
elif lines <= 0:
|
||||
line_limit = None
|
||||
else:
|
||||
line_limit = max(1, min(lines, self.max_snapshot_lines))
|
||||
char_limit = max(100, min(max_chars if max_chars else self.max_snapshot_chars, self.max_snapshot_chars))
|
||||
|
||||
terminal = self.terminals[target_session]
|
||||
char_limit = None if line_limit is None else char_limit
|
||||
snapshot = terminal.get_snapshot(line_limit, char_limit)
|
||||
snapshot.update({
|
||||
"line_limit": line_limit,
|
||||
|
||||
@ -99,8 +99,8 @@
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: 22px;
|
||||
gap: 22px;
|
||||
padding: 22px 12px;
|
||||
gap: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.terminal-wrapper {
|
||||
@ -152,24 +152,38 @@
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
min-height: 0;
|
||||
}
|
||||
#terminal {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
margin: 0 28px 0 36px;
|
||||
margin: 0 14px 0 18px;
|
||||
background: #ffffff;
|
||||
color: var(--claude-text);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
#terminal .xterm {
|
||||
flex: 1;
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
min-height: 0;
|
||||
}
|
||||
#terminal .xterm-viewport {
|
||||
overflow-x: hidden !important;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
#terminal .xterm-scroll-area {
|
||||
margin-bottom: 32px;
|
||||
overflow-y: auto !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
#terminal .xterm-scroll-area,
|
||||
#terminal .xterm-rows {
|
||||
padding-bottom: 20px;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
#terminal .xterm-viewport::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.sidebar {
|
||||
width: 300px;
|
||||
@ -179,6 +193,10 @@
|
||||
border: 1px solid var(--claude-border);
|
||||
box-shadow: 0 18px 40px rgba(61, 57, 41, 0.1);
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.sidebar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.info-section {
|
||||
margin-bottom: 20px;
|
||||
@ -751,7 +769,7 @@
|
||||
pendingHistory[name] = needsHistory;
|
||||
renderCurrentSessionLog();
|
||||
if (needsHistory && socket) {
|
||||
socket.emit('get_terminal_output', { session: name });
|
||||
socket.emit('get_terminal_output', { session: name, lines: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user