- {isLoadingDetails || isLoadingContainers ? (
+ {isDetailsLoading ? (
- ) : !detailsRes?.success ? (
+ ) : null}
+ {showDetailsError ? (
{detailsRes?.message || t('Failed to fetch deployment details')}
- ) : (
+ ) : null}
+ {showDetailsContent ? (
<>
@@ -225,7 +236,9 @@ export function ViewDetailsDialog({
{containers.map((c) => {
const id = c?.container_id
- if (typeof id !== 'string' || !id) return null
+ if (typeof id !== 'string' || !id) {
+ return null
+ }
const status =
typeof c?.status === 'string' ? c.status : undefined
const url =
@@ -263,13 +276,13 @@ export function ViewDetailsDialog({
{t('Raw JSON')}
-
+
{payloadJson || '-'}
>
- )}
+ ) : null}
)
diff --git a/web/default/src/features/models/components/dialogs/view-logs-dialog.tsx b/web/default/src/features/models/components/dialogs/view-logs-dialog.tsx
index 24315029..df1988d4 100644
--- a/web/default/src/features/models/components/dialogs/view-logs-dialog.tsx
+++ b/web/default/src/features/models/components/dialogs/view-logs-dialog.tsx
@@ -1,3 +1,5 @@
+import { useQuery } from '@tanstack/react-query'
+import { Download, Loader2, RefreshCcw, Terminal } from 'lucide-react'
/*
Copyright (C) 2023-2026 QuantumNous
@@ -16,10 +18,10 @@ along with this program. If not, see
.
For commercial licensing, please contact support@quantumnous.com
*/
-import { useEffect, useMemo, useRef, useState } from 'react'
-import { useQuery } from '@tanstack/react-query'
-import { Download, Loader2, RefreshCcw, Terminal } from 'lucide-react'
+import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
+
+import { Dialog } from '@/components/dialog'
import { Button } from '@/components/ui/button'
import {
Select,
@@ -30,7 +32,7 @@ import {
SelectValue,
} from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
-import { Dialog } from '@/components/dialog'
+
import { getDeploymentLogs, listDeploymentContainers } from '../../api'
interface ViewLogsDialogProps {
@@ -114,9 +116,17 @@ export function ViewLogsDialog({
}, [logsData?.data])
const logLines = useMemo(() => {
- const normalized = logsText.replace(/\r\n?/g, '\n')
+ const normalized = logsText.replaceAll(/\r\n?/g, '\n')
return normalized ? normalized.split('\n') : []
}, [logsText])
+ const keyedLogLines = useMemo(() => {
+ const seen = new Map
()
+ return logLines.map((line) => {
+ const count = seen.get(line) ?? 0
+ seen.set(line, count + 1)
+ return { key: `${line}-${count}`, line }
+ })
+ }, [logLines])
// Auto-scroll to bottom
useEffect(() => {
@@ -135,6 +145,44 @@ export function ViewLogsDialog({
a.click()
URL.revokeObjectURL(url)
}
+ let containerPlaceholder = t('Select')
+ if (isLoadingContainers) {
+ containerPlaceholder = t('Loading...')
+ } else if (containers.length === 0) {
+ containerPlaceholder = t('No containers')
+ }
+ let logsContent: ReactNode
+ if (isLoadingContainers || isLoadingLogs) {
+ logsContent = (
+
+
+
+ )
+ } else if (containers.length === 0) {
+ logsContent = (
+ {t('No containers')}
+ )
+ } else if (!containerId) {
+ logsContent = (
+
+ {t('Please select a container')}
+
+ )
+ } else if (!logsText.trim()) {
+ logsContent = (
+ {t('No logs')}
+ )
+ } else {
+ logsContent = (
+
+ {keyedLogLines.map(({ key, line }) => (
+
+ {line}
+
+ ))}
+
+ )
+ }
return (
{
const target = e.target as HTMLDivElement
const isAtBottom =
@@ -287,29 +327,7 @@ export function ViewLogsDialog({
setAutoScroll(isAtBottom)
}}
>
- {isLoadingContainers || isLoadingLogs ? (
-
-
-
- ) : containers.length === 0 ? (
-
- {t('No containers')}
-
- ) : !containerId ? (
-
- {t('Please select a container')}
-
- ) : !logsText.trim() ? (
-
{t('No logs')}
- ) : (
-
- {logLines.map((line, idx) => (
-
- {line}
-
- ))}
-
- )}
+ {logsContent}
)
diff --git a/web/default/src/features/usage-logs/components/model-badge.tsx b/web/default/src/features/usage-logs/components/model-badge.tsx
index 2c8dedd8..7ef3df97 100644
--- a/web/default/src/features/usage-logs/components/model-badge.tsx
+++ b/web/default/src/features/usage-logs/components/model-badge.tsx
@@ -18,14 +18,15 @@ For commercial licensing, please contact support@quantumnous.com
*/
import { Route } from 'lucide-react'
import { useTranslation } from 'react-i18next'
-import { getLobeIcon } from '@/lib/lobe-icon'
-import { cn } from '@/lib/utils'
+
+import { StatusBadge } from '@/components/status-badge'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover'
-import { StatusBadge } from '@/components/status-badge'
+import { getLobeIcon } from '@/lib/lobe-icon'
+import { cn } from '@/lib/utils'
interface ModelBadgeProps {
modelName: string
@@ -109,11 +110,11 @@ function ModelBadgeContent(props: ModelBadgeProps) {
{provider && (
- {getLobeIcon(provider.icon, 14)}
+ {getLobeIcon(provider.icon, 18)}
)}
{props.modelName}
diff --git a/web/default/src/lib/show-submitted-data.tsx b/web/default/src/lib/show-submitted-data.tsx
index 2e9dea5a..99e72383 100644
--- a/web/default/src/lib/show-submitted-data.tsx
+++ b/web/default/src/lib/show-submitted-data.tsx
@@ -25,8 +25,8 @@ export function showSubmittedData(
toast.message(title, {
description: (
// w-[340px]
-
- {JSON.stringify(data, null, 2)}
+
+ {JSON.stringify(data, null, 2)}
),
})
diff --git a/web/default/src/styles/theme-presets.css b/web/default/src/styles/theme-presets.css
index dc237392..d9603332 100644
--- a/web/default/src/styles/theme-presets.css
+++ b/web/default/src/styles/theme-presets.css
@@ -353,32 +353,32 @@ For commercial licensing, please contact support@quantumnous.com
--spacing: 0.3rem;
}
.dark [data-theme-preset='simple-large'] {
- --background: oklch(0.12 0 0);
- --foreground: oklch(0.98 0 0);
- --card: oklch(0.18 0 0);
- --card-foreground: oklch(0.98 0 0);
- --popover: oklch(0.2 0 0);
- --popover-foreground: oklch(0.98 0 0);
+ --background: oklch(0.215 0 0);
+ --foreground: oklch(0.97 0 0);
+ --card: oklch(0.27 0 0);
+ --card-foreground: oklch(0.97 0 0);
+ --popover: oklch(0.29 0 0);
+ --popover-foreground: oklch(0.97 0 0);
--primary: oklch(0.94 0 0);
- --primary-foreground: oklch(0.12 0 0);
- --secondary: oklch(0.24 0 0);
- --secondary-foreground: oklch(0.98 0 0);
- --muted: oklch(0.24 0 0);
+ --primary-foreground: oklch(0.145 0 0);
+ --secondary: oklch(0.315 0 0);
+ --secondary-foreground: oklch(0.97 0 0);
+ --muted: oklch(0.315 0 0);
--muted-foreground: oklch(0.82 0 0);
- --accent: oklch(0.3 0 0);
+ --accent: oklch(0.36 0 0);
--accent-foreground: oklch(0.98 0 0);
--destructive: oklch(0.68 0.19 25);
--destructive-foreground: oklch(0.98 0 0);
--success: oklch(0.72 0.13 145);
- --success-foreground: oklch(0.12 0 0);
+ --success-foreground: oklch(0.145 0 0);
--warning: oklch(0.82 0.13 75);
- --warning-foreground: oklch(0.12 0 0);
+ --warning-foreground: oklch(0.145 0 0);
--info: oklch(0.72 0.12 250);
- --info-foreground: oklch(0.12 0 0);
+ --info-foreground: oklch(0.145 0 0);
--neutral: oklch(0.82 0 0);
- --neutral-foreground: oklch(0.12 0 0);
+ --neutral-foreground: oklch(0.145 0 0);
--border: oklch(1 0 0 / 18%);
--input: oklch(1 0 0 / 24%);
@@ -390,17 +390,17 @@ For commercial licensing, please contact support@quantumnous.com
--chart-4: oklch(0.82 0.13 75);
--chart-5: oklch(0.68 0.19 25);
- --sidebar: oklch(0.16 0 0);
- --sidebar-foreground: oklch(0.98 0 0);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.97 0 0);
--sidebar-primary: oklch(0.94 0 0);
- --sidebar-primary-foreground: oklch(0.12 0 0);
- --sidebar-accent: oklch(0.28 0 0);
+ --sidebar-primary-foreground: oklch(0.145 0 0);
+ --sidebar-accent: oklch(0.34 0 0);
--sidebar-accent-foreground: oklch(0.98 0 0);
--sidebar-border: oklch(1 0 0 / 18%);
--sidebar-ring: oklch(0.94 0 0);
- --skeleton-base: oklch(0.24 0 0);
- --skeleton-highlight: oklch(0.34 0 0);
+ --skeleton-base: oklch(0.315 0 0);
+ --skeleton-highlight: oklch(0.415 0 0);
}
/* ── Semantic surface bridge ──────────────────────────────────────────── */
@@ -552,21 +552,21 @@ For commercial licensing, please contact support@quantumnous.com
.dark [data-theme-preset='anthropic'] {
/* Warm near-black product surfaces, not pure black — keeps the editorial
* personality even when inverted. Coral lifts slightly for legibility. */
- --background: oklch(0.205 0.004 60); /* ≈ #181715 */
- --foreground: oklch(0.965 0.005 92); /* ≈ #faf9f5 */
- --card: oklch(0.245 0.004 60);
+ --background: oklch(0.215 0.004 60);
+ --foreground: oklch(0.965 0.005 92);
+ --card: oklch(0.255 0.004 60);
--card-foreground: oklch(0.965 0.005 92);
- --popover: oklch(0.265 0.004 60);
+ --popover: oklch(0.275 0.004 60);
--popover-foreground: oklch(0.965 0.005 92);
--primary: oklch(0.72 0.135 40);
--primary-foreground: oklch(0.18 0.005 60);
- --secondary: oklch(0.295 0.004 60);
+ --secondary: oklch(0.305 0.004 60);
--secondary-foreground: oklch(0.945 0.005 92);
- --muted: oklch(0.275 0.004 60);
+ --muted: oklch(0.285 0.004 60);
--muted-foreground: oklch(0.76 0.006 75);
- --accent: oklch(0.32 0.006 60);
+ --accent: oklch(0.33 0.006 60);
--accent-foreground: oklch(0.985 0.005 92);
--destructive: oklch(0.7 0.19 22);
@@ -590,17 +590,17 @@ For commercial licensing, please contact support@quantumnous.com
--chart-4: oklch(0.78 0.13 0);
--chart-5: oklch(0.83 0.027 175);
- --sidebar: oklch(0.175 0.004 60);
+ --sidebar: oklch(0.205 0.004 60);
--sidebar-foreground: oklch(0.95 0.005 92);
--sidebar-primary: oklch(0.72 0.135 40);
--sidebar-primary-foreground: oklch(0.18 0.005 60);
- --sidebar-accent: oklch(0.31 0.006 60);
+ --sidebar-accent: oklch(0.32 0.006 60);
--sidebar-accent-foreground: oklch(0.985 0.005 92);
- --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-border: oklch(1 0 0 / 11%);
--sidebar-ring: oklch(0.72 0.135 40);
- --skeleton-base: oklch(0.295 0.004 60);
- --skeleton-highlight: oklch(0.4 0.004 60);
+ --skeleton-base: oklch(0.305 0.004 60);
+ --skeleton-highlight: oklch(0.41 0.004 60);
}
/* ── Font axis ────────────────────────────────────────────────────────────
diff --git a/web/default/src/styles/theme.css b/web/default/src/styles/theme.css
index cd163d4f..45857453 100644
--- a/web/default/src/styles/theme.css
+++ b/web/default/src/styles/theme.css
@@ -142,22 +142,48 @@ For commercial licensing, please contact support@quantumnous.com
--sidebar-ring: oklch(0.708 0 0);
--skeleton-base: oklch(0.97 0 0);
--skeleton-highlight: oklch(0.985 0 0);
+ --table-row: var(--background);
+ --table-header: color-mix(
+ in oklch,
+ var(--foreground) 1.5%,
+ var(--background)
+ );
+ --table-header-hover: color-mix(
+ in oklch,
+ var(--foreground) 3%,
+ var(--background)
+ );
+ --table-disabled: color-mix(
+ in oklch,
+ var(--foreground) 5.5%,
+ var(--background)
+ );
+ --table-disabled-hover: color-mix(
+ in oklch,
+ var(--foreground) 7%,
+ var(--background)
+ );
+ --table-disabled-border: color-mix(
+ in oklch,
+ var(--foreground) 24%,
+ var(--background)
+ );
}
.dark {
- /* OpenAI-like dark mode with a softer charcoal canvas. */
- --background: oklch(0.225 0 0);
+ /* OpenAI-like dark mode with a crisp charcoal canvas. */
+ --background: oklch(0.235 0 0);
--foreground: oklch(0.965 0 0);
- --card: oklch(0.275 0 0);
+ --card: oklch(0.285 0 0);
--card-foreground: oklch(0.965 0 0);
- --popover: oklch(0.295 0 0);
+ --popover: oklch(0.305 0 0);
--popover-foreground: oklch(0.965 0 0);
--primary: oklch(0.965 0 0);
- --primary-foreground: oklch(0.145 0 0);
- --secondary: oklch(0.325 0 0);
+ --primary-foreground: oklch(0.155 0 0);
+ --secondary: oklch(0.335 0 0);
--secondary-foreground: oklch(0.965 0 0);
- --muted: oklch(0.295 0 0);
- --muted-foreground: oklch(0.76 0 0);
+ --muted: oklch(0.305 0 0);
+ --muted-foreground: oklch(0.78 0 0);
--accent: oklch(0.365 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
@@ -169,23 +195,49 @@ For commercial licensing, please contact support@quantumnous.com
--info: oklch(0.68 0.17 237.323);
--info-foreground: oklch(0.145 0 0);
--neutral: oklch(0.76 0 0);
- --neutral-foreground: oklch(0.145 0 0);
- --border: oklch(1 0 0 / 9%);
- --input: oklch(1 0 0 / 16%);
+ --neutral-foreground: oklch(0.155 0 0);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 17%);
--ring: oklch(0.68 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.195 0 0);
+ --sidebar: oklch(0.225 0 0);
--sidebar-foreground: oklch(0.95 0 0);
--sidebar-primary: oklch(0.965 0 0);
- --sidebar-primary-foreground: oklch(0.145 0 0);
- --sidebar-accent: oklch(0.35 0 0);
+ --sidebar-primary-foreground: oklch(0.155 0 0);
+ --sidebar-accent: oklch(0.355 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-border: oklch(1 0 0 / 11%);
--sidebar-ring: oklch(0.68 0 0);
- --skeleton-base: oklch(0.325 0 0);
- --skeleton-highlight: oklch(0.43 0 0);
+ --skeleton-base: oklch(0.335 0 0);
+ --skeleton-highlight: oklch(0.44 0 0);
+ --table-row: var(--background);
+ --table-header: color-mix(
+ in oklch,
+ var(--foreground) 6%,
+ var(--background)
+ );
+ --table-header-hover: color-mix(
+ in oklch,
+ var(--foreground) 9%,
+ var(--background)
+ );
+ --table-disabled: color-mix(
+ in oklch,
+ var(--foreground) 10%,
+ var(--background)
+ );
+ --table-disabled-hover: color-mix(
+ in oklch,
+ var(--foreground) 13%,
+ var(--background)
+ );
+ --table-disabled-border: color-mix(
+ in oklch,
+ var(--foreground) 34%,
+ var(--background)
+ );
}