From 2481009b06eb9b4578ac3c56cd49163698961cfd Mon Sep 17 00:00:00 2001 From: sup Date: Thu, 14 May 2026 16:18:34 +0800 Subject: [PATCH] fix(ui): use feature detection for crypto.randomUUID to support HTTP deployments crypto.randomUUID() requires a Secure Context (HTTPS or localhost). Deployments served over plain HTTP crash on /agents/new with "Uncaught TypeError: crypto.randomUUID is not a function". Add a generateId() utility that detects crypto.randomUUID availability at runtime and falls back to the uuid package (already a dependency) when unavailable. Replace all 8 crypto.randomUUID() call sites. Signed-off-by: wsszh Signed-off-by: sup --- ui/src/app/agents/new/page.tsx | 5 +++-- ui/src/components/prompts/FragmentEntriesEditor.tsx | 9 +++++---- ui/src/lib/openClawSandboxForm.ts | 3 ++- ui/src/lib/promptSourceRow.ts | 4 +++- ui/src/lib/utils.ts | 8 ++++++++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/ui/src/app/agents/new/page.tsx b/ui/src/app/agents/new/page.tsx index 4d4ca211e..a1577e808 100644 --- a/ui/src/app/agents/new/page.tsx +++ b/ui/src/app/agents/new/page.tsx @@ -8,6 +8,7 @@ import { formAgentTypeFromApi, formUsesByoSections, formUsesDeclarativeSections import { ModelConfig, AgentType, ContextConfig, type DeclarativeRuntime } from "@/types"; import { SystemPromptSection } from "@/components/create/SystemPromptSection"; import { newPromptSourceRow, type PromptSourceRow } from "@/lib/promptSourceRow"; +import { generateId } from "@/lib/utils"; import { ModelSelectionSection } from "@/components/create/ModelSelectionSection"; import { ToolsSection } from "@/components/create/ToolsSection"; import { MemorySection } from "@/components/create/MemorySection"; @@ -162,7 +163,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo return { ...prev, errors: { ...prev.errors, promptSources: undefined }, - promptSourceRows: [...nonEmpty, { id: crypto.randomUUID(), name: t, alias: "" }], + promptSourceRows: [...nonEmpty, { id: generateId(), name: t, alias: "" }], }; }); }, []); @@ -207,7 +208,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo const pt = decl?.promptTemplate; const srcRows: PromptSourceRow[] = pt?.dataSources?.map((ds) => ({ - id: crypto.randomUUID(), + id: generateId(), name: ds.name || "", alias: ds.alias || "", })) ?? [newPromptSourceRow()]; diff --git a/ui/src/components/prompts/FragmentEntriesEditor.tsx b/ui/src/components/prompts/FragmentEntriesEditor.tsx index e07bbe876..73124a301 100644 --- a/ui/src/components/prompts/FragmentEntriesEditor.tsx +++ b/ui/src/components/prompts/FragmentEntriesEditor.tsx @@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Plus, Trash2 } from "lucide-react"; +import { generateId } from "@/lib/utils"; export interface FragmentRow { id: string; @@ -16,10 +17,10 @@ export interface FragmentRow { export function rowsFromData(data: Record): FragmentRow[] { const keys = Object.keys(data); if (keys.length === 0) { - return [{ id: crypto.randomUUID(), key: "", value: "" }]; + return [{ id: generateId(), key: "", value: "" }]; } return keys.map((key) => ({ - id: crypto.randomUUID(), + id: generateId(), key, value: data[key] ?? "", })); @@ -51,11 +52,11 @@ export function FragmentEntriesEditor({ const removeRow = (id: string) => { const next = rows.filter((r) => r.id !== id); - onRowsChange(next.length > 0 ? next : [{ id: crypto.randomUUID(), key: "", value: "" }]); + onRowsChange(next.length > 0 ? next : [{ id: generateId(), key: "", value: "" }]); }; const addRow = () => { - onRowsChange([...rows, { id: crypto.randomUUID(), key: "", value: "" }]); + onRowsChange([...rows, { id: generateId(), key: "", value: "" }]); }; return ( diff --git a/ui/src/lib/openClawSandboxForm.ts b/ui/src/lib/openClawSandboxForm.ts index 92e8cced6..f6d844249 100644 --- a/ui/src/lib/openClawSandboxForm.ts +++ b/ui/src/lib/openClawSandboxForm.ts @@ -1,5 +1,6 @@ import type { ValueSource } from "@/types"; import { k8sRefUtils } from "@/lib/k8sUtils"; +import { generateId } from "@/lib/utils"; /** Sandbox CR backend; UI always uses openclaw for now. */ const SANDBOX_BACKEND_OPENCLAW = "openclaw" as const; @@ -26,7 +27,7 @@ export interface OpenClawChannelRow { export function newOpenClawChannelRow(): OpenClawChannelRow { return { - id: crypto.randomUUID(), + id: generateId(), name: "", channelType: "telegram", botTokenSource: "inline", diff --git a/ui/src/lib/promptSourceRow.ts b/ui/src/lib/promptSourceRow.ts index 39604c7a6..bcc652510 100644 --- a/ui/src/lib/promptSourceRow.ts +++ b/ui/src/lib/promptSourceRow.ts @@ -1,3 +1,5 @@ +import { generateId } from "@/lib/utils"; + export interface PromptSourceRow { id: string; name: string; @@ -5,5 +7,5 @@ export interface PromptSourceRow { } export function newPromptSourceRow(): PromptSourceRow { - return { id: crypto.randomUUID(), name: "", alias: "" }; + return { id: generateId(), name: "", alias: "" }; } diff --git a/ui/src/lib/utils.ts b/ui/src/lib/utils.ts index 9e52915fb..e2893d196 100644 --- a/ui/src/lib/utils.ts +++ b/ui/src/lib/utils.ts @@ -1,5 +1,6 @@ import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; +import { v4 as uuidv4 } from "uuid"; import { Message as A2AMessage, Task as A2ATask, TaskStatusUpdateEvent as A2ATaskStatusUpdateEvent, TaskArtifactUpdateEvent as A2ATaskArtifactUpdateEvent } from "@a2a-js/sdk"; export function cn(...inputs: ClassValue[]) { @@ -36,6 +37,13 @@ export function getBackendUrl() { return "http://localhost:8083/api"; } +export function generateId(): string { + if (typeof crypto.randomUUID === "function") { + return crypto.randomUUID(); + } + return uuidv4(); +} + export function getRelativeTimeString(date: string | number | Date): string { const now = new Date(); const past = new Date(date);