From 1413a18257219732d0194949cd3a1fe14c8ecaad Mon Sep 17 00:00:00 2001 From: "Optimus (AI Agent)" Date: Thu, 5 Mar 2026 10:38:10 +0000 Subject: [PATCH 1/3] feat: add configurable UI headline and subtitle via environment variables Allow customizing the chat headline text and the landing page subtitle via NEXT_PUBLIC_HEADLINE and NEXT_PUBLIC_SUBTITLE environment variables, enabling enterprise deployments to personalize the UI without forking. Closes #1345 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Optimus (AI Agent) --- .../agents/[namespace]/[name]/chat/[chatId]/page.tsx | 1 + ui/src/app/agents/[namespace]/[name]/chat/page.tsx | 5 +++-- ui/src/app/agents/page.tsx | 8 ++++++-- ui/src/app/page.tsx | 5 ++++- ui/src/components/AgentList.tsx | 11 +++++++++-- ui/src/components/chat/ChatInterface.tsx | 5 +++-- 6 files changed, 26 insertions(+), 9 deletions(-) diff --git a/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx b/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx index 42ba1f5a1..30c951dc3 100644 --- a/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx +++ b/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx @@ -9,5 +9,6 @@ export default function ChatPageView({ params }: { params: Promise<{ name: strin selectedAgentName={name} selectedNamespace={namespace} sessionId={chatId} + headline={process.env.NEXT_PUBLIC_HEADLINE} />; } diff --git a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx index 78620d94e..afbead041 100644 --- a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx +++ b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx @@ -4,5 +4,6 @@ import { use } from 'react'; // This page component receives props (like params) from the Layout export default function ChatAgentPage({ params }: { params: Promise<{ name: string, namespace: string }> }) { const { name, namespace } = use(params); - return ; -} \ No newline at end of file + const headline = process.env.NEXT_PUBLIC_HEADLINE; + return ; +} diff --git a/ui/src/app/agents/page.tsx b/ui/src/app/agents/page.tsx index 6a32de824..6d4323b29 100644 --- a/ui/src/app/agents/page.tsx +++ b/ui/src/app/agents/page.tsx @@ -1,4 +1,8 @@ import AgentList from "@/components/AgentList"; -export default function AgentListPage() { - return ; + +export const dynamic = "force-dynamic"; + +export default async function AgentListPage() { + const subtitle = process.env.NEXT_PUBLIC_SUBTITLE; + return ; } diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx index c99ffadc1..6d4323b29 100644 --- a/ui/src/app/page.tsx +++ b/ui/src/app/page.tsx @@ -1,5 +1,8 @@ import AgentList from "@/components/AgentList"; +export const dynamic = "force-dynamic"; + export default async function AgentListPage() { - return ; + const subtitle = process.env.NEXT_PUBLIC_SUBTITLE; + return ; } diff --git a/ui/src/components/AgentList.tsx b/ui/src/components/AgentList.tsx index 2cda4f683..efd88dda3 100644 --- a/ui/src/components/AgentList.tsx +++ b/ui/src/components/AgentList.tsx @@ -8,7 +8,11 @@ import { Button } from "./ui/button"; import { LoadingState } from "./LoadingState"; import { useAgents } from "./AgentsProvider"; -export default function AgentList() { +interface AgentListProps { + subtitle?: string; +} + +export default function AgentList({ subtitle }: AgentListProps) { const { agents , loading, error } = useAgents(); if (error) { @@ -23,7 +27,10 @@ export default function AgentList() {
-

Agents

+
+

Agents

