mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
593 lines
23 KiB
TypeScript
593 lines
23 KiB
TypeScript
import path from "path";
|
|
import { readFileSync } from "fs";
|
|
import { aiSetupPrompt, cliSetupPrompt, convexSetupPrompt, getSdkSetupPrompt, pythonBackendSetupPrompt, restApiBackendSetupPrompt, supabaseSetupPrompt } from "../packages/stack-shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt";
|
|
import { deindent } from "../packages/stack-shared/src/utils/strings";
|
|
import { writeFileSyncIfChanged } from "./utils";
|
|
import { remindersPrompt } from "../packages/stack-shared/src/ai/unified-prompts/reminders";
|
|
import { buildLlmsFullTxt } from "../packages/stack-shared/src/ai/llms/llms";
|
|
|
|
const generatedComment = "This file is auto-generated by scripts/generate-setup-prompt-docs.ts. Do not edit it manually; edit packages/stack-shared/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts instead.";
|
|
type SdkSetupToolCategory = "frontend" | "backend" | "database" | "other";
|
|
type SdkSetupTool = {
|
|
label: string,
|
|
where: SdkSetupToolCategory[],
|
|
filterGroups: ("js" | "python" | "other")[],
|
|
imageUrl: string,
|
|
monochromeLogo: boolean,
|
|
tabs: { label: string, mdContent: string }[],
|
|
extraFeatures: string[],
|
|
};
|
|
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
const docsJson = JSON.parse(readFileSync(path.join(repoRoot, "docs-mintlify/docs.json"), "utf-8"));
|
|
const setupPromptText = aiSetupPrompt;
|
|
|
|
const sdkSetupTools: Record<string, SdkSetupTool> = {
|
|
nextjs: {
|
|
label: "Next.js",
|
|
where: ["frontend", "backend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/nextjs.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "Next.js",
|
|
mdContent: getSdkSetupPrompt("nextjs"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
react: {
|
|
label: "React",
|
|
where: ["frontend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/react.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "React",
|
|
mdContent: getSdkSetupPrompt("react"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
js: {
|
|
label: "Other JS/TS",
|
|
where: ["frontend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/javascript.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "JS/TS",
|
|
mdContent: getSdkSetupPrompt("js"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
"tanstack-start": {
|
|
label: "Tanstack Start",
|
|
where: ["frontend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/tanstack.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "Tanstack Start",
|
|
mdContent: getSdkSetupPrompt("tanstack-start"),
|
|
}],
|
|
extraFeatures: ["tanstack-query"],
|
|
},
|
|
"tanstack-query": {
|
|
label: "Tanstack Query",
|
|
where: ["frontend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/tanstack.svg",
|
|
monochromeLogo: true,
|
|
tabs: [],
|
|
extraFeatures: ["tanstack-query"],
|
|
},
|
|
nodejs: {
|
|
label: "Node.js",
|
|
where: ["backend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/nodejs.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Node.js",
|
|
mdContent: getSdkSetupPrompt("nodejs"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
bun: {
|
|
label: "Bun",
|
|
where: ["backend"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/bun.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Bun",
|
|
mdContent: getSdkSetupPrompt("bun"),
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
python: {
|
|
label: "Python",
|
|
where: ["backend"],
|
|
filterGroups: ["python"],
|
|
imageUrl: "/images/setup-tools/python.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Python",
|
|
mdContent: pythonBackendSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
"rest-api": {
|
|
label: "Other (REST API)",
|
|
where: ["backend"],
|
|
filterGroups: ["other"],
|
|
imageUrl: "/images/setup-tools/cli.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "Other (REST API)",
|
|
mdContent: restApiBackendSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
convex: {
|
|
label: "Convex",
|
|
where: ["backend", "database"],
|
|
filterGroups: ["js"],
|
|
imageUrl: "/images/setup-tools/convex.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Convex",
|
|
mdContent: convexSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
supabase: {
|
|
label: "Supabase",
|
|
where: ["database"],
|
|
filterGroups: ["other"],
|
|
imageUrl: "/images/setup-tools/supabase.svg",
|
|
monochromeLogo: false,
|
|
tabs: [{
|
|
label: "Supabase",
|
|
mdContent: supabaseSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
cli: {
|
|
label: "CLI",
|
|
where: ["other"],
|
|
filterGroups: ["other"],
|
|
imageUrl: "/images/setup-tools/cli.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "CLI",
|
|
mdContent: cliSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},
|
|
/*mcp: {
|
|
label: "MCP",
|
|
where: ["other"],
|
|
filterGroups: ["other"],
|
|
imageUrl: "/images/setup-tools/mcp.svg",
|
|
monochromeLogo: true,
|
|
tabs: [{
|
|
label: "MCP",
|
|
mdContent: mcpSetupPrompt,
|
|
}],
|
|
extraFeatures: [],
|
|
},*/
|
|
};
|
|
|
|
function slugify(value: string) {
|
|
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
}
|
|
|
|
const categoryLabels = new Map<SdkSetupToolCategory, string>([
|
|
["frontend", "Frontend"],
|
|
["backend", "Backend"],
|
|
["database", "Database"],
|
|
["other", "Other"],
|
|
]);
|
|
|
|
const setupToolIds = Object.keys(sdkSetupTools);
|
|
const setupTabs = Object.entries(sdkSetupTools).flatMap(([toolId, tool]) => {
|
|
return tool.tabs.map((tab, tabIndex) => ({
|
|
id: `${toolId}-${slugify(tab.label)}-${tabIndex}`,
|
|
toolLabel: tool.label,
|
|
toolId,
|
|
tabLabel: tab.label,
|
|
mdContent: tab.mdContent,
|
|
}));
|
|
});
|
|
const setupTabMetadata = setupTabs.map((tab) => ({
|
|
toolId: tab.toolId,
|
|
title: tab.tabLabel,
|
|
}));
|
|
const unifiedAiPromptTabTitle = "Unified AI Prompt";
|
|
|
|
function renderToolCards(category: SdkSetupToolCategory) {
|
|
const tools = Object.entries(sdkSetupTools).filter(([, tool]) => tool.where.includes(category));
|
|
return tools.map(([toolId, tool]) => {
|
|
const hasTabs = tool.tabs.length > 0;
|
|
const iconMarkup = tool.monochromeLogo
|
|
? deindent`
|
|
<span
|
|
aria-hidden="true"
|
|
className="h-8 w-8 bg-black opacity-80 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-90 dark:bg-white dark:opacity-95"
|
|
style={{
|
|
WebkitMask: "url(${tool.imageUrl}) center / contain no-repeat",
|
|
mask: "url(${tool.imageUrl}) center / contain no-repeat",
|
|
}}
|
|
/>
|
|
`
|
|
: deindent`
|
|
<img
|
|
src="${tool.imageUrl}"
|
|
alt=""
|
|
aria-hidden="true"
|
|
className="h-8 w-8 object-contain opacity-90 transition-opacity duration-150 group-hover:transition-none group-hover:opacity-100"
|
|
/>
|
|
`;
|
|
return deindent`
|
|
<button
|
|
type="button"
|
|
aria-pressed="false"
|
|
data-setup-tool-card="true"
|
|
data-tool-id="${toolId}"
|
|
data-tool-label="${tool.label}"
|
|
data-tool-has-tabs="${hasTabs ? "true" : "false"}"
|
|
data-tool-extra-features="${tool.extraFeatures.join(",")}"
|
|
data-tool-filter-groups="${tool.filterGroups.join(",")}"
|
|
onClick={onSetupToolClick}
|
|
className="group flex flex-col items-center gap-1 rounded-xl px-1 py-1 text-center transition-colors duration-150 hover:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/60 aria-pressed:ring-2 aria-pressed:ring-[#6b5df7] dark:aria-pressed:bg-white/10"
|
|
title="${tool.label}"
|
|
>
|
|
<div className="relative flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl border border-[#b8cff7] bg-gradient-to-b from-[#f2f7ff] via-[#ebf2ff] to-[#e4edff] shadow-[inset_0_1px_0_rgba(255,255,255,0.95),0_7px_18px_rgba(43,76,140,0.2)] transition-[border-color,box-shadow,transform] duration-150 group-hover:transition-none group-hover:border-[#78a8f0] group-hover:shadow-[inset_0_1px_0_rgba(255,255,255,1),0_0_20px_rgba(82,138,234,0.38),0_10px_22px_rgba(43,76,140,0.24)] dark:border-[#2c4c7d]/70 dark:from-[#183155] dark:via-[#112542] dark:to-[#0a1830] dark:shadow-[inset_0_1px_0_rgba(160,200,255,0.24),0_8px_24px_rgba(2,8,20,0.62)] dark:group-hover:border-[#4f84d7] dark:group-hover:shadow-[inset_0_1px_0_rgba(188,218,255,0.42),0_0_26px_rgba(77,138,239,0.5),0_12px_30px_rgba(2,8,20,0.72)]">
|
|
${iconMarkup}
|
|
<span className="absolute right-2 top-2 hidden h-5 min-w-5 items-center justify-center rounded-full bg-[#6b5df7] px-1 text-[9px] font-bold uppercase tracking-tight text-white group-aria-pressed:flex">On</span>
|
|
</div>
|
|
<span
|
|
className="min-h-8 max-w-20 text-center text-xs font-medium leading-4 text-[#2e446f] transition-colors duration-150 group-hover:transition-none group-hover:text-[#182b50] dark:text-[#d8e7ff] dark:group-hover:text-white"
|
|
title="${tool.label}"
|
|
>
|
|
${tool.label}
|
|
</span>
|
|
</button>
|
|
`;
|
|
}).join("\n");
|
|
}
|
|
|
|
function renderToolCategory(category: SdkSetupToolCategory) {
|
|
return deindent`
|
|
<section data-setup-tool-category="true" className="grid gap-3 sm:grid-cols-[6rem_1fr] sm:items-start">
|
|
<h3 className="pt-1 text-sm font-semibold text-[#2e446f] dark:text-[#d8e7ff]">${categoryLabels.get(category)}</h3>
|
|
<div className="grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-4 sm:gap-x-3 sm:gap-y-3 lg:grid-cols-6">
|
|
${renderToolCards(category)}
|
|
</div>
|
|
</section>
|
|
`;
|
|
}
|
|
|
|
function renderSetupFilterButton(filterId: string, label: string) {
|
|
return deindent`
|
|
<button
|
|
type="button"
|
|
aria-pressed="true"
|
|
data-setup-filter-button="true"
|
|
data-filter-id="${filterId}"
|
|
onClick={onSetupFilterClick}
|
|
className="inline-flex items-center justify-center rounded-full px-2.5 py-1 text-xs font-medium text-[#526994] transition-colors duration-150 hover:transition-none hover:bg-white/45 hover:text-[#2a4272] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 aria-pressed:bg-white/70 aria-pressed:text-[#243d70] aria-pressed:shadow-sm dark:text-[#9eb3d8] dark:hover:bg-white/10 dark:hover:text-white dark:aria-pressed:bg-white/14 dark:aria-pressed:text-white"
|
|
>
|
|
${label}
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
function renderMarkdownInTabPanel(mdContent: string) {
|
|
return mdContent.replace(/<Note>\n([\s\S]*?)\n\s*<\/Note>/g, (_match, noteContent: string) => {
|
|
const blockquoteContent = deindent(noteContent)
|
|
.split("\n")
|
|
.map((line) => line.length === 0 ? ">" : `> ${line}`)
|
|
.join("\n");
|
|
return blockquoteContent;
|
|
});
|
|
}
|
|
|
|
function renderTabPanels() {
|
|
return setupTabs.map((tab) => deindent`
|
|
<Tab title="${tab.tabLabel}">
|
|
<div className="not-prose mb-1 flex justify-end">
|
|
<button
|
|
type="button"
|
|
onClick={copyGeneratedSetupPrompt}
|
|
className="inline-flex items-center justify-center rounded-md border border-[#9fb5e4] bg-[#eaf1ff] px-2 py-1 text-[11px] font-semibold leading-none text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
|
|
>
|
|
Copy prompt
|
|
</button>
|
|
</div>
|
|
|
|
${renderMarkdownInTabPanel(tab.mdContent)}
|
|
</Tab>
|
|
`).join("\n\n");
|
|
}
|
|
|
|
function renderUnifiedAiPromptTab() {
|
|
return deindent`
|
|
<Tab title="${unifiedAiPromptTabTitle}">
|
|
Setting up with AI? Use this single prompt in your coding agent to set up Hexclave for your selected stack.
|
|
|
|
<div className="not-prose relative mt-3">
|
|
<pre className="max-h-40 overflow-auto whitespace-pre-wrap rounded-2xl border border-[#cdd7f4] bg-white/75 px-4 py-3 pr-32 font-mono text-xs leading-6 text-zinc-700 backdrop-blur-sm sm:text-sm dark:border-[#33476d] dark:bg-black/20 dark:text-zinc-200"><code>{generatedSetupPromptText}</code></pre>
|
|
<button
|
|
type="button"
|
|
onClick={copyGeneratedSetupPrompt}
|
|
className="absolute right-2 top-2 inline-flex items-center justify-center rounded-lg border border-[#9fb5e4] bg-[#eaf1ff] px-3 py-1.5 text-xs font-semibold text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51]"
|
|
>
|
|
Copy prompt
|
|
</button>
|
|
</div>
|
|
</Tab>
|
|
`;
|
|
}
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "packages/stack-shared/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts"),
|
|
deindent`
|
|
// This file is generated from docs-mintlify/docs.json.
|
|
|
|
const docsJson = ${JSON.stringify(docsJson, null, 2)} as const;
|
|
|
|
export default docsJson;
|
|
` + "\n",
|
|
);
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "docs-mintlify/snippets/home-prompt-island.jsx"),
|
|
deindent`
|
|
// ${generatedComment}
|
|
|
|
export const generatedSetupPromptText = ${JSON.stringify(setupPromptText)};
|
|
export const setupToolIds = ${JSON.stringify(setupToolIds)};
|
|
export const setupTabMetadata = ${JSON.stringify(setupTabMetadata)};
|
|
export const unifiedAiPromptTabTitle = ${JSON.stringify(unifiedAiPromptTabTitle)};
|
|
|
|
export const GeneratedSetupPromptText = ({ className }) => (
|
|
<textarea
|
|
readOnly
|
|
aria-label="Generated setup prompt"
|
|
value={generatedSetupPromptText}
|
|
className={className}
|
|
/>
|
|
);
|
|
` + "\n",
|
|
);
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "docs-mintlify/guides/getting-started/setup.mdx"),
|
|
deindent`
|
|
---
|
|
title: Setup
|
|
description: Install and configure Hexclave for your project
|
|
sidebarTitle: Setup
|
|
---
|
|
|
|
{/* ${generatedComment} */}
|
|
|
|
export const generatedSetupPromptText = ${JSON.stringify(setupPromptText)};
|
|
export const setupToolIds = ${JSON.stringify(setupToolIds)};
|
|
export const setupTabMetadata = ${JSON.stringify(setupTabMetadata)};
|
|
import { hexclaveAgentRemindersText } from "/snippets/hexclave-agent-reminders.jsx";
|
|
export const unifiedAiPromptTabTitle = ${JSON.stringify(unifiedAiPromptTabTitle)};
|
|
|
|
export const copyGeneratedSetupPrompt = async (event) => {
|
|
const button = event.currentTarget;
|
|
try {
|
|
await navigator.clipboard.writeText(generatedSetupPromptText);
|
|
button.textContent = "Copied";
|
|
} catch {
|
|
button.textContent = "Copy failed";
|
|
}
|
|
window.setTimeout(() => {
|
|
button.textContent = "Copy prompt";
|
|
}, 1300);
|
|
};
|
|
|
|
export const getSelectedSetupToolIdsFromUrl = () => {
|
|
if (typeof window === "undefined") {
|
|
return [];
|
|
}
|
|
const selectedToolIds = new Set(setupToolIds);
|
|
return (new URLSearchParams(window.location.search).get("tools") ?? "")
|
|
.split(",")
|
|
.map((toolId) => toolId.trim())
|
|
.filter((toolId) => selectedToolIds.has(toolId));
|
|
};
|
|
|
|
export const writeSelectedSetupToolIdsToUrl = (selectedToolIds) => {
|
|
if (typeof window === "undefined") {
|
|
return;
|
|
}
|
|
const url = new URL(window.location.href);
|
|
const orderedSelectedToolIds = setupToolIds.filter((toolId) => selectedToolIds.has(toolId));
|
|
if (orderedSelectedToolIds.length === 0) {
|
|
url.searchParams.delete("tools");
|
|
} else {
|
|
url.searchParams.set("tools", orderedSelectedToolIds.join(","));
|
|
}
|
|
window.history.replaceState(null, "", url.pathname + url.search + url.hash);
|
|
};
|
|
|
|
export const updateSetupBuilder = (root, syncUrl = true) => {
|
|
const activeFilterIds = new Set(
|
|
Array.from(root.querySelectorAll("[data-setup-filter-button='true'][aria-pressed='true']"))
|
|
.map((button) => button.getAttribute("data-filter-id"))
|
|
.filter((filterId) => filterId != null)
|
|
);
|
|
for (const toolCard of root.querySelectorAll("[data-setup-tool-card='true']")) {
|
|
const filterGroups = (toolCard.getAttribute("data-tool-filter-groups") ?? "")
|
|
.split(",")
|
|
.filter((filterGroup) => filterGroup.length > 0);
|
|
const shouldShow = filterGroups.some((filterGroup) => activeFilterIds.has(filterGroup));
|
|
toolCard.hidden = !shouldShow;
|
|
toolCard.style.display = shouldShow ? "" : "none";
|
|
}
|
|
for (const toolCategory of root.querySelectorAll("[data-setup-tool-category='true']")) {
|
|
const hasVisibleCard = Array.from(toolCategory.querySelectorAll("[data-setup-tool-card='true']"))
|
|
.some((toolCard) => !toolCard.hidden);
|
|
toolCategory.hidden = !hasVisibleCard;
|
|
toolCategory.style.display = hasVisibleCard ? "" : "none";
|
|
}
|
|
const selectedToolIds = new Set(
|
|
Array.from(root.querySelectorAll("[data-setup-tool-card='true'][aria-pressed='true']"))
|
|
.filter((card) => !card.hidden)
|
|
.map((card) => card.getAttribute("data-tool-id"))
|
|
.filter((toolId) => toolId != null)
|
|
);
|
|
if (syncUrl) {
|
|
writeSelectedSetupToolIdsToUrl(selectedToolIds);
|
|
}
|
|
const visibleTabTitles = new Set(setupTabMetadata
|
|
.filter((tab) => selectedToolIds.has(tab.toolId))
|
|
.map((tab) => tab.title)
|
|
);
|
|
if (visibleTabTitles.size > 0) {
|
|
visibleTabTitles.add(unifiedAiPromptTabTitle);
|
|
}
|
|
const tabsRoot = root.querySelector("[data-setup-tabs-root='true']");
|
|
|
|
const emptyState = root.querySelector("[data-setup-tabs-empty='true']");
|
|
if (emptyState != null) {
|
|
emptyState.hidden = visibleTabTitles.size > 0;
|
|
emptyState.style.display = visibleTabTitles.size > 0 ? "none" : "";
|
|
}
|
|
if (tabsRoot == null) {
|
|
return;
|
|
}
|
|
|
|
tabsRoot.hidden = visibleTabTitles.size === 0;
|
|
tabsRoot.style.display = visibleTabTitles.size === 0 ? "none" : "";
|
|
const tabButtons = Array.from(tabsRoot.querySelectorAll("[role='tab']"));
|
|
let firstVisibleTabButton = null;
|
|
let selectedVisibleTabButton = null;
|
|
for (const tabButton of tabButtons) {
|
|
const title = tabButton.textContent?.trim() ?? "";
|
|
const shouldShow = visibleTabTitles.has(title);
|
|
tabButton.hidden = !shouldShow;
|
|
tabButton.style.display = shouldShow ? "" : "none";
|
|
if (shouldShow && firstVisibleTabButton == null) {
|
|
firstVisibleTabButton = tabButton;
|
|
}
|
|
if (shouldShow && tabButton.getAttribute("aria-selected") === "true") {
|
|
selectedVisibleTabButton = tabButton;
|
|
}
|
|
}
|
|
if (visibleTabTitles.size > 0 && selectedVisibleTabButton == null) {
|
|
firstVisibleTabButton?.click();
|
|
}
|
|
};
|
|
|
|
export const initializeSetupBuilder = (node) => {
|
|
if (node == null || node.dataset.setupBuilderInitialized === "true") {
|
|
return;
|
|
}
|
|
node.dataset.setupBuilderInitialized = "true";
|
|
const selectedToolIds = new Set(getSelectedSetupToolIdsFromUrl());
|
|
for (const toolCard of node.querySelectorAll("[data-setup-tool-card='true']")) {
|
|
toolCard.setAttribute("aria-pressed", selectedToolIds.has(toolCard.getAttribute("data-tool-id")) ? "true" : "false");
|
|
}
|
|
updateSetupBuilder(node, false);
|
|
};
|
|
|
|
export const onSetupToolClick = (event) => {
|
|
const button = event.currentTarget;
|
|
const root = button.closest("[data-setup-builder='true']");
|
|
if (root == null) {
|
|
return;
|
|
}
|
|
button.setAttribute("aria-pressed", button.getAttribute("aria-pressed") === "true" ? "false" : "true");
|
|
updateSetupBuilder(root);
|
|
};
|
|
|
|
export const onSetupFilterClick = (event) => {
|
|
const button = event.currentTarget;
|
|
const root = button.closest("[data-setup-builder='true']");
|
|
if (root == null) {
|
|
return;
|
|
}
|
|
const nextPressed = button.getAttribute("aria-pressed") !== "true";
|
|
const currentlyPressedFilters = root.querySelectorAll("[data-setup-filter-button='true'][aria-pressed='true']").length;
|
|
if (!nextPressed && currentlyPressedFilters <= 1) {
|
|
return;
|
|
}
|
|
button.setAttribute("aria-pressed", nextPressed ? "true" : "false");
|
|
updateSetupBuilder(root);
|
|
};
|
|
|
|
<Visibility for="agents">
|
|
{hexclaveAgentRemindersText}
|
|
</Visibility>
|
|
|
|
<Note>
|
|
<p className="font-semibold">Setting up with AI? Use this single prompt:</p>
|
|
<div className="not-prose relative mt-3">
|
|
<pre className="max-h-40 overflow-auto whitespace-pre-wrap rounded-2xl border border-[#cdd7f4] bg-white/75 px-4 py-3 pr-32 font-mono text-xs leading-6 text-zinc-700 backdrop-blur-sm sm:text-sm dark:border-[#33476d] dark:bg-black/20 dark:text-zinc-200"><code>{generatedSetupPromptText}</code></pre>
|
|
<button
|
|
type="button"
|
|
onClick={copyGeneratedSetupPrompt}
|
|
className="absolute right-2 top-2 inline-flex items-center justify-center rounded-lg border border-[#9fb5e4] bg-[#eaf1ff] px-3 py-1.5 text-xs font-semibold text-[#2a4272] transition-colors duration-150 hover:transition-none hover:bg-[#dde8ff] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500 focus-visible:ring-offset-2 focus-visible:ring-offset-[#f3f6ff] dark:border-[#3d5a91] dark:bg-[#12213d] dark:text-[#d5e6ff] dark:hover:bg-[#1a2e51] dark:focus-visible:ring-offset-[#0f1a2e]"
|
|
>
|
|
Copy prompt
|
|
</button>
|
|
</div>
|
|
</Note>
|
|
|
|
<div ref={initializeSetupBuilder} data-setup-builder="true" className="mt-10">
|
|
<div>
|
|
<h2 className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white">Choose your tech stack</h2>
|
|
<p className="mt-2 text-sm font-medium text-slate-500 dark:text-slate-400">Choose all that apply.</p>
|
|
</div>
|
|
|
|
<div className="not-prose mt-5 space-y-4 rounded-2xl border border-[#d6e4ff] bg-gradient-to-b from-[#f7faff] to-[#eaf2ff] p-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.9),0_10px_30px_-24px_rgba(47,79,140,0.35)] dark:border-[#1f2d45] dark:from-[#11203a] dark:to-[#070f1f] dark:shadow-[inset_0_1px_0_rgba(112,152,224,0.18),0_16px_34px_-24px_rgba(2,8,20,0.85)] sm:p-4">
|
|
<div className="flex flex-col gap-2 border-b border-[#d6e4ff]/80 pb-3 dark:border-[#263a5f] sm:flex-row sm:items-center">
|
|
<span className="text-sm font-medium text-[#526994] dark:text-[#9eb3d8]">Filter:</span>
|
|
<div className="flex flex-wrap gap-2">
|
|
${renderSetupFilterButton("js", "JS")}
|
|
${renderSetupFilterButton("python", "Python")}
|
|
${renderSetupFilterButton("other", "Other")}
|
|
</div>
|
|
</div>
|
|
${renderToolCategory("frontend")}
|
|
${renderToolCategory("backend")}
|
|
${renderToolCategory("database")}
|
|
${renderToolCategory("other")}
|
|
</div>
|
|
|
|
<div className="mt-8">
|
|
<p data-setup-tabs-empty="true" className="not-prose rounded-2xl border border-[#c5d7f6] bg-white/65 p-5 text-sm text-[#4a5f89] dark:border-[#2c4c7d] dark:bg-[#0c1627]/45 dark:text-[#8fa4cc]">
|
|
Select a tool to show setup instructions.
|
|
</p>
|
|
<div data-setup-tabs-root="true" hidden style={{ display: "none" }}>
|
|
<Tabs>
|
|
${renderUnifiedAiPromptTab()}
|
|
|
|
${renderTabPanels()}
|
|
</Tabs>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
` + "\n",
|
|
);
|
|
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "docs-mintlify/snippets/hexclave-agent-reminders.jsx"),
|
|
deindent`
|
|
export const hexclaveAgentRemindersText = ${JSON.stringify(remindersPrompt)};
|
|
` + "\n",
|
|
);
|
|
|
|
writeFileSyncIfChanged(
|
|
path.join(repoRoot, "docs-mintlify/llms-full.txt"),
|
|
buildLlmsFullTxt(docsJson).replace(/[ \t]+$/gm, "") + "\n",
|
|
);
|