diff --git a/AppImage/components/script-terminal-modal.tsx b/AppImage/components/script-terminal-modal.tsx index 78ab7ec..8afebfc 100644 --- a/AppImage/components/script-terminal-modal.tsx +++ b/AppImage/components/script-terminal-modal.tsx @@ -9,7 +9,8 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { CheckCircle2, XCircle, Loader2, Activity, GripHorizontal } from "lucide-react" import { TerminalPanel } from "./terminal-panel" -import { API_PORT } from "@/lib/api-config" +const API_PORT = + typeof window !== "undefined" && process.env.NEXT_PUBLIC_API_PORT ? process.env.NEXT_PUBLIC_API_PORT : "8008" import { useIsMobile } from "@/hooks/use-mobile" interface WebInteraction { @@ -58,6 +59,28 @@ export function ScriptTerminalModal({ const startYRef = useRef(0) const startHeightRef = useRef(80) + const terminalRef = useRef(null) + + useEffect(() => { + if (!terminalRef.current) return + + const resizeObserver = new ResizeObserver(() => { + // Notificar a la terminal que necesita redimensionarse + const event = new CustomEvent("terminal-resize-needed") + window.dispatchEvent(event) + }) + + resizeObserver.observe(terminalRef.current) + + return () => { + resizeObserver.disconnect() + } + }, []) + + const handleTerminalResize = () => { + // Este callback será usado por TerminalPanel para saber cuándo redimensionar + } + useEffect(() => { if (open) { setIsComplete(false) @@ -216,7 +239,7 @@ export function ScriptTerminalModal({ -
+
{isWaitingNextInteraction && !currentInteraction && ( diff --git a/AppImage/components/terminal-panel.tsx b/AppImage/components/terminal-panel.tsx index 49a9be9..c4be060 100644 --- a/AppImage/components/terminal-panel.tsx +++ b/AppImage/components/terminal-panel.tsx @@ -2,8 +2,8 @@ import type React from "react" import { useEffect, useRef, useState } from "react" -import { API_PORT } from "../lib/api-config" -import { fetchApi } from "@/lib/api-config" // Cambiando import para usar fetchApi directamente +const API_PORT = + typeof window !== "undefined" && process.env.NEXT_PUBLIC_API_PORT ? process.env.NEXT_PUBLIC_API_PORT : "8008" import { Activity, Trash2, @@ -22,6 +22,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } f import { Input } from "@/components/ui/input" import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" import type { CheatSheetResult } from "@/lib/cheat-sheet-result" // Declare CheatSheetResult here +import { ResizablePanelGroup, ResizablePanel } from "@/components/ui/resizable-panel-group" // Added import for ResizablePanelGroup and ResizablePanel type TerminalPanelProps = { websocketUrl?: string @@ -31,6 +32,7 @@ type TerminalPanelProps = { onWebSocketCreated?: (ws: WebSocket) => void onTerminalOutput?: () => void isScriptModal?: boolean + onResizeNeeded?: () => void // Added prop for notifying when resize is needed } interface TerminalInstance { @@ -145,6 +147,7 @@ export function TerminalPanel({ onWebSocketCreated, onTerminalOutput, isScriptModal = false, + onResizeNeeded, }: TerminalPanelProps) { const [terminals, setTerminals] = useState([]) const [activeTerminalId, setActiveTerminalId] = useState("") @@ -241,11 +244,17 @@ export function TerminalPanel({ const searchEndpoint = `/api/terminal/search-command?q=${encodeURIComponent(query)}` - const data = await fetchApi<{ success: boolean; examples: any[] }>(searchEndpoint, { + const response = await fetch(searchEndpoint, { method: "GET", signal: AbortSignal.timeout(10000), }) + if (!response.ok) { + throw new Error("Network response was not ok") + } + + const data = await response.json() + if (!data.success || !data.examples || data.examples.length === 0) { throw new Error("No examples found") } @@ -360,6 +369,48 @@ export function TerminalPanel({ }) }, [terminalHeight, layout, terminals, isMobile]) + useEffect(() => { + if (onResizeNeeded && terminals.length > 0) { + const terminal = terminals[0] + if (terminal.term && terminal.fitAddon && terminal.isConnected) { + const triggerResize = () => { + try { + setTimeout(() => { + terminal.fitAddon?.fit() + if (terminal.ws?.readyState === WebSocket.OPEN) { + const cols = terminal.term?.cols || 80 + const rows = terminal.term?.rows || 24 + terminal.ws.send( + JSON.stringify({ + type: "resize", + cols, + rows, + }), + ) + } + }, 100) + } catch (err) { + console.warn("[Terminal] resize failed:", err) + } + } + + // Llamar automáticamente cuando sea necesario + const resizeObserver = new ResizeObserver(() => { + triggerResize() + }) + + const terminalElement = document.querySelector(".xterm") + if (terminalElement) { + resizeObserver.observe(terminalElement) + } + + return () => { + resizeObserver.disconnect() + } + } + } + }, [terminals, onResizeNeeded]) + const initializeTerminal = async (terminal: TerminalInstance, container: HTMLDivElement) => { const [TerminalClass, FitAddonClass] = await Promise.all([ import("xterm").then((mod) => mod.Terminal), @@ -604,7 +655,7 @@ export function TerminalPanel({ const activeTerminal = terminals.find((t) => t.id === activeTerminalId) return ( -
+
{!isScriptModal && (
@@ -704,89 +755,108 @@ export function TerminalPanel({
)} -
{ - containerRefs.current["main"] = el - }} - className={`overflow-hidden flex flex-col ${isMobile ? "flex-1 h-[60vh]" : "overflow-hidden"} w-full max-w-full`} - style={ - isScriptModal - ? { height: "100%", flexShrink: 0 } - : !isMobile || isTablet - ? { height: `${terminalHeight}px`, flexShrink: 0 } - : undefined - } - > - {isMobile ? ( - - - {terminals.map((terminal) => ( - - {terminal.title} - {terminals.length > 1 && ( - - )} - - ))} - - {terminals.map((terminal) => ( - -
(containerRefs.current[terminal.id] = el)} - className="w-full h-full flex-1 bg-black overflow-hidden" - /> - - ))} - - ) : ( -
- {terminals.map((terminal) => ( -
1 && activeTerminalId === terminal.id ? "ring-2 ring-blue-500" : "" + + +
+ {!isScriptModal && ( + - {terminals.length > 1 && ( - - )} -
-
(containerRefs.current[terminal.id] = el)} - onClick={() => setActiveTerminalId(terminal.id)} - className="flex-1 w-full max-w-full bg-black overflow-hidden cursor-pointer" - data-terminal-container - /> -
- ))} + Terminal {1} + + )}
- )} -
+ +
{ + containerRefs.current["main"] = el + }} + className={`overflow-hidden flex flex-col ${isMobile ? "flex-1 h-[60vh]" : "overflow-hidden"} w-full max-w-full`} + style={ + isScriptModal + ? { height: "100%", flexShrink: 0 } + : !isMobile || isTablet + ? { height: `${terminalHeight}px`, flexShrink: 0 } + : undefined + } + > + {isMobile ? ( + + + {terminals.map((terminal) => ( + + {terminal.title} + {terminals.length > 1 && ( + + )} + + ))} + + {terminals.map((terminal) => ( + +
(containerRefs.current[terminal.id] = el)} + className="w-full h-full flex-1 bg-black overflow-hidden" + /> + + ))} + + ) : ( +
+ {terminals.map((terminal) => ( +
1 && activeTerminalId === terminal.id ? "ring-2 ring-blue-500" : "" + }`} + > +
+ + {terminals.length > 1 && ( + + )} +
+
(containerRefs.current[terminal.id] = el)} + onClick={() => setActiveTerminalId(terminal.id)} + className="flex-1 w-full max-w-full bg-black overflow-hidden cursor-pointer" + data-terminal-container + /> +
+ ))} +
+ )} +
+ + {!isScriptModal && (isTablet || (!isMobile && !isTablet)) && terminals.length > 0 && (
(endpoint: string, options?: RequestInit): Prom if (token) { headers["Authorization"] = `Bearer ${token}` - console.log("[v0] fetchApi:", endpoint, "- Authorization header ADDED") - } else { - console.log("[v0] fetchApi:", endpoint, "- NO TOKEN - Request will fail if endpoint is protected") } try { @@ -108,11 +93,8 @@ export async function fetchApi(endpoint: string, options?: RequestInit): Prom cache: "no-store", }) - console.log("[v0] fetchApi:", endpoint, "- Response status:", response.status) - if (!response.ok) { if (response.status === 401) { - console.error("[v0] fetchApi: 401 UNAUTHORIZED -", endpoint, "- Token present:", !!token) throw new Error(`Unauthorized: ${endpoint}`) } throw new Error(`API request failed: ${response.status} ${response.statusText}`) @@ -120,7 +102,7 @@ export async function fetchApi(endpoint: string, options?: RequestInit): Prom return response.json() } catch (error) { - console.error("[v0] fetchApi error for", endpoint, ":", error) + console.error("API fetch error for", endpoint, ":", error) throw error } }