diff --git a/modules/terminal_ops.py b/modules/terminal_ops.py index 6503e64..f35a7e3 100644 --- a/modules/terminal_ops.py +++ b/modules/terminal_ops.py @@ -42,6 +42,15 @@ class TerminalOperator: print(f"{OUTPUT_FORMATS['info']} 检测到Python命令: {self.python_cmd}") self._toolbox: Optional[ToolboxContainer] = None self.container_session: Optional["ContainerHandle"] = container_session + + def _reset_toolbox(self): + """强制关闭并重建工具终端,保证每次命令/脚本运行独立环境。""" + if self._toolbox: + try: + self._toolbox.shutdown() + except Exception: + pass + self._toolbox = None def _detect_python_command(self) -> str: """ @@ -137,6 +146,8 @@ class TerminalOperator: Returns: 执行结果字典 """ + # 每次执行前重置工具容器,防止上一条命令的输出/状态干扰 + self._reset_toolbox() # 替换命令中的python3为实际可用的命令 if "python3" in command and self.python_cmd != "python3": command = command.replace("python3", self.python_cmd) @@ -296,6 +307,9 @@ class TerminalOperator: 执行结果字典 """ timeout = timeout or CODE_EXECUTION_TIMEOUT + + # 强制重置工具容器,避免上一段代码仍在运行时输出混入 + self._reset_toolbox() # 创建临时Python文件 temp_file = self.project_path / ".temp_code.py" diff --git a/static/src/components/chat/VirtualMonitorSurface.vue b/static/src/components/chat/VirtualMonitorSurface.vue index 0b1d374..a4b39d2 100644 --- a/static/src/components/chat/VirtualMonitorSurface.vue +++ b/static/src/components/chat/VirtualMonitorSurface.vue @@ -89,6 +89,32 @@ +
+
+ + + + 命令行 +
+
+
+
+
+
+ +
+
+ + + + Python +
+
+
+
+
+
+
@@ -218,6 +244,15 @@ const terminalTabs = ref(null); const terminalTabList = ref(null); const terminalAddButton = ref(null); const terminalBody = ref(null); +const commandWindow = ref(null); +const commandTitle = ref(null); +const commandInput = ref(null); +const commandOutput = ref(null); +const pythonWindow = ref(null); +const pythonTitle = ref(null); +const pythonBody = ref(null); +const pythonInput = ref(null); +const pythonOutput = ref(null); const readerWindow = ref(null); const readerTitle = ref(null); const readerLines = ref(null); @@ -251,11 +286,11 @@ const assets = { fileIcon: new URL('../../icons/file.svg', import.meta.url).href, apps: { browser: new URL('../../icons/globe.svg', import.meta.url).href, - terminal: new URL('../../icons/terminal.svg', import.meta.url).href, - command: new URL('../../icons/laptop.svg', import.meta.url).href, + terminal: new URL('../../icons/laptop.svg', import.meta.url).href, + command: new URL('../../icons/terminal.svg', import.meta.url).href, + python: new URL('../../icons/python.svg', import.meta.url).href, memory: new URL('../../icons/sticky-note.svg', import.meta.url).href, todo: new URL('../../icons/clipboard.svg', import.meta.url).href, - reader: new URL('../../icons/book.svg', import.meta.url).href, subagent: new URL('../../icons/bot.svg', import.meta.url).href } }; @@ -291,6 +326,15 @@ const mountDirector = async () => { terminalTabList: terminalTabList.value!, terminalAddButton: terminalAddButton.value!, terminalBody: terminalBody.value!, + commandWindow: commandWindow.value!, + commandTitle: commandTitle.value!, + commandInput: commandInput.value!, + commandOutput: commandOutput.value!, + pythonWindow: pythonWindow.value!, + pythonTitle: pythonTitle.value!, + pythonBody: pythonBody.value!, + pythonInput: pythonInput.value!, + pythonOutput: pythonOutput.value!, readerWindow: readerWindow.value!, readerTitle: readerTitle.value!, readerLines: readerLines.value!, diff --git a/static/src/components/chat/monitor/MonitorDirector.ts b/static/src/components/chat/monitor/MonitorDirector.ts index 5c21e00..7e30e19 100644 --- a/static/src/components/chat/monitor/MonitorDirector.ts +++ b/static/src/components/chat/monitor/MonitorDirector.ts @@ -44,6 +44,15 @@ export interface MonitorElements { terminalTabList: HTMLElement; terminalAddButton: HTMLElement; terminalBody: HTMLElement; + commandWindow: HTMLElement; + commandTitle: HTMLElement; + commandInput: HTMLElement; + commandOutput: HTMLElement; + pythonWindow: HTMLElement; + pythonTitle: HTMLElement; + pythonBody: HTMLElement; + pythonInput: HTMLElement; + pythonOutput: HTMLElement; readerWindow: HTMLElement; readerTitle: HTMLElement; readerLines: HTMLElement; @@ -129,9 +138,9 @@ const DESKTOP_APPS: Array<{ id: string; label: string; assetKey: string }> = [ { id: 'browser', label: '浏览器', assetKey: 'browser' }, { id: 'terminal', label: '终端', assetKey: 'terminal' }, { id: 'command', label: '命令行', assetKey: 'command' }, + { id: 'python', label: 'Python', assetKey: 'python' }, { id: 'memory', label: '记忆', assetKey: 'memory' }, { id: 'todo', label: '看板', assetKey: 'todo' }, - { id: 'reader', label: '阅读器', assetKey: 'reader' }, { id: 'subagent', label: '子代理', assetKey: 'subagent' } ]; @@ -190,6 +199,7 @@ export class MonitorDirector implements MonitorDriver { private folderIcons = new Map(); // 用于控制编辑器动画全局加速(根据本次补丁的总体改动量动态调整) private editorSpeedBoost = 1; + private pythonRunToken = 0; private pendingDesktopFolders = new Set(); private fileIcons = new Map(); private browserResultMap = new Map(); @@ -218,6 +228,7 @@ export class MonitorDirector implements MonitorDriver { placeholder: false }; private editorSnapshots = new Map(); + private commandCurrentText = ''; private progressBubbleTimer: number | null = null; private progressBubbleBase: string | null = null; private progressSceneName: string | null = null; @@ -226,9 +237,27 @@ export class MonitorDirector implements MonitorDriver { private waitingBubbleTimer: number | null = null; private waitingBubbleBase: string | null = null; private progressBubbleActive = false; + // 当实际执行进度快于动画播放时,用于压制“正在 xxx”提示 + private playbackLagging = false; + // 记录最近一次 Python 执行的ID,用于丢弃过期动画/结果 + private latestPythonExecutionId: string | number | null = null; private lastTerminalSessionId: string | null = null; private terminalLastFocusedAt = 0; + private refreshScreenRect() { + const prev = this.screenRect; + const rect = this.elements.screen.getBoundingClientRect(); + this.screenRect = rect; + if (prev && prev.width > 0 && prev.height > 0) { + const relX = this.pointerBase.x / prev.width; + const relY = this.pointerBase.y / prev.height; + this.pointerBase = { + x: relX * rect.width, + y: relY * rect.height + }; + } + } + private applySceneStatus(runtime: MonitorSceneRuntime, sceneName: string, fallback: string) { if (!runtime || typeof runtime.setStatus !== 'function') { return; @@ -256,7 +285,7 @@ export class MonitorDirector implements MonitorDriver { (window as any).__TERMINAL_MENU_DEBUG_BUILD = '2025-12-13-1'; terminalMenuDebug('constructor:init', { build: (window as any).__TERMINAL_MENU_DEBUG_BUILD }); const resizeHandler = () => { - this.screenRect = this.elements.screen.getBoundingClientRect(); + this.refreshScreenRect(); this.layoutFloatingWindows(); }; window.addEventListener('resize', resizeHandler, { passive: true }); @@ -502,11 +531,19 @@ export class MonitorDirector implements MonitorDriver { if (progressLabel && !isPlaybackPhase) { ensureProgressBubble(); } + + // 若进入播放阶段且有执行结果已完成,压制“正在…”提示 + if (isPlaybackPhase) { + this.playbackLagging = true; + } else { + this.playbackLagging = false; + } + const wrappedRuntime: MonitorSceneRuntime = { ...runtime, waitForResult: async (id?: string | number | null) => { const waitFn = runtime.waitForResult || (() => Promise.resolve(null)); - if (!isPlaybackPhase) { + if (!isPlaybackPhase && !this.playbackLagging) { ensureProgressBubble(); } const waitKey = id ?? payload?.executionId ?? payload?.id; @@ -514,6 +551,7 @@ export class MonitorDirector implements MonitorDriver { try { const result = await waitFn(id); progressDebug('playScene:waitForResult:resolved', { scene: name, id: waitKey }); + this.playbackLagging = false; return result; } finally { clearProgressBubble(); @@ -527,6 +565,7 @@ export class MonitorDirector implements MonitorDriver { monitorLifecycleDebug('playScene:end', { scene: name }); + this.playbackLagging = false; } } @@ -1189,6 +1228,157 @@ export class MonitorDirector implements MonitorDriver { return top === instance.element; } + private resetCommandWindow(title = '命令行', options: { clearOutput?: boolean } = {}) { + if (this.elements.commandTitle) { + this.elements.commandTitle.textContent = title; + } + if (this.elements.commandInput) { + this.elements.commandInput.textContent = ''; + } + if (options.clearOutput && this.elements.commandOutput) { + this.elements.commandOutput.innerHTML = ''; + } + this.commandCurrentText = ''; + } + + private async revealCommandWindow(title = '命令行', options: { reset?: boolean; focusInput?: boolean } = {}) { + const { reset = true, focusInput = false } = options; + const visible = this.isWindowVisible(this.elements.commandWindow); + if (!visible) { + await this.movePointerToApp('command'); + await this.click(); + } + if (reset) { + this.resetCommandWindow(title, { clearOutput: true }); + } else { + if (this.elements.commandTitle) { + this.elements.commandTitle.textContent = title; + } + } + this.showWindow(this.elements.commandWindow); + if (focusInput) { + await this.focusCommandInput(); + } + } + + private async focusCommandInput() { + if (!this.elements.commandInput) { + return; + } + await this.movePointerToElement(this.elements.commandInput, { duration: 360 }); + await this.click(); + } + + private async typeCommandText(text: string) { + if (!this.elements.commandInput) { + return; + } + const target = this.elements.commandInput; + await this.focusCommandInput(); + const toDelete = this.commandCurrentText; + if (toDelete) { + for (let i = toDelete.length; i > 0; i -= 1) { + target.textContent = toDelete.slice(0, i - 1); + await sleep(18); + } + this.commandCurrentText = ''; + } + const chars = Array.from(text); + for (const ch of chars) { + target.textContent = (target.textContent || '') + ch; + await sleep(28); + } + this.commandCurrentText = text; + } + + private appendCommandOutput(lines: string[]) { + if (!this.elements.commandOutput) { + return; + } + const frag = document.createDocumentFragment(); + lines.forEach(line => { + const row = document.createElement('div'); + row.className = 'command-line'; + row.textContent = line; + frag.appendChild(row); + }); + this.elements.commandOutput.appendChild(frag); + this.elements.commandOutput.scrollTop = this.elements.commandOutput.scrollHeight; + } + + private resetPythonWindow(title = 'Python') { + if (this.elements.pythonTitle) { + this.elements.pythonTitle.textContent = title; + } + if (this.elements.pythonInput) { + this.elements.pythonInput.textContent = ''; + } + if (this.elements.pythonOutput) { + this.elements.pythonOutput.innerHTML = ''; + } + } + + private appendPythonOutput(label: string, content: string) { + if (!this.elements.pythonOutput) return; + const wrapper = document.createElement('div'); + wrapper.className = 'python-block output'; + const title = document.createElement('div'); + title.className = 'python-block-title'; + title.textContent = label; + const pre = document.createElement('pre'); + pre.textContent = content; + wrapper.appendChild(title); + wrapper.appendChild(pre); + this.elements.pythonOutput.innerHTML = ''; + this.elements.pythonOutput.appendChild(wrapper); + this.scrollPythonToResult(); + } + + private async revealPythonWindow(title = 'Python') { + await this.movePointerToApp('python'); + await this.click(); + this.resetPythonWindow(title); + this.showWindow(this.elements.pythonWindow); + } + + private async focusPythonInput() { + if (!this.elements.pythonInput) return; + await this.movePointerToElement(this.elements.pythonInput, { duration: 360 }); + await this.click(); + } + + private async typePythonCode(code: string, options: { deletePrevious?: boolean; animate?: boolean } = {}) { + if (!this.elements.pythonInput) return; + const { deletePrevious = true, animate = true } = options; + const target = this.elements.pythonInput; + if (deletePrevious) { + target.textContent = ''; + } + await this.focusPythonInput(); + if (!animate) { + target.textContent = code; + return; + } + const chars = Array.from(code); + for (const ch of chars) { + target.textContent = (target.textContent || '') + ch; + await sleep(24); + } + } + + private scrollPythonToTop() { + if (this.elements.pythonBody) { + this.elements.pythonBody.scrollTo({ top: 0, behavior: 'smooth' }); + } + } + + private scrollPythonToResult() { + if (this.elements.pythonBody && this.elements.pythonOutput) { + const top = this.elements.pythonOutput.offsetTop - this.elements.pythonBody.offsetTop; + this.elements.pythonBody.scrollTo({ top, behavior: 'smooth' }); + } + } + private async revealTerminalWindow(instance: TerminalShell, title: string) { await this.movePointerToApp('terminal'); await this.click(); @@ -1675,6 +1865,8 @@ export class MonitorDirector implements MonitorDriver { this.windowAnchors.set(this.elements.folderWindow, { x: 0.68, y: 0.26 }); this.windowAnchors.set(this.elements.editorWindow, { x: 0.62, y: 0.58 }); this.windowAnchors.set(this.elements.terminalWindow, { x: 0.18, y: 0.42 }); + this.windowAnchors.set(this.elements.commandWindow, { x: 0.2, y: 0.7 }); + this.windowAnchors.set(this.elements.pythonWindow, { x: 0.52, y: 0.16 }); this.windowAnchors.set(this.elements.readerWindow, { x: 0.42, y: 0.05 }); this.windowAnchors.set(this.elements.memoryWindow, { x: 0.28, y: 0.32 }); this.windowAnchors.set(this.elements.todoWindow, { x: 0.5, y: 0.32 }); @@ -2099,9 +2291,18 @@ export class MonitorDirector implements MonitorDriver { this.sceneHandlers.runCommand = async (payload, runtime) => { this.applySceneStatus(runtime, 'runCommand', '正在执行命令'); - const { sessionId } = await this.ensureTerminalSessionReady(payload, { focusPrompt: true, createIfMissing: true }); - const command = payload?.arguments?.command || 'echo "Hello"'; - await this.typeSessionCommand(sessionId, command); + const command = payload?.arguments?.command || payload?.result?.command || 'echo \"Hello\"'; + const reuse = this.isWindowVisible(this.elements.commandWindow); + if (reuse) { + this.showWindow(this.elements.commandWindow); + await this.focusCommandInput(); + if (this.elements.commandOutput) { + this.elements.commandOutput.innerHTML = ''; + } + } else { + await this.revealCommandWindow('命令行', { reset: true, focusInput: true }); + } + await this.typeCommandText(command); const completion = await runtime.waitForResult(payload.executionId || payload.id); const output = completion?.result?.output || completion?.result?.stdout || '命令执行完成'; const lines = this.sanitizeTerminalOutput( @@ -2111,16 +2312,47 @@ export class MonitorDirector implements MonitorDriver { ? output.map(String) : [String(output || '')] ); - this.appendTerminalOutputs(sessionId, command, lines.length ? lines : ['命令执行完成']); + this.appendCommandOutput(lines.length ? lines : ['命令执行完成']); await sleep(500); }; this.sceneHandlers.runPython = async (payload, runtime) => { this.applySceneStatus(runtime, 'runPython', '正在执行 Python'); - const { sessionId } = await this.ensureTerminalSessionReady(payload, { focusPrompt: true, createIfMissing: true }); - const code = payload?.arguments?.code || 'print("Hello")'; - await this.typeSessionCommand(sessionId, code); + const runId = + payload?.executionId || + payload?.execution_id || + payload?.id || + `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`; + // 丢弃过期的回放任务 + if (this.latestPythonExecutionId && this.latestPythonExecutionId !== runId) { + const older = + typeof runId === 'number' && typeof this.latestPythonExecutionId === 'number' + ? runId < this.latestPythonExecutionId + : false; + if (older) { + return; + } + } + this.latestPythonExecutionId = runId; + const code = payload?.arguments?.code || 'print(\"Hello\")'; + const codeLines = this.normalizeLines(code); + const animate = codeLines.length <= 15; + const runToken = ++this.pythonRunToken; + const reuse = this.isWindowVisible(this.elements.pythonWindow); + if (reuse) { + this.showWindow(this.elements.pythonWindow); + this.scrollPythonToTop(); + await this.focusPythonInput(); + this.elements.pythonOutput.innerHTML = ''; + } else { + await this.revealPythonWindow('Python'); + } + await this.typePythonCode(code, { deletePrevious: true, animate }); const completion = await runtime.waitForResult(payload.executionId || payload.id); + if (runToken !== this.pythonRunToken) { + // 有新的 Python 运行已启动,本次结果丢弃 + return; + } const output = completion?.result?.output || completion?.result?.stdout || '>>> 执行完成'; const lines = this.sanitizeTerminalOutput( typeof output === 'string' @@ -2129,7 +2361,7 @@ export class MonitorDirector implements MonitorDriver { ? output.map(String) : [String(output || '')] ); - this.appendTerminalOutputs(sessionId, code, lines.length ? lines : ['>>> 执行完成']); + this.appendPythonOutput('输出', lines.join('\n') || '>>> 执行完成'); await sleep(500); }; @@ -2771,6 +3003,7 @@ export class MonitorDirector implements MonitorDriver { }; private async movePointerToApp(appId: string) { + this.refreshScreenRect(); const icon = this.appIcons.get(appId); if (icon) { await this.movePointerToElement(icon); @@ -2780,6 +3013,7 @@ export class MonitorDirector implements MonitorDriver { } private async movePointerToDesktop() { + this.refreshScreenRect(); return this.movePointerToElement(this.elements.desktopGrid, { offsetX: 160, offsetY: 120, duration: 700 }); } @@ -2787,6 +3021,7 @@ export class MonitorDirector implements MonitorDriver { if (!target) { return; } + this.refreshScreenRect(); this.raiseWindowForTarget(target); if (!this.progressBubbleBase) { this.dismissBubble(true); diff --git a/static/src/composables/useLegacySocket.ts b/static/src/composables/useLegacySocket.ts index d0ac893..83699c9 100644 --- a/static/src/composables/useLegacySocket.ts +++ b/static/src/composables/useLegacySocket.ts @@ -831,6 +831,9 @@ export async function initializeLegacySocket(ctx: any) { // 工具准备中事件 - 实时显示 ctx.socket.on('tool_preparing', (data) => { + if (ctx.dropToolEvents) { + return; + } socketLog('工具准备中:', data.name); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过tool_preparing(对话不匹配)', data.conversation_id); @@ -876,6 +879,9 @@ export async function initializeLegacySocket(ctx: any) { // 工具状态更新事件 - 实时显示详细状态 ctx.socket.on('tool_status', (data) => { + if (ctx.dropToolEvents) { + return; + } socketLog('工具状态:', data); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过tool_status(对话不匹配)', data.conversation_id); @@ -899,6 +905,9 @@ export async function initializeLegacySocket(ctx: any) { // 工具开始(从准备转为执行) ctx.socket.on('tool_start', (data) => { + if (ctx.dropToolEvents) { + return; + } socketLog('工具开始执行:', data.name); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过tool_start(对话不匹配)', data.conversation_id); @@ -956,6 +965,9 @@ export async function initializeLegacySocket(ctx: any) { // 更新action(工具完成) ctx.socket.on('update_action', (data) => { + if (ctx.dropToolEvents) { + return; + } socketLog('更新action:', data.id, 'status:', data.status); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过update_action(对话不匹配)', data.conversation_id); @@ -1047,6 +1059,9 @@ export async function initializeLegacySocket(ctx: any) { }); ctx.socket.on('append_payload', (data) => { + if (ctx.dropToolEvents) { + return; + } socketLog('收到append_payload事件:', data); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过append_payload(对话不匹配)', data.conversation_id); @@ -1064,6 +1079,9 @@ export async function initializeLegacySocket(ctx: any) { }); ctx.socket.on('modify_payload', (data) => { + if (ctx.dropToolEvents) { + return; + } socketLog('收到modify_payload事件:', data); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过modify_payload(对话不匹配)', data.conversation_id); @@ -1084,6 +1102,13 @@ export async function initializeLegacySocket(ctx: any) { ctx.socket.on('stop_requested', (data) => { socketLog('停止请求已接收:', data.message); // 可以显示提示信息 + try { + if (typeof ctx.clearPendingTools === 'function') { + ctx.clearPendingTools('socket:stop_requested'); + } + } catch (error) { + console.warn('清理未完成工具失败', error); + } }); // 任务停止 diff --git a/static/src/styles/components/chat/_virtual-monitor.scss b/static/src/styles/components/chat/_virtual-monitor.scss index f478436..d46e2c2 100644 --- a/static/src/styles/components/chat/_virtual-monitor.scss +++ b/static/src/styles/components/chat/_virtual-monitor.scss @@ -251,6 +251,158 @@ flex-direction: column; } +.virtual-monitor-surface .command-window { + width: 520px; + height: 260px; + top: 540px; + left: 120px; + display: flex; + flex-direction: column; + background: #f7f9fd; + color: #0f172a; + border: 1px solid rgba(15, 23, 42, 0.08); + box-sizing: border-box; +} + +.virtual-monitor-surface .command-body { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + background: #f7f9fd; + color: #0f172a; + font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; + font-size: 13px; + border-top: 1px solid rgba(15, 23, 42, 0.06); + padding: 8px 10px 10px; + box-sizing: border-box; +} + +.virtual-monitor-surface .command-input { + padding: 12px 14px; + border-bottom: 1px solid rgba(15, 23, 42, 0.08); + background: #ffffff; + white-space: pre-wrap; + overflow: hidden; + border-radius: 12px; + box-shadow: 0 6px 20px rgba(15, 23, 42, 0.08); + min-height: 48px; + display: flex; + align-items: center; + line-height: 1.5; + word-break: break-word; +} + +.virtual-monitor-surface .command-output { + padding: 12px 14px; + overflow-y: auto; + background: #eef2f9; + border-radius: 12px; + border: 1px solid rgba(15, 23, 42, 0.06); + flex: 1; + max-height: 100%; + scrollbar-gutter: stable; + box-sizing: border-box; +} + +.virtual-monitor-surface .command-line { + line-height: 1.5; + white-space: pre-wrap; +} + +.virtual-monitor-surface .command-line.highlight { + color: #16a34a; +} + +.virtual-monitor-surface .python-window { + width: 520px; + height: 320px; + top: 120px; + left: 520px; + display: flex; + flex-direction: column; + background: #f9fbff; + color: #0f172a; + border: 1px solid rgba(15, 23, 42, 0.08); + box-sizing: border-box; +} + +.virtual-monitor-surface .python-body { + flex: 1; + background: #f1f5fb; + color: #0f172a; + font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; + font-size: 13px; + padding: 10px 12px; + display: grid; + grid-template-rows: auto 1fr; + gap: 10px; + border-top: 1px solid rgba(15, 23, 42, 0.06); + box-sizing: border-box; + scrollbar-width: none; + min-height: 0; /* 防止子元素撑开导致整体溢出 */ +} + +.virtual-monitor-surface .python-body::-webkit-scrollbar { + width: 0; + height: 0; +} + +.virtual-monitor-surface .python-input { + background: #ffffff; + border: 1px solid rgba(15, 23, 42, 0.08); + border-radius: 12px; + padding: 12px 14px; + min-height: 64px; + max-height: 180px; + box-shadow: 0 6px 20px rgba(15, 23, 42, 0.08); + line-height: 1.55; + white-space: pre-wrap; + word-break: break-word; + overflow-y: auto; + width: 100%; + box-sizing: border-box; + scrollbar-width: none; +} +.virtual-monitor-surface .python-input::-webkit-scrollbar { + width: 0; + height: 0; +} + +.virtual-monitor-surface .python-output { + background: transparent; + border: 0; + border-radius: 0; + padding: 0; + box-shadow: none; + overflow-y: auto; + min-height: 140px; + scrollbar-width: none; +} +.virtual-monitor-surface .python-output::-webkit-scrollbar { + width: 0; + height: 0; +} + +.virtual-monitor-surface .python-block { + background: #f6f8fe; + border: 1px solid rgba(15, 23, 42, 0.06); + border-radius: 10px; + padding: 10px 12px; +} + +.virtual-monitor-surface .python-block-title { + font-weight: 600; + color: #1d4ed8; + margin-bottom: 6px; +} + +.virtual-monitor-surface .python-block pre { + margin: 0; + white-space: pre-wrap; + line-height: 1.55; +} + .virtual-monitor-surface .reader-window { width: 360px; height: 320px;