Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions agentex-ui/app/agentex-ui-root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use client';

import { Suspense, useCallback, useEffect, useState } from 'react';

import { ToastContainer } from 'react-toastify';

import { TaskSidebar } from '@/components/agentex/task-sidebar';
import { TracesSidebar } from '@/components/agentex/traces-sidebar';
import { AgentexProvider } from '@/components/providers';
import { QueryProvider } from '@/components/providers/query-provider';
import { useLocalStorageState } from '@/hooks/use-local-storage-state';
import {
SearchParamKey,
useSafeSearchParams,
} from '@/hooks/use-safe-search-params';

import { PrimaryContent } from './primary-content';

type AgentexUIRootProps = {
sgpAppURL: string;
agentexAPIBaseURL: string;
};

export function AgentexUIRoot({
sgpAppURL,
agentexAPIBaseURL,
}: AgentexUIRootProps) {
const { agentName, updateParams } = useSafeSearchParams();
const [isTracesSidebarOpen, setIsTracesSidebarOpen] = useState(false);
const [localAgentName] = useLocalStorageState<string | undefined>(
'lastSelectedAgent',
undefined
);

useEffect(() => {
if (!agentName && localAgentName) {
updateParams({ [SearchParamKey.AGENT_NAME]: localAgentName });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Comment thread
MichaelSun48 marked this conversation as resolved.

const handleSelectTask = useCallback(
(taskId: string | null) => {
updateParams({
[SearchParamKey.TASK_ID]: taskId,
});
},
[updateParams]
);

// Global keyboard shortcut: cmd + k for new chat
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
event.preventDefault();
handleSelectTask(null);
}
};

window.addEventListener('keydown', handleKeyDown);

return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, [handleSelectTask]);

return (
<QueryProvider>
<AgentexProvider
sgpAppURL={sgpAppURL ?? ''}
agentexAPIBaseURL={agentexAPIBaseURL}
>
<Suspense fallback={<div>Loading...</div>}>
<div className="fixed inset-0 flex w-full">
<TaskSidebar />
<PrimaryContent
isTracesSidebarOpen={isTracesSidebarOpen}
toggleTracesSidebar={() =>
setIsTracesSidebarOpen(!isTracesSidebarOpen)
}
/>
<TracesSidebar isOpen={isTracesSidebarOpen} />
Comment on lines +75 to +82
Copy link
Copy Markdown
Contributor Author

@MichaelSun48 MichaelSun48 Oct 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note here — hopefully this is much more intuitive and conveys the three part page structure we have

</div>
</Suspense>
</AgentexProvider>
<ToastContainer />
</QueryProvider>
);
}
30 changes: 30 additions & 0 deletions agentex-ui/app/chat-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useRef } from 'react';

import { motion } from 'framer-motion';

import { MemoizedTaskMessagesComponent } from '@/components/agentex/task-messages';
import { TaskProvider } from '@/components/providers';

export function ChatView({ taskID }: { taskID: string }) {
const scrollContainerRef = useRef<HTMLDivElement>(null);

return (
<motion.div
key="chat-view"
layout
ref={scrollContainerRef}
className="relative flex-1 overflow-y-auto"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.25, ease: 'easeInOut' }}
>
<div className="flex min-h-full w-full flex-col items-center px-4 sm:px-6 md:px-8">
<div className="w-full max-w-3xl">
<TaskProvider taskId={taskID}>
<MemoizedTaskMessagesComponent taskId={taskID} />
</TaskProvider>
</div>
</div>
</motion.div>
);
}
46 changes: 46 additions & 0 deletions agentex-ui/app/home-view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { motion } from 'framer-motion';

import { AgentsList } from '@/components/agentex/agents-list';
import { useAgentexClient } from '@/components/providers/agentex-provider';
import { useAgents } from '@/hooks/use-agents';
import { useLocalStorageState } from '@/hooks/use-local-storage-state';
import {
SearchParamKey,
useSafeSearchParams,
} from '@/hooks/use-safe-search-params';

export function HomeView() {
const { agentName, updateParams } = useSafeSearchParams();
const { agentexClient } = useAgentexClient();
const { data: agents = [], isLoading } = useAgents(agentexClient);
const [, setLocalAgentName] = useLocalStorageState<string | undefined>(
'lastSelectedAgent',
undefined
);

return (
<motion.div
key="home-view"
className="flex items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.25, ease: 'easeInOut' }}
>
<div className="flex flex-col items-center justify-center px-4">
<div className="mb-6 text-2xl font-bold">Agentex</div>
<AgentsList agents={agents} isLoading={isLoading} />
<button
onClick={() => {
updateParams({ [SearchParamKey.AGENT_NAME]: null });
setLocalAgentName(undefined);
}}
className={`text-muted-foreground cursor-pointer text-xs transition-opacity duration-200 hover:underline ${
agentName ? 'opacity-100' : 'pointer-events-none opacity-0'
}`}
>
view all agents
</button>
</div>
</motion.div>
);
}
Loading