Canvas: Preview tab

Change-Id: I8a66cd977db5864828ad7ba9025270d624143091
diff --git a/apps/canvas/front/src/App.tsx b/apps/canvas/front/src/App.tsx
index 711b076..5100576 100644
--- a/apps/canvas/front/src/App.tsx
+++ b/apps/canvas/front/src/App.tsx
@@ -10,6 +10,7 @@
 import { ChatManager } from "./components/ChatManager";
 import { useAgents } from "./lib/state";
 import { Bot } from "lucide-react";
+import { Preview } from "./components/preview";
 
 export default function App() {
 	return (
@@ -30,6 +31,7 @@
 			<div className="flex justify-between border-b">
 				<TabsList className="!rounded-none">
 					<TabsTrigger value="build">Build</TabsTrigger>
+					<TabsTrigger value="preview">Preview</TabsTrigger>
 					<TabsTrigger value="monitoring">Monitoring</TabsTrigger>
 					<TabsTrigger value="config">Config</TabsTrigger>
 					<TabsTrigger value="integrations">Integrations</TabsTrigger>
@@ -51,6 +53,9 @@
 			<TabsContent value="build" className="!mt-0 flex-1 min-h-0">
 				<Build />
 			</TabsContent>
+			<TabsContent value="preview" className="!mt-0 flex-1 min-h-0">
+				<Preview />
+			</TabsContent>
 			<TabsContent value="config" className="!mt-0 flex-1 min-h-0">
 				<Config />
 			</TabsContent>
diff --git a/apps/canvas/front/src/components/preview.tsx b/apps/canvas/front/src/components/preview.tsx
new file mode 100644
index 0000000..f5ae9cf
--- /dev/null
+++ b/apps/canvas/front/src/components/preview.tsx
@@ -0,0 +1,64 @@
+import { useMemo, useState } from "react";
+import { useEnv, useLeadAgent } from "@/lib/state";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
+import { Button } from "./ui/button";
+import { RefreshCw } from "lucide-react";
+import { Agent } from "@/Agent";
+import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable";
+
+export function Preview() {
+	const leadAgent = useLeadAgent();
+	if (leadAgent == null) {
+		return <PreviewImpl />;
+	}
+	return (
+		<ResizablePanelGroup direction="horizontal" className="h-full">
+			<ResizablePanel defaultSize={25}>
+				<div className="p-4 h-full">
+					{leadAgent ? <Agent agent={leadAgent} /> : <div>No agent available</div>}
+				</div>
+			</ResizablePanel>
+			<ResizableHandle withHandle />
+			<ResizablePanel>
+				<PreviewImpl />
+			</ResizablePanel>
+		</ResizablePanelGroup>
+	);
+}
+
+function PreviewImpl() {
+	const env = useEnv();
+	const httpsAccess = useMemo(() => env.access.filter((a) => a.type === "https"), [env]);
+	const [selectedUrl, setSelectedUrl] = useState<string | null>(null);
+	const [refreshKey, setRefreshKey] = useState(0);
+	return (
+		<div className="w-full h-full flex flex-col">
+			<div className="p-4 border-b flex items-center gap-2">
+				<Select onValueChange={setSelectedUrl}>
+					<SelectTrigger className="w-[300px]">
+						<SelectValue placeholder="Select a gateway" />
+					</SelectTrigger>
+					<SelectContent>
+						{httpsAccess.map((a) => (
+							<SelectItem key={a.address} value={a.address}>
+								{a.address}
+							</SelectItem>
+						))}
+					</SelectContent>
+				</Select>
+				<Button variant="outline" size="icon" onClick={() => setRefreshKey((prev) => prev + 1)}>
+					<RefreshCw className="h-4 w-4" />
+				</Button>
+			</div>
+			<div className="flex-1">
+				{selectedUrl ? (
+					<iframe key={refreshKey} src={selectedUrl} className="w-full h-full border-0" />
+				) : (
+					<div className="flex items-center justify-center h-full">
+						<p>Select a gateway to preview</p>
+					</div>
+				)}
+			</div>
+		</div>
+	);
+}