From 2a59bd4baf9e34dacb834ccc8556778851205fdb Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Mon, 21 Apr 2025 15:38:27 +0100 Subject: [PATCH] port & other --- backend/agent/api.py | 2 +- backend/agent/prompt.py | 3 +- backend/agent/tools/sb_expose_tool.py | 4 +- backend/sandbox/docker/Dockerfile | 2 +- backend/sandbox/docker/docker-compose.yml | 2 +- backend/sandbox/docker/server.py | 2 +- backend/sandbox/sandbox.py | 4 +- .../(dashboard)/agents/[threadId]/page.tsx | 20 +-- frontend/src/app/globals.css | 25 ++++ frontend/src/app/test-japanese.md | 38 ++++++ frontend/src/app/test-unicode/page.tsx | 74 +++++++++++ .../file-renderers/image-renderer.tsx | 22 +--- .../file-renderers/markdown-renderer.tsx | 29 ++++- .../components/thread/thread-site-header.tsx | 120 +++++++++++------- .../thread/tool-call-side-panel.tsx | 59 ++++++--- .../tool-views/FileOperationToolView.tsx | 10 +- 16 files changed, 300 insertions(+), 116 deletions(-) create mode 100644 frontend/src/app/test-japanese.md create mode 100644 frontend/src/app/test-unicode/page.tsx diff --git a/backend/agent/api.py b/backend/agent/api.py index c42ff458..e6c048e4 100644 --- a/backend/agent/api.py +++ b/backend/agent/api.py @@ -302,7 +302,7 @@ async def start_agent( # Get preview links vnc_link = sandbox.get_preview_link(6080) - website_link = sandbox.get_preview_link(8080) + website_link = sandbox.get_preview_link(8099) # Extract the actual URLs and token from the preview link objects vnc_url = vnc_link.url if hasattr(vnc_link, 'url') else str(vnc_link).split("url='")[1].split("'")[0] diff --git a/backend/agent/prompt.py b/backend/agent/prompt.py index 7e2b5d2d..43b9419e 100644 --- a/backend/agent/prompt.py +++ b/backend/agent/prompt.py @@ -51,7 +51,8 @@ You have the ability to execute operations using both Python and CLI tools: - Executing scheduled or event-driven tasks - Exposing ports to the public internet using the 'expose-port' tool: * Use this tool to make services running in the sandbox accessible to users - * Example: Expose a web server on port 8080 to share with users + * Example: Expose something running on port 8000 to share with users + * Port 8099 is already exposed by default and running a WEB HTTP server - no need to expose it again. * The tool generates a public URL that users can access * Essential for sharing web applications, APIs, and other network services * Always expose ports when you need to show running services to users diff --git a/backend/agent/tools/sb_expose_tool.py b/backend/agent/tools/sb_expose_tool.py index c8d78064..f593dce1 100644 --- a/backend/agent/tools/sb_expose_tool.py +++ b/backend/agent/tools/sb_expose_tool.py @@ -34,10 +34,10 @@ class SandboxExposeTool(SandboxToolsBase): {"param_name": "port", "node_type": "content", "path": "."} ], example=''' - + - 8080 + 8099 diff --git a/backend/sandbox/docker/Dockerfile b/backend/sandbox/docker/Dockerfile index 79fe5b5d..48c180af 100644 --- a/backend/sandbox/docker/Dockerfile +++ b/backend/sandbox/docker/Dockerfile @@ -123,6 +123,6 @@ ENV RESOLUTION_HEIGHT=1080 RUN mkdir -p /var/log/supervisor COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf -EXPOSE 7788 6080 5901 8000 8080 +EXPOSE 7788 6080 5901 8000 8099 CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/backend/sandbox/docker/docker-compose.yml b/backend/sandbox/docker/docker-compose.yml index 95fe3ac9..dbcf24d6 100644 --- a/backend/sandbox/docker/docker-compose.yml +++ b/backend/sandbox/docker/docker-compose.yml @@ -12,7 +12,7 @@ services: - "5901:5901" # VNC port - "9222:9222" # Chrome remote debugging port - "8000:8000" # API server port - - "8080:8080" # HTTP server port + - "8099:8099" # HTTP server port environment: - ANONYMIZED_TELEMETRY=${ANONYMIZED_TELEMETRY:-false} - CHROME_PATH=/usr/bin/google-chrome diff --git a/backend/sandbox/docker/server.py b/backend/sandbox/docker/server.py index defa5f0a..0239597e 100644 --- a/backend/sandbox/docker/server.py +++ b/backend/sandbox/docker/server.py @@ -26,4 +26,4 @@ app.mount('/', StaticFiles(directory=workspace_dir, html=True), name='site') if __name__ == '__main__': print(f"Starting server with auto-reload, serving files from: {workspace_dir}") # Don't use reload directly in the run call - uvicorn.run("server:app", host="0.0.0.0", port=8080, reload=True) \ No newline at end of file + uvicorn.run("server:app", host="0.0.0.0", port=8099, reload=True) \ No newline at end of file diff --git a/backend/sandbox/sandbox.py b/backend/sandbox/sandbox.py index 6bc08fbe..3da86b40 100644 --- a/backend/sandbox/sandbox.py +++ b/backend/sandbox/sandbox.py @@ -116,7 +116,7 @@ def create_sandbox(password: str): 5900, # VNC port 5901, # VNC port 9222, # Chrome remote debugging port - 8080, # HTTP website port + 8099, # HTTP website port 8002, # The browser api port ] )) @@ -154,7 +154,7 @@ class SandboxToolsBase(Tool): # Get preview links vnc_link = self.sandbox.get_preview_link(6080) - website_link = self.sandbox.get_preview_link(8080) + website_link = self.sandbox.get_preview_link(8099) # Extract the actual URLs from the preview link objects vnc_url = vnc_link.url if hasattr(vnc_link, 'url') else str(vnc_link) diff --git a/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx b/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx index e3e30a2a..d08ee4a6 100644 --- a/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx +++ b/frontend/src/app/(dashboard)/agents/[threadId]/page.tsx @@ -1107,6 +1107,7 @@ export default function ThreadPage({ params }: { params: Promise } projectId={project?.id || ""} onViewFiles={handleOpenFileViewer} onToggleSidePanel={toggleSidePanel} + isMobileView={isMobile} />
@@ -1134,16 +1135,15 @@ export default function ThreadPage({ params }: { params: Promise } return (
- {!isMobile && ( - - )} +
{ + // Fetch the test markdown file + fetch('/test-japanese.md') + .then(response => response.text()) + .then(text => { + setMarkdownContent(text); + + // Create a test string with Unicode escape sequences + const japaneseWithEscapes = "\\u3053\\u3093\\u306b\\u3061\\u306f (Hello)\\n\\u3042\\u308a\\u304c\\u3068\\u3046 (Thank you)"; + setEscapedContent(japaneseWithEscapes); + }) + .catch(error => console.error('Error loading markdown file:', error)); + }, []); + + return ( +
+

Unicode/Japanese Text Testing

+ +
+
+
+

Direct Japanese Characters

+
+
+

+ こんにちは - Hello
+ おはようございます - Good morning
+ ありがとうございます - Thank you
+ 日本語を表示するテスト - Test displaying Japanese +

+
+
+ +
+
+

Unicode Escape Sequence Test

+
+
+

Raw escaped content:

+
+              {escapedContent}
+            
+

Processed content:

+
+ +
+
+
+
+ +
+
+

Markdown File with Japanese Characters

+
+
+ {markdownContent ? ( + + ) : ( +

Loading markdown content...

+ )} +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/file-renderers/image-renderer.tsx b/frontend/src/components/file-renderers/image-renderer.tsx index 11390214..28de5297 100644 --- a/frontend/src/components/file-renderers/image-renderer.tsx +++ b/frontend/src/components/file-renderers/image-renderer.tsx @@ -3,7 +3,7 @@ import React, { useState, useRef, useEffect } from "react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; -import { ZoomIn, ZoomOut, RotateCw, Download, Maximize2, Minimize2, Info } from "lucide-react"; +import { ZoomIn, ZoomOut, RotateCw, Maximize2, Minimize2, Info } from "lucide-react"; interface ImageRendererProps { url: string; @@ -77,17 +77,6 @@ export function ImageRenderer({ url, className }: ImageRendererProps) { setRotation(prev => (prev + 90) % 360); }; - // Function for download - const handleDownload = () => { - const link = document.createElement('a'); - link.href = url; - const filename = url.split('/').pop() || 'image'; - link.download = filename; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - }; - // Toggle fit to screen const toggleFitToScreen = () => { if (isFitToScreen) { @@ -197,15 +186,6 @@ export function ImageRenderer({ url, className }: ImageRendererProps) { )} -
diff --git a/frontend/src/components/file-renderers/markdown-renderer.tsx b/frontend/src/components/file-renderers/markdown-renderer.tsx index eb48ead8..f7955919 100644 --- a/frontend/src/components/file-renderers/markdown-renderer.tsx +++ b/frontend/src/components/file-renderers/markdown-renderer.tsx @@ -2,12 +2,30 @@ import React, { forwardRef } from "react"; import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; import rehypeRaw from "rehype-raw"; import rehypeSanitize from "rehype-sanitize"; import { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; import { CodeRenderer } from "./code-renderer"; +// Process Unicode escape sequences in content +export const processUnicodeContent = (content: string): string => { + if (!content) return ''; + + // Process \uXXXX Unicode escape sequences (BMP characters) + const bmpProcessed = content.replace(/\\u([0-9a-fA-F]{4})/g, (_, codePoint) => { + return String.fromCharCode(parseInt(codePoint, 16)); + }); + + // Process \uXXXXXXXX Unicode escape sequences (supplementary plane characters) + return bmpProcessed.replace(/\\u([0-9a-fA-F]{8})/g, (_, codePoint) => { + const highSurrogate = parseInt(codePoint.substring(0, 4), 16); + const lowSurrogate = parseInt(codePoint.substring(4, 8), 16); + return String.fromCharCode(highSurrogate, lowSurrogate); + }); +}; + interface MarkdownRendererProps { content: string; className?: string; @@ -15,10 +33,14 @@ interface MarkdownRendererProps { export const MarkdownRenderer = forwardRef( ({ content, className }, ref) => { + // Process Unicode escape sequences in the content + const processedContent = processUnicodeContent(content); + return (

, h3: ({ node, ...props }) =>

, a: ({ node, ...props }) => , - p: ({ node, ...props }) =>

, + p: ({ node, ...props }) =>

, ul: ({ node, ...props }) =>

    , ol: ({ node, ...props }) =>
      , li: ({ node, ...props }) =>
    1. , @@ -59,9 +81,12 @@ export const MarkdownRenderer = forwardRef ), pre: ({ node, ...props }) =>
      ,
      +              table: ({ node, ...props }) => ,
      +              th: ({ node, ...props }) => 
      , + td: ({ node, ...props }) => , }} > - {content} + {processedContent} diff --git a/frontend/src/components/thread/thread-site-header.tsx b/frontend/src/components/thread/thread-site-header.tsx index 4ac1a4db..81d7fc45 100644 --- a/frontend/src/components/thread/thread-site-header.tsx +++ b/frontend/src/components/thread/thread-site-header.tsx @@ -14,6 +14,8 @@ import { useState, useRef, KeyboardEvent } from "react" import { Input } from "@/components/ui/input" import { updateProject } from "@/lib/api" import { Skeleton } from "@/components/ui/skeleton" +import { useIsMobile } from "@/hooks/use-mobile" +import { cn } from "@/lib/utils" interface ThreadSiteHeaderProps { threadId: string @@ -22,6 +24,7 @@ interface ThreadSiteHeaderProps { onViewFiles: () => void onToggleSidePanel: () => void onProjectRenamed?: (newName: string) => void + isMobileView?: boolean } export function SiteHeader({ @@ -30,12 +33,14 @@ export function SiteHeader({ projectName, onViewFiles, onToggleSidePanel, - onProjectRenamed + onProjectRenamed, + isMobileView }: ThreadSiteHeaderProps) { const pathname = usePathname() const [isEditing, setIsEditing] = useState(false) const [editName, setEditName] = useState(projectName) const inputRef = useRef(null) + const isMobile = useIsMobile() || isMobileView const copyCurrentUrl = () => { const url = window.location.origin + pathname @@ -100,7 +105,10 @@ export function SiteHeader({ } return ( -
      +
      {isEditing ? (
      @@ -144,55 +152,69 @@ export function SiteHeader({
      - - - - - - -

      View Files in Task

      -
      -
      + {isMobile ? ( + // Mobile view - only show the side panel toggle + + ) : ( + // Desktop view - show all buttons with tooltips + + + + + + +

      View Files in Task

      +
      +
      - - - - - -

      Copy Link

      -
      -
      + + + + + +

      Copy Link

      +
      +
      - - - - - -

      Toggle Computer Preview (CMD+I)

      -
      -
      -
      + + + + + +

      Toggle Computer Preview (CMD+I)

      +
      +
      +
      + )}
      ) diff --git a/frontend/src/components/thread/tool-call-side-panel.tsx b/frontend/src/components/thread/tool-call-side-panel.tsx index a1ff58be..0f1fbaad 100644 --- a/frontend/src/components/thread/tool-call-side-panel.tsx +++ b/frontend/src/components/thread/tool-call-side-panel.tsx @@ -5,8 +5,10 @@ import { getToolIcon } from "@/components/thread/utils"; import React from "react"; import { Slider } from "@/components/ui/slider"; import { ApiMessageType } from '@/components/thread/types'; -import { CircleDashed } from "lucide-react"; +import { CircleDashed, X } from "lucide-react"; import { cn } from "@/lib/utils"; +import { useIsMobile } from "@/hooks/use-mobile"; +import { Button } from "@/components/ui/button"; // Import tool view components from the tool-views directory import { CommandToolView } from "./tool-views/CommandToolView"; @@ -220,6 +222,7 @@ export function ToolCallSidePanel({ const CurrentToolIcon = getToolIcon(currentToolName === 'Tool Call' ? 'unknown' : currentToolName); const isStreaming = currentToolCall?.toolResult?.content === "STREAMING"; const isSuccess = currentToolCall?.toolResult?.isSuccess ?? true; + const isMobile = useIsMobile(); // Add keyboard shortcut for CMD+I to close panel React.useEffect(() => { @@ -298,31 +301,41 @@ export function ToolCallSidePanel({

      Suna's Computer

      - {/*
      - -
      - {currentToolName} */}
      + {isMobile && ( + + )} + {currentToolCall.toolResult?.content && !isStreaming && ( - -
      -
      - -
      - {currentToolName} -
      - {isSuccess ? 'Success' : 'Failed'} +
      +
      + +
      + + {currentToolName} + +
      + {isSuccess ? 'Success' : 'Failed'}
      )} - {isStreaming && (
      @@ -339,7 +352,13 @@ export function ToolCallSidePanel({ }; return ( -
      +
      {renderContent()}
      diff --git a/frontend/src/components/thread/tool-views/FileOperationToolView.tsx b/frontend/src/components/thread/tool-views/FileOperationToolView.tsx index d2d22087..1e8c01c7 100644 --- a/frontend/src/components/thread/tool-views/FileOperationToolView.tsx +++ b/frontend/src/components/thread/tool-views/FileOperationToolView.tsx @@ -5,7 +5,7 @@ import { oneDark, oneLight } from 'react-syntax-highlighter/dist/esm/styles/pris import { ToolViewProps } from "./types"; import { extractFilePath, extractFileContent, getFileType, formatTimestamp, getToolTitle } from "./utils"; import { GenericToolView } from "./GenericToolView"; -import { MarkdownRenderer } from "@/components/file-renderers/markdown-renderer"; +import { MarkdownRenderer, processUnicodeContent } from "@/components/file-renderers/markdown-renderer"; import { CsvRenderer } from "@/components/file-renderers/csv-renderer"; import { cn } from "@/lib/utils"; import { useTheme } from "next-themes"; @@ -343,7 +343,7 @@ export function FileOperationToolView({ })} showLineNumbers={false} > - {fileContent} + {processUnicodeContent(fileContent)}
      @@ -355,7 +355,7 @@ export function FileOperationToolView({ {idx + 1}
      - {line || ' '} + {processUnicodeContent(line) || ' '}
      ))} @@ -381,14 +381,14 @@ export function FileOperationToolView({ {/* Markdown Preview */} {isMarkdown && viewMode === 'preview' && isSuccess && (
      - +
      )} {/* CSV Preview */} {isCsv && viewMode === 'preview' && isSuccess && (
      - +
      )}