- {(selectedVersion ? versionFiles : files).map((file) => (
+ {displayPanelFiles.map((file) => (
- {(selectedVersion ? versionFiles : files).length} {(selectedVersion ? versionFiles : files).length === 1 ? 'item' : 'items'}
+ {displayPanelFiles.length} {displayPanelFiles.length === 1 ? 'item' : 'items'}
diff --git a/apps/frontend/src/components/thread/kortix-computer/KortixComputer.tsx b/apps/frontend/src/components/thread/kortix-computer/KortixComputer.tsx
index cf8e01f1b2..f655e4ebde 100644
--- a/apps/frontend/src/components/thread/kortix-computer/KortixComputer.tsx
+++ b/apps/frontend/src/components/thread/kortix-computer/KortixComputer.tsx
@@ -621,6 +621,7 @@ export const KortixComputer = memo(function KortixComputer({
sandboxId={effectiveSandboxId}
project={project}
projectId={projectId}
+ variant="inline-library"
/>
);
};
@@ -712,9 +713,10 @@ export const KortixComputer = memo(function KortixComputer({
variant="motion"
currentView={activeView}
onViewChange={setActiveView}
- showFilesTab={true}
+ showFilesTab={false}
isMaximized={isMaximized}
isSuiteMode={isSuiteMode}
+ hideViewToggle={true}
onToggleSuiteMode={() => {
if (isSuiteMode) {
// Exit suite mode - restore previous size
@@ -773,7 +775,8 @@ export const KortixComputer = memo(function KortixComputer({
variant="drawer"
currentView={activeView}
onViewChange={setActiveView}
- showFilesTab={true}
+ showFilesTab={false}
+ hideViewToggle={true}
/>
diff --git a/apps/frontend/src/components/thread/kortix-computer/components/Desktop.tsx b/apps/frontend/src/components/thread/kortix-computer/components/Desktop.tsx
index b99ccc1e6b..ed679e376a 100644
--- a/apps/frontend/src/components/thread/kortix-computer/components/Desktop.tsx
+++ b/apps/frontend/src/components/thread/kortix-computer/components/Desktop.tsx
@@ -1042,7 +1042,7 @@ export const SandboxDesktop = memo(function SandboxDesktop({
isMaximized={true}
currentView={currentView}
onViewChange={handleSystemAppClick}
- showFilesTab={true}
+ showFilesTab={false}
isFilesWindowOpen={isFilesWindowOpen}
isBrowserWindowOpen={isBrowserWindowOpen}
isTerminalWindowOpen={isTerminalWindowOpen}
diff --git a/apps/frontend/src/components/thread/kortix-computer/components/NavigationControls.tsx b/apps/frontend/src/components/thread/kortix-computer/components/NavigationControls.tsx
index 4912eb4d30..ebdb53bcc4 100644
--- a/apps/frontend/src/components/thread/kortix-computer/components/NavigationControls.tsx
+++ b/apps/frontend/src/components/thread/kortix-computer/components/NavigationControls.tsx
@@ -43,11 +43,11 @@ export const NavigationControls = memo(function NavigationControls({
if (agentStatus === 'running') {
return (
-
-
Live Updates
+
+
Live Updates
);
} else {
@@ -62,11 +62,11 @@ export const NavigationControls = memo(function NavigationControls({
if (agentStatus === 'running') {
return (
-
-
Jump to Live
+
+
Jump to Live
);
} else {
diff --git a/apps/frontend/src/components/thread/kortix-computer/components/PanelHeader.tsx b/apps/frontend/src/components/thread/kortix-computer/components/PanelHeader.tsx
index 464b478e07..409c5adf75 100644
--- a/apps/frontend/src/components/thread/kortix-computer/components/PanelHeader.tsx
+++ b/apps/frontend/src/components/thread/kortix-computer/components/PanelHeader.tsx
@@ -1,8 +1,7 @@
'use client';
import { memo, useState, useEffect } from 'react';
-import { Minimize2, Maximize2, Wifi, Battery, BatteryLow, BatteryMedium, BatteryFull, BatteryCharging } from 'lucide-react';
-import { KortixLoader } from '@/components/ui/kortix-loader';
+import { Minimize2, Wifi, BatteryLow, BatteryMedium, BatteryFull, BatteryCharging, Library, Zap } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { DrawerTitle } from '@/components/ui/drawer';
import { ViewType } from '@/stores/kortix-computer-store';
@@ -98,6 +97,54 @@ function StatusBar() {
);
}
+interface ActionLibrarySwitcherProps {
+ currentView: ViewType;
+ onViewChange: (view: ViewType) => void;
+ size?: 'sm' | 'md';
+}
+
+function ActionLibrarySwitcher({ currentView, onViewChange, size = 'md' }: ActionLibrarySwitcherProps) {
+ const isAction = currentView === 'tools';
+ const isLibrary = currentView === 'files';
+
+ const buttonClasses = size === 'sm'
+ ? "px-2.5 py-1 text-[11px] gap-1"
+ : "px-3 py-1.5 text-xs gap-1.5";
+
+ const iconSize = size === 'sm' ? "h-3 w-3" : "h-3.5 w-3.5";
+
+ return (
+
+ onViewChange('tools')}
+ className={cn(
+ "flex items-center rounded-md font-medium transition-all duration-200",
+ buttonClasses,
+ isAction
+ ? "bg-background text-foreground shadow-sm"
+ : "text-muted-foreground hover:text-foreground"
+ )}
+ >
+
+ Action
+
+ onViewChange('files')}
+ className={cn(
+ "flex items-center rounded-md font-medium transition-all duration-200",
+ buttonClasses,
+ isLibrary
+ ? "bg-background text-foreground shadow-sm"
+ : "text-muted-foreground hover:text-foreground"
+ )}
+ >
+
+ Library
+
+
+ );
+}
+
interface PanelHeaderProps {
agentName?: string;
onClose: () => void;
@@ -152,7 +199,11 @@ export const PanelHeader = memo(function PanelHeader({
Kortix Computer
-
+
- {isStreaming && (
-
-
- Running
-
- )}
- {!hideViewToggle && (
-
- )}
+
{isMaximized && (
<>
diff --git a/apps/frontend/src/components/thread/mode-indicator.tsx b/apps/frontend/src/components/thread/mode-indicator.tsx
new file mode 100644
index 0000000000..c1d77a1f03
--- /dev/null
+++ b/apps/frontend/src/components/thread/mode-indicator.tsx
@@ -0,0 +1,130 @@
+'use client';
+
+import React, { memo, useCallback, useMemo } from 'react';
+import { Button } from '@/components/ui/button';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+import { ChevronDown, Check, Lock } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import { useModelSelection } from '@/hooks/agents';
+import { KortixLogo } from '@/components/sidebar/kortix-logo';
+import { usePricingModalStore } from '@/stores/pricing-modal-store';
+
+export const ModeIndicator = memo(function ModeIndicator() {
+ const {
+ selectedModel,
+ allModels: modelOptions,
+ canAccessModel,
+ handleModelChange,
+ } = useModelSelection();
+
+ const basicModel = useMemo(
+ () => modelOptions.find((m) => m.id === 'kortix/basic' || m.label === 'Kortix Basic'),
+ [modelOptions]
+ );
+
+ const powerModel = useMemo(
+ () => modelOptions.find((m) => m.id === 'kortix/power' || m.label === 'Kortix Advanced Mode'),
+ [modelOptions]
+ );
+
+ const canAccessPower = powerModel ? canAccessModel(powerModel.id) : false;
+ const isPowerSelected = powerModel && selectedModel === powerModel.id;
+ const isBasicSelected = basicModel && selectedModel === basicModel.id;
+
+ const handleBasicClick = useCallback(() => {
+ if (basicModel) {
+ handleModelChange(basicModel.id);
+ }
+ }, [basicModel, handleModelChange]);
+
+ const handleAdvancedClick = useCallback(() => {
+ if (powerModel) {
+ if (canAccessPower) {
+ handleModelChange(powerModel.id);
+ } else {
+ usePricingModalStore.getState().openPricingModal({
+ isAlert: true,
+ alertTitle: 'Upgrade to access Kortix Advanced mode',
+ });
+ }
+ }
+ }, [powerModel, canAccessPower, handleModelChange]);
+
+ // Determine current mode label
+ const currentModeLabel = isPowerSelected ? 'Advanced' : 'Basic';
+
+ return (
+
+
+
+
+
+ {isPowerSelected && }
+ {currentModeLabel}
+
+
+
+
+
+ Switch mode
+
+
+
+
+ {/* Basic Mode */}
+
+ Basic
+ {isBasicSelected && }
+
+
+ {/* Advanced Mode */}
+
+
+
+
+ Advanced
+
+
+ {isPowerSelected &&
}
+ {!canAccessPower &&
}
+
+
+
+ );
+});
+
+export default ModeIndicator;
+
diff --git a/apps/frontend/src/components/thread/thread-site-header.tsx b/apps/frontend/src/components/thread/thread-site-header.tsx
index ead77b1daa..bcfaca8a9b 100644
--- a/apps/frontend/src/components/thread/thread-site-header.tsx
+++ b/apps/frontend/src/components/thread/thread-site-header.tsx
@@ -2,7 +2,7 @@
import { Button } from "@/components/ui/button"
import { FolderOpen, Upload, PanelRightOpen, PanelRightClose, Copy, Check } from "lucide-react"
-import { usePathname } from "next/navigation"
+import { usePathname, useRouter } from "next/navigation"
import { toast } from "@/lib/toast"
import {
Tooltip,
@@ -20,6 +20,7 @@ import { SharePopover } from "@/components/sidebar/share-modal"
import { useQueryClient } from "@tanstack/react-query";
import { projectKeys } from "@/hooks/threads/keys";
import { threadKeys } from "@/hooks/threads/keys";
+import { ModeIndicator } from "@/components/thread/mode-indicator";
interface ThreadSiteHeaderProps {
threadId?: string;
@@ -45,6 +46,7 @@ export function SiteHeader({
variant = 'default',
}: ThreadSiteHeaderProps) {
const pathname = usePathname()
+ const router = useRouter()
const [isEditing, setIsEditing] = useState(false)
const [editName, setEditName] = useState(projectName)
const inputRef = useRef
(null)
@@ -172,6 +174,9 @@ export function SiteHeader({
+ {/* Mode Indicator - only show for non-shared variant */}
+ {variant !== 'shared' && }
+
{variant === 'shared' ? (
@@ -200,12 +205,19 @@ export function SiteHeader({
) : null}
+ {/* Hidden: View Files in Task button - functionality moved to Library
onViewFiles()}
+ onClick={() => {
+ if (projectId) {
+ router.push(`/library/${projectId}`);
+ } else {
+ onViewFiles();
+ }
+ }}
className="h-9 w-9 cursor-pointer"
>
@@ -215,6 +227,7 @@ export function SiteHeader({
View Files in Task
+ */}
diff --git a/apps/frontend/src/components/thread/tool-views/apify-tool/ToolView.tsx b/apps/frontend/src/components/thread/tool-views/apify-tool/ToolView.tsx
index 91053f1ada..c35f9b5cc2 100644
--- a/apps/frontend/src/components/thread/tool-views/apify-tool/ToolView.tsx
+++ b/apps/frontend/src/components/thread/tool-views/apify-tool/ToolView.tsx
@@ -304,18 +304,18 @@ export function ApifyToolView({
{viewType === 'run' && (
-
+
Initializing actor run...
{elapsedTime > 5 && (
-
+
Executing actor tasks...
)}
{elapsedTime > 15 && (
-
+
Collecting results...
)}
diff --git a/apps/frontend/src/components/thread/tool-views/canvas-tool/CanvasToolView.tsx b/apps/frontend/src/components/thread/tool-views/canvas-tool/CanvasToolView.tsx
index 124be42eef..64e74d155c 100644
--- a/apps/frontend/src/components/thread/tool-views/canvas-tool/CanvasToolView.tsx
+++ b/apps/frontend/src/components/thread/tool-views/canvas-tool/CanvasToolView.tsx
@@ -249,7 +249,7 @@ export function CanvasToolView({
{displayName}
{isStreaming && (
-
+
)}
diff --git a/apps/frontend/src/components/thread/tool-views/file-operation/FileOperationToolView.tsx b/apps/frontend/src/components/thread/tool-views/file-operation/FileOperationToolView.tsx
index 69de171c60..4d98340505 100644
--- a/apps/frontend/src/components/thread/tool-views/file-operation/FileOperationToolView.tsx
+++ b/apps/frontend/src/components/thread/tool-views/file-operation/FileOperationToolView.tsx
@@ -67,6 +67,7 @@ import {
} from './_utils';
import { ToolViewProps } from '../types';
import { LoadingState } from '../shared/LoadingState';
+import { StreamingLoader } from '../shared/StreamingLoader';
import { ToolViewIconTitle } from '../shared/ToolViewIconTitle';
import { ToolViewFooter } from '../shared/ToolViewFooter';
import { toast } from '@/lib/toast';
@@ -1105,12 +1106,7 @@ export function FileOperationToolView({
showProgress={false}
/>
) : !fileContent && isStreaming ? (
-
-
-
-
Waiting for content...
-
-
+
) : operation === 'delete' ? (
@@ -1157,12 +1153,7 @@ export function FileOperationToolView({
showProgress={false}
/>
) : !fileContent && isStreaming ? (
-
-
-
-
Waiting for content...
-
-
+
) : operation === 'delete' ? (
renderDeleteOperation()
) : (
diff --git a/apps/frontend/src/components/thread/tool-views/presentation-tools/PresentationSlideSkeleton.tsx b/apps/frontend/src/components/thread/tool-views/presentation-tools/PresentationSlideSkeleton.tsx
index edabddf069..bb0ecb2979 100644
--- a/apps/frontend/src/components/thread/tool-views/presentation-tools/PresentationSlideSkeleton.tsx
+++ b/apps/frontend/src/components/thread/tool-views/presentation-tools/PresentationSlideSkeleton.tsx
@@ -108,7 +108,7 @@ ${streamingContent}
{isGenerating && (
)}
diff --git a/apps/frontend/src/components/thread/tool-views/presentation-tools/PresentationViewer.tsx b/apps/frontend/src/components/thread/tool-views/presentation-tools/PresentationViewer.tsx
index 1bd8d293ae..22daab659e 100644
--- a/apps/frontend/src/components/thread/tool-views/presentation-tools/PresentationViewer.tsx
+++ b/apps/frontend/src/components/thread/tool-views/presentation-tools/PresentationViewer.tsx
@@ -657,7 +657,7 @@ export function PresentationViewer({
{metadata?.title || metadata?.presentation_name || streamingPresentationName || toolTitle}
{isStreaming && (
-
+
)}
diff --git a/apps/frontend/src/components/thread/tool-views/shared/StreamingLoader.tsx b/apps/frontend/src/components/thread/tool-views/shared/StreamingLoader.tsx
new file mode 100644
index 0000000000..0f41fbf2ea
--- /dev/null
+++ b/apps/frontend/src/components/thread/tool-views/shared/StreamingLoader.tsx
@@ -0,0 +1,200 @@
+'use client';
+
+import React, { useState, useEffect, memo } from 'react';
+import Image from 'next/image';
+import { KortixLoader } from '@/components/ui/kortix-loader';
+
+const streamingPhrases = [
+ 'Generating content',
+ 'Writing code',
+ 'Crafting your file',
+ 'Building structure',
+ 'Almost there',
+ 'Putting it together',
+];
+
+interface StreamingLoaderProps {
+ /** Optional custom message to display instead of cycling phrases */
+ message?: string;
+ /** Whether to show the Kortix Computer branding */
+ showBranding?: boolean;
+ /** Custom class name */
+ className?: string;
+}
+
+export const StreamingLoader = memo(function StreamingLoader({
+ message,
+ showBranding = true,
+ className,
+}: StreamingLoaderProps) {
+ const [phraseIndex, setPhraseIndex] = useState(0);
+ const [isTransitioning, setIsTransitioning] = useState(false);
+ const [dots, setDots] = useState('');
+
+ // Cycle through phrases
+ useEffect(() => {
+ if (message) return; // Don't cycle if custom message provided
+
+ const interval = setInterval(() => {
+ setIsTransitioning(true);
+ setTimeout(() => {
+ setPhraseIndex((prev) => (prev + 1) % streamingPhrases.length);
+ setIsTransitioning(false);
+ }, 200);
+ }, 2400);
+
+ return () => clearInterval(interval);
+ }, [message]);
+
+ // Animate dots
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setDots((prev) => (prev.length >= 3 ? '' : prev + '.'));
+ }, 400);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ const displayText = message || streamingPhrases[phraseIndex];
+
+ return (
+
+
+ {/* Animated logo section */}
+
+ {/* Outer pulsing ring */}
+
+
+ {/* Spinning gradient border */}
+
+
+ {/* Main container */}
+
+ {/* Subtle gradient overlay */}
+
+
+ {/* Kortix loader */}
+
+
+
+
+ {/* Branding */}
+ {showBranding && (
+
+
+
+
+ )}
+
+ {/* Status text with animation */}
+
+
+ {/* Cycling text */}
+
+ {displayText}
+
+
+ {/* Animated dots */}
+
+ {dots}
+
+
+
+ {/* Subtitle */}
+
+ Content will appear as it's generated
+
+
+
+ {/* Bouncing activity indicator */}
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+StreamingLoader.displayName = 'StreamingLoader';
+
diff --git a/apps/frontend/src/components/thread/tool-views/wrapper/ToolViewWrapper.tsx b/apps/frontend/src/components/thread/tool-views/wrapper/ToolViewWrapper.tsx
index 28383e2b78..6b5be6abc7 100644
--- a/apps/frontend/src/components/thread/tool-views/wrapper/ToolViewWrapper.tsx
+++ b/apps/frontend/src/components/thread/tool-views/wrapper/ToolViewWrapper.tsx
@@ -56,7 +56,15 @@ export function ToolViewWrapper({
{toolTitle}
- {headerContent}
+