diff --git a/config/terminal.py b/config/terminal.py index bf7eeac..bcf9a8c 100644 --- a/config/terminal.py +++ b/config/terminal.py @@ -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__ = [ diff --git a/modules/persistent_terminal.py b/modules/persistent_terminal.py index f77c3ac..5182e34 100644 --- a/modules/persistent_terminal.py +++ b/modules/persistent_terminal.py @@ -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, diff --git a/modules/terminal_manager.py b/modules/terminal_manager.py index 811c62a..62b7e6f 100644 --- a/modules/terminal_manager.py +++ b/modules/terminal_manager.py @@ -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, diff --git a/static/terminal.html b/static/terminal.html index f93a1c5..9354e18 100644 --- a/static/terminal.html +++ b/static/terminal.html @@ -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 }); } }