mirror of
https://github.com/usestrix/strix.git
synced 2026-08-17 01:29:42 +02:00
Steer each agent from its modal; drop the Pro upsell there
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
+131
-131
File diff suppressed because one or more lines are too long
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>Strix Results</title>
|
||||
<script type="module" crossorigin src="./assets/index-Cm9zWfr2.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CHD1Rail.css">
|
||||
<script type="module" crossorigin src="./assets/index-S0W-37yp.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-DIFG4xqP.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
Radio,
|
||||
ArrowUpRight,
|
||||
History,
|
||||
Send,
|
||||
} from "lucide-react";
|
||||
import type { Vulnerability, VulnerabilitySeverity } from "@/types/issues";
|
||||
import { SEVERITY_COLORS } from "@/types/issues";
|
||||
@@ -26,6 +25,7 @@ import { IssueSeveritySummary } from "@/components/IssueSeveritySummary";
|
||||
import AgentGraph from "@/components/live/AgentGraph";
|
||||
import { buildGraphAgents } from "@/components/live/AgentTranscript";
|
||||
import AgentDetailModal from "@/components/live/AgentDetailModal";
|
||||
import { AgentSteerInput } from "@/components/live/AgentSteerInput";
|
||||
import { severityCounts, type ParsedRunSummary } from "@/lib/local-run-parser";
|
||||
import {
|
||||
fetchAll,
|
||||
@@ -36,13 +36,12 @@ import {
|
||||
fetchTranscript,
|
||||
fetchVulnerabilities,
|
||||
forgetAuth,
|
||||
steerAgent,
|
||||
type AuthStatus,
|
||||
type LoadedRun,
|
||||
type RunsPayload,
|
||||
type TranscriptAgent,
|
||||
} from "@/data/serverSource";
|
||||
import { SIGNUP_URL, ctaUrl, track, trackCta } from "@/lib/cta";
|
||||
import { SIGNUP_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import { runTitle } from "@/lib/target-utils";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import PastRunsView from "@/components/PastRunsView";
|
||||
@@ -802,6 +801,7 @@ function AgentsTab({ run, canSteer }: { run: LoadedRun; canSteer: boolean }) {
|
||||
<AgentDetailModal
|
||||
agent={selectedAgent}
|
||||
events={events}
|
||||
steerable={steerable}
|
||||
onClose={() => setSelectedId(null)}
|
||||
/>
|
||||
)}
|
||||
@@ -819,31 +819,10 @@ function SteerComposer({
|
||||
agents: TranscriptAgent[];
|
||||
selectedId: string | null;
|
||||
}) {
|
||||
const [message, setMessage] = useState("");
|
||||
const [sending, setSending] = useState(false);
|
||||
const [feedback, setFeedback] = useState<string | null>(null);
|
||||
|
||||
const rootAgent = agents.find((a) => !a.parent_id) ?? agents[0] ?? null;
|
||||
const target =
|
||||
(selectedId ? (agents.find((a) => a.id === selectedId) ?? null) : null) ?? rootAgent;
|
||||
|
||||
const send = useCallback(async () => {
|
||||
const text = message.trim();
|
||||
if (!text || sending || !target) return;
|
||||
setSending(true);
|
||||
setFeedback(null);
|
||||
const res = await steerAgent(target.id, text);
|
||||
setSending(false);
|
||||
if (res.ok) {
|
||||
setMessage("");
|
||||
setFeedback(`Sent to ${target.name}`);
|
||||
track("agent_steered");
|
||||
} else if (res.error === "not_delivered") {
|
||||
setFeedback("Could not reach that agent (it may have finished).");
|
||||
} else {
|
||||
setFeedback("Could not send that message. Try again.");
|
||||
}
|
||||
}, [message, sending, target]);
|
||||
if (!target) return null;
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
@@ -852,36 +831,10 @@ function SteerComposer({
|
||||
<h2 className="text-sm font-semibold text-white">Steer the agents</h2>
|
||||
</div>
|
||||
<p className="mt-1 mb-3 text-xs text-[#666]">
|
||||
Send a live instruction to{" "}
|
||||
<span className="text-[#aaa]">{target ? target.name : "the agent"}</span>
|
||||
{selectedId ? " (selected)" : " (root)"}. Click an agent in the graph to steer it directly.
|
||||
Send a live instruction to <span className="text-[#aaa]">{target.name}</span>
|
||||
{selectedId ? " (selected)" : " (root)"}. Open an agent to steer it directly.
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
void send();
|
||||
}
|
||||
}}
|
||||
placeholder="e.g. Focus on the authentication flow next"
|
||||
maxLength={4000}
|
||||
disabled={sending || !target}
|
||||
className="flex-1 min-w-0 rounded-lg border border-[#2a2a2a] bg-[rgba(255,255,255,0.03)] px-3 py-2 text-sm text-white placeholder:text-[#555] focus:outline-none focus:border-[#3a3a3a] disabled:opacity-50"
|
||||
/>
|
||||
<button
|
||||
onClick={() => void send()}
|
||||
disabled={sending || !message.trim() || !target}
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-white px-3 py-2 text-sm font-medium text-black transition-opacity hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
<Send className="w-3.5 h-3.5" aria-hidden="true" />
|
||||
{sending ? "Sending" : "Send"}
|
||||
</button>
|
||||
</div>
|
||||
{feedback && <p className="mt-2 text-xs text-[#888]">{feedback}</p>}
|
||||
<AgentSteerInput key={target.id} agentId={target.id} agentName={target.name} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { Radio, X } from "lucide-react";
|
||||
import { AgentTranscript } from "./AgentTranscript";
|
||||
import { SIGNUP_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import { AgentSteerInput } from "./AgentSteerInput";
|
||||
import type { TranscriptAgent, TranscriptEvent } from "@/data/serverSource";
|
||||
|
||||
/** Status -> the small leading dot color, matching the graph node styling. */
|
||||
@@ -27,10 +27,12 @@ const NEAR_BOTTOM_PX = 80;
|
||||
export function AgentDetailModal({
|
||||
agent,
|
||||
events,
|
||||
steerable,
|
||||
onClose,
|
||||
}: {
|
||||
agent: TranscriptAgent;
|
||||
events: TranscriptEvent[];
|
||||
steerable: boolean;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
@@ -98,18 +100,15 @@ export function AgentDetailModal({
|
||||
<AgentTranscript agent={agent} events={events} showHeader={false} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-t border-[#222] px-5 py-3">
|
||||
<span className="text-xs text-[#888]">Steer agents live from your terminal.</span>
|
||||
<a
|
||||
href={ctaUrl(SIGNUP_URL, "steer_agent")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta("steer_agent", "agents")}
|
||||
className="text-xs font-medium text-[#60a5fa] hover:underline"
|
||||
>
|
||||
Upgrade to Pro to steer agents from the web →
|
||||
</a>
|
||||
</div>
|
||||
{steerable && (
|
||||
<div className="border-t border-[#222] px-5 py-3">
|
||||
<div className="mb-2 flex items-center gap-1.5">
|
||||
<Radio className="h-3.5 w-3.5 text-[#888]" aria-hidden="true" />
|
||||
<span className="text-xs font-medium text-[#aaa]">Steer this agent</span>
|
||||
</div>
|
||||
<AgentSteerInput agentId={agent.id} agentName={agent.name} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { Send } from "lucide-react";
|
||||
import { steerAgent } from "@/data/serverSource";
|
||||
import { track } from "@/lib/cta";
|
||||
|
||||
/**
|
||||
* Send a live instruction to one specific running agent. Only rendered when the
|
||||
* viewer runs in-process during a live scan (steering available). Reused by the
|
||||
* Agents graph composer and the per-agent detail modal, so both steer through
|
||||
* the same path.
|
||||
*/
|
||||
export function AgentSteerInput({
|
||||
agentId,
|
||||
agentName,
|
||||
placeholder = "e.g. Focus on the authentication flow next",
|
||||
}: {
|
||||
agentId: string;
|
||||
agentName: string;
|
||||
placeholder?: string;
|
||||
}) {
|
||||
const [message, setMessage] = useState("");
|
||||
const [sending, setSending] = useState(false);
|
||||
const [feedback, setFeedback] = useState<string | null>(null);
|
||||
|
||||
const send = useCallback(async () => {
|
||||
const text = message.trim();
|
||||
if (!text || sending) return;
|
||||
setSending(true);
|
||||
setFeedback(null);
|
||||
const res = await steerAgent(agentId, text);
|
||||
setSending(false);
|
||||
if (res.ok) {
|
||||
setMessage("");
|
||||
setFeedback(`Sent to ${agentName}`);
|
||||
track("agent_steered");
|
||||
} else if (res.error === "not_delivered") {
|
||||
setFeedback("Could not reach that agent (it may have finished).");
|
||||
} else {
|
||||
setFeedback("Could not send that message. Try again.");
|
||||
}
|
||||
}, [message, sending, agentId, agentName]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
void send();
|
||||
}
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
maxLength={4000}
|
||||
disabled={sending}
|
||||
className="flex-1 min-w-0 rounded-lg border border-[#2a2a2a] bg-[rgba(255,255,255,0.03)] px-3 py-2 text-sm text-white placeholder:text-[#555] focus:outline-none focus:border-[#3a3a3a] disabled:opacity-50"
|
||||
/>
|
||||
<button
|
||||
onClick={() => void send()}
|
||||
disabled={sending || !message.trim()}
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-white px-3 py-2 text-sm font-medium text-black transition-opacity hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
||||
>
|
||||
<Send className="w-3.5 h-3.5" aria-hidden="true" />
|
||||
{sending ? "Sending" : "Send"}
|
||||
</button>
|
||||
</div>
|
||||
{feedback && <p className="mt-2 text-xs text-[#888]">{feedback}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgentSteerInput;
|
||||
Reference in New Issue
Block a user