From 0ffc045b5ed604e0c3f596a9917b7fe560297469 Mon Sep 17 00:00:00 2001 From: Vukasin Date: Fri, 30 May 2025 23:26:09 +0200 Subject: [PATCH] fix: index port fix and panel ajustments --- backend/agent/tools/sb_files_tool.py | 29 ++-- .../[projectId]/thread/[threadId]/page.tsx | 6 +- .../thread/tool-call-side-panel.tsx | 128 ++++++++---------- 3 files changed, 78 insertions(+), 85 deletions(-) diff --git a/backend/agent/tools/sb_files_tool.py b/backend/agent/tools/sb_files_tool.py index 30272d10..cc595f35 100644 --- a/backend/agent/tools/sb_files_tool.py +++ b/backend/agent/tools/sb_files_tool.py @@ -1,4 +1,3 @@ - from agentpress.tool import ToolResult, openapi_schema, xml_schema from sandbox.tool_base import SandboxToolsBase from utils.files_utils import should_exclude_file, clean_path @@ -129,11 +128,17 @@ class SandboxFilesTool(SandboxToolsBase): self.sandbox.fs.upload_file(full_path, file_contents.encode()) self.sandbox.fs.set_file_permissions(full_path, permissions) - # Get preview URL if it's an HTML file - # preview_url = self._get_preview_url(file_path) message = f"File '{file_path}' created successfully." - # if preview_url: - # message += f"\n\nYou can preview this HTML file at the automatically served HTTP server: {preview_url}" + + # Check if index.html was created and add 8080 server info (only in root workspace) + if file_path.lower() == 'index.html': + try: + website_link = self.sandbox.get_preview_link(8080) + website_url = website_link.url if hasattr(website_link, 'url') else str(website_link).split("url='")[1].split("'")[0] + message += f"\n\n[Auto-detected index.html - HTTP server available at: {website_url}]" + message += "\n[Note: Use the provided HTTP server URL above instead of starting a new server]" + except Exception as e: + logger.warning(f"Failed to get website URL for index.html: {str(e)}") return self.success_response(message) except Exception as e: @@ -274,11 +279,17 @@ class SandboxFilesTool(SandboxToolsBase): self.sandbox.fs.upload_file(full_path, file_contents.encode()) self.sandbox.fs.set_file_permissions(full_path, permissions) - # Get preview URL if it's an HTML file - # preview_url = self._get_preview_url(file_path) message = f"File '{file_path}' completely rewritten successfully." - # if preview_url: - # message += f"\n\nYou can preview this HTML file at: {preview_url}" + + # Check if index.html was rewritten and add 8080 server info (only in root workspace) + if file_path.lower() == 'index.html': + try: + website_link = self.sandbox.get_preview_link(8080) + website_url = website_link.url if hasattr(website_link, 'url') else str(website_link).split("url='")[1].split("'")[0] + message += f"\n\n[Auto-detected index.html - HTTP server available at: {website_url}]" + message += "\n[Note: Use the provided HTTP server URL above instead of starting a new server]" + except Exception as e: + logger.warning(f"Failed to get website URL for index.html: {str(e)}") return self.success_response(message) except Exception as e: diff --git a/frontend/src/app/(dashboard)/projects/[projectId]/thread/[threadId]/page.tsx b/frontend/src/app/(dashboard)/projects/[projectId]/thread/[threadId]/page.tsx index 422bf3ea..5b141114 100644 --- a/frontend/src/app/(dashboard)/projects/[projectId]/thread/[threadId]/page.tsx +++ b/frontend/src/app/(dashboard)/projects/[projectId]/thread/[threadId]/page.tsx @@ -5,6 +5,7 @@ import React, { useEffect, useRef, useState, + useMemo, } from 'react'; import { useSearchParams } from 'next/navigation'; import { BillingError } from '@/lib/api'; @@ -127,7 +128,10 @@ export default function ThreadPage({ ? 'active' : 'no_subscription'; - useVncPreloader(project); + // Memoize project for VNC preloader to prevent re-preloading on every render + const memoizedProject = useMemo(() => project, [project?.id, project?.sandbox?.vnc_preview, project?.sandbox?.pass]); + + useVncPreloader(memoizedProject); const handleProjectRenamed = useCallback((newName: string) => { diff --git a/frontend/src/components/thread/tool-call-side-panel.tsx b/frontend/src/components/thread/tool-call-side-panel.tsx index f9285e27..56e53da1 100644 --- a/frontend/src/components/thread/tool-call-side-panel.tsx +++ b/frontend/src/components/thread/tool-call-side-panel.tsx @@ -73,7 +73,7 @@ export function ToolCallSidePanel({ const [navigationMode, setNavigationMode] = React.useState<'live' | 'manual'>('live'); const [toolCallSnapshots, setToolCallSnapshots] = React.useState([]); const [isInitialized, setIsInitialized] = React.useState(false); - + const isMobile = useIsMobile(); React.useEffect(() => { @@ -83,23 +83,23 @@ export function ToolCallSidePanel({ index, timestamp: Date.now(), })); - + const hadSnapshots = toolCallSnapshots.length > 0; const hasNewSnapshots = newSnapshots.length > toolCallSnapshots.length; setToolCallSnapshots(newSnapshots); - + if (!isInitialized && newSnapshots.length > 0) { - const completedCount = newSnapshots.filter(s => - s.toolCall.toolResult?.content && + const completedCount = newSnapshots.filter(s => + s.toolCall.toolResult?.content && s.toolCall.toolResult.content !== 'STREAMING' ).length; - + if (completedCount > 0) { let lastCompletedIndex = -1; for (let i = newSnapshots.length - 1; i >= 0; i--) { const snapshot = newSnapshots[i]; - if (snapshot.toolCall.toolResult?.content && - snapshot.toolCall.toolResult.content !== 'STREAMING') { + if (snapshot.toolCall.toolResult?.content && + snapshot.toolCall.toolResult.content !== 'STREAMING') { lastCompletedIndex = i; break; } @@ -113,21 +113,21 @@ export function ToolCallSidePanel({ const latestSnapshot = newSnapshots[newSnapshots.length - 1]; const isLatestStreaming = latestSnapshot?.toolCall.toolResult?.content === 'STREAMING'; if (isLatestStreaming) { - let lastCompletedIndex = -1; - for (let i = newSnapshots.length - 1; i >= 0; i--) { - const snapshot = newSnapshots[i]; - if (snapshot.toolCall.toolResult?.content && - snapshot.toolCall.toolResult.content !== 'STREAMING') { - lastCompletedIndex = i; - break; - } - } - if (lastCompletedIndex >= 0) { - setInternalIndex(lastCompletedIndex); - } else { - setInternalIndex(newSnapshots.length - 1); - } - } else { + let lastCompletedIndex = -1; + for (let i = newSnapshots.length - 1; i >= 0; i--) { + const snapshot = newSnapshots[i]; + if (snapshot.toolCall.toolResult?.content && + snapshot.toolCall.toolResult.content !== 'STREAMING') { + lastCompletedIndex = i; + break; + } + } + if (lastCompletedIndex >= 0) { + setInternalIndex(lastCompletedIndex); + } else { + setInternalIndex(newSnapshots.length - 1); + } + } else { setInternalIndex(newSnapshots.length - 1); } } else if (hasNewSnapshots && navigationMode === 'manual') { @@ -144,17 +144,17 @@ export function ToolCallSidePanel({ const currentSnapshot = toolCallSnapshots[safeInternalIndex]; const currentToolCall = currentSnapshot?.toolCall; const totalCalls = toolCallSnapshots.length; - - const completedToolCalls = toolCallSnapshots.filter(snapshot => - snapshot.toolCall.toolResult?.content && + + const completedToolCalls = toolCallSnapshots.filter(snapshot => + snapshot.toolCall.toolResult?.content && snapshot.toolCall.toolResult.content !== 'STREAMING' ); const totalCompletedCalls = completedToolCalls.length; - + let displayToolCall = currentToolCall; let displayIndex = safeInternalIndex; let displayTotalCalls = totalCalls; - + const isCurrentToolStreaming = currentToolCall?.toolResult?.content === 'STREAMING'; if (isCurrentToolStreaming && totalCompletedCalls > 0) { const lastCompletedSnapshot = completedToolCalls[completedToolCalls.length - 1]; @@ -168,7 +168,7 @@ export function ToolCallSidePanel({ displayTotalCalls = totalCompletedCalls; } } - + const currentToolName = displayToolCall?.assistantCall?.name || 'Tool Call'; const CurrentToolIcon = getToolIcon( currentToolName === 'Tool Call' ? 'unknown' : currentToolName, @@ -178,19 +178,19 @@ export function ToolCallSidePanel({ const internalNavigate = React.useCallback((newIndex: number, source: string = 'internal') => { if (newIndex < 0 || newIndex >= totalCalls) return; - + const isNavigatingToLatest = newIndex === totalCalls - 1; - + console.log(`[INTERNAL_NAV] ${source}: ${internalIndex} -> ${newIndex}, mode will be: ${isNavigatingToLatest ? 'live' : 'manual'}`); - + setInternalIndex(newIndex); - + if (isNavigatingToLatest) { setNavigationMode('live'); } else { setNavigationMode('manual'); } - + if (source === 'user_explicit') { onNavigate(newIndex); } @@ -213,7 +213,7 @@ export function ToolCallSidePanel({ } } }, [displayIndex, completedToolCalls, toolCallSnapshots, internalNavigate]); - + const navigateToNext = React.useCallback(() => { if (displayIndex < displayTotalCalls - 1) { const targetCompletedIndex = displayIndex + 1; @@ -254,7 +254,7 @@ export function ToolCallSidePanel({ } else { setNavigationMode('manual'); } - + internalNavigate(actualIndex, 'user_explicit'); } } @@ -453,7 +453,7 @@ export function ToolCallSidePanel({ ); } - + return (
@@ -620,18 +620,24 @@ export function ToolCallSidePanel({
- {isLiveMode && agentStatus === 'running' ? ( + {showJumpToLive && ( +
+
+ Jump to Live +
+ )} + {showJumpToLatest && ( +
+
+ Jump to Latest +
+ )} + {isLiveMode && agentStatus === 'running' && !showJumpToLive && (
Live
- ) : ( -
-
- Live -
)} - Step {displayIndex + 1} of {displayTotalCalls} @@ -664,7 +670,7 @@ export function ToolCallSidePanel({ Live
)} - + {displayIndex + 1} / {displayTotalCalls} {isCurrentToolStreaming && totalCompletedCalls > 0 && ( @@ -716,36 +722,8 @@ export function ToolCallSidePanel({ onValueChange={handleSliderChange} className="w-full [&>span:first-child]:h-1 [&>span:first-child]:bg-zinc-200 dark:[&>span:first-child]:bg-zinc-800 [&>span:first-child>span]:bg-zinc-500 dark:[&>span:first-child>span]:bg-zinc-400 [&>span:first-child>span]:h-1" /> - - {showJumpToLive && ( -
-
- -
-
-
- )} - {showJumpToLatest && ( -
-
- -
-
-
- )} + +
)}