2ee2aaaffb
Add deepseek to resume commands Rename app to spaceshell Add SurfacePicker component for preset panel configuration Extract agent selection logic to shared agents.ts Update landing
71 lines
3.6 KiB
TypeScript
71 lines
3.6 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { whichAgents } from "./socketBridge";
|
|
import { KNOWN_AGENTS, SHELL, CUSTOM, agentLabel, specForChoice } from "./agents";
|
|
|
|
type SlotSpec = { command?: string; args?: string[] };
|
|
|
|
/**
|
|
* Asks what to open in each new panel before a preset spawns it: Terminal
|
|
* (shell), one of the installed CLIs (claude/codex/gemini/deepseek), or a
|
|
* custom command. `count` is the number of new panels the preset will add.
|
|
*/
|
|
export function SurfacePicker({ count, onConfirm, onCancel }: { count: number; onConfirm: (specs: SlotSpec[]) => void; onCancel: () => void }) {
|
|
const [installed, setInstalled] = useState<string[]>([]);
|
|
const [choices, setChoices] = useState<string[]>([]);
|
|
const [customCmds, setCustomCmds] = useState<string[]>([]);
|
|
const choiceList = [SHELL, ...installed, CUSTOM];
|
|
|
|
useEffect(() => { void whichAgents(KNOWN_AGENTS).then(setInstalled).catch(() => {}); }, []);
|
|
|
|
function confirm() {
|
|
const specs = Array.from({ length: count }, (_, i) => specForChoice(choices[i] ?? SHELL, customCmds[i] ?? ""));
|
|
onConfirm(specs);
|
|
}
|
|
|
|
function onKeyDown(e: React.KeyboardEvent) {
|
|
e.stopPropagation();
|
|
if (e.key === "Escape") { e.preventDefault(); onCancel(); }
|
|
else if (e.key === "Enter" && (e.target as HTMLElement).tagName !== "SELECT") { e.preventDefault(); confirm(); }
|
|
}
|
|
|
|
return (
|
|
<div
|
|
onMouseDown={onCancel}
|
|
style={{ position: "fixed", inset: 0, zIndex: 2000, background: "#000A", display: "flex", alignItems: "center", justifyContent: "center" }}
|
|
>
|
|
<div
|
|
onMouseDown={(e) => e.stopPropagation()}
|
|
onKeyDown={onKeyDown}
|
|
style={{ width: 420, background: "#0E1116", border: "1px solid #323C49", borderRadius: 14, padding: 24, color: "#E6EDF3" }}
|
|
>
|
|
<div style={{ fontWeight: 700, fontSize: 16, marginBottom: 4 }}>{count > 1 ? `Open ${count} new panels` : "Open new panel"}</div>
|
|
<div style={{ fontSize: 12, color: "#8B97A6", marginBottom: 16 }}>Choose what to run in each new panel.</div>
|
|
<div style={{ display: "grid", gridTemplateColumns: count > 1 ? "1fr 1fr" : "1fr", gap: 8, marginBottom: 20 }}>
|
|
{Array.from({ length: count }, (_, i) => {
|
|
const val = choices[i] ?? SHELL;
|
|
return (
|
|
<div key={i} style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
|
<select value={val} onChange={(e) => setChoices((c) => { const n = [...c]; n[i] = e.target.value; return n; })}
|
|
style={{ padding: 8, background: "#1A2029", color: "#E6EDF3", border: "1px solid #323C49", borderRadius: 6 }}>
|
|
{choiceList.map((c) => <option key={c} value={c}>{agentLabel(c)}</option>)}
|
|
</select>
|
|
{val === CUSTOM && (
|
|
<input value={customCmds[i] ?? ""} placeholder="e.g. npm run dev" autoFocus
|
|
onChange={(e) => setCustomCmds((c) => { const n = [...c]; n[i] = e.target.value; return n; })}
|
|
style={{ padding: 8, background: "#0A0D12", color: "#E6EDF3", border: "1px solid #4C8DFF", borderRadius: 6, fontFamily: "monospace", fontSize: 12 }} />
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<div style={{ display: "flex", justifyContent: "flex-end", gap: 10 }}>
|
|
<button onClick={onCancel} style={{ padding: "8px 16px" }}>Cancel</button>
|
|
<button onClick={confirm} style={{ padding: "8px 16px", background: "#4C8DFF", color: "#0A0D12", border: "none", borderRadius: 8, fontWeight: 700 }}>
|
|
Open
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|