Add Submit to Antigravity or Codex for Development modal with model picker and deep links

This commit is contained in:
airent106 committed 2026-10-29 22:30:48 -06:00
1 parent b5af03b628
commit 92c5039bcb
6 files changed
+752 -1

No files matched your search

+285
View File
@@ -416,3 +416,288 @@ export async function generateNameSuggestions({
};
}
export const DEV_HANDOFF_TARGETS = {
antigravity: {
id: "antigravity",
label: "🛸 Google Antigravity",
protocol: "antigravity://",
defaultModel: "gemini-3.1-pro",
models: [
{ id: "gemini-3.1-pro", label: "Gemini 3.1 Pro (Recommended — Full-Stack & Architecture)" },
{ id: "gemini-3.0-pro", label: "Gemini 3.0 Pro (Deep Reasoning & Complex Refactors)" },
{ id: "gemini-3.1-flash", label: "Gemini 3.1 Flash (Fast Iteration & UI Prototyping)" },
{ id: "gemini-3.1-flash-lite", label: "Gemini 3.1 Flash-Lite (Lightweight & Rapid Tasks)" },
{ id: "claude-sonnet-4.5", label: "Claude 4.5 Sonnet (via Antigravity Model Selector)" },
{ id: "claude-opus-4.5", label: "Claude 4.5 Opus (via Antigravity Model Selector)" }
]
},
codex: {
id: "codex",
label: "🤖 OpenAI Codex",
protocol: "codex://threads/new",
defaultModel: "gpt-5.6-sol",
models: [
{ id: "gpt-5.6-sol", label: "GPT-5.6-Sol (Recommended — Active Codex Default)" },
{ id: "gpt-5.3-codex", label: "GPT-5.3-Codex (Specialized Agentic Coding)" },
{ id: "gpt-5.4", label: "GPT-5.4 (Flagship Reasoning & Implementation)" },
{ id: "o3", label: "OpenAI o3 (Deep Architecture & Debugging)" },
{ id: "codex-mini-latest", label: "Codex Mini (Fast Lightweight Execution)" }
]
}
};
export function slugifyIdeaTitle(title, id = "") {
const base = String(title || "spark")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 36);
return base || `spark-${id || "app"}`;
}
export function buildDevelopmentSpecMarkdown({
idea,
targetApp = "antigravity",
model = "",
includeHomelab = true,
extraInstructions = "",
publicBaseUrl = "https://sparkvault.vloser.us",
userBackground = DEFAULT_USER_BACKGROUND
}) {
const targetMeta = DEV_HANDOFF_TARGETS[targetApp] || DEV_HANDOFF_TARGETS.antigravity;
const chosenModel = model || targetMeta.defaultModel;
const slug = slugifyIdeaTitle(idea.title, idea.id);
const lines = [
`# 🚀 Development Handoff Specification: ${idea.title}`,
``,
`- **Target Environment**: ${targetMeta.label}`,
`- **Selected AI Model**: \`${chosenModel}\``,
`- **Spark ID**: #${idea.id} (\`${slug}\`)`,
`- **Category**: \`${idea.category}\` | **Current Stage**: \`${idea.status}\` | **Priority Score**: ${idea.composite_score}/100`,
`- **Ratings**: 🔥 Excitement ${idea.rating_excitement}/5 • 🎯 Impact ${idea.rating_impact}/5 • ⚡ Ease ${idea.rating_ease}/5`,
`- **Tags**: ${(idea.tags || []).map((t) => `#${t}`).join(", ") || "None"}`,
``
];
if (idea.elevator_pitch) {
lines.push(`## 💡 Elevator Pitch`, idea.elevator_pitch, ``);
}
if (idea.notes_markdown) {
lines.push(`## 📝 Detailed Specs, Brainstorm & Notes`, idea.notes_markdown, ``);
}
if (idea.checklist && idea.checklist.length > 0) {
lines.push(
`## ✅ Action Checklist & Requirements`,
...idea.checklist.map((c) => `- [${c.done ? "x" : " "}] ${c.text}`),
``
);
}
if ((idea.pros && idea.pros.length > 0) || (idea.cons && idea.cons.length > 0)) {
lines.push(`## ⚖️ Pros, Value Props & Constraints`);
if (idea.pros?.length) {
lines.push(`### Why It Works (Pros)`, ...idea.pros.map((p) => `- ✅ ${p}`), ``);
}
if (idea.cons?.length) {
lines.push(`### Risks & Constraints (Cons)`, ...idea.cons.map((c) => `- ⚠️ ${c}`), ``);
}
}
if (idea.whack_history && idea.whack_history.length > 0) {
lines.push(
`## 🃏 Creative Direction & Lateral Twists`,
...idea.whack_history.map(
(w) => `- **Card #${w.cardNumber} (${w.cardTitle})**: ${w.reflection}`
),
``
);
}
if (idea.mockups && idea.mockups.length > 0) {
lines.push(
`## 🖼️ Visual Mockups & Design Assets`,
...idea.mockups.map((m) => {
const fullUrl = m.imageUrl?.startsWith("http")
? m.imageUrl
: `${publicBaseUrl}${m.imageUrl}`;
return `- **${m.title}** (\`${m.template || "mockup"}\`, generated via \`${m.modelUsed || "AI"}\`): ${fullUrl}${m.prompt ? `\n - Prompt: *${m.prompt}*` : ""}`;
}),
``
);
}
if (idea.attachments && idea.attachments.length > 0) {
lines.push(
`## 📎 Attached Reference Notes & Images`,
...idea.attachments.map((a) => {
const imgPart = a.imageUrl
? ` — Image: ${a.imageUrl.startsWith("http") ? a.imageUrl : `${publicBaseUrl}${a.imageUrl}`}`
: "";
const srcPart = a.sourceUrl ? ` (Source: ${a.sourceUrl})` : "";
const txtPart = a.textContent ? `\n > ${a.textContent.replace(/\n/g, "\n > ")}` : "";
return `- **[${a.type.toUpperCase()}] ${a.title}**${imgPart}${srcPart}${txtPart}`;
}),
``
);
}
if (idea.links && idea.links.length > 0) {
lines.push(
`## 🔗 Reference Links`,
...idea.links.map((l) => `- [${l.title || l.url}](${l.url})`),
``
);
}
if (userBackground) {
lines.push(`## 👤 Creator Background & Preferences`, userBackground, ``);
}
if (includeHomelab) {
lines.push(
`## 🛠️ Target Architecture & Homelab Deployment Requirements`,
`1. Build a clean, self-contained full-stack web application (or service) for **${idea.title}** (\`${slug}\`).`,
`2. Create/push a Gitea repository at \`https://gitea.vloser.us/chrisf/${slug}\`.`,
`3. Deploy via Docker Compose on \`vloser\` (\`/home/chrisf/docker/${slug}\`) with persistent storage under \`/home/chrisf/docker/appdata/${slug}\`.`,
`4. Expose via Traefik dynamic config at \`https://${slug}.vloser.us\` and add an entry with a custom SVG icon to Homepage (\`/home/chrisf/docker/appdata/homepage/services.yaml\`).`,
`5. Include mobile-responsive UI styling and automated verification tests.`,
``
);
}
if (extraInstructions && extraInstructions.trim()) {
lines.push(`## 🎯 Additional Development Instructions`, extraInstructions.trim(), ``);
}
return lines.join("\n");
}
export async function executeDevelopmentHandoff({
idea,
targetApp = "antigravity",
model = "",
customPrompt = "",
includeHomelab = true,
extraInstructions = "",
executeServerCodex = false,
publicBaseUrl = "https://sparkvault.vloser.us",
userBackground = DEFAULT_USER_BACKGROUND,
workspacesRootDir
}) {
const targetMeta = DEV_HANDOFF_TARGETS[targetApp] || DEV_HANDOFF_TARGETS.antigravity;
const chosenModel = model || targetMeta.defaultModel;
const slug = slugifyIdeaTitle(idea.title, idea.id);
const compiledSpec =
customPrompt && customPrompt.trim()
? customPrompt.trim()
: buildDevelopmentSpecMarkdown({
idea,
targetApp,
model: chosenModel,
includeHomelab,
extraInstructions,
publicBaseUrl,
userBackground
});
let workspaceDir = "";
if (workspacesRootDir) {
workspaceDir = path.join(workspacesRootDir, `${idea.id}-${slug}`);
fs.mkdirSync(workspaceDir, { recursive: true });
fs.writeFileSync(path.join(workspaceDir, "SPEC.md"), compiledSpec, "utf8");
fs.writeFileSync(
path.join(workspaceDir, "spark-context.json"),
JSON.stringify({ idea, targetApp, model: chosenModel, createdAt: new Date().toISOString() }, null, 2),
"utf8"
);
}
const specUrl = `${publicBaseUrl}/api/ideas/${idea.id}/spec.md?target=${encodeURIComponent(targetApp)}&model=${encodeURIComponent(chosenModel)}`;
// Build OS Deep Link URL for the corresponding desktop application
let deepLinkUrl = "";
let cliCommand = "";
if (targetApp === "codex") {
// Keep URL prompt within safe Windows protocol command-line limits (~1600 chars)
const compactPrompt =
compiledSpec.length <= 1500
? compiledSpec
: `${compiledSpec.slice(0, 1350)}\n\n[Full specification copied to clipboard & hosted at: ${specUrl}]`;
const codexUrl = new URL("codex://threads/new");
codexUrl.searchParams.set("mode", "codex");
codexUrl.searchParams.set("prompt", compactPrompt);
deepLinkUrl = codexUrl.toString();
cliCommand = `codex -m ${chosenModel} "Read SPEC.md and implement ${idea.title}"`;
} else {
// Antigravity desktop deep link + CLI command
deepLinkUrl = `antigravity://new?model=${encodeURIComponent(chosenModel)}&sparkId=${idea.id}`;
cliCommand = `agy --model ${chosenModel}`;
}
let codexExecution = null;
if (targetApp === "codex" && executeServerCodex && isCodexConfigured()) {
try {
const reportFile = workspaceDir
? path.join(workspaceDir, "CODEX_PLAN.md")
: path.join(os.tmpdir(), `codex-plan-${idea.id}-${Date.now()}.md`);
const args = [
"exec",
"-m",
chosenModel,
"--skip-git-repo-check",
"-o",
reportFile,
`You are an autonomous senior engineer. Review the following SparkVault project specification and produce a complete implementation blueprint, file structure, and starter code plan:\n\n${compiledSpec}`
];
if (workspaceDir) {
args.splice(4, 0, "-C", workspaceDir);
}
await new Promise((resolve, reject) => {
const child = spawn("codex", args, {
stdio: ["ignore", "pipe", "pipe"],
env: { ...process.env }
});
child.on("error", reject);
child.on("close", () => resolve());
});
if (fs.existsSync(reportFile)) {
const planText = fs.readFileSync(reportFile, "utf8").trim();
if (planText) {
codexExecution = {
ran: true,
model: chosenModel,
output: planText.slice(0, 6000)
};
}
}
} catch (err) {
codexExecution = {
ran: false,
error: err.message
};
}
}
return {
targetApp,
targetLabel: targetMeta.label,
model: chosenModel,
slug,
specMarkdown: compiledSpec,
specUrl,
deepLinkUrl,
cliCommand,
workspaceDir,
codexExecution,
submittedAt: new Date().toISOString()
};
}