Skip to content

Commit 4d29dec

Browse files
authored
misc UX/UI fixes (#114)
* cleanup, removing unused code, mobile header Signed-off-by: Peter Jausovec <[email protected]> * improve the ux for the tools dialog Signed-off-by: Peter Jausovec <[email protected]> * fix duplicate message issue Signed-off-by: Peter Jausovec <[email protected]> --------- Signed-off-by: Peter Jausovec <[email protected]>
1 parent 974c65d commit 4d29dec

18 files changed

+788
-668
lines changed

ui/src/app/actions/tools.ts

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export async function getBuiltInTools(): Promise<BaseResponse<Component<ToolConf
2323
}
2424

2525
export async function discoverMCPTools(payload: DiscoverToolsRequest): Promise<BaseResponse<Tool[]>> {
26-
console.log("Discovering tools with payload:", JSON.stringify(payload));
2726
try {
2827
const response = await fetchApi<Tool[]>(`/tools/discover`, {
2928
method: "POST",

ui/src/app/agents/[id]/chat/[chatId]/page.tsx

+21-13
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,42 @@
11
"use client";
2-
import { use } from "react";
2+
import { use, useEffect, useState } from "react";
33
import { LoadingState } from "@/components/LoadingState";
44
import ChatInterface from "@/components/chat/ChatInterface";
5-
import { useChatData } from "@/lib/useChatData"; // Adjust path as needed
6-
import { useSessionActions } from "@/lib/useSessionActions";
75
import { AgentDetailsSidebar } from "@/components/sidebars/AgentDetailsSidebar";
86
import SessionsSidebar from "@/components/sidebars/SessionsSidebar";
97
import { SidebarProvider } from "@/components/ui/sidebar";
8+
import useChatStore from "@/lib/useChatStore";
109

1110
export default function ChatPageView({ params }: { params: Promise<{ id: string; chatId: string }> }) {
1211
const { id, chatId } = use(params);
13-
const [chatData, chatActions] = useChatData({
14-
agentId: id,
15-
chatId,
16-
});
12+
const { loadChat, run, session } = useChatStore();
13+
const [loading, setLoading] = useState(true);
1714

18-
const { createNewSession } = useSessionActions({
19-
agentId: id,
20-
handleNewSession: chatActions.handleNewSession,
21-
});
15+
useEffect(() => {
16+
const loadData = async (chatId: string) => {
17+
try {
18+
await loadChat(chatId);
19+
} catch (e) {
20+
console.error(e);
21+
} finally {
22+
setLoading(false);
23+
}
24+
};
2225

23-
if (chatData.isLoading || !chatData.agent) {
26+
if (chatId) {
27+
loadData(chatId);
28+
}
29+
}, [chatId, loadChat]);
30+
31+
if (loading) {
2432
return <LoadingState />;
2533
}
2634

2735
return (
2836
<SidebarProvider>
2937
<SessionsSidebar agentId={id} />
3038
<main className="w-full max-w-6xl mx-auto">
31-
<ChatInterface selectedTeamId={id} onNewSession={createNewSession} selectedRun={chatData.viewState?.run} selectedSession={chatData.viewState?.session} />;
39+
<ChatInterface selectedTeamId={id} selectedRun={run} selectedSession={session} />;
3240
</main>
3341
<AgentDetailsSidebar selectedTeamId={id} />
3442
</SidebarProvider>

ui/src/components/Header.tsx

+57-13
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,84 @@
1+
'use client'
2+
import { useState } from "react";
13
import Link from "next/link";
2-
// import { ThemeToggle } from "./theme-toggle";
34
import { Button } from "./ui/button";
45
import KAgentLogoWithText from "./kagent-logo-text";
5-
import { Plus } from "lucide-react";
6+
import { Plus, Menu, X } from "lucide-react";
67
import { ThemeToggle } from "./ThemeToggle";
78

89
export function Header() {
10+
const [isMenuOpen, setIsMenuOpen] = useState(false);
11+
12+
const toggleMenu = () => {
13+
setIsMenuOpen(!isMenuOpen);
14+
};
15+
916
return (
10-
<nav className="py-8">
11-
<div className="max-w-6xl mx-auto px-6">
17+
<nav className="py-4 md:py-8">
18+
<div className="max-w-4xl mx-auto px-4 md:px-6">
1219
<div className="flex justify-between items-center">
1320
<Link href="/">
1421
<KAgentLogoWithText className="h-5" />
1522
</Link>
16-
<div className="flex items-center space-x-8">
23+
24+
{/* Mobile menu button */}
25+
<button
26+
className="md:hidden p-2 focus:outline-none"
27+
onClick={toggleMenu}
28+
aria-label="Toggle menu"
29+
>
30+
{isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
31+
</button>
32+
33+
{/* Desktop navigation */}
34+
<div className="hidden md:flex items-center space-x-6 lg:space-x-8">
1735
<Button variant="link" className="text-secondary-foreground" asChild>
1836
<Link href="/agents">My Agents</Link>
1937
</Button>
20-
<Button variant="link" className="text-secondary-foreground " asChild>
21-
<Link href={"https://github.com/kagent-dev/kagent"} target="_blank">Contribute</Link>
38+
<Button variant="link" className="text-secondary-foreground" asChild>
39+
<Link href="https://github.com/kagent-dev/kagent" target="_blank">Contribute</Link>
2240
</Button>
23-
<Button variant="link" className="text-secondary-foreground " asChild>
24-
<Link href={"https://discord.gg/Fu3k65f2k3"}>Join the community</Link>
41+
<Button variant="link" className="text-secondary-foreground" asChild>
42+
<Link href="https://discord.gg/Fu3k65f2k3">Join the community</Link>
2543
</Button>
26-
<div className="">
27-
<ThemeToggle />
44+
<div>
45+
<ThemeToggle />
2846
</div>
29-
<Button variant={"default"} asChild>
47+
<Button variant="default" asChild>
3048
<Link href="/agents/new">
3149
<Plus className="h-4 w-4 mr-2" />
3250
New Agent
3351
</Link>
3452
</Button>
3553
</div>
3654
</div>
55+
56+
{/* Mobile menu */}
57+
{isMenuOpen && (
58+
<div className="md:hidden pt-4 pb-2 animate-in fade-in slide-in-from-top duration-300">
59+
<div className="flex flex-col space-y-3">
60+
<Button variant="link" className="text-secondary-foreground justify-start px-1" asChild>
61+
<Link href="/agents">My Agents</Link>
62+
</Button>
63+
<Button variant="link" className="text-secondary-foreground justify-start px-1" asChild>
64+
<Link href="https://github.com/kagent-dev/kagent" target="_blank">Contribute</Link>
65+
</Button>
66+
<Button variant="link" className="text-secondary-foreground justify-start px-1" asChild>
67+
<Link href="https://discord.gg/Fu3k65f2k3">Join the community</Link>
68+
</Button>
69+
<div className="flex items-center justify-between py-2">
70+
<Button variant="default" size="sm" asChild>
71+
<Link href="/agents/new">
72+
<Plus className="h-4 w-4 mr-2" />
73+
New Agent
74+
</Link>
75+
</Button>
76+
<ThemeToggle />
77+
</div>
78+
</div>
79+
</div>
80+
)}
3781
</div>
3882
</nav>
3983
);
40-
}
84+
}

ui/src/components/chat/ChatInterface.tsx

+14-102
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,27 @@
22

33
import type React from "react";
44
import { useState, useRef, useEffect } from "react";
5-
import { ArrowLeft, ArrowRightFromLine, ArrowBigUp, AlertTriangle, CheckCircle, MessageSquare, StopCircle, X } from "lucide-react";
5+
import { ArrowBigUp, X } from "lucide-react";
66
import { Button } from "@/components/ui/button";
77
import { Textarea } from "@/components/ui/textarea";
8-
import type { Message, RunStatus, Session, Run } from "@/types/datamodel";
8+
import type { Session, Run } from "@/types/datamodel";
99
import { ScrollArea } from "@/components/ui/scroll-area";
1010
import ChatMessage from "@/components/chat/ChatMessage";
1111
import useChatStore from "@/lib/useChatStore";
12-
import { ChatStatus } from "@/lib/ws";
1312
import StreamingMessage from "./StreamingMessage";
1413
import NoMessagesState from "./NoMessagesState";
15-
16-
interface TokenStats {
17-
total: number;
18-
input: number;
19-
output: number;
20-
}
21-
22-
function calculateTokenStats(messages: Message[]): TokenStats {
23-
return messages.reduce(
24-
(stats, message) => {
25-
const usage = message.config?.models_usage;
26-
if (usage) {
27-
return {
28-
total: stats.total + (usage.prompt_tokens + usage.completion_tokens),
29-
input: stats.input + usage.prompt_tokens,
30-
output: stats.output + usage.completion_tokens,
31-
};
32-
}
33-
return stats;
34-
},
35-
{ total: 0, input: 0, output: 0 }
36-
);
37-
}
14+
import TokenStatsDisplay, { calculateTokenStats } from "./TokenStats";
15+
import { TokenStats } from "@/lib/types";
16+
import StatusDisplay from "./StatusDisplay";
17+
import Link from "next/link";
3818

3919
interface ChatInterfaceProps {
4020
selectedTeamId: string;
4121
selectedSession?: Session | null;
4222
selectedRun?: Run | null;
43-
onNewSession?: () => void;
4423
}
4524

46-
export default function ChatInterface({ selectedTeamId, selectedRun, onNewSession }: ChatInterfaceProps) {
25+
export default function ChatInterface({ selectedTeamId, selectedRun }: ChatInterfaceProps) {
4726
const containerRef = useRef<HTMLDivElement>(null);
4827
const [message, setMessage] = useState("");
4928
const [tokenStats, setTokenStats] = useState<TokenStats>({
@@ -98,62 +77,6 @@ export default function ChatInterface({ selectedTeamId, selectedRun, onNewSessio
9877
setMessage("");
9978
};
10079

101-
const getStatusDisplay = (chatStatus: ChatStatus, runStatus?: RunStatus) => {
102-
switch (chatStatus) {
103-
case "error":
104-
return (
105-
<div className=" text-xs justify-center items-center flex">
106-
<AlertTriangle size={16} className="mr-2 text-red-500" />
107-
{run?.error_message || "An error occurred"}
108-
</div>
109-
);
110-
111-
case "ready":
112-
default:
113-
// If run status is available and indicates a completed state, show that instead
114-
if (runStatus) {
115-
switch (runStatus) {
116-
case "complete":
117-
return (
118-
<div className=" text-xs justify-center items-center flex">
119-
<CheckCircle size={16} className="mr-2 text-green-500" />
120-
Task completed
121-
</div>
122-
);
123-
case "error":
124-
case "timeout":
125-
return (
126-
<div className=" text-xs justify-center items-center flex">
127-
<AlertTriangle size={16} className="mr-2 text-red-500" />
128-
{run?.error_message || "An error occurred"}
129-
</div>
130-
);
131-
case "stopped":
132-
return (
133-
<div className=" text-xs justify-center items-center flex">
134-
<StopCircle size={16} className="mr-2 text-orange-500" />
135-
Task was stopped
136-
</div>
137-
);
138-
default:
139-
return (
140-
<div className=" text-xs justify-center items-center flex">
141-
<MessageSquare size={16} className="mr-2" />
142-
Ready
143-
</div>
144-
);
145-
}
146-
}
147-
148-
return (
149-
<div className=" text-xs justify-center items-center flex">
150-
<MessageSquare size={16} className="mr-2 text-white" />
151-
Ready
152-
</div>
153-
);
154-
}
155-
};
156-
15780
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
15881
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
15982
e.preventDefault();
@@ -177,7 +100,7 @@ export default function ChatInterface({ selectedTeamId, selectedRun, onNewSessio
177100
<div className="flex-1 w-full overflow-hidden relative">
178101
<ScrollArea ref={containerRef} className="w-full h-full py-12">
179102
<div className="flex flex-col space-y-5">
180-
{displayMessages.length === 0 && !showStreamingMessage && <NoMessagesState onNewSession={onNewSession} />}
103+
{displayMessages.length === 0 && !showStreamingMessage && <NoMessagesState agentId={selectedTeamId} />}
181104
{displayMessages.map((msg, index) => (
182105
<ChatMessage key={`${msg.run_id}-${msg.config.source}-${index}`} message={msg} run={actualRun} />
183106
))}
@@ -200,21 +123,8 @@ export default function ChatInterface({ selectedTeamId, selectedRun, onNewSessio
200123

201124
<div className="w-full sticky bg-secondary bottom-0 md:bottom-2 rounded-none md:rounded-lg p-4 border overflow-hidden transition-all duration-300 ease-in-out">
202125
<div className="flex items-center justify-between mb-4">
203-
{getStatusDisplay(status, runStatus)}
204-
<div className="flex items-center gap-2 text-xs">
205-
<span>Usage: </span>
206-
<span>{tokenStats.total}</span>
207-
<div className="flex items-center gap-2">
208-
<div className="flex items-center gap-1">
209-
<ArrowLeft className="h-3 w-3" />
210-
<span>{tokenStats.input}</span>
211-
</div>
212-
<div className="flex items-center gap-1">
213-
<ArrowRightFromLine className="h-3 w-3" />
214-
<span>{tokenStats.output}</span>
215-
</div>
216-
</div>
217-
</div>
126+
<StatusDisplay chatStatus={status} errorMessage={actualRun?.error_message || selectedRun?.error_message} />
127+
<TokenStatsDisplay stats={tokenStats} />
218128
</div>
219129

220130
<form onSubmit={handleSendMessage}>
@@ -235,9 +145,11 @@ export default function ChatInterface({ selectedTeamId, selectedRun, onNewSessio
235145
</Button>
236146
)}
237147

238-
{(runStatus === "complete" || runStatus === "error" || runStatus === "stopped") && onNewSession && (
239-
<Button onClick={onNewSession} className="bg-violet-500 hover:bg-violet-600" type="button">
148+
{(runStatus === "complete" || runStatus === "error" || runStatus === "stopped") && (
149+
<Button className="bg-violet-500 hover:bg-violet-600" asChild>
150+
<Link href={`/agents/${selectedTeamId}/chat`}>
240151
Start New Chat
152+
</Link>
241153
</Button>
242154
)}
243155

+8-10
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
import { MessageSquarePlus } from "lucide-react";
22
import { Button } from "../ui/button";
3+
import Link from "next/link";
34

45
interface NoMessagesStateProps {
5-
onNewSession?: () => void;
6+
agentId: string;
67
}
78

8-
export default function NoMessagesState({ onNewSession }: NoMessagesStateProps) {
9+
export default function NoMessagesState({ agentId }: NoMessagesStateProps) {
910
return (
1011
<div className="flex flex-col items-center justify-center px-4 py-12 text-center">
1112
<MessageSquarePlus className="mb-4 h-8 w-8 text-violet-500" />
1213
<h3 className="mb-2 text-xl font-medium">Ready to start chatting?</h3>
13-
<p className="text-base">
14-
Begin a new conversation with your agent
15-
</p>
16-
{onNewSession && (
17-
<Button onClick={onNewSession} className="mt-4">
18-
Start New Chat
19-
</Button>
20-
)}
14+
<p className="text-base">Begin a new conversation with your agent</p>
15+
16+
<Button className="mt-4" asChild>
17+
<Link href={`/agents/${agentId}/chat`}>Start New Chat</Link>
18+
</Button>
2119
</div>
2220
);
2321
}

0 commit comments

Comments
 (0)