mirror of https://github.com/kortix-ai/suna.git
port & other
This commit is contained in:
parent
2b8c3a9ffb
commit
2a59bd4baf
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,10 +34,10 @@ class SandboxExposeTool(SandboxToolsBase):
|
|||
{"param_name": "port", "node_type": "content", "path": "."}
|
||||
],
|
||||
example='''
|
||||
<!-- Example 1: Expose a web server running on port 8080 -->
|
||||
<!-- Example 1: Expose a web server running on port 8099 -->
|
||||
<!-- This will generate a public URL that users can access to view the web application -->
|
||||
<expose-port>
|
||||
8080
|
||||
8099
|
||||
</expose-port>
|
||||
|
||||
<!-- Example 2: Expose an API service running on port 3000 -->
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
uvicorn.run("server:app", host="0.0.0.0", port=8099, reload=True)
|
|
@ -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)
|
||||
|
|
|
@ -1107,6 +1107,7 @@ export default function ThreadPage({ params }: { params: Promise<ThreadParams> }
|
|||
projectId={project?.id || ""}
|
||||
onViewFiles={handleOpenFileViewer}
|
||||
onToggleSidePanel={toggleSidePanel}
|
||||
isMobileView={isMobile}
|
||||
/>
|
||||
<div className="flex flex-1 items-center justify-center p-4">
|
||||
<div className="flex w-full max-w-md flex-col items-center gap-4 rounded-lg border bg-card p-6 text-center">
|
||||
|
@ -1134,16 +1135,15 @@ export default function ThreadPage({ params }: { params: Promise<ThreadParams> }
|
|||
return (
|
||||
<div className="flex h-screen">
|
||||
<div className={`flex flex-col flex-1 overflow-hidden transition-all duration-200 ease-in-out ${isSidePanelOpen ? 'mr-[90%] sm:mr-[450px] md:mr-[500px] lg:mr-[550px] xl:mr-[650px]' : ''}`}>
|
||||
{!isMobile && (
|
||||
<SiteHeader
|
||||
threadId={threadId}
|
||||
projectName={projectName}
|
||||
projectId={project?.id || ""}
|
||||
onViewFiles={handleOpenFileViewer}
|
||||
onToggleSidePanel={toggleSidePanel}
|
||||
onProjectRenamed={handleProjectRenamed}
|
||||
/>
|
||||
)}
|
||||
<SiteHeader
|
||||
threadId={threadId}
|
||||
projectName={projectName}
|
||||
projectId={project?.id || ""}
|
||||
onViewFiles={handleOpenFileViewer}
|
||||
onToggleSidePanel={toggleSidePanel}
|
||||
onProjectRenamed={handleProjectRenamed}
|
||||
isMobileView={isMobile}
|
||||
/>
|
||||
<div
|
||||
ref={messagesContainerRef}
|
||||
className="flex-1 overflow-y-auto px-6 py-4 pb-24 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
|
||||
|
|
|
@ -296,9 +296,34 @@
|
|||
html {
|
||||
scroll-behavior: smooth;
|
||||
zoom: 100%;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "palt"; /* Adjusts spacing for CJK text */
|
||||
}
|
||||
|
||||
/* Add font fallbacks for CJK characters */
|
||||
.cjk-text,
|
||||
.prose p,
|
||||
.prose li,
|
||||
.prose table td,
|
||||
.prose table th,
|
||||
.markdown-content {
|
||||
font-family: var(--font-sans), ui-sans-serif, -apple-system, "Segoe UI", "Helvetica Neue",
|
||||
"Noto Sans", "Noto Sans CJK JP", "Noto Sans CJK KR", "Noto Sans CJK SC",
|
||||
"Noto Sans CJK TC", sans-serif;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* Specific handling for monospace/code with CJK character support */
|
||||
code, pre, .font-mono {
|
||||
font-family: var(--font-mono), ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco,
|
||||
Consolas, "Liberation Mono", "Courier New", monospace,
|
||||
"Noto Sans Mono CJK JP", "Noto Sans Mono CJK KR", "Noto Sans Mono CJK SC",
|
||||
"Noto Sans Mono CJK TC";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# Japanese Language Test
|
||||
|
||||
## Basic Phrases
|
||||
| Japanese | Pronunciation | English |
|
||||
|----------|--------------|---------|
|
||||
| こんにちは | Kon-nee-chee-wah | Hello |
|
||||
| おはようございます | Oh-hah-yoh goh-zai-mahs | Good morning |
|
||||
| こんばんは | Kon-bahn-wah | Good evening |
|
||||
| さようなら | Sah-yoh-nah-rah | Goodbye |
|
||||
| ありがとうございます | Ah-ree-gah-toh goh-zai-mahs | Thank you very much |
|
||||
| どういたしまして | Doh-ee-tah-shee-mah-shteh | You're welcome |
|
||||
| すみません | Soo-mee-mah-sen | Excuse me / Sorry |
|
||||
| はい | Hai | Yes |
|
||||
| いいえ | Ee-eh | No |
|
||||
| お願いします | Oh-neh-gai shee-mahs | Please |
|
||||
| わかりません | Wah-kah-ree-mah-sen | I don't understand |
|
||||
| 英語を話せますか? | Ay-go oh hah-nah-seh-mahs-kah? | Do you speak English? |
|
||||
| ゆっくりお願いします | Yook-koo-ree oh-neh-gai shee-mahs | Please speak slowly |
|
||||
|
||||
## Travel & Transportation
|
||||
| Japanese | Pronunciation | English |
|
||||
|----------|--------------|---------|
|
||||
| __はどこですか? | _____ wa doh-koh dehs-kah? | Where is _____? |
|
||||
| 駅 | Eh-kee | Station |
|
||||
| トイレ | Toy-reh | Toilet/bathroom |
|
||||
| 入口 | Ee-ree-goo-chee | Entrance |
|
||||
| 出口 | Deh-goo-chee | Exit |
|
||||
| 新幹線 | Sheen-kahn-sen | Bullet train |
|
||||
| 電車 | Den-shah | Train |
|
||||
| バス | Bah-soo | Bus |
|
||||
| タクシー | Tak-shee | Taxi |
|
||||
| いくらですか? | Ee-koo-rah dehs-kah? | How much is it? |
|
||||
| __に行きたいです | _____ nee ee-kee-tai dehs | I want to go to _____ |
|
||||
| 予約 | Yoh-yah-koo | Reservation |
|
||||
| 切符 | Kee-ppoo | Ticket |
|
||||
|
||||
## Escaped Unicode Test
|
||||
This is testing Unicode escape sequences: \u3053\u3093\u306b\u3061\u306f (should show as こんにちは)
|
|
@ -0,0 +1,74 @@
|
|||
'use client';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { MarkdownRenderer } from '@/components/file-renderers/markdown-renderer';
|
||||
|
||||
export default function TestUnicodePage() {
|
||||
const [markdownContent, setMarkdownContent] = useState('');
|
||||
const [escapedContent, setEscapedContent] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
// 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 (
|
||||
<div className="container mx-auto py-8">
|
||||
<h1 className="text-2xl font-bold mb-6">Unicode/Japanese Text Testing</h1>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div className="border rounded-md shadow-sm">
|
||||
<div className="p-4 border-b bg-slate-50 dark:bg-slate-800">
|
||||
<h2 className="font-medium">Direct Japanese Characters</h2>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<p className="mb-4 cjk-text">
|
||||
こんにちは - Hello<br />
|
||||
おはようございます - Good morning<br />
|
||||
ありがとうございます - Thank you<br />
|
||||
日本語を表示するテスト - Test displaying Japanese
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border rounded-md shadow-sm">
|
||||
<div className="p-4 border-b bg-slate-50 dark:bg-slate-800">
|
||||
<h2 className="font-medium">Unicode Escape Sequence Test</h2>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<p className="mb-2">Raw escaped content:</p>
|
||||
<pre className="bg-slate-100 dark:bg-slate-900 p-2 rounded mb-4 overflow-x-auto">
|
||||
{escapedContent}
|
||||
</pre>
|
||||
<p className="mb-2">Processed content:</p>
|
||||
<div className="bg-slate-100 dark:bg-slate-900 p-2 rounded">
|
||||
<MarkdownRenderer content={escapedContent} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 border rounded-md shadow-sm">
|
||||
<div className="p-4 border-b bg-slate-50 dark:bg-slate-800">
|
||||
<h2 className="font-medium">Markdown File with Japanese Characters</h2>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
{markdownContent ? (
|
||||
<MarkdownRenderer content={markdownContent} />
|
||||
) : (
|
||||
<p>Loading markdown content...</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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) {
|
|||
<Minimize2 className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={handleDownload}
|
||||
title="Download"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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<HTMLDivElement, MarkdownRendererProps>(
|
||||
({ content, className }, ref) => {
|
||||
// Process Unicode escape sequences in the content
|
||||
const processedContent = processUnicodeContent(content);
|
||||
|
||||
return (
|
||||
<ScrollArea className={cn("w-full h-full rounded-md relative", className)}>
|
||||
<div className="p-4 markdown prose prose-sm dark:prose-invert max-w-none" ref={ref}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw, rehypeSanitize]}
|
||||
components={{
|
||||
code(props) {
|
||||
|
@ -48,7 +70,7 @@ export const MarkdownRenderer = forwardRef<HTMLDivElement, MarkdownRendererProps
|
|||
h2: ({ node, ...props }) => <h2 className="text-xl font-bold my-3" {...props} />,
|
||||
h3: ({ node, ...props }) => <h3 className="text-lg font-bold my-2" {...props} />,
|
||||
a: ({ node, ...props }) => <a className="text-primary hover:underline" {...props} />,
|
||||
p: ({ node, ...props }) => <p className="my-2" {...props} />,
|
||||
p: ({ node, ...props }) => <p className="my-2 font-sans cjk-text" {...props} />,
|
||||
ul: ({ node, ...props }) => <ul className="list-disc pl-5 my-2" {...props} />,
|
||||
ol: ({ node, ...props }) => <ol className="list-decimal pl-5 my-2" {...props} />,
|
||||
li: ({ node, ...props }) => <li className="my-1" {...props} />,
|
||||
|
@ -59,9 +81,12 @@ export const MarkdownRenderer = forwardRef<HTMLDivElement, MarkdownRendererProps
|
|||
<img className="max-w-full h-auto rounded-md my-2" {...props} alt={props.alt || ""} />
|
||||
),
|
||||
pre: ({ node, ...props }) => <pre className="p-0 my-2 bg-transparent" {...props} />,
|
||||
table: ({ node, ...props }) => <table className="w-full border-collapse my-3 text-sm" {...props} />,
|
||||
th: ({ node, ...props }) => <th className="border border-slate-300 dark:border-zinc-700 px-3 py-2 text-left font-semibold bg-slate-100 dark:bg-zinc-800" {...props} />,
|
||||
td: ({ node, ...props }) => <td className="border border-slate-300 dark:border-zinc-700 px-3 py-2 cjk-text" {...props} />,
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
{processedContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
|
|
@ -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<HTMLInputElement>(null)
|
||||
const isMobile = useIsMobile() || isMobileView
|
||||
|
||||
const copyCurrentUrl = () => {
|
||||
const url = window.location.origin + pathname
|
||||
|
@ -100,7 +105,10 @@ export function SiteHeader({
|
|||
}
|
||||
|
||||
return (
|
||||
<header className="bg-background sticky top-0 flex h-14 shrink-0 items-center gap-2 z-20 border-b w-full">
|
||||
<header className={cn(
|
||||
"bg-background sticky top-0 flex h-14 shrink-0 items-center gap-2 z-20 border-b w-full",
|
||||
isMobile && "px-2"
|
||||
)}>
|
||||
<div className="flex flex-1 items-center gap-2 px-3">
|
||||
{isEditing ? (
|
||||
<div className="flex items-center gap-1">
|
||||
|
@ -144,55 +152,69 @@ export function SiteHeader({
|
|||
</div>
|
||||
|
||||
<div className="flex items-center gap-1 pr-4">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onViewFiles}
|
||||
className="h-9 w-9 cursor-pointer"
|
||||
>
|
||||
<FolderOpen className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>View Files in Task</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{isMobile ? (
|
||||
// Mobile view - only show the side panel toggle
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onToggleSidePanel}
|
||||
className="h-9 w-9 cursor-pointer"
|
||||
aria-label="Toggle computer panel"
|
||||
>
|
||||
<PanelRightOpen className="h-4 w-4" />
|
||||
</Button>
|
||||
) : (
|
||||
// Desktop view - show all buttons with tooltips
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onViewFiles}
|
||||
className="h-9 w-9 cursor-pointer"
|
||||
>
|
||||
<FolderOpen className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>View Files in Task</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={copyCurrentUrl}
|
||||
className="h-9 w-9 cursor-pointer"
|
||||
>
|
||||
<Link className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Copy Link</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={copyCurrentUrl}
|
||||
className="h-9 w-9 cursor-pointer"
|
||||
>
|
||||
<Link className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Copy Link</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onToggleSidePanel}
|
||||
className="h-9 w-9 cursor-pointer"
|
||||
>
|
||||
<PanelRightOpen className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Toggle Computer Preview (CMD+I)</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onToggleSidePanel}
|
||||
className="h-9 w-9 cursor-pointer"
|
||||
>
|
||||
<PanelRightOpen className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Toggle Computer Preview (CMD+I)</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
|
|
@ -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({
|
|||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-lg font-medium text-zinc-900 dark:text-zinc-100">Suna's Computer</h2>
|
||||
{/* <div className="h-6 w-6 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center">
|
||||
<CurrentToolIcon className="h-3.5 w-3.5 text-zinc-800 dark:text-zinc-300" />
|
||||
</div>
|
||||
<span className="text-sm text-zinc-700 dark:text-zinc-300">{currentToolName}</span> */}
|
||||
</div>
|
||||
|
||||
{isMobile && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onClose}
|
||||
className="h-8 w-8 -mr-2"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentToolCall.toolResult?.content && !isStreaming && (
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-6 w-6 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center">
|
||||
<CurrentToolIcon className="h-3.5 w-3.5 text-zinc-800 dark:text-zinc-300" />
|
||||
</div>
|
||||
<span className="text-sm text-zinc-700 dark:text-zinc-300">{currentToolName}</span>
|
||||
<div className={cn(
|
||||
"px-2.5 py-0.5 rounded-full text-xs font-medium",
|
||||
isSuccess
|
||||
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-400"
|
||||
: "bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400"
|
||||
)}>
|
||||
{isSuccess ? 'Success' : 'Failed'}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-6 w-6 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center">
|
||||
<CurrentToolIcon className="h-3.5 w-3.5 text-zinc-800 dark:text-zinc-300" />
|
||||
</div>
|
||||
<span className={cn(
|
||||
"text-sm text-zinc-700 dark:text-zinc-300",
|
||||
isMobile && "hidden sm:inline" // Hide on small mobile
|
||||
)}>
|
||||
{currentToolName}
|
||||
</span>
|
||||
<div className={cn(
|
||||
"px-2.5 py-0.5 rounded-full text-xs font-medium",
|
||||
isSuccess
|
||||
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-400"
|
||||
: "bg-red-50 text-red-700 dark:bg-red-900/20 dark:text-red-400"
|
||||
)}>
|
||||
{isSuccess ? 'Success' : 'Failed'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{isStreaming && (
|
||||
<div className="px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 dark:bg-blue-900/20 dark:text-blue-400 flex items-center gap-1.5">
|
||||
<CircleDashed className="h-3 w-3 animate-spin" />
|
||||
|
@ -339,7 +352,13 @@ export function ToolCallSidePanel({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className={`fixed inset-y-0 right-0 w-[90%] sm:w-[450px] md:w-[500px] lg:w-[550px] xl:w-[650px] border-l flex flex-col z-10 h-screen transition-all duration-200 ease-in-out ${!isOpen ? 'translate-x-full' : ''}`}>
|
||||
<div className={cn(
|
||||
"fixed inset-y-0 right-0 border-l flex flex-col z-10 h-screen transition-all duration-200 ease-in-out",
|
||||
isMobile
|
||||
? "w-[90%]"
|
||||
: "w-[90%] sm:w-[450px] md:w-[500px] lg:w-[550px] xl:w-[650px]",
|
||||
!isOpen && "translate-x-full"
|
||||
)}>
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
{renderContent()}
|
||||
</div>
|
||||
|
|
|
@ -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)}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -355,7 +355,7 @@ export function FileOperationToolView({
|
|||
{idx + 1}
|
||||
</div>
|
||||
<div className="table-cell pl-3 py-0.5 text-xs font-mono whitespace-pre text-zinc-800 dark:text-zinc-300">
|
||||
{line || ' '}
|
||||
{processUnicodeContent(line) || ' '}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
@ -381,14 +381,14 @@ export function FileOperationToolView({
|
|||
{/* Markdown Preview */}
|
||||
{isMarkdown && viewMode === 'preview' && isSuccess && (
|
||||
<div className="flex-1 overflow-auto bg-white dark:bg-zinc-950 text-zinc-900 dark:text-zinc-100">
|
||||
<MarkdownRenderer content={fileContent} />
|
||||
<MarkdownRenderer content={processUnicodeContent(fileContent)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CSV Preview */}
|
||||
{isCsv && viewMode === 'preview' && isSuccess && (
|
||||
<div className="flex-1 overflow-hidden bg-white dark:bg-zinc-950">
|
||||
<CsvRenderer content={fileContent} />
|
||||
<CsvRenderer content={processUnicodeContent(fileContent)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
Loading…
Reference in New Issue