From c12ea250bbba112f2c13d496c07263977b712187 Mon Sep 17 00:00:00 2001 From: Robert Field Date: Tue, 7 Oct 2025 14:47:55 +0100 Subject: [PATCH 1/2] feat: copilot init commit --- platform/wab/package.json | 3 +- platform/wab/src/wab/server/AppServer.ts | 22 ++ .../wab/src/wab/server/app-backend-real.ts | 2 + .../src/wab/server/copilot-backend-real.ts | 17 ++ .../wab/src/wab/server/copilot-backend.ts | 7 + platform/wab/src/wab/server/copilot/llms.ts | 66 ++-- .../wab/server/copilot/ui-copilot-chain.ts | 228 ++++++++++++++ platform/wab/src/wab/server/routes/copilot.ts | 283 ++++++++++++++++++ platform/wab/src/wab/server/simple-cache.ts | 30 ++ 9 files changed, 624 insertions(+), 34 deletions(-) create mode 100644 platform/wab/src/wab/server/copilot-backend-real.ts create mode 100644 platform/wab/src/wab/server/copilot-backend.ts create mode 100644 platform/wab/src/wab/server/copilot/ui-copilot-chain.ts create mode 100644 platform/wab/src/wab/server/routes/copilot.ts diff --git a/platform/wab/package.json b/platform/wab/package.json index c270cfb1d..5fef42f1f 100644 --- a/platform/wab/package.json +++ b/platform/wab/package.json @@ -449,7 +449,8 @@ "validator": "^13.1.1", "workerpool": "^6.1.4", "yargs": "^16.2.0", - "zod": "3.25.28" + "zod": "3.25.28", + "zod-to-json-schema": "^3.24.6" }, "resolutionsComments": { "ini": "CVE-2020-7788 for 1.3.5" diff --git a/platform/wab/src/wab/server/AppServer.ts b/platform/wab/src/wab/server/AppServer.ts index 962815cbe..a486589d6 100644 --- a/platform/wab/src/wab/server/AppServer.ts +++ b/platform/wab/src/wab/server/AppServer.ts @@ -79,6 +79,13 @@ import { updateTable, } from "@/wab/server/routes/cmse"; import { addCommentsRoutes } from "@/wab/server/routes/comments"; +import { + queryCopilot, + queryCopilotFeedback, + queryPublicUiCopilot, + queryUiCopilot, + sendCopilotFeedback, +} from "@/wab/server/routes/copilot"; import { ROUTES_WITH_TIMING, addInternalRoutes, @@ -1241,6 +1248,21 @@ export function addCodegenRoutes(app: express.Application) { ); } +export function addCopilotRoutes(app: express.Application) { + // Main copilot endpoint for code/chat/sql/debug + app.post("/api/v1/copilot", apiAuth, withNext(queryCopilot)); + + // UI copilot endpoint for HTML/token generation + app.post("/api/v1/copilot/ui", apiAuth, withNext(queryUiCopilot)); + + // Public UI copilot endpoint (no auth required) + app.post("/api/v1/copilot/ui/public", withNext(queryPublicUiCopilot)); + + // Copilot feedback endpoints + app.post("/api/v1/copilot-feedback", apiAuth, withNext(sendCopilotFeedback)); + app.get("/api/v1/copilot-feedback", apiAuth, withNext(queryCopilotFeedback)); +} + export function addMainAppServerRoutes( app: express.Application, config: Config diff --git a/platform/wab/src/wab/server/app-backend-real.ts b/platform/wab/src/wab/server/app-backend-real.ts index 943afc230..bc31ede09 100644 --- a/platform/wab/src/wab/server/app-backend-real.ts +++ b/platform/wab/src/wab/server/app-backend-real.ts @@ -2,6 +2,7 @@ // the top so that we know if we are running production and want newrelic. import { addCodegenRoutes, + addCopilotRoutes, addMainAppServerRoutes, createApp, } from "@/wab/server/AppServer"; @@ -58,6 +59,7 @@ export async function runAppServer(config: Config) { // protected against. logger().info("Adding codegen routes"); addCodegenRoutes(application); + addCopilotRoutes(application); } }, (application) => { diff --git a/platform/wab/src/wab/server/copilot-backend-real.ts b/platform/wab/src/wab/server/copilot-backend-real.ts new file mode 100644 index 000000000..33148c532 --- /dev/null +++ b/platform/wab/src/wab/server/copilot-backend-real.ts @@ -0,0 +1,17 @@ +import { addCopilotRoutes, createApp } from "@/wab/server/AppServer"; +import { Config } from "@/wab/server/config"; +import { ensureDbConnections } from "@/wab/server/db/DbCon"; +import { runExpressApp, setupServerCli } from "@/wab/server/server-common"; +import "core-js"; + +async function runAppServer(config: Config) { + await ensureDbConnections(config.databaseUri); + + const { app } = await createApp("copilot", config, addCopilotRoutes); + return runExpressApp(app); +} + +export async function copilotBackendMain() { + const { opts, config } = setupServerCli(); + await runAppServer(config); +} diff --git a/platform/wab/src/wab/server/copilot-backend.ts b/platform/wab/src/wab/server/copilot-backend.ts new file mode 100644 index 000000000..23063ad43 --- /dev/null +++ b/platform/wab/src/wab/server/copilot-backend.ts @@ -0,0 +1,7 @@ +// tslint:disable:ordered-imports +import { spawn } from "@/wab/shared/common"; +import { copilotBackendMain } from "@/wab/server/copilot-backend-real"; + +if (require.main === module) { + spawn(copilotBackendMain()); +} diff --git a/platform/wab/src/wab/server/copilot/llms.ts b/platform/wab/src/wab/server/copilot/llms.ts index 34eeda244..a3ab6fcb9 100644 --- a/platform/wab/src/wab/server/copilot/llms.ts +++ b/platform/wab/src/wab/server/copilot/llms.ts @@ -9,7 +9,11 @@ import { getDynamoDbSecrets, getOpenaiApiKey, } from "@/wab/server/secrets"; -import { DynamoDbCache, SimpleCache } from "@/wab/server/simple-cache"; +import { + DynamoDbCache, + InMemoryCache, + SimpleCache, +} from "@/wab/server/simple-cache"; import { last, mkShortId } from "@/wab/shared/common"; import { ChatCompletionRequestMessageRoleEnum, @@ -42,9 +46,6 @@ export class OpenAIWrapper { createChatCompletionRequest: CreateChatCompletionRequest, options?: CreateChatCompletionRequestOptions ) => { - if (verbose) { - logger().debug(showCompletionRequest(createChatCompletionRequest)); - } const key = hash( JSON.stringify([ "OpenAI.createChatCompletion", @@ -178,35 +179,34 @@ export function getOpenAI() { return new OpenAI({ apiKey: openaiApiKey }); } +function createCache(): SimpleCache { + // Only use DynamoDB if we have valid credentials + if ( + dynamoDbCredentials?.accessKeyId && + dynamoDbCredentials?.secretAccessKey + ) { + try { + return new DynamoDbCache( + new DynamoDBClient({ + credentials: { + ...dynamoDbCredentials, + }, + region: "us-west-2", + }) + ); + } catch (error) { + console.warn( + "Failed to create DynamoDB cache, falling back to in-memory cache:", + error + ); + } + } + // Use in-memory cache as fallback + return new InMemoryCache(); +} + export const createOpenAIClient = (_?: DbMgr) => - new OpenAIWrapper( - getOpenAI(), - new DynamoDbCache( - new DynamoDBClient({ - ...(dynamoDbCredentials - ? { - credentials: { - ...dynamoDbCredentials, - }, - } - : {}), - region: "us-west-2", - }) - ) - ); + new OpenAIWrapper(getOpenAI(), createCache()); export const createAnthropicClient = (_?: DbMgr) => - new AnthropicWrapper( - new DynamoDbCache( - new DynamoDBClient({ - ...(dynamoDbCredentials - ? { - credentials: { - ...dynamoDbCredentials, - }, - } - : {}), - region: "us-west-2", - }) - ) - ); + new AnthropicWrapper(createCache()); diff --git a/platform/wab/src/wab/server/copilot/ui-copilot-chain.ts b/platform/wab/src/wab/server/copilot/ui-copilot-chain.ts new file mode 100644 index 000000000..e0b2c03b7 --- /dev/null +++ b/platform/wab/src/wab/server/copilot/ui-copilot-chain.ts @@ -0,0 +1,228 @@ +import { createOpenAIClient } from "@/wab/server/copilot/llms"; +import { DbMgr } from "@/wab/server/db/DbMgr"; +import { + CopilotUiActions, + CopilotUiActionsSchema, + CopilotUiChainProps, + CreateChatCompletionRequest, + WholeChatCompletionResponse, +} from "@/wab/shared/copilot/prompt-utils"; +import { zodToJsonSchema } from "zod-to-json-schema"; + +const SYSTEM_PROMPT = `You are an expert UI designer and developer assistant for Plasmic, a visual web builder. + Your task is to help users create UI components by generating clean, semantic HTML with inline styles. + + When users ask for complex layouts like "landing page", "website", or "homepage", generate a COMPLETE multi-section layout, not just a single section. + + Guidelines: + - Generate modern, responsive, and accessible HTML fragments + - Use semantic HTML5 elements (div, section, button, etc.) but NO document-level tags + - Apply inline styles using style attributes + - When design tokens are provided, use CSS variables like var(--token-primary) instead of hardcoded values + - Ensure proper color contrast for accessibility + - Use flexbox or grid for layouts when appropriate + - Keep the HTML structure simple and maintainable + - Include placeholder content that makes sense in context + - Use rem/em units for better scalability + - Add appropriate aria-labels for accessibility + - NEVER include , , , , , or