Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ImageModal } from './components/ImageModal';
import { FullscreenModal } from './components/FullscreenModal';
import { StandardPanelContent, ZoomedImageData, FullscreenFileData } from './types/panelContent';
import { FileDisplayMode } from './types';
import { ToggleSwitchProps } from './renderers/generic/components';
import { ToggleSwitchProps } from './components/shared';
import { workspaceDisplayModeAtom, WorkspaceDisplayMode } from '@/common/state/atoms/workspace';
import { rawToolMappingAtom } from '@/common/state/atoms/rawEvents';
import { getFileTypeInfo, getDefaultDisplayMode } from './utils/fileTypeUtils';
Expand All @@ -24,7 +24,7 @@ import { ScriptResultRenderer } from './renderers/ScriptResultRenderer';
import { BrowserResultRenderer } from './renderers/BrowserResultRenderer';
import { BrowserControlRenderer } from './renderers/BrowserControlRenderer';

import { GenericResultRenderer } from './renderers/generic/GenericResultRenderer';
import { TerminalRenderer } from './renderers/TerminalRenderer';
import { DeliverableRenderer } from './renderers/DeliverableRenderer';
import { DiffRenderer } from './renderers/DiffRenderer';
import { FileResultRenderer } from './renderers/FileResultRenderer';
Expand All @@ -48,7 +48,7 @@ const CONTENT_RENDERERS: Record<
browser_vision_control: BrowserControlRenderer,

research_report: ResearchReportRenderer,
json: GenericResultRenderer,
json: TerminalRenderer,
deliverable: DeliverableRenderer,
file_result: FileResultRenderer,
diff_result: DiffRenderer,
Expand Down Expand Up @@ -239,7 +239,7 @@ export const WorkspaceDetail: React.FC = () => {
}
}

const RendererComponent = CONTENT_RENDERERS[panelContent.type] || GenericResultRenderer;
const RendererComponent = CONTENT_RENDERERS[panelContent.type] || TerminalRenderer;

