Spaces:
Sleeping
Sleeping
File size: 6,809 Bytes
ca6c0e5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | import { useState, useRef, useEffect } from "react";
import { Send, Sparkles } from "lucide-react";
import api from "@/lib/api";
const SUGGESTIONS = [
"Why is the hallucination risk highest at step 1?",
"Did internal state at step 1 causally affect step 3?",
"Which features drift the most across the trajectory?",
"Summarize the cross-step patch matrix findings.",
];
export default function CircuitQueryBox({ runId, trajectoryReady }) {
const [msgs, setMsgs] = useState([]);
const [val, setVal] = useState("");
const [busy, setBusy] = useState(false);
const endRef = useRef(null);
useEffect(() => {
if (!runId) return;
api.listQueries(runId)
.then((d) => {
const ordered = (d.queries || []).slice().reverse();
setMsgs(
ordered.flatMap((q) => [
{ role: "user", text: q.query },
{ role: "assistant", text: q.answer },
]),
);
})
.catch(() => {});
}, [runId]);
useEffect(() => {
endRef.current?.scrollIntoView({ behavior: "smooth" });
}, [msgs.length]);
async function send(text) {
const t = (text ?? val).trim();
if (!t || !trajectoryReady) return;
setMsgs((m) => [...m, { role: "user", text: t }]);
setVal("");
setBusy(true);
try {
const res = await api.query(runId, { query: t });
setMsgs((m) => [...m, { role: "assistant", text: res.answer }]);
} catch (e) {
setMsgs((m) => [
...m,
{ role: "assistant", text: `[error: ${e.message}]` },
]);
} finally {
setBusy(false);
}
}
return (
<div
className="ns-card flex h-[400px] flex-col"
data-testid="nl-circuit-query-box"
>
<header
className="flex items-center gap-2 px-4 pt-3.5 pb-2"
style={{ borderBottom: "1px solid var(--ns-border-subtle)" }}
>
<Sparkles size={14} color="var(--ns-accent)" />
<span className="ns-section-title">NL circuit query</span>
<span className="font-mono text-[10px] text-[color:var(--ns-fg-muted)]">
grounded in run artifacts • claude-sonnet-4-5
</span>
</header>
<div className="flex-1 overflow-y-auto px-4 py-3 text-[12px] leading-6">
{msgs.length === 0 ? (
<div className="space-y-2">
<div className="text-[11px] text-[color:var(--ns-fg-muted)]">
Ask any question about the trajectory. Suggestions:
</div>
{SUGGESTIONS.map((s, i) => (
<button
key={i}
type="button"
onClick={() => send(s)}
className="block w-full rounded-md border px-2.5 py-1.5 text-left font-mono text-[11px] text-[color:var(--ns-fg-secondary)] hover:bg-[color:var(--ns-bg-surface-2)]"
style={{ borderColor: "var(--ns-border-subtle)" }}
data-testid={`nl-query-suggestion-${i}`}
>
{s}
</button>
))}
</div>
) : (
msgs.map((m, i) => (
<div
key={i}
className="mb-3"
data-testid="nl-circuit-query-message"
>
<div
className="mb-0.5 font-mono text-[10px] uppercase tracking-wider"
style={{
color:
m.role === "user"
? "var(--ns-accent)"
: "var(--ns-mint)",
}}
>
{m.role === "user" ? "you" : "neuroscope"}
</div>
<div
className="whitespace-pre-wrap text-[color:var(--ns-fg-primary)]"
style={{ fontSize: 12 }}
>
{m.text}
</div>
</div>
))
)}
{busy && (
<div className="font-mono text-[10px] text-[color:var(--ns-fg-muted)]">
thinking… (model + sae + patch context being summarized for the LLM)
</div>
)}
<div ref={endRef} />
</div>
<div
className="flex items-center gap-2 px-3 py-2"
style={{ borderTop: "1px solid var(--ns-border-subtle)" }}
>
<input
value={val}
onChange={(e) => setVal(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && send()}
placeholder={
trajectoryReady
? "e.g. why is risk highest at step 3?"
: "trajectory still running…"
}
disabled={!trajectoryReady || busy}
className="flex-1 rounded-md border bg-transparent px-2 py-1.5 font-mono text-[12px] text-[color:var(--ns-fg-primary)] placeholder:text-[color:var(--ns-fg-faint)] focus:outline-none"
style={{ borderColor: "var(--ns-border-subtle)" }}
data-testid="nl-circuit-query-input"
/>
<button
type="button"
onClick={() => send()}
disabled={!trajectoryReady || busy || !val.trim()}
className="flex h-7 items-center gap-1 rounded-md px-2.5 text-[11px]"
style={{
background: trajectoryReady && val.trim() ? "var(--ns-accent)" : "var(--ns-bg-surface-2)",
color: trajectoryReady && val.trim() ? "var(--ns-bg-canvas)" : "var(--ns-fg-muted)",
}}
data-testid="nl-circuit-query-send-button"
>
<Send size={11} /> send
</button>
</div>
</div>
);
}
|