+ {subtitle &&

{subtitle}

} +
diff --git a/ui/src/components/chat/ChatInterface.tsx b/ui/src/components/chat/ChatInterface.tsx index cbf01b287..404a5095f 100644 --- a/ui/src/components/chat/ChatInterface.tsx +++ b/ui/src/components/chat/ChatInterface.tsx @@ -33,9 +33,10 @@ interface ChatInterfaceProps { selectedNamespace: string; selectedSession?: Session | null; sessionId?: string; + headline?: string; } -export default function ChatInterface({ selectedAgentName, selectedNamespace, selectedSession, sessionId }: ChatInterfaceProps) { +export default function ChatInterface({ selectedAgentName, selectedNamespace, selectedSession, sessionId, headline }: ChatInterfaceProps) { const router = useRouter(); const containerRef = useRef(null); const [currentInputMessage, setCurrentInputMessage] = useState(""); @@ -626,7 +627,7 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se ) : storedMessages.length === 0 && streamingMessages.length === 0 && !isStreaming ? (
-

Start a conversation

+

{headline || "Start a conversation"}

To begin chatting with the agent, type your message in the input box below.

From 7dce822f2b33c3e626ddbfc9dd0b928fe937aa09 Mon Sep 17 00:00:00 2001 From: "Optimus (AI Agent)" Date: Thu, 5 Mar 2026 11:02:45 +0000 Subject: [PATCH 2/3] fix: ensure runtime env var reading for configurable headline - Convert chat pages to async server components so process.env reads happen at runtime instead of build time - Add force-dynamic to all routes reading env vars - Use nullish coalescing (??) instead of logical OR (||) for headline fallback to allow intentionally empty strings Co-Authored-By: Claude Opus 4.6 Signed-off-by: Optimus (AI Agent) --- .../app/agents/[namespace]/[name]/chat/[chatId]/page.tsx | 8 ++++---- ui/src/app/agents/[namespace]/[name]/chat/page.tsx | 7 ++++--- ui/src/components/chat/ChatInterface.tsx | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx b/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx index 30c951dc3..259eea2e8 100644 --- a/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx +++ b/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx @@ -1,9 +1,9 @@ -"use client"; -import { use } from "react"; import ChatInterface from "@/components/chat/ChatInterface"; -export default function ChatPageView({ params }: { params: Promise<{ name: string; namespace: string; chatId: string }> }) { - const { name, namespace, chatId } = use(params); +export const dynamic = "force-dynamic"; + +export default async function ChatPageView({ params }: { params: Promise<{ name: string; namespace: string; chatId: string }> }) { + const { name, namespace, chatId } = await params; return }) { - const { name, namespace } = use(params); +export default async function ChatAgentPage({ params }: { params: Promise<{ name: string, namespace: string }> }) { + const { name, namespace } = await params; const headline = process.env.NEXT_PUBLIC_HEADLINE; return ; } diff --git a/ui/src/components/chat/ChatInterface.tsx b/ui/src/components/chat/ChatInterface.tsx index 404a5095f..3dc5ab734 100644 --- a/ui/src/components/chat/ChatInterface.tsx +++ b/ui/src/components/chat/ChatInterface.tsx @@ -627,7 +627,7 @@ export default function ChatInterface({ selectedAgentName, selectedNamespace, se ) : storedMessages.length === 0 && streamingMessages.length === 0 && !isStreaming ? (
-

{headline || "Start a conversation"}

+

{headline ?? "Start a conversation"}

To begin chatting with the agent, type your message in the input box below.

From 00705e2e0529fd9a2df076749889a473bfe50f99 Mon Sep 17 00:00:00 2001 From: "Optimus (AI Agent)" Date: Wed, 11 Mar 2026 15:18:29 +0000 Subject: [PATCH 3/3] fix: use runtime env vars instead of NEXT_PUBLIC_ build-time vars NEXT_PUBLIC_* env vars are statically replaced at build time by the Next.js compiler, even in server components. Since these variables are only read in server components (and passed as props to client components), they don't need the NEXT_PUBLIC_ prefix. Renamed to KAGENT_UI_HEADLINE and KAGENT_UI_SUBTITLE so they are read from process.env at request time, making them truly configurable without rebuilding the Docker image. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Optimus (AI Agent) --- ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx | 2 +- ui/src/app/agents/[namespace]/[name]/chat/page.tsx | 2 +- ui/src/app/agents/page.tsx | 2 +- ui/src/app/page.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx b/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx index 259eea2e8..847084ce8 100644 --- a/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx +++ b/ui/src/app/agents/[namespace]/[name]/chat/[chatId]/page.tsx @@ -9,6 +9,6 @@ export default async function ChatPageView({ params }: { params: Promise<{ name: selectedAgentName={name} selectedNamespace={namespace} sessionId={chatId} - headline={process.env.NEXT_PUBLIC_HEADLINE} + headline={process.env.KAGENT_UI_HEADLINE} />; } diff --git a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx index 5b8734b46..8692c6992 100644 --- a/ui/src/app/agents/[namespace]/[name]/chat/page.tsx +++ b/ui/src/app/agents/[namespace]/[name]/chat/page.tsx @@ -5,6 +5,6 @@ export const dynamic = "force-dynamic"; // This page component receives props (like params) from the Layout export default async function ChatAgentPage({ params }: { params: Promise<{ name: string, namespace: string }> }) { const { name, namespace } = await params; - const headline = process.env.NEXT_PUBLIC_HEADLINE; + const headline = process.env.KAGENT_UI_HEADLINE; return ; } diff --git a/ui/src/app/agents/page.tsx b/ui/src/app/agents/page.tsx index 6d4323b29..fece044e4 100644 --- a/ui/src/app/agents/page.tsx +++ b/ui/src/app/agents/page.tsx @@ -3,6 +3,6 @@ import AgentList from "@/components/AgentList"; export const dynamic = "force-dynamic"; export default async function AgentListPage() { - const subtitle = process.env.NEXT_PUBLIC_SUBTITLE; + const subtitle = process.env.KAGENT_UI_SUBTITLE; return ; } diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx index 6d4323b29..fece044e4 100644 --- a/ui/src/app/page.tsx +++ b/ui/src/app/page.tsx @@ -3,6 +3,6 @@ import AgentList from "@/components/AgentList"; export const dynamic = "force-dynamic"; export default async function AgentListPage() { - const subtitle = process.env.NEXT_PUBLIC_SUBTITLE; + const subtitle = process.env.KAGENT_UI_SUBTITLE; return ; }