Spaces:
Running on Zero
Running on Zero
File size: 5,306 Bytes
5f43c7d | 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 | // Tactical Grey palette — the ONLY palette. Orange is reserved for cost-heat and
// proven indirect dataflow; never decoration. No Elastic blue for structure.
// (Source of truth: docs/UI-SPEC.md "Skin (Tactical Grey)".)
import {
FileText, Terminal, FilePen, Search, Cpu, Globe, Box, MessageSquare,
Rocket, Database, HelpCircle, SlidersHorizontal, GitCommit, Bell, Server,
} from "lucide-react";
export const C = {
bg: "#2b2927", panel: "#363432", card: "#46433f", elevated: "#504d4a",
black: "#1b1a19", header: "#211f1e",
orange: "#F06A17", orangeHi: "#FF7B28",
orangeMut: "rgba(240,106,23,0.13)", orangeBd: "rgba(240,106,23,0.42)",
text: "#f4f1ee", text2: "#b3afaa", muted: "#7e7a76",
border: "#565350", borderSoft: "#403e3c",
cyan: "#2dd4bf", amber: "#fbbf24", red: "#f87171", blue: "#7aa2d6",
};
export const FD = "'Chakra Petch',sans-serif";
export const FB = "'IBM Plex Sans',sans-serif";
export const FM = "'JetBrains Mono',monospace";
// Severity colour logic (legend): orange = heaviest cost · amber = has a fixable
// tip · cyan = clean/efficient. This is the entity classification for Mode A.
export const SEV = {
heavy: { color: C.orange, label: "Heavy turn" },
tip: { color: C.amber, label: "Has-tip" },
clean: { color: C.cyan, label: "Clean" },
};
export function turnSeverity(t) {
if (t.heavy) return "heavy";
if (t.guide) return "tip";
return "clean";
}
export const fmt = (n) =>
n >= 1e6 ? (n / 1e6).toFixed(1) + "M" : n >= 1e3 ? Math.round(n / 1e3) + "k" : "" + n;
// Real session timestamp (Shripal: tell sessions apart). Accepts an ISO string or
// epoch-ms/sec number; returns e.g. "Jun 04, 21:30" (local), or "" when absent.
export function fmtWhen(v) {
if (!v && v !== 0) return "";
let d;
if (typeof v === "number") d = new Date(v < 1e12 ? v * 1000 : v);
else d = new Date(v);
if (isNaN(d.getTime())) return "";
const M = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const pad = (n) => String(n).padStart(2, "0");
return `${M[d.getMonth()]} ${pad(d.getDate())}, ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
// Map a tool name to an icon + a coarse "type" bucket used for the legend tallies.
// MCP names are prefixed mcp__<server>__<tool>; treat all as the MCP bucket.
export function toolBucket(name) {
if (/^mcp__/i.test(name) || /hugging|notion|gmail|drive|calendar/i.test(name)) return "MCP";
if (name === "Read") return "Read";
if (name === "Bash") return "Bash";
if (["Edit", "Write", "MultiEdit", "NotebookEdit"].includes(name)) return "Edit";
if (name === "Task" || /^Task/.test(name)) return "Task";
return "Other";
}
// Legend tool-type eye -> bucket. Returns whether a tool of this name is
// currently shown given the legend `vis` map. "Other" has no toggle (always on).
const _BUCKET_KEY = { Read: "tRead", Bash: "tBash", Edit: "tEdit", MCP: "tMCP", Task: "tTask" };
export function toolTypeVisible(vis, name) {
const key = _BUCKET_KEY[toolBucket(name)];
return key ? vis[key] !== false : true;
}
// A journey node is "tool-dimmed" when it uses TOGGLEABLE tool types but none of
// them are currently visible — so e.g. leaving only MCP on dims every non-MCP
// turn. "Other"-bucket tools (ToolSearch, AskUserQuestion, …) have no eye, so a
// turn made only of those is never dimmed (toggles don't apply to it).
export function turnToolDimmed(vis, turn) {
let toggleable = 0;
let anyVisible = false;
for (const tl of turn.tools || []) {
const key = _BUCKET_KEY[toolBucket(tl.name)];
if (!key) continue; // Other — unaffected by tool-type toggles
toggleable += 1;
if (vis[key] !== false) anyVisible = true;
}
return toggleable > 0 && !anyVisible;
}
export const toolIcon = (name) =>
/^mcp__/i.test(name) || /hugging|notion|gmail|drive|calendar/i.test(name)
? Globe
: name === "Read"
? FileText
: name === "Bash"
? Terminal
: ["Edit", "Write", "MultiEdit", "NotebookEdit"].includes(name)
? FilePen
: ["Grep", "Glob"].includes(name)
? Search
: name === "Task" || /^Task/.test(name)
? Cpu
: Box;
// Deterministic intent guess from the prompt text — used ONLY for the node glyph.
// This is cosmetic (icon choice), not a finding; it never asserts causation.
export function intentOf(turn) {
if (turn.origin === "system") return "task";
const p = (turn.prompt || "").toLowerCase();
if (/\bdeploy|railway|ship|production|build\b/.test(p)) return "deploy";
if (/\bpsql|postgres|surreal|migrat|database|\bdb\b|role|schema\b/.test(p)) return "db";
if (/\bmodel|embed|llama|onnx|nomic|opus|temperature\b/.test(p)) return "model";
if (/\bcommit|git \b/.test(p)) return "commit";
if (/\bconfig|env|variable|disable|enable\b/.test(p)) return "config";
if (/\?$|why |can'?t |how |what /.test(p)) return "ask";
return "msg";
}
export const intentIcon = (k) =>
({
deploy: Rocket, db: Database, model: Cpu, ask: HelpCircle,
config: SlidersHorizontal, commit: GitCommit, task: Bell,
server: Server, msg: MessageSquare,
}[k] || MessageSquare);
// First sentence / clipped prompt for compact rows.
export function shortPrompt(turn, n = 68) {
const p = (turn.prompt || "").replace(/\s+/g, " ").trim();
return p.length > n ? p.slice(0, n) : p;
}
|