return (
<RendererComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { FiX } from 'react-icons/fi';
import { MarkdownRenderer } from '@tarko/ui';
import { MessageContent } from '../renderers/generic/components/MessageContent';
import { MessageContent } from './shared';
import { FullscreenFileData } from '../types/panelContent';
import { normalizeFilePath } from '@tarko/ui';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { formatTimestamp } from '@/common/utils/formatters';
import { useTool } from '@/common/hooks/useTool';
import { normalizeFilePath } from '@tarko/ui';
import { StandardPanelContent } from '../types/panelContent';
import { ToggleSwitch, ToggleSwitchProps } from '../renderers/generic/components/ToggleSwitch';
import { ToggleSwitch, ToggleSwitchProps } from './shared';
import { ShareButton } from './ShareButton';
import { FileDisplayMode } from '../types';
import { WorkspaceDisplayMode } from '@/common/state/atoms/workspace';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import React from 'react';
import { motion } from 'framer-motion';
import { MarkdownRenderer } from '@tarko/ui';
import { wrapMarkdown } from '@/common/utils/markdown';
import { DisplayMode } from '../types';

type DisplayMode = 'source' | 'rendered';

interface MessageContentProps {
message: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { MessageContent } from './MessageContent';
export { ToggleSwitch, type ToggleSwitchProps } from './ToggleSwitch';
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React from 'react';
import { MonacoCodeEditor } from '@tarko/ui';
import { FileDisplayMode } from '../types';
import { StandardPanelContent } from '../types/panelContent';
import { MessageContent } from './generic/components/MessageContent';
import { DisplayMode } from './generic/types';
import { MessageContent } from '../components/shared';

type DisplayMode = 'source' | 'rendered';
import { useStableCodeContent } from '@/common/hooks/useStableValue';
import { ThrottledHtmlRenderer } from '../components/ThrottledHtmlRenderer';
import { formatBytes } from '../utils/codeUtils';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React from 'react';
import { StandardPanelContent } from '../types/panelContent';
import { FileDisplayMode } from '../types';
import { TerminalOutput } from '../components/TerminalOutput';
import { getAgentTitle } from '@/config/web-ui-config';
import { CodeHighlight } from '@tarko/ui';

interface TerminalRendererProps {
panelContent: StandardPanelContent;
onAction?: (action: string, data: unknown) => void;
displayMode?: FileDisplayMode;
}

/**
* Format tool arguments as JSON string
*/
function formatArguments(args: Record<string, any>): string {
if (!args || Object.keys(args).length === 0) {
return '';
}
return JSON.stringify(args, null, 2);
}

/**
* Format tool output as JSON string when applicable
*/
function formatOutput(source: any): string {
if (Array.isArray(source)) {
// Handle array format (like command results)
const outputLines: string[] = [];
for (const item of source) {
if (typeof item === 'object' && item !== null) {
if ('type' in item && 'text' in item) {
if (item.name) {
outputLines.push(`[${item.name}]`);
}
// Try to parse and format the text if it's JSON
let textContent = String(item.text);
try {
const parsed = JSON.parse(textContent);
textContent = JSON.stringify(parsed, null, 2);
} catch {
// Keep original text if not JSON
}
outputLines.push(textContent);
} else {
outputLines.push(JSON.stringify(item, null, 2));
}
} else {
outputLines.push(String(item));
}
}
return outputLines.join('\n');
} else if (typeof source === 'string') {
// Try to parse and reformat if it's a JSON string
try {
const parsed = JSON.parse(source);
return JSON.stringify(parsed, null, 2);
} catch {
return source;
}
} else if (source && typeof source === 'object') {
return JSON.stringify(source, null, 2);
} else {
return '(no output)';
}
}

/**
* Create terminal command display with syntax highlighting
*/
function formatCommand(title: string, args?: Record<string, any>): React.ReactNode {
const parts: React.ReactNode[] = [];

// Tool name in cyan
parts.push(
<span key="tool" className="text-cyan-400 font-bold">
{title}
</span>,
);

// Add key arguments inline if they exist
if (args && Object.keys(args).length > 0) {
// Show key arguments inline for common tools
const keyArgs = ['command', 'path', 'url', 'query'].filter((key) => args[key]);
if (keyArgs.length > 0) {
parts.push(
<span key="args" className="text-gray-400 ml-2">
{keyArgs.map((key) => (
<span key={key}>
<span className="text-yellow-300">--{key}</span>
<span className="text-orange-300 ml-1">'{args[key]}'</span>
<span className="ml-2"></span>
</span>
))}
</span>,
);
}
}

return <div className="flex flex-wrap items-center">{parts}</div>;
}

export const TerminalRenderer: React.FC<TerminalRendererProps> = ({
panelContent,
onAction,
displayMode,
}) => {
const command = formatCommand(panelContent.title, panelContent.arguments);
const argumentsJson = formatArguments(panelContent.arguments);
const output = formatOutput(panelContent.source);

return (
<div className="space-y-4 md:text-base text-sm font-mono">
<div className="md:[&_pre]:text-sm [&_pre]:text-xs md:[&_pre]:p-4 [&_pre]:p-2">
{/* Terminal with JSON highlighting using CodeEditor */}
<div className="rounded-lg overflow-hidden border border-gray-900 shadow-[0_8px_24px_rgba(0,0,0,0.3)]">
{/* Terminal title bar */}
<div className="bg-[#111111] px-3 py-1.5 border-b border-gray-900 flex items-center">
<div className="flex space-x-1.5 mr-3">
<div className="w-3 h-3 rounded-full bg-red-500 shadow-sm"></div>
<div className="w-3 h-3 rounded-full bg-yellow-500 shadow-sm"></div>
<div className="w-3 h-3 rounded-full bg-green-500 shadow-sm"></div>
</div>
<div className="text-gray-400 text-xs font-medium mx-auto">
user@{getAgentTitle().toLowerCase().replace(/\s+/g, '-')}
</div>
</div>

{/* Terminal content area */}
<div className="bg-black">
<div className="overflow-x-auto min-w-full">
{/* Command section */}
<div className="flex items-start p-3 pb-0 text-sm">
<span className="select-none text-green-400 mr-2 font-bold">$</span>
<div className="flex-1 text-gray-200">{command}</div>
</div>

{/* Arguments section */}
{argumentsJson && (
<div className="p-3 pb-0 bg-[#121212] rounded m-3 mb-0 max-h-[40vh] overflow-auto">
<CodeHighlight code={argumentsJson} language="json" />
</div>
)}

{/* Output section */}
{output && (
<div className="p-3 bg-[#121212] rounded m-3 max-h-[80vh] overflow-auto">
<CodeHighlight code={output} language="json" />
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
};
Loading
Loading