diff --git a/apps/app/src/app/api/health/route.ts b/apps/app/src/app/api/health/route.ts new file mode 100644 index 0000000..41063cf --- /dev/null +++ b/apps/app/src/app/api/health/route.ts @@ -0,0 +1,3 @@ +export function GET() { + return Response.json({ status: "ok" }); +} diff --git a/apps/app/src/components/generative-ui/save-template-overlay.tsx b/apps/app/src/components/generative-ui/save-template-overlay.tsx index e071bc9..2606ec5 100644 --- a/apps/app/src/components/generative-ui/save-template-overlay.tsx +++ b/apps/app/src/components/generative-ui/save-template-overlay.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback, useMemo, useRef, type ReactNode } from "react"; +import { useState, useCallback, useEffect, useMemo, type ReactNode } from "react"; import { useAgent } from "@copilotkit/react-core/v2"; import { SEED_TEMPLATES } from "@/components/template-library/seed-templates"; @@ -35,25 +35,29 @@ export function SaveTemplateOverlay({ const [saveState, setSaveState] = useState("idle"); const [templateName, setTemplateName] = useState(""); - // Capture pending_template at mount time — it may be cleared by the agent later + // Capture pending_template once — it may be cleared by the agent later. + // Syncs external agent state into local state (legitimate effect-based setState). const pending = agent.state?.pending_template as { id: string; name: string } | null | undefined; - const sourceRef = useRef<{ id: string; name: string } | null>(null); - if (pending?.id && !sourceRef.current) { - sourceRef.current = pending; - } + const [capturedSource, setCapturedSource] = useState<{ id: string; name: string } | null>(null); + useEffect(() => { + if (pending?.id) { + // eslint-disable-next-line react-hooks/set-state-in-effect -- one-time capture of external agent state + setCapturedSource((prev) => prev ?? pending); + } + }, [pending]); // Check if this content matches an existing template: // 1. Exact HTML match (seed templates rendered as-is) // 2. Source template captured from pending_template (applied templates with modified data) const matchedTemplate = useMemo(() => { // First check source template from apply flow - if (sourceRef.current) { + if (capturedSource) { const allTemplates = [ ...SEED_TEMPLATES, ...((agent.state?.templates as { id: string; name: string }[]) || []), ]; - const source = allTemplates.find((t) => t.id === sourceRef.current!.id); - if (source) return source; + const found = allTemplates.find((t) => t.id === capturedSource.id); + if (found) return found; } // Then check exact HTML match if (!html) return null; @@ -64,7 +68,7 @@ export function SaveTemplateOverlay({ ...((agent.state?.templates as { id: string; name: string; html: string }[]) || []), ]; return allTemplates.find((t) => t.html && normalise(t.html) === norm) ?? null; - }, [html, agent.state?.templates]); + }, [html, agent.state?.templates, capturedSource]); const handleSave = useCallback(() => { const name = templateName.trim() || title || "Untitled Template"; diff --git a/render.yaml b/render.yaml index cb296a0..25b182a 100644 --- a/render.yaml +++ b/render.yaml @@ -1,11 +1,16 @@ services: - # ── Agent (LangGraph Python) — private, not exposed to internet ── - - type: pserv + # ── Agent (LangGraph Python) — Docker required for langgraph-api image ── + - type: web name: open-generative-ui-agent runtime: docker plan: starter dockerfilePath: docker/Dockerfile.agent healthCheckPath: /ok + scaling: + minInstances: 1 + maxInstances: 3 + targetMemoryPercent: 80 + targetCPUPercent: 70 envVars: - key: OPENAI_API_KEY sync: false @@ -18,17 +23,28 @@ services: - apps/agent/** - docker/Dockerfile.agent - # ── Frontend (Next.js) — public web service ── + # ── Frontend (Next.js) — native Node runtime ── - type: web name: open-generative-ui-app - runtime: docker + runtime: node plan: starter - dockerfilePath: docker/Dockerfile.app + scaling: + minInstances: 1 + maxInstances: 3 + targetMemoryPercent: 80 + targetCPUPercent: 70 + buildCommand: corepack enable && pnpm install --no-frozen-lockfile && pnpm --filter @repo/app build + startCommand: pnpm --filter @repo/app start + healthCheckPath: /api/health envVars: + - key: NODE_VERSION + value: "22" + - key: SKIP_INSTALL_DEPS + value: "true" - key: LANGGRAPH_DEPLOYMENT_URL fromService: name: open-generative-ui-agent - type: pserv + type: web property: hostport - key: LANGSMITH_API_KEY sync: false @@ -44,4 +60,3 @@ services: - package.json - pnpm-lock.yaml - turbo.json - - docker/Dockerfile.app