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
+3
-1
@@ -1,4 +1,6 @@
|
|||||||
# Server only. Never put this key in browser scripts or commit your .env file.
|
# Server only. Never put this key in browser scripts or commit your .env file.
|
||||||
APIMART_API_KEY=
|
APIMART_API_KEY=
|
||||||
STUDIO_PUBLIC_ORIGIN=https://component.wrnexusjs.dev
|
STUDIO_PUBLIC_ORIGIN=https://component.wrnexusjs.dev
|
||||||
# Design Studio uses the gpt-6-luna model at https://api.apimart.ai/v1/chat/completions.
|
# Change the model here, then restart the server.
|
||||||
|
APIMART_MODEL=gpt-5-mini
|
||||||
|
# APIMart endpoint: https://api.apimart.ai/v1/chat/completions.
|
||||||
@@ -117,7 +117,7 @@ APIMART_API_KEY=your_apimart_key
|
|||||||
|
|
||||||
Restart the app after setting the key. No key goes in the browser or application
|
Restart the app after setting the key. No key goes in the browser or application
|
||||||
configuration. The server calls https://api.apimart.ai/v1/chat/completions with
|
configuration. The server calls https://api.apimart.ai/v1/chat/completions with
|
||||||
model `gpt-6-luna`, bearer authentication, and `stream: false`, as described in
|
model `gpt-5-mini`, bearer authentication, and `stream: false`, as described in
|
||||||
https://docs.apimart.ai/en/api-reference/texts/general/chat-completions.
|
https://docs.apimart.ai/en/api-reference/texts/general/chat-completions.
|
||||||
Model access must be available on your APIMart account. No live provider call is
|
Model access must be available on your APIMart account. No live provider call is
|
||||||
part of the normal build or test suite.
|
part of the normal build or test suite.
|
||||||
@@ -191,3 +191,6 @@ component gallery page.
|
|||||||
### Interactive Studio gallery
|
### Interactive Studio gallery
|
||||||
|
|
||||||
Choose Component gallery in the preview Content menu, then select pages 1–7. Each of the 104 entries has populated sample data and local icons. Controls use their shipped browser functions; charts and workflow graphs include their real runtimes. Sample events appear below each example. Preview links stay in the demo; forms and uploads do not submit data. Switch tracks stay rounded across presets and existing drafts. The editor reports when the preview is rendering, ready or unable to load.
|
Choose Component gallery in the preview Content menu, then select pages 1–7. Each of the 104 entries has populated sample data and local icons. Controls use their shipped browser functions; charts and workflow graphs include their real runtimes. Sample events appear below each example. Preview links stay in the demo; forms and uploads do not submit data. Switch tracks stay rounded across presets and existing drafts. The editor reports when the preview is rendering, ready or unable to load.
|
||||||
|
|
||||||
|
Manual design works without an API key: choose a preset or Create new, edit light/dark colors, fonts, corner radii, spacing and shadows, select Update preview, then export under Review & apply. Undo and decision locks protect changes.
|
||||||
|
Set `APIMART_MODEL=gpt-5-mini` in the server `.env` to choose the AI model. Restart the server after changing it; the Studio displays the active model.
|
||||||
+19
-10
@@ -3,7 +3,7 @@ import type {
|
|||||||
GenerationRequest,
|
GenerationRequest,
|
||||||
} from "@wrnexus/design-studio";
|
} 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_TIMEOUT_MS = 60_000;
|
||||||
export const AI_OUTPUT_TOKENS = 8192;
|
export const AI_OUTPUT_TOKENS = 8192;
|
||||||
type FailureKind =
|
type FailureKind =
|
||||||
@@ -22,7 +22,7 @@ const failureMessages: Record<FailureKind, string> = {
|
|||||||
quota:
|
quota:
|
||||||
"APIMart reports insufficient credit or quota. Check the provider account balance and limits.",
|
"APIMart reports insufficient credit or quota. Check the provider account balance and limits.",
|
||||||
model:
|
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.",
|
rate: "APIMart is rate limiting requests. Wait a minute and generate again.",
|
||||||
unavailable:
|
unavailable:
|
||||||
"APIMart is temporarily unavailable. Try generating again shortly.",
|
"APIMart is temporarily unavailable. Try generating again shortly.",
|
||||||
@@ -70,6 +70,7 @@ function failureFor(status: number, code?: unknown) {
|
|||||||
export function apimartProvider(
|
export function apimartProvider(
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
transport: typeof fetch = fetch,
|
transport: typeof fetch = fetch,
|
||||||
|
model = APIMART_MODEL,
|
||||||
): GenerationProvider {
|
): GenerationProvider {
|
||||||
return {
|
return {
|
||||||
name: "APIMart",
|
name: "APIMart",
|
||||||
@@ -87,7 +88,7 @@ export function apimartProvider(
|
|||||||
"content-type": "application/json",
|
"content-type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: APIMART_MODEL,
|
model,
|
||||||
stream: false,
|
stream: false,
|
||||||
max_tokens: options.maxTokens ?? AI_OUTPUT_TOKENS,
|
max_tokens: options.maxTokens ?? AI_OUTPUT_TOKENS,
|
||||||
messages: [
|
messages: [
|
||||||
@@ -137,7 +138,7 @@ export function apimartProvider(
|
|||||||
return {
|
return {
|
||||||
value: choice.message.content,
|
value: choice.message.content,
|
||||||
provider: "APIMart",
|
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,
|
let active = 0,
|
||||||
windowStart = Date.now(),
|
windowStart = Date.now(),
|
||||||
count = 0;
|
count = 0;
|
||||||
@@ -198,7 +203,7 @@ function synchronousStudioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
|||||||
if (url.pathname.endsWith("/generation") && request.method === "GET")
|
if (url.pathname.endsWith("/generation") && request.method === "GET")
|
||||||
return json({
|
return json({
|
||||||
provider: "APIMart",
|
provider: "APIMart",
|
||||||
model: APIMART_MODEL,
|
model,
|
||||||
templateVersion: 1,
|
templateVersion: 1,
|
||||||
delivery: "polling",
|
delivery: "polling",
|
||||||
maxAttempts: 2,
|
maxAttempts: 2,
|
||||||
@@ -236,7 +241,7 @@ function synchronousStudioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
|||||||
let calls = 0;
|
let calls = 0;
|
||||||
let exhausted = false;
|
let exhausted = false;
|
||||||
let failure: APIMartFailure | undefined;
|
let failure: APIMartFailure | undefined;
|
||||||
const upstream = apimartProvider(apiKey, transport);
|
const upstream = apimartProvider(apiKey, transport, model);
|
||||||
const provider: GenerationProvider = {
|
const provider: GenerationProvider = {
|
||||||
name: upstream.name,
|
name: upstream.name,
|
||||||
async generate(prompt, options) {
|
async generate(prompt, options) {
|
||||||
@@ -266,7 +271,7 @@ function synchronousStudioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
|||||||
const generate = () =>
|
const generate = () =>
|
||||||
generateDesigns(input, {
|
generateDesigns(input, {
|
||||||
provider,
|
provider,
|
||||||
model: APIMART_MODEL,
|
model,
|
||||||
signal: deadline,
|
signal: deadline,
|
||||||
timeoutMs: AI_TIMEOUT_MS,
|
timeoutMs: AI_TIMEOUT_MS,
|
||||||
maxOutputTokens: AI_OUTPUT_TOKENS,
|
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. */
|
/** Short HTTP requests keep the deployment proxy out of the provider wait. */
|
||||||
export function studioAI(apiKey?: string, transport: typeof fetch = fetch) {
|
export function studioAI(
|
||||||
const run = synchronousStudioAI(apiKey, transport);
|
apiKey?: string,
|
||||||
|
transport: typeof fetch = fetch,
|
||||||
|
model = APIMART_MODEL,
|
||||||
|
) {
|
||||||
|
const run = synchronousStudioAI(apiKey, transport, model);
|
||||||
type Job = {
|
type Job = {
|
||||||
status: "pending" | "complete" | "failed" | "cancelled";
|
status: "pending" | "complete" | "failed" | "cancelled";
|
||||||
controller: AbortController;
|
controller: AbortController;
|
||||||
|
|||||||
+14
-3
@@ -13,13 +13,24 @@ export const studioHelpScript = `(() => {
|
|||||||
window.addEventListener("unhandledrejection", fail);
|
window.addEventListener("unhandledrejection", fail);
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
if (failed) report();
|
if (failed) report();
|
||||||
const button = document.querySelector('[data-tab="guided"]');
|
const button = document.querySelector('[data-tab="manual"]');
|
||||||
if (button) button.click();
|
if (button) button.click();
|
||||||
});
|
});
|
||||||
})();`;
|
})();`;
|
||||||
|
|
||||||
export function addStudioGuide(html: string, configured: boolean) {
|
export function addStudioGuide(
|
||||||
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>`;
|
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
|
return html
|
||||||
.replace(
|
.replace(
|
||||||
'<script type="module"',
|
'<script type="module"',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Middleware } from "@wrnexus/core";
|
import type { Middleware } from "@wrnexus/core";
|
||||||
import type { createStudioHandler } from "@wrnexus/design-studio/server";
|
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 { addStudioGuide, studioHelpScript } from "../lib/studio-guide.ts";
|
||||||
import { studioRequest } from "../lib/studio-origin.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();
|
if (!url.pathname.startsWith("/design-studio/")) return next();
|
||||||
const request = studioRequest(ctx.req);
|
const request = studioRequest(ctx.req);
|
||||||
if (request instanceof Response) return request;
|
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);
|
const generated = await ai(request);
|
||||||
if (generated) return generated;
|
if (generated) return generated;
|
||||||
if (
|
if (
|
||||||
@@ -54,6 +58,7 @@ const designStudio: Middleware = async (ctx, next) => {
|
|||||||
addStudioGuide(
|
addStudioGuide(
|
||||||
await response.text(),
|
await response.text(),
|
||||||
!!process.env.APIMART_API_KEY?.trim(),
|
!!process.env.APIMART_API_KEY?.trim(),
|
||||||
|
process.env.APIMART_MODEL?.trim() || APIMART_MODEL,
|
||||||
),
|
),
|
||||||
{ status: response.status, headers },
|
{ status: response.status, headers },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "0.8.25",
|
"@wrnexus/core": "0.8.25",
|
||||||
"@wrnexus/csr": "0.8.75",
|
"@wrnexus/csr": "0.8.75",
|
||||||
"@wrnexus/design-studio": "0.1.7",
|
"@wrnexus/design-studio": "0.1.8",
|
||||||
"@wrnexus/design-system": "0.1.2",
|
"@wrnexus/design-system": "0.1.2",
|
||||||
"@wrnexus/dev-server": "0.8.84",
|
"@wrnexus/dev-server": "0.8.84",
|
||||||
"@wrnexus/helpers": "0.8.10",
|
"@wrnexus/helpers": "0.8.10",
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
|
|
||||||
"@wrnexus/db": ["@wrnexus/db@0.8.24", "https://registry.workroot.in/repository/npm/@wrnexus/db/-/db-0.8.24.tgz", {}, "sha512-zLT7e+C1nWhYs3BnqEjEO0QSli0JNl5sSnSHObjSX0C+wty30ASWD1WyBnfu9xFjOFn1eucZGthzo1xdGGQ1TQ=="],
|
"@wrnexus/db": ["@wrnexus/db@0.8.24", "https://registry.workroot.in/repository/npm/@wrnexus/db/-/db-0.8.24.tgz", {}, "sha512-zLT7e+C1nWhYs3BnqEjEO0QSli0JNl5sSnSHObjSX0C+wty30ASWD1WyBnfu9xFjOFn1eucZGthzo1xdGGQ1TQ=="],
|
||||||
|
|
||||||
"@wrnexus/design-studio": ["@wrnexus/design-studio@0.1.7", "https://registry.workroot.in/repository/npm/@wrnexus/design-studio/-/design-studio-0.1.7.tgz", { "dependencies": { "@iconify-json/lucide": "1.2.123", "@wrnexus/ai": "^0.8.10", "@wrnexus/compiler": "^0.8.45", "@wrnexus/core": "^0.8.25", "@wrnexus/csr": "^0.8.75", "@wrnexus/design-system": "^0.1.2", "@wrnexus/ssr": "^0.8.20", "@wrnexus/styles": "^0.8.27", "@wrnexus/ui": "^0.8.122" }, "bin": { "wrnexus-design-studio": "dist/cli.js" } }, "sha512-K5EGiEHjif/pLFtmaGkIOZ7+RskJ+q0/kUSCj8KNvuNk4qNui+7KhoCYwye5mCfMVUtlw6q1TIv7gPT0ffe9wQ=="],
|
"@wrnexus/design-studio": ["@wrnexus/design-studio@0.1.8", "https://registry.workroot.in/repository/npm/@wrnexus/design-studio/-/design-studio-0.1.8.tgz", { "dependencies": { "@iconify-json/lucide": "1.2.123", "@wrnexus/ai": "^0.8.10", "@wrnexus/compiler": "^0.8.47", "@wrnexus/core": "^0.8.25", "@wrnexus/csr": "^0.8.76", "@wrnexus/design-system": "^0.1.2", "@wrnexus/ssr": "^0.8.20", "@wrnexus/styles": "^0.8.27", "@wrnexus/ui": "^0.8.122" }, "bin": { "wrnexus-design-studio": "dist/cli.js" } }, "sha512-mMiagxexdYrS0hHO5c/SHtH2jYqVYeXVlEABVLgZn6Ydv2IBr0pnuciNLHjZfC355kH5WmcimZpvdno7gOc+Lw=="],
|
||||||
|
|
||||||
"@wrnexus/design-system": ["@wrnexus/design-system@0.1.2", "https://registry.workroot.in/repository/npm/@wrnexus/design-system/-/design-system-0.1.2.tgz", {}, "sha512-43/1Pibj91xdd0HxIhkfwEChjgpK+WQutEwo4DLAd9oELFqqherlrmx47TXs3Qzq9vTCJ2uD6mW6zo9VaqkY2g=="],
|
"@wrnexus/design-system": ["@wrnexus/design-system@0.1.2", "https://registry.workroot.in/repository/npm/@wrnexus/design-system/-/design-system-0.1.2.tgz", {}, "sha512-43/1Pibj91xdd0HxIhkfwEChjgpK+WQutEwo4DLAd9oELFqqherlrmx47TXs3Qzq9vTCJ2uD6mW6zo9VaqkY2g=="],
|
||||||
|
|
||||||
@@ -377,6 +377,10 @@
|
|||||||
|
|
||||||
"@wrnexus/design-studio/@iconify-json/lucide": ["@iconify-json/lucide@1.2.123", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-0CozmpKXEOEEhltrfT2zt+/t1hez67Y+hM2VJWoIcBKdBrb83VYuKf+b5A0QU9HfQm+RNn2GjFWlTOwi+Ss6IA=="],
|
"@wrnexus/design-studio/@iconify-json/lucide": ["@iconify-json/lucide@1.2.123", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-0CozmpKXEOEEhltrfT2zt+/t1hez67Y+hM2VJWoIcBKdBrb83VYuKf+b5A0QU9HfQm+RNn2GjFWlTOwi+Ss6IA=="],
|
||||||
|
|
||||||
|
"@wrnexus/design-studio/@wrnexus/compiler": ["@wrnexus/compiler@0.8.47", "https://registry.workroot.in/repository/npm/@wrnexus/compiler/-/compiler-0.8.47.tgz", { "dependencies": { "@wrnexus/csr": "^0.8.76", "@wrnexus/store": "^0.8.13", "@wrnexus/syntax": "^0.8.20", "@wrnexus/validation": "^0.8.14", "entities": "^7.0.1" } }, "sha512-f/dEQZ1V1THbys6V4twK1ANgygGS9LID0X4FsxZRZH2IhJ41WrrMD0SrBlU+ORj2nt1SBq+EavtHPSHJi2rppQ=="],
|
||||||
|
|
||||||
|
"@wrnexus/design-studio/@wrnexus/csr": ["@wrnexus/csr@0.8.76", "https://registry.workroot.in/repository/npm/@wrnexus/csr/-/csr-0.8.76.tgz", { "dependencies": { "@wrnexus/core": "^0.8.12" } }, "sha512-5OXYccMTNqWwx3+NbfbhIQ31IRBrovEEoGb6a88OVTkBiFGUna4rAwSqxornGk3/jlxxZnAgc94rvtfiQro8aw=="],
|
||||||
|
|
||||||
"@wrnexus/typecheck/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
"@wrnexus/typecheck/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||||
|
|
||||||
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
|
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
|
||||||
|
|||||||
Generated
+46
-6
@@ -10,7 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "0.8.25",
|
"@wrnexus/core": "0.8.25",
|
||||||
"@wrnexus/csr": "0.8.75",
|
"@wrnexus/csr": "0.8.75",
|
||||||
"@wrnexus/design-studio": "0.1.7",
|
"@wrnexus/design-studio": "0.1.8",
|
||||||
"@wrnexus/design-system": "0.1.2",
|
"@wrnexus/design-system": "0.1.2",
|
||||||
"@wrnexus/dev-server": "0.8.84",
|
"@wrnexus/dev-server": "0.8.84",
|
||||||
"@wrnexus/helpers": "0.8.10",
|
"@wrnexus/helpers": "0.8.10",
|
||||||
@@ -1186,16 +1186,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@wrnexus/design-studio": {
|
"node_modules/@wrnexus/design-studio": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.workroot.in/repository/npm/@wrnexus/design-studio/-/design-studio-0.1.7.tgz",
|
"resolved": "https://registry.workroot.in/repository/npm/@wrnexus/design-studio/-/design-studio-0.1.8.tgz",
|
||||||
"integrity": "sha512-K5EGiEHjif/pLFtmaGkIOZ7+RskJ+q0/kUSCj8KNvuNk4qNui+7KhoCYwye5mCfMVUtlw6q1TIv7gPT0ffe9wQ==",
|
"integrity": "sha512-mMiagxexdYrS0hHO5c/SHtH2jYqVYeXVlEABVLgZn6Ydv2IBr0pnuciNLHjZfC355kH5WmcimZpvdno7gOc+Lw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify-json/lucide": "1.2.123",
|
"@iconify-json/lucide": "1.2.123",
|
||||||
"@wrnexus/ai": "^0.8.10",
|
"@wrnexus/ai": "^0.8.10",
|
||||||
"@wrnexus/compiler": "^0.8.45",
|
"@wrnexus/compiler": "^0.8.47",
|
||||||
"@wrnexus/core": "^0.8.25",
|
"@wrnexus/core": "^0.8.25",
|
||||||
"@wrnexus/csr": "^0.8.75",
|
"@wrnexus/csr": "^0.8.76",
|
||||||
"@wrnexus/design-system": "^0.1.2",
|
"@wrnexus/design-system": "^0.1.2",
|
||||||
"@wrnexus/ssr": "^0.8.20",
|
"@wrnexus/ssr": "^0.8.20",
|
||||||
"@wrnexus/styles": "^0.8.27",
|
"@wrnexus/styles": "^0.8.27",
|
||||||
@@ -1217,6 +1217,46 @@
|
|||||||
"@iconify/types": "*"
|
"@iconify/types": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@wrnexus/design-studio/node_modules/@wrnexus/compiler": {
|
||||||
|
"version": "0.8.47",
|
||||||
|
"resolved": "https://registry.workroot.in/repository/npm/@wrnexus/compiler/-/compiler-0.8.47.tgz",
|
||||||
|
"integrity": "sha512-f/dEQZ1V1THbys6V4twK1ANgygGS9LID0X4FsxZRZH2IhJ41WrrMD0SrBlU+ORj2nt1SBq+EavtHPSHJi2rppQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@wrnexus/csr": "^0.8.76",
|
||||||
|
"@wrnexus/store": "^0.8.13",
|
||||||
|
"@wrnexus/syntax": "^0.8.20",
|
||||||
|
"@wrnexus/validation": "^0.8.14",
|
||||||
|
"entities": "^7.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@wrnexus/design-studio/node_modules/@wrnexus/csr": {
|
||||||
|
"version": "0.8.76",
|
||||||
|
"resolved": "https://registry.workroot.in/repository/npm/@wrnexus/csr/-/csr-0.8.76.tgz",
|
||||||
|
"integrity": "sha512-5OXYccMTNqWwx3+NbfbhIQ31IRBrovEEoGb6a88OVTkBiFGUna4rAwSqxornGk3/jlxxZnAgc94rvtfiQro8aw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@wrnexus/core": "^0.8.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@wrnexus/design-studio/node_modules/entities": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@wrnexus/design-system": {
|
"node_modules/@wrnexus/design-system": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.workroot.in/repository/npm/@wrnexus/design-system/-/design-system-0.1.2.tgz",
|
"resolved": "https://registry.workroot.in/repository/npm/@wrnexus/design-system/-/design-system-0.1.2.tgz",
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "0.8.25",
|
"@wrnexus/core": "0.8.25",
|
||||||
"@wrnexus/csr": "0.8.75",
|
"@wrnexus/csr": "0.8.75",
|
||||||
"@wrnexus/design-studio": "0.1.7",
|
"@wrnexus/design-studio": "0.1.8",
|
||||||
"@wrnexus/design-system": "0.1.2",
|
"@wrnexus/design-system": "0.1.2",
|
||||||
"@wrnexus/dev-server": "0.8.84",
|
"@wrnexus/dev-server": "0.8.84",
|
||||||
"@wrnexus/helpers": "0.8.10",
|
"@wrnexus/helpers": "0.8.10",
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ try {
|
|||||||
html.match(/<select id="preset">([\s\S]*?)<\/select>/)?.[1] ?? "";
|
html.match(/<select id="preset">([\s\S]*?)<\/select>/)?.[1] ?? "";
|
||||||
if (
|
if (
|
||||||
(presetMenu.match(/<option value="[^"]+"/g) ?? []).length !== 13 ||
|
(presetMenu.match(/<option value="[^"]+"/g) ?? []).length !== 13 ||
|
||||||
!html.includes('id="show-draft"')
|
!html.includes('id="show-draft"') ||
|
||||||
|
!html.includes('id="manual"')
|
||||||
)
|
)
|
||||||
throw Error(
|
throw Error(
|
||||||
"The deployed Studio is missing its thirteen presets or proposal preview controls.",
|
"The deployed Studio is missing its thirteen presets or proposal preview controls.",
|
||||||
@@ -103,7 +104,8 @@ try {
|
|||||||
if (
|
if (
|
||||||
process.env.APIMART_API_KEY?.trim() &&
|
process.env.APIMART_API_KEY?.trim() &&
|
||||||
(generation.provider !== "APIMart" ||
|
(generation.provider !== "APIMart" ||
|
||||||
generation.model !== "gpt-6-luna" ||
|
generation.model !==
|
||||||
|
(process.env.APIMART_MODEL?.trim() || "gpt-5-mini") ||
|
||||||
generation.delivery !== "polling" ||
|
generation.delivery !== "polling" ||
|
||||||
generation.maxAttempts !== 2 ||
|
generation.maxAttempts !== 2 ||
|
||||||
generation.maxOutputTokens !== 8192 ||
|
generation.maxOutputTokens !== 8192 ||
|
||||||
|
|||||||
+29
-4
@@ -56,7 +56,7 @@ test("APIMart uses the specified model, bearer authentication, non-streaming JSO
|
|||||||
);
|
);
|
||||||
expect(init?.signal).toBe(controller.signal);
|
expect(init?.signal).toBe(controller.signal);
|
||||||
const body = JSON.parse(init?.body as string);
|
const body = JSON.parse(init?.body as string);
|
||||||
expect(body.model).toBe("gpt-6-luna");
|
expect(body.model).toBe("gpt-5-mini");
|
||||||
expect(body.stream).toBe(false);
|
expect(body.stream).toBe(false);
|
||||||
expect(body.max_tokens).toBe(AI_OUTPUT_TOKENS);
|
expect(body.max_tokens).toBe(AI_OUTPUT_TOKENS);
|
||||||
expect(body.messages).toEqual([
|
expect(body.messages).toEqual([
|
||||||
@@ -87,7 +87,7 @@ test("AI proposals use engine validation, preserve draft source, locks and revis
|
|||||||
const batch = await response!.json();
|
const batch = await response!.json();
|
||||||
expect(batch.alternatives).toHaveLength(3);
|
expect(batch.alternatives).toHaveLength(3);
|
||||||
expect(batch.provenance.provider).toBe("APIMart");
|
expect(batch.provenance.provider).toBe("APIMart");
|
||||||
expect(batch.provenance.model).toBe("gpt-6-luna");
|
expect(batch.provenance.model).toBe("gpt-5-mini");
|
||||||
expect(JSON.stringify(system)).toBe(original);
|
expect(JSON.stringify(system)).toBe(original);
|
||||||
const locked = await handler(
|
const locked = await handler(
|
||||||
await request(await input(["/tokens/shape.control"])),
|
await request(await input(["/tokens/shape.control"])),
|
||||||
@@ -114,7 +114,7 @@ test("metadata and errors never contain the key or raw upstream response", async
|
|||||||
new Request("https://components.example/design-studio/api/generation"),
|
new Request("https://components.example/design-studio/api/generation"),
|
||||||
);
|
);
|
||||||
const text = await meta!.text();
|
const text = await meta!.text();
|
||||||
expect(text).toContain("gpt-6-luna");
|
expect(text).toContain("gpt-5-mini");
|
||||||
expect(JSON.parse(text).timeoutMs).toBe(AI_TIMEOUT_MS);
|
expect(JSON.parse(text).timeoutMs).toBe(AI_TIMEOUT_MS);
|
||||||
expect(AI_TIMEOUT_MS).toBe(60_000);
|
expect(AI_TIMEOUT_MS).toBe(60_000);
|
||||||
expect(text).not.toContain("test-secret");
|
expect(text).not.toContain("test-secret");
|
||||||
@@ -227,7 +227,7 @@ test("authentication and quota failures provide actionable redacted diagnostics
|
|||||||
for (const [status, code, hint] of [
|
for (const [status, code, hint] of [
|
||||||
[401, "invalid_api_key", "server API key"],
|
[401, "invalid_api_key", "server API key"],
|
||||||
[402, "insufficient_quota", "credit or quota"],
|
[402, "insufficient_quota", "credit or quota"],
|
||||||
[404, "model_not_found", "gpt-6-luna"],
|
[404, "model_not_found", "configured model"],
|
||||||
] as const) {
|
] as const) {
|
||||||
let calls = 0;
|
let calls = 0;
|
||||||
const handler = studioAI(
|
const handler = studioAI(
|
||||||
@@ -407,3 +407,28 @@ test("background generation cancellation aborts the provider and leaves no propo
|
|||||||
expect((await response!.json()).status).toBe("cancelled");
|
expect((await response!.json()).status).toBe("cancelled");
|
||||||
expect(aborted).toBe(true);
|
expect(aborted).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("the configured model is used consistently for metadata and upstream requests", async () => {
|
||||||
|
let selected = "";
|
||||||
|
const handler = studioAI(
|
||||||
|
"test-secret",
|
||||||
|
mockFetch(async (_url, init) => {
|
||||||
|
selected = JSON.parse(init!.body as string).model;
|
||||||
|
return completion();
|
||||||
|
}),
|
||||||
|
"custom-model",
|
||||||
|
);
|
||||||
|
const metadata = await (await handler(
|
||||||
|
new Request("https://components.example/design-studio/api/generation"),
|
||||||
|
))!.json();
|
||||||
|
expect(metadata.model).toBe("custom-model");
|
||||||
|
const batch = await (await handler(await request()))!.json();
|
||||||
|
expect(selected).toBe("custom-model");
|
||||||
|
expect(batch.provenance.model).toBe("custom-model");
|
||||||
|
expect(
|
||||||
|
addStudioGuide('<div id="diagnostics"></div>', true, "custom-model"),
|
||||||
|
).toContain("custom-model");
|
||||||
|
expect(
|
||||||
|
addStudioGuide('<div id="diagnostics"></div>', true, "<script>"),
|
||||||
|
).not.toContain("with <script>");
|
||||||
|
});
|
||||||
Reference in new issue
Block a user