feat(studio): deploy manual designer and configurable AI model
This commit is contained in:
1 parent
58687d8679
commit
eefc86a057
10 files changed
+133
-32
No files matched your search
+19
-10
@@ -3,7 +3,7 @@ import type {
|
||||
GenerationRequest,
|
||||
} from "@wrnexus/design-studio";
|
||||
|
||||
export const APIMART_MODEL = "gpt-6-luna";
|
||||
export const APIMART_MODEL = "gpt-5-mini";
|
||||
export const AI_TIMEOUT_MS = 60_000;
|
||||
export const AI_OUTPUT_TOKENS = 8192;
|
||||
type FailureKind =
|
||||
@@ -22,7 +22,7 @@ const failureMessages: Record<FailureKind, string> = {
|
||||
quota:
|
||||
"APIMart reports insufficient credit or quota. Check the provider account balance and limits.",
|
||||
model:
|
||||
"APIMart could not access gpt-6-luna. Check that this model is enabled for the server API key.",
|
||||
"APIMart could not access the configured model. Check that this model is enabled for the server API key.",
|
||||
rate: "APIMart is rate limiting requests. Wait a minute and generate again.",
|
||||
unavailable:
|
||||
"APIMart is temporarily unavailable. Try generating again shortly.",
|
||||
@@ -70,6 +70,7 @@ function failureFor(status: number, code?: unknown) {
|
||||
export function apimartProvider(
|
||||
apiKey: string,
|
||||
transport: typeof fetch = fetch,
|
||||
model = APIMART_MODEL,
|
||||
): GenerationProvider {
|
||||
return {
|
||||
name: "APIMart",
|
||||
@@ -87,7 +88,7 @@ export function apimartProvider(
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: APIMART_MODEL,
|
||||
model,
|
||||
stream: false,
|
||||
max_tokens: options.maxTokens ?? AI_OUTPUT_TOKENS,
|
||||
messages: [
|
||||
@@ -137,7 +138,7 @@ export function apimartProvider(
|
||||
return {
|
||||
value: choice.message.content,
|
||||
provider: "APIMart",
|
||||
model: APIMART_MODEL,
|
||||
model,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -166,7 +167,11 @@ async function readBounded(source: Request | Response, limit: number) {
|
||||
}
|
||||
}
|
||||
|
||||
function synchronousStudioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
||||
function synchronousStudioAI(
|
||||
apiKey?: string,
|
||||
transport: typeof fetch = fetch,
|
||||
model = APIMART_MODEL,
|
||||
) {
|
||||
let active = 0,
|
||||
windowStart = Date.now(),
|
||||
count = 0;
|
||||
@@ -198,7 +203,7 @@ function synchronousStudioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
||||
if (url.pathname.endsWith("/generation") && request.method === "GET")
|
||||
return json({
|
||||
provider: "APIMart",
|
||||
model: APIMART_MODEL,
|
||||
model,
|
||||
templateVersion: 1,
|
||||
delivery: "polling",
|
||||
maxAttempts: 2,
|
||||
@@ -236,7 +241,7 @@ function synchronousStudioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
||||
let calls = 0;
|
||||
let exhausted = false;
|
||||
let failure: APIMartFailure | undefined;
|
||||
const upstream = apimartProvider(apiKey, transport);
|
||||
const upstream = apimartProvider(apiKey, transport, model);
|
||||
const provider: GenerationProvider = {
|
||||
name: upstream.name,
|
||||
async generate(prompt, options) {
|
||||
@@ -266,7 +271,7 @@ function synchronousStudioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
||||
const generate = () =>
|
||||
generateDesigns(input, {
|
||||
provider,
|
||||
model: APIMART_MODEL,
|
||||
model,
|
||||
signal: deadline,
|
||||
timeoutMs: AI_TIMEOUT_MS,
|
||||
maxOutputTokens: AI_OUTPUT_TOKENS,
|
||||
@@ -351,8 +356,12 @@ function synchronousStudioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
||||
}
|
||||
|
||||
/** Short HTTP requests keep the deployment proxy out of the provider wait. */
|
||||
export function studioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
||||
const run = synchronousStudioAI(apiKey, transport);
|
||||
export function studioAI(
|
||||
apiKey?: string,
|
||||
transport: typeof fetch = fetch,
|
||||
model = APIMART_MODEL,
|
||||
) {
|
||||
const run = synchronousStudioAI(apiKey, transport, model);
|
||||
type Job = {
|
||||
status: "pending" | "complete" | "failed" | "cancelled";
|
||||
controller: AbortController;
|
||||
|
||||
+14
-3
@@ -13,13 +13,24 @@ export const studioHelpScript = `(() => {
|
||||
window.addEventListener("unhandledrejection", fail);
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
if (failed) report();
|
||||
const button = document.querySelector('[data-tab="guided"]');
|
||||
const button = document.querySelector('[data-tab="manual"]');
|
||||
if (button) button.click();
|
||||
});
|
||||
})();`;
|
||||
|
||||
export function addStudioGuide(html: string, configured: boolean) {
|
||||
const guide = `<details open class="help"><summary><strong>How to use Design Studio</strong></summary><ol><li><strong>Describe your website.</strong> Open Guided design, enter your product and audience, then choose colors, shapes and spacing with Next.</li><li><strong>Generate and compare.</strong> Click Generate three directions. The first valid alternative appears in the large preview. Review another to compare, then choose Use reviewed proposal to save it. Show current draft returns without accepting.</li><li><strong>Refine it.</strong> Try “make buttons square and use blue as the primary color” under Edit with words. Lock decisions you want to keep.</li><li><strong>Use it in your app.</strong> Open Review & apply and export Theme config or Compiled CSS. This website previews and exports designs; it does not change your deployed app automatically.</li></ol><p>${configured ? "APIMart AI is enabled with gpt-6-luna. Generation sends your brief and current design to APIMart only when you request it." : "AI is not configured. Local presets and supported rule-based edits still work. Set APIMART_API_KEY in the server .env and restart to enable gpt-6-luna."}</p></details>`;
|
||||
export function addStudioGuide(
|
||||
html: string,
|
||||
configured: boolean,
|
||||
model = "gpt-5-mini",
|
||||
) {
|
||||
const safeModel = model.replace(
|
||||
/[&<>"']/g,
|
||||
(char) =>
|
||||
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[
|
||||
char
|
||||
]!,
|
||||
);
|
||||
const guide = `<details open class="help"><summary><strong>How to use Design Studio</strong></summary><ol><li><strong>Build manually.</strong> Open Manual design to choose colors, fonts, corner radii, spacing and shadows. Switch light/dark modes, select Update preview to save settings, then export. No AI is required.</li><li><strong>Describe your website for AI.</strong> Open Guided design, enter your product and audience, then choose colors, shapes and spacing with Next.</li><li><strong>Generate and compare.</strong> Click Generate three directions. The first valid alternative appears in the large preview. Review another to compare, then choose Use reviewed proposal to save it. Show current draft returns without accepting.</li><li><strong>Refine it.</strong> Try “make buttons square and use blue as the primary color” under Edit with words. Lock decisions you want to keep.</li><li><strong>Use it in your app.</strong> Open Review & apply and export Theme config or Compiled CSS. This website previews and exports designs; it does not change your deployed app automatically.</li></ol><p>${configured ? `APIMart AI is enabled with ${safeModel}. Generation sends your brief and current design to APIMart only when you request it.` : "AI is not configured. Local presets and supported rule-based edits still work. Set APIMART_API_KEY and APIMART_MODEL in the server .env and restart to enable AI."}</p></details>`;
|
||||
return html
|
||||
.replace(
|
||||
'<script type="module"',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Middleware } from "@wrnexus/core";
|
||||
import type { createStudioHandler } from "@wrnexus/design-studio/server";
|
||||
import { studioAI } from "../lib/studio-ai.ts";
|
||||
import { APIMART_MODEL, studioAI } from "../lib/studio-ai.ts";
|
||||
import { addStudioGuide, studioHelpScript } from "../lib/studio-guide.ts";
|
||||
import { studioRequest } from "../lib/studio-origin.ts";
|
||||
|
||||
@@ -17,7 +17,11 @@ const designStudio: Middleware = async (ctx, next) => {
|
||||
if (!url.pathname.startsWith("/design-studio/")) return next();
|
||||
const request = studioRequest(ctx.req);
|
||||
if (request instanceof Response) return request;
|
||||
ai ??= studioAI(process.env.APIMART_API_KEY?.trim());
|
||||
ai ??= studioAI(
|
||||
process.env.APIMART_API_KEY?.trim(),
|
||||
fetch,
|
||||
process.env.APIMART_MODEL?.trim() || APIMART_MODEL,
|
||||
);
|
||||
const generated = await ai(request);
|
||||
if (generated) return generated;
|
||||
if (
|
||||
@@ -54,6 +58,7 @@ const designStudio: Middleware = async (ctx, next) => {
|
||||
addStudioGuide(
|
||||
await response.text(),
|
||||
!!process.env.APIMART_API_KEY?.trim(),
|
||||
process.env.APIMART_MODEL?.trim() || APIMART_MODEL,
|
||||
),
|
||||
{ status: response.status, headers },
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user