"use client"; import { useEffect, useState } from "react"; import Link from "next/link"; import { Play, Square } from "lucide-react"; import { api, useApi } from "@/lib/api"; import { ago, truncate } from "@/lib/format"; import { Empty, Panel, Skeleton, StatusPill, Table, Tag } from "@/components/ui"; interface Job { job_id: string; session_id: string | null; platform: string; mode: string; goal: string; budget: { minutes: number; actions: number; media: number; query?: string | null; headless: boolean; account: string }; status: string; created_at: string; finished_at: string | null; result: { steps?: number; entities?: number; videos?: number; ended_because?: string } | null; actions: number; entities: number; } const PRESETS = [ { label: "Topic · AI Québec on YouTube", platform: "youtube", mode: "topic", query: "intelligence artificielle Québec", goal: "Discover public Quebec creators and channels discussing artificial intelligence", minutes: 10, actions: 60, media: 2 }, { label: "Learn · YouTube runtime", platform: "youtube", mode: "learn", query: "", goal: "", minutes: 15, actions: 80, media: 1 }, { label: "Observe · YouTube home feed", platform: "youtube", mode: "observe", query: "", goal: "Observe the recommendation feed without navigating", minutes: 5, actions: 30, media: 0 }, { label: "Research · Reddit r/Quebec AI", platform: "reddit", mode: "research", query: "intelligence artificielle Québec", goal: "Discover public Reddit posts and users discussing AI in Quebec", minutes: 10, actions: 60, media: 1 }, { label: "Facebook · public page prototype (§52)", platform: "facebook", mode: "research", query: "", goal: "Observe one public Facebook page: its posts, videos, comments and the public pages/profiles they link to", minutes: 8, actions: 40, media: 1 }, { label: "Observe · Facebook feed", platform: "facebook", mode: "observe", query: "", goal: "Observe the Facebook home feed and its recommendation exposure without navigating", minutes: 5, actions: 30, media: 0 }, ]; export default function Missions() { const jobs = useApi("/jobs", { refreshMs: 5000 }); const [form, setForm] = useState({ platform: "youtube", mode: "research", query: "intelligence artificielle Québec", goal: "Discover public Quebec creators discussing AI", minutes: 10, actions: 60, media: 1, headless: true, account: "research-01" }); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState(null); const [logJob, setLogJob] = useState(null); const input = "w-full rounded-lg bg-bg-2 border border-line-2 px-3 py-2 text-sm outline-none focus:border-cyan/60"; async function launch() { setBusy(true); setMsg(null); try { const r = await api<{ job_id: string }>("/jobs", { method: "POST", body: JSON.stringify(form) }); setMsg(`mission ${r.job_id} launched`); setLogJob(r.job_id); jobs.refresh(); } catch (e) { setMsg((e as Error).message); } finally { setBusy(false); } } async function stop(id: string) { await api(`/jobs/${id}/stop`, { method: "POST", body: "{}" }); jobs.refresh(); } return (

Missions

A mission opens one browser on the cluster node, gives the agent a goal and a budget, and lets it explore. Read-only: the crawler never likes, follows, comments or subscribes.

{PRESETS.map((p) => ( ))}