Fix crypto.randomUUID runtime error, add logo as default sidebar icon

Replace all crypto.randomUUID() calls with generateId() helper that
falls back to a Math.random-based UUID in non-secure contexts (HTTP,
older browsers). Fixes #1. Also sets the ClawPort logo as the default
sidebar icon in place of the lobster emoji.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
JohnRiceML
2026-03-06 16:51:09 -06:00
co-authored by Claude Opus 4.6
parent 6e6d99507a
commit 95a7bdf26f
11 changed files with 53 additions and 58 deletions
+14 -29
View File
@@ -109,23 +109,16 @@ export function MobileSidebar({
}}
/>
) : (
<span
<img
src="/clawport-logo.png"
alt=""
style={{
width: '24px',
height: '24px',
borderRadius: '6px',
background: settings.accentColor
? `linear-gradient(135deg, ${settings.accentColor}, ${settings.accentColor}dd)`
: 'transparent',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '13px',
width: '48px',
height: '48px',
objectFit: 'contain',
flexShrink: 0,
}}
>
{settings.portalEmoji ?? '\ud83e\udd9e'}
</span>
/>
)}
<span
style={{
@@ -198,24 +191,16 @@ export function MobileSidebar({
}}
/>
) : (
<div
<img
src="/clawport-logo.png"
alt=""
style={{
width: '36px',
height: '36px',
borderRadius: '10px',
background: settings.accentColor
? `linear-gradient(135deg, ${settings.accentColor}, ${settings.accentColor}dd)`
: 'transparent',
boxShadow: 'var(--shadow-card)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '18px',
width: '72px',
height: '72px',
objectFit: 'contain',
flexShrink: 0,
}}
>
{settings.portalEmoji ?? '\ud83e\udd9e'}
</div>
/>
)}
<div>
<div
+7 -17
View File
@@ -51,26 +51,16 @@ export function Sidebar() {
}}
/>
) : (
<div
<img
src="/clawport-logo.png"
alt=""
style={{
width: '36px',
height: '36px',
borderRadius: '10px',
background: settings.iconBgHidden
? 'transparent'
: settings.accentColor
? `linear-gradient(135deg, ${settings.accentColor}, ${settings.accentColor}dd)`
: 'transparent',
boxShadow: settings.iconBgHidden ? 'none' : 'var(--shadow-card)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: settings.iconBgHidden ? '28px' : '18px',
width: '72px',
height: '72px',
objectFit: 'contain',
flexShrink: 0,
}}
>
{settings.portalEmoji ?? '\ud83e\udd9e'}
</div>
/>
)}
<div>
<div
+4 -3
View File
@@ -5,6 +5,7 @@ import type { Agent } from '@/lib/types'
import type { Conversation, ConversationStore, Message, MediaAttachment } from '@/lib/conversations'
import { parseMedia, addMessage, updateLastMessage } from '@/lib/conversations'
import { buildApiContent } from '@/lib/multimodal'
import { generateId } from '@/lib/id'
import { useSettings } from '@/app/settings-provider'
import { FileAttachment } from './FileAttachment'
import { MediaPreview } from './MediaPreview'
@@ -329,14 +330,14 @@ export function ConversationView({ agent, conversation, onUpdate, onBack }: Conv
}
const userMsg: Message = {
id: crypto.randomUUID(),
id: generateId(),
role: 'user',
content,
timestamp: Date.now(),
media: hasMedia ? mediaToSend : undefined,
}
const assistantMsgId = crypto.randomUUID()
const assistantMsgId = generateId()
const assistantMsg: Message = {
id: assistantMsgId,
role: 'assistant',
@@ -572,7 +573,7 @@ export function ConversationView({ agent, conversation, onUpdate, onBack }: Conv
[agent.id]: {
agentId: agent.id,
messages: [{
id: crypto.randomUUID(),
id: generateId(),
role: 'assistant' as const,
content: `I'm ${agent.name}. ${agent.description} What do you need?`,
timestamp: Date.now(),
+3 -2
View File
@@ -6,6 +6,7 @@ import type { Agent } from '@/lib/types'
import type { KanbanTicket, TicketStatus, TicketPriority } from '@/lib/kanban/types'
import { PRIORITY_COLORS, ROLE_LABELS, COLUMNS } from '@/lib/kanban/types'
import { AgentAvatar } from '@/components/AgentAvatar'
import { generateId } from '@/lib/id'
/* ── Chat message type (local to kanban) ─────────────── */
@@ -249,13 +250,13 @@ export function TicketDetailPanel({
if (!text || isStreaming || !agent) return
const userMsg: ChatMessage = {
id: crypto.randomUUID(),
id: generateId(),
role: 'user',
content: text,
timestamp: Date.now(),
}
const assistantMsgId = crypto.randomUUID()
const assistantMsgId = generateId()
const assistantMsg: ChatMessage = {
id: assistantMsgId,
role: 'assistant',
+2 -1
View File
@@ -1,6 +1,7 @@
'use client'
import type { Agent } from './types'
import { generateId } from './id'
export type MediaType = 'image' | 'audio' | 'file'
@@ -56,7 +57,7 @@ export function getOrCreateConversation(store: ConversationStore, agent: Agent):
return {
agentId: agent.id,
messages: [{
id: crypto.randomUUID(),
id: generateId(),
role: 'assistant',
content: `I'm ${agent.name}. ${agent.description} What do you need?`,
timestamp: Date.now(),
+15
View File
@@ -0,0 +1,15 @@
/**
* Generate a unique ID, with a fallback for non-secure contexts
* where crypto.randomUUID() is not available (e.g. HTTP, older browsers).
*/
export function generateId(): string {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID()
}
// Fallback: generate a UUID v4-like string
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0
const v = c === 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
}
+3 -2
View File
@@ -1,6 +1,7 @@
'use client'
import type { KanbanTicket, TeamRole } from './types'
import { generateId } from '../id'
/* ── Role-specific work prompts ──────────────────────── */
@@ -159,8 +160,8 @@ export function persistWorkChat(
): void {
const now = Date.now()
const messages = [
{ id: crypto.randomUUID(), role: 'user' as const, content: prompt, timestamp: now },
{ id: crypto.randomUUID(), role: 'assistant' as const, content: response, timestamp: now + 1 },
{ id: generateId(), role: 'user' as const, content: prompt, timestamp: now },
{ id: generateId(), role: 'assistant' as const, content: response, timestamp: now + 1 },
]
fetch(`/api/kanban/chat-history/${ticketId}`, {
+2 -1
View File
@@ -1,6 +1,7 @@
'use client'
import type { KanbanTicket, TicketStatus, TicketPriority, WorkState } from './types'
import { generateId } from '../id'
export type KanbanStore = Record<string, KanbanTicket>
@@ -82,7 +83,7 @@ export function createTicket(
workResult?: KanbanTicket['workResult']
},
): KanbanStore {
const id = crypto.randomUUID()
const id = generateId()
const now = Date.now()
return {
...store,
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "clawport-ui",
"version": "0.5.5",
"version": "0.6.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "clawport-ui",
"version": "0.5.5",
"version": "0.6.1",
"license": "MIT",
"dependencies": {
"@dagrejs/dagre": "^2.0.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawport-ui",
"version": "0.6.0",
"version": "0.6.1",
"description": "Open-source dashboard for managing, monitoring, and chatting with your OpenClaw AI agents.",
"homepage": "https://clawport.dev",
"repository": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB