mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-30 21:01:54 +08:00
fix: update AI model selection matrix and custom dashboard generation
- Refresh model selection matrix (models.ts): swap deprecated/placeholder model IDs (grok-build-0.1, deepseek-v4-flash) for gpt-5.5, gemini-3.5-flash, glm-5.2, and nemotron-3-super for unauthenticated tiers - Forward x-stack-*/x-hexclave-* auth headers through the template-source rewrite path so the inner AI call resolves the authenticated model tier - Lower email template rewrite quality to 'dumb'/'slow' - Speed up dashboard generation: smart/fast instead of smart/slow - Pin @babel/standalone to 7.29.7 in the dashboard sandbox - Disable analytics in generated dashboards - Bump MCP RPC timeout 15s -> 45s
This commit is contained in:
parent
c50f1e64ed
commit
e6c335a913
@ -26,8 +26,21 @@ export const POST = createSmartRouteHandler({
|
||||
tsx_source: yupString().defined(),
|
||||
}).defined(),
|
||||
}),
|
||||
handler: async ({ body }) => {
|
||||
const rewriteResult = await rewriteTemplateSourceWithAI(body.template_tsx_source);
|
||||
handler: async ({ body }, fullReq) => {
|
||||
// Forward the caller's Hexclave/Stack auth headers so the inner AI call
|
||||
// (which is a fresh HTTP request to /ai/query/generate) is authenticated
|
||||
// and resolves to the authenticated model tier rather than falling back
|
||||
// to the unauthenticated one.
|
||||
const authHeaders: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(fullReq.headers)) {
|
||||
if (value == null) continue;
|
||||
const lower = key.toLowerCase();
|
||||
if (lower.startsWith("x-stack-") || lower.startsWith("x-hexclave-")) {
|
||||
authHeaders[key] = value.join(",");
|
||||
}
|
||||
}
|
||||
|
||||
const rewriteResult = await rewriteTemplateSourceWithAI(body.template_tsx_source, authHeaders);
|
||||
if (rewriteResult.status === "error") {
|
||||
throw new KnownErrors.TemplateSourceRewriteError(rewriteResult.error);
|
||||
}
|
||||
|
||||
@ -19,31 +19,31 @@ const MODEL_SELECTION_MATRIX: Record<
|
||||
dumb: {
|
||||
slow: {
|
||||
authenticated: { modelId: "z-ai/glm-4.5-air:free" },
|
||||
unauthenticated: { modelId: "z-ai/glm-4.5-air:free" },
|
||||
unauthenticated: { modelId: "nvidia/nemotron-3-super-120b-a12b" },
|
||||
},
|
||||
fast: {
|
||||
authenticated: { modelId: "openai/gpt-oss-120b:nitro" },
|
||||
unauthenticated: { modelId: "openai/gpt-oss-120b:nitro" },
|
||||
unauthenticated: { modelId: "nvidia/nemotron-3-super-120b-a12b:nitro" },
|
||||
},
|
||||
},
|
||||
smart: {
|
||||
slow: {
|
||||
authenticated: { modelId: "x-ai/grok-build-0.1" },
|
||||
unauthenticated: { modelId: "deepseek/deepseek-v4-flash" },
|
||||
authenticated: { modelId: "openai/gpt-5.5" },
|
||||
unauthenticated: { modelId: "z-ai/glm-5.2" },
|
||||
},
|
||||
fast: {
|
||||
authenticated: { modelId: "x-ai/grok-build-0.1" },
|
||||
unauthenticated: { modelId: "nvidia/nemotron-3-super-120b-a12b:nitro" },
|
||||
authenticated: { modelId: "google/gemini-3.5-flash" },
|
||||
unauthenticated: { modelId: "google/gemini-3.5-flash" },
|
||||
},
|
||||
},
|
||||
smartest: {
|
||||
slow: {
|
||||
authenticated: { modelId: "openai/gpt-5.5" },
|
||||
unauthenticated: { modelId: "deepseek/deepseek-v4-flash" },
|
||||
unauthenticated: { modelId: "z-ai/glm-5.2" },
|
||||
},
|
||||
fast: {
|
||||
authenticated: { modelId: "openai/gpt-5.5" },
|
||||
unauthenticated: { modelId: "deepseek/deepseek-v4-flash:nitro" },
|
||||
unauthenticated: { modelId: "google/gemini-3.5-flash" },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -13,16 +13,16 @@ function isMockMode() {
|
||||
return key === MOCK_API_KEY_SENTINEL || key === "FORWARD_TO_PRODUCTION";
|
||||
}
|
||||
|
||||
async function rewriteTemplateSourceWithCurrentAIPlumbing(templateTsxSource: string): Promise<Result<string, string>> {
|
||||
async function rewriteTemplateSourceWithCurrentAIPlumbing(templateTsxSource: string, authHeaders: Record<string, string>): Promise<Result<string, string>> {
|
||||
const backendUrl = getEnvVariable("NEXT_PUBLIC_STACK_API_URL");
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), AI_REQUEST_TIMEOUT_MS);
|
||||
try {
|
||||
const response = await fetch(`${backendUrl}/api/latest/ai/query/generate`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
headers: { "content-type": "application/json", ...authHeaders },
|
||||
body: JSON.stringify({
|
||||
quality: "smart",
|
||||
quality: "dumb",
|
||||
speed: "slow",
|
||||
tools: [],
|
||||
systemPrompt: "rewrite-template-source",
|
||||
@ -115,7 +115,7 @@ function stripCodeFences(text: string): string {
|
||||
return output;
|
||||
}
|
||||
|
||||
export async function rewriteTemplateSourceWithAI(templateTsxSource: string): Promise<Result<string, string>> {
|
||||
export async function rewriteTemplateSourceWithAI(templateTsxSource: string, authHeaders: Record<string, string>): Promise<Result<string, string>> {
|
||||
if (isMockMode()) {
|
||||
const mockRewrittenSource = rewriteTemplateSourceInMockMode(templateTsxSource);
|
||||
const mockRenderResult = await renderEmailWithTemplate(mockRewrittenSource, emptyEmailTheme, {
|
||||
@ -130,7 +130,7 @@ export async function rewriteTemplateSourceWithAI(templateTsxSource: string): Pr
|
||||
|
||||
let lastError = "Unknown rewrite failure";
|
||||
for (let attempt = 0; attempt < MAX_REWRITE_ATTEMPTS; attempt++) {
|
||||
const rewriteResult = await rewriteTemplateSourceWithCurrentAIPlumbing(templateTsxSource);
|
||||
const rewriteResult = await rewriteTemplateSourceWithCurrentAIPlumbing(templateTsxSource, authHeaders);
|
||||
if (rewriteResult.status === "error") {
|
||||
lastError = rewriteResult.error;
|
||||
continue;
|
||||
|
||||
@ -124,7 +124,7 @@ const CreateDashboardPreviewInner = memo(function CreateDashboardPreviewInner({
|
||||
systemPrompt: "create-dashboard",
|
||||
tools: ["update-dashboard"],
|
||||
quality: "smart",
|
||||
speed: "slow",
|
||||
speed: "fast",
|
||||
projectId: projectIdRef.current,
|
||||
transformMessages: async (userMessages) => {
|
||||
const contextMessages = await buildDashboardMessages(
|
||||
|
||||
@ -333,12 +333,7 @@ function getSandboxDocument(artifact: DashboardArtifact, baseUrl: string, dashbo
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<!-- Babel (for JSX transpilation). crossorigin=anonymous is required so that
|
||||
errors thrown from inside Babel (e.g. JSX SyntaxErrors from AI-generated
|
||||
code) are not sanitized to "Script error." with no message — unpkg sends
|
||||
the matching Access-Control-Allow-Origin header. -->
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.min.js" crossorigin="anonymous"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone@7.29.7/babel.min.js" crossorigin="anonymous"></script>
|
||||
|
||||
<!-- Install a global error listener BEFORE any AI code runs so that Babel parse
|
||||
errors, uncaught runtime throws, and async rejections all reach the parent.
|
||||
@ -464,6 +459,7 @@ function getSandboxDocument(artifact: DashboardArtifact, baseUrl: string, dashbo
|
||||
projectOwnerSession: async () => {
|
||||
return await requestAccessToken();
|
||||
},
|
||||
analytics: { enabled: false },
|
||||
});
|
||||
|
||||
// Expose under both names. AI-generated dashboards (post-PR2 prompt)
|
||||
|
||||
@ -151,7 +151,7 @@ export function createDashboardChatAdapter(
|
||||
systemPrompt: "create-dashboard",
|
||||
tools,
|
||||
quality: "smart",
|
||||
speed: "slow",
|
||||
speed: "fast",
|
||||
projectId,
|
||||
sanitizeContent: sanitizeAiContent,
|
||||
transformMessages: async (messages) => {
|
||||
|
||||
@ -10,7 +10,7 @@ const TOOL_ROUTE_HEADERS = {
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
};
|
||||
|
||||
const MCP_RPC_TIMEOUT_MS = 15_000;
|
||||
const MCP_RPC_TIMEOUT_MS = 45_000;
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user