fix: index port fix and panel ajustments

This commit is contained in:
Vukasin 2025-05-30 23:26:09 +02:00
parent fc8a167510
commit 0ffc045b5e
3 changed files with 78 additions and 85 deletions

View File

@ -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:

View File

@ -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) => {

View File

@ -73,7 +73,7 @@ export function ToolCallSidePanel({
const [navigationMode, setNavigationMode] = React.useState<'live' | 'manual'>('live');
const [toolCallSnapshots, setToolCallSnapshots] = React.useState<ToolCallSnapshot[]>([]);
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({
</div>
);
}
return (
<div className="flex flex-col h-full">
<div className="pt-4 pl-4 pr-4">
@ -620,18 +620,24 @@ export function ToolCallSidePanel({
</div>
<div className="flex items-center gap-2">
{isLiveMode && agentStatus === 'running' ? (
{showJumpToLive && (
<div className="flex cursor-pointer items-center gap-1.5 px-2 py-0.5 rounded-full bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800" onClick={jumpToLive}>
<div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse"></div>
<span className="text-xs font-medium text-green-700 dark:text-green-400">Jump to Live</span>
</div>
)}
{showJumpToLatest && (
<div className="flex cursor-pointer items-center gap-1.5 px-2 py-0.5 rounded-full bg-neutral-50 dark:bg-neutral-900/20 border border-neutral-200 dark:border-neutral-800" onClick={jumpToLatest}>
<div className="w-1.5 h-1.5 bg-neutral-500 rounded-full"></div>
<span className="text-xs font-medium text-neutral-700 dark:text-neutral-400">Jump to Latest</span>
</div>
)}
{isLiveMode && agentStatus === 'running' && !showJumpToLive && (
<div className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800">
<div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse"></div>
<span className="text-xs font-medium text-green-700 dark:text-green-400">Live</span>
</div>
) : (
<div className="flex items-center gap-1.5 px-2 py-0.5 rounded-full bg-neutral-50 dark:bg-neutral-900/20 border border-neutral-200 dark:border-neutral-800">
<div className="w-1.5 h-1.5 bg-neutral-500 rounded-full"></div>
<span className="text-xs font-medium text-neutral-700 dark:text-neutral-400">Live</span>
</div>
)}
<span className="text-xs text-zinc-500 dark:text-zinc-400 flex-shrink-0">
Step {displayIndex + 1} of {displayTotalCalls}
</span>
@ -664,7 +670,7 @@ export function ToolCallSidePanel({
<span className="text-xs font-medium text-neutral-700 dark:text-neutral-400">Live</span>
</div>
)}
<span className="text-xs text-zinc-500 dark:text-zinc-400">
{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 && (
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-12 z-50">
<div className="relative">
<Button
onClick={jumpToLive}
size="sm"
className="h-8 px-3 bg-red-500 hover:bg-red-600 text-white shadow-lg dark:border-red-400 flex items-center gap-1.5"
>
<Radio className="h-3 w-3" />
<span className="text-xs font-medium">Jump to Live</span>
</Button>
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-red-500"></div>
</div>
</div>
)}
{showJumpToLatest && (
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-12 z-50">
<div className="relative">
<Button
onClick={jumpToLatest}
size="sm"
className="h-8 px-3 shadow-lg flex items-center gap-1.5"
>
<span className="text-xs font-medium">Jump to Latest</span>
</Button>
<div className="absolute top-full left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-primary"></div>
</div>
</div>
)}
</div>
</div>
)}