feat(webui): wire version badge + system config dialog into header, admin gate
This commit is contained in:
+58
-25
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useCallback, Suspense } from "react";
|
||||
import Image from "next/image";
|
||||
import { useQueryState } from "nuqs";
|
||||
import { AccountDialog } from "@/app/components/AccountDialog";
|
||||
import { ChangePasswordDialog } from "@/app/components/ChangePasswordDialog";
|
||||
import { ConfigDialog } from "@/app/components/ConfigDialog";
|
||||
import { ErrorLogBell } from "@/app/components/ErrorLogBell";
|
||||
import { ImageModelsDialog } from "@/app/components/ImageModelsDialog";
|
||||
import { SystemConfigDialog } from "@/app/components/system-config/SystemConfigDialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
PanelRight,
|
||||
PanelRightClose,
|
||||
LogOut,
|
||||
ServerCog,
|
||||
SlidersHorizontal,
|
||||
UserRound,
|
||||
} from "lucide-react";
|
||||
@@ -44,9 +45,15 @@ import { SkillsMarketplace } from "@/app/components/SkillsMarketplace";
|
||||
import { MemoryPanel } from "@/app/components/MemoryPanel";
|
||||
import { ScheduledTasksPanel } from "@/app/components/ScheduledTasksPanel";
|
||||
import { ThemeToggle } from "@/app/components/ThemeToggle";
|
||||
import { VersionBadge } from "@/app/components/VersionBadge";
|
||||
import { HealthIndicator } from "@/app/components/HealthIndicator";
|
||||
import { InspectorPanel } from "@/app/components/InspectorPanel";
|
||||
import { setThreadAutoApprove } from "@/lib/autoApprove";
|
||||
import {
|
||||
DEFAULT_WORDMARK,
|
||||
logoSrc,
|
||||
usePublicSystemConfig,
|
||||
} from "@/app/hooks/usePublicSystemConfig";
|
||||
import type { MainChatReporter } from "@/lib/asyncAgents";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -54,9 +61,11 @@ const FALLBACK_ASSISTANT_ID = "EvoScientist";
|
||||
|
||||
interface HomePageInnerProps {
|
||||
authEnabled: boolean;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
function HomePageInner({ authEnabled }: HomePageInnerProps) {
|
||||
function HomePageInner({ authEnabled, isAdmin }: HomePageInnerProps) {
|
||||
const publicConfig = usePublicSystemConfig();
|
||||
const [threadId, setThreadId] = useQueryState("threadId");
|
||||
const [sidebar, setSidebar] = useQueryState("sidebar");
|
||||
const [view, setView] = useQueryState("view");
|
||||
@@ -75,6 +84,7 @@ function HomePageInner({ authEnabled }: HomePageInnerProps) {
|
||||
const [changePasswordOpen, setChangePasswordOpen] = useState(false);
|
||||
const [modelConfigOpen, setModelConfigOpen] = useState(false);
|
||||
const [imageModelsOpen, setImageModelsOpen] = useState(false);
|
||||
const [systemConfigOpen, setSystemConfigOpen] = useState(false);
|
||||
// "Submit a message on the main thread" — registered by ChatInterface (only
|
||||
// while it's mounted, i.e. on the chat view), used by the Agents board to loop
|
||||
// an async result back to the main agent. Null when not on the chat view.
|
||||
@@ -293,25 +303,31 @@ function HomePageInner({ authEnabled }: HomePageInnerProps) {
|
||||
open={imageModelsOpen}
|
||||
onOpenChange={setImageModelsOpen}
|
||||
/>
|
||||
<SystemConfigDialog
|
||||
open={systemConfigOpen}
|
||||
onOpenChange={setSystemConfigOpen}
|
||||
/>
|
||||
<div className="flex h-screen flex-col">
|
||||
<header className="flex h-14 items-center justify-between gap-2 border-b border-border px-2.5 sm:px-4">
|
||||
<div className="flex min-w-0 items-center gap-2 sm:gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<Image
|
||||
src="/evoscientist-logo.png"
|
||||
alt="EvoScientist"
|
||||
width={28}
|
||||
height={28}
|
||||
<img
|
||||
src={logoSrc(publicConfig)}
|
||||
alt={publicConfig?.wordmark ?? DEFAULT_WORDMARK}
|
||||
className="size-6 shrink-0"
|
||||
priority
|
||||
/>
|
||||
{/* Show the wordmark only when the thread sidebar is open (it
|
||||
titles the panel). When collapsed, keep just the logo + the
|
||||
toggle / new-chat icons for a compact header. */}
|
||||
{sidebar && (
|
||||
<h1 className="truncate text-base font-semibold sm:text-lg">
|
||||
EvoScientist
|
||||
</h1>
|
||||
<div className="flex min-w-0 flex-col justify-center">
|
||||
<h1 className="truncate text-sm font-semibold leading-tight sm:text-base">
|
||||
{publicConfig?.wordmark ?? DEFAULT_WORDMARK}
|
||||
</h1>
|
||||
<div className="-ml-2 -my-1">
|
||||
<VersionBadge />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5">
|
||||
@@ -394,19 +410,6 @@ function HomePageInner({ authEnabled }: HomePageInnerProps) {
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setImageModelsOpen(true)}
|
||||
aria-label="Image models"
|
||||
title="Image models"
|
||||
className="size-8"
|
||||
>
|
||||
<ImageIcon
|
||||
className="size-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Button>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
@@ -449,6 +452,16 @@ function HomePageInner({ authEnabled }: HomePageInnerProps) {
|
||||
<SlidersHorizontal aria-hidden="true" />
|
||||
Model configuration
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={() => setImageModelsOpen(true)}>
|
||||
<ImageIcon aria-hidden="true" />
|
||||
Image models
|
||||
</DropdownMenuItem>
|
||||
{isAdmin && (
|
||||
<DropdownMenuItem onSelect={() => setSystemConfigOpen(true)}>
|
||||
<ServerCog aria-hidden="true" />
|
||||
System config
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -602,6 +615,7 @@ function HomePageInner({ authEnabled }: HomePageInnerProps) {
|
||||
|
||||
function HomePageContent() {
|
||||
const [authEnabled, setAuthEnabled] = useState(false);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -618,7 +632,26 @@ function HomePageContent() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <HomePageInner authEnabled={authEnabled} />;
|
||||
useEffect(() => {
|
||||
if (!authEnabled) {
|
||||
setIsAdmin(true); // auth disabled → every request acts as local admin
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
fetch("/api/auth/me", { cache: "no-store" })
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.then((body: { role?: unknown } | null) => {
|
||||
if (!cancelled) setIsAdmin(body?.role === "admin");
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setIsAdmin(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [authEnabled]);
|
||||
|
||||
return <HomePageInner authEnabled={authEnabled} isAdmin={isAdmin} />;
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
|
||||
Reference in New Issue
Block a user