From 1685c60e3cb832eca082b8c5b25d4594eefdfeb3 Mon Sep 17 00:00:00 2001 From: Raghav Tirumale <62105787+MythicalCow@users.noreply.github.com> Date: Sat, 27 Jul 2024 04:42:00 -0400 Subject: [PATCH] Nav Menu Upgrades and Minor UX Improvements (#869) * Converted navigation menu into a dropdown menu * Moved collapsed side panel menu icons into top row * Auto refresh when conversation is deleted to update side panel and route back to main page if deletion is on current conversation * Highlight the current conversation in the side panel * Dynamic homepage messages with current day and time of day. * `colorutils` upgraded to have more expansive tailwind color options and dynamic class name generation. * Converted create agent button alert into shadcn `ToolTip` * Colored lines and icons for agents in chat window * Cleaned up border styling in dark mode * fixed three dot menu in side panel to be more easier to click * Add the KhojLogo import in the nav menu and use a default user profile icon when not authenticated * Get rid of custom --box-shadow CSS variable * Pass the agent metadat through the chat body data in order to style the send button * Add login to the unauthenticated login view, redirecto to home if conversation history not loaded * Set a max height for the input text area * Simplify tailwind class names --------- Co-authored-by: sabaimran --- .../web/app/agents/agents.module.css | 4 +- .../web/app/agents/agentsLayout.module.css | 10 - src/interface/web/app/agents/layout.tsx | 25 +- src/interface/web/app/agents/page.tsx | 110 +- .../app/automations/automations.module.css | 15 + .../automations/automationsLayout.module.css | 11 - src/interface/web/app/automations/layout.tsx | 5 +- src/interface/web/app/automations/page.tsx | 364 +- src/interface/web/app/chat/chat.module.css | 1 - src/interface/web/app/chat/page.tsx | 57 +- src/interface/web/app/common/colorUtils.ts | 102 +- src/interface/web/app/common/iconUtils.tsx | 1 - .../chatHistory/chatHistory.module.css | 3 +- .../components/chatHistory/chatHistory.tsx | 30 +- .../chatInputArea/chatInputArea.tsx | 46 +- .../chatMessage/chatMessage.module.css | 2 - .../components/chatMessage/chatMessage.tsx | 26 +- .../web/app/components/logo/khogLogo.tsx | 31 + .../app/components/navMenu/navMenu.module.css | 7 +- .../web/app/components/navMenu/navMenu.tsx | 234 +- .../components/profileCard/profileCard.tsx | 2 +- .../sidePanel/chatHistorySidePanel.tsx | 76 +- .../components/sidePanel/sidePanel.module.css | 2 +- src/interface/web/app/globals.css | 18 +- src/interface/web/app/page.module.css | 1 - src/interface/web/app/page.tsx | 30 +- src/interface/web/app/share/chat/page.tsx | 6 +- .../web/app/share/chat/sharedChat.module.css | 1 - src/interface/web/image-loader.ts | 6 +- src/interface/web/public/khoj-logo.svg | 5385 ----------------- src/interface/web/tailwind.config.ts | 20 +- src/interface/web/yarn.lock | 2928 ++++----- src/khoj/routers/api_agents.py | 2 +- 33 files changed, 1790 insertions(+), 7771 deletions(-) delete mode 100644 src/interface/web/app/agents/agentsLayout.module.css delete mode 100644 src/interface/web/app/automations/automationsLayout.module.css create mode 100644 src/interface/web/app/components/logo/khogLogo.tsx delete mode 100644 src/interface/web/public/khoj-logo.svg diff --git a/src/interface/web/app/agents/agents.module.css b/src/interface/web/app/agents/agents.module.css index 3c622031..8433a88f 100644 --- a/src/interface/web/app/agents/agents.module.css +++ b/src/interface/web/app/agents/agents.module.css @@ -21,9 +21,7 @@ div.sidePanel { height: 100%; } -div.chatLayout { - display: grid; - grid-template-columns: auto 1fr; +div.pageLayout { gap: 1rem; } diff --git a/src/interface/web/app/agents/agentsLayout.module.css b/src/interface/web/app/agents/agentsLayout.module.css deleted file mode 100644 index 228997b5..00000000 --- a/src/interface/web/app/agents/agentsLayout.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.agentsLayout { - max-width: 100vw; - margin: auto; -} - -@media screen and (max-width: 700px) { - .agentsLayout { - max-width: 90vw; - } -} diff --git a/src/interface/web/app/agents/layout.tsx b/src/interface/web/app/agents/layout.tsx index 341960f7..a0ae26d6 100644 --- a/src/interface/web/app/agents/layout.tsx +++ b/src/interface/web/app/agents/layout.tsx @@ -5,8 +5,11 @@ import "../globals.css"; const inter = Noto_Sans({ subsets: ["latin"] }); export const metadata: Metadata = { - title: "Khoj AI - Chat", - description: "Use this page to chat with Khoj AI.", + title: "Khoj AI - Agents", + description: "Find a specialized agent that can help you address more specific needs.", + icons: { + icon: '/static/favicon.ico', + }, }; export default function RootLayout({ @@ -14,10 +17,10 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { - return ( - - + - - {children} - - - ); + + {children} + + + ); } diff --git a/src/interface/web/app/agents/page.tsx b/src/interface/web/app/agents/page.tsx index 4e43a6b1..e2e16071 100644 --- a/src/interface/web/app/agents/page.tsx +++ b/src/interface/web/app/agents/page.tsx @@ -10,15 +10,11 @@ import { useEffect, useState } from 'react'; import { useAuthenticatedData, UserProfile } from '../common/auth'; import { Button } from '@/components/ui/button'; import { - AlertDialog, - AlertDialogAction, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog" + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip" import { PaperPlaneTilt, @@ -35,12 +31,13 @@ import SidePanel from '../components/sidePanel/chatHistorySidePanel'; import NavMenu from '../components/navMenu/navMenu'; import { getIconFromIconName } from '../common/iconUtils'; import { convertColorToTextClass } from '../common/colorUtils'; +import { Alert, AlertDescription } from '@/components/ui/alert'; export interface AgentData { slug: string; avatar: string; name: string; - personality: string; + persona: string; color: string; icon: string; } @@ -123,13 +120,13 @@ function AgentCard(props: AgentCardProps) { ) : ( )} @@ -148,7 +145,7 @@ function AgentCard(props: AgentCardProps) {
- {props.data.personality} + {props.data.persona}
@@ -188,13 +185,13 @@ function AgentCard(props: AgentCardProps) { ) : ( )} @@ -203,7 +200,7 @@ function AgentCard(props: AgentCardProps) { {props.data.name} Full Prompt - {props.data.personality} + {props.data.persona} Done @@ -217,7 +214,7 @@ function AgentCard(props: AgentCardProps) {
@@ -225,11 +222,6 @@ function AgentCard(props: AgentCardProps) { ) } -function createAgent() { - //just show a dialog for now similar to the agent card when the text is pressed -} - - export default function Agents() { const { data, error } = useSWR('agents', agentsFetcher, { revalidateOnFocus: false }); const authenticatedData = useAuthenticatedData(); @@ -273,7 +265,7 @@ export default function Agents() { } return ( -
+
@@ -283,7 +275,7 @@ export default function Agents() { loginRedirectMessage="Sign in to start chatting with a specialized agent" onOpenChange={setShowLoginPrompt} /> } -
+
-
-
-

Agents

-
- - - - - - - Custom Agents - - Custom Agents will be coming to Khoj soon! - - - - - - - - - +
+
+

Agents

+
+ + + +
+ +

Create Agent

+
+
+ +

Coming Soon!

+
+
+
-
- - - - -

- How it works - Use any of these specialized agents to tune your conversation to your needs. -

-
-
-
-
+ + + + How it works Use any of these specialized personas to tune your conversation to your needs. + +
{data.map(agent => ( diff --git a/src/interface/web/app/automations/automations.module.css b/src/interface/web/app/automations/automations.module.css index 25b4b79f..99f094c3 100644 --- a/src/interface/web/app/automations/automations.module.css +++ b/src/interface/web/app/automations/automations.module.css @@ -9,8 +9,23 @@ div.automationCard { grid-template-rows: auto 1fr auto; } +div.pageLayout { + max-width: 60vw; + margin: auto; + margin-bottom: 2rem; +} + +div.sidePanel { + position: fixed; + height: 100%; +} + @media screen and (max-width: 768px) { div.automationsLayout { grid-template-columns: 1fr; } + + div.pageLayout { + max-width: 90vw; + } } diff --git a/src/interface/web/app/automations/automationsLayout.module.css b/src/interface/web/app/automations/automationsLayout.module.css deleted file mode 100644 index 6e27ee7a..00000000 --- a/src/interface/web/app/automations/automationsLayout.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.automationsLayout { - max-width: 70vw; - margin: auto; - margin-bottom: 2rem; -} - -@media screen and (max-width: 700px) { - .automationsLayout { - max-width: 90vw; - } -} diff --git a/src/interface/web/app/automations/layout.tsx b/src/interface/web/app/automations/layout.tsx index 7e6f8fc8..ac0fd376 100644 --- a/src/interface/web/app/automations/layout.tsx +++ b/src/interface/web/app/automations/layout.tsx @@ -1,6 +1,4 @@ import type { Metadata } from "next"; -import NavMenu from '../components/navMenu/navMenu'; -import styles from './automationsLayout.module.css'; import { Toaster } from "@/components/ui/toaster"; import "../globals.css"; @@ -20,8 +18,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( -
- +
{children}
diff --git a/src/interface/web/app/automations/page.tsx b/src/interface/web/app/automations/page.tsx index 52606781..38590c9f 100644 --- a/src/interface/web/app/automations/page.tsx +++ b/src/interface/web/app/automations/page.tsx @@ -53,6 +53,8 @@ import LoginPrompt from '../components/loginPrompt/loginPrompt'; import { useToast } from '@/components/ui/use-toast'; import { ToastAction } from '@/components/ui/toast'; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" +import SidePanel from '../components/sidePanel/chatHistorySidePanel'; +import NavMenu from '../components/navMenu/navMenu'; const automationsFetcher = () => window.fetch('/api/automations').then(res => res.json()).catch(err => console.log(err)); @@ -899,9 +901,20 @@ export default function Automations() { const [allNewAutomations, setAllNewAutomations] = useState([]); const [suggestedAutomations, setSuggestedAutomations] = useState([]); const [showLoginPrompt, setShowLoginPrompt] = useState(false); + const [isMobileWidth, setIsMobileWidth] = useState(false); const ipLocationData = useIPLocationData(); + useEffect(() => { + if (window.innerWidth < 768) { + setIsMobileWidth(true); + } + + window.addEventListener('resize', () => { + setIsMobileWidth(window.innerWidth < 768); + }); + }, []); + useEffect(() => { if (newAutomationData) { setAllNewAutomations([...allNewAutomations, newAutomationData]); @@ -923,186 +936,201 @@ export default function Automations() { if (error) return
Failed to load
; - if (isLoading) return ; - return ( -
-
-

- Automations -

-
- { - authenticatedData ? ( - {authenticatedData.email} - ) - : null - } - { - ipLocationData && ( - {ipLocationData ? `${ipLocationData.city}, ${ipLocationData.country}` : 'Unknown'} - ) - } - { - ipLocationData && ( - {ipLocationData ? `${ipLocationData.timezone}` : 'Unknown'} - - ) - } +
+
+ +
+
+
+
-
- { - showLoginPrompt && ( - - ) - } - - - - How it works Automations help you structure your time by automating tasks you do regularly. Build your own, or try out our presets. Get results straight to your inbox. - - -
-

- Your Creations -

- { - authenticatedData ? ( - { - setIsCreating(open); - }} - > - - - - - Create Automation - - - - ) - : ( - - ) - } -
- - - - { - ((!personalAutomations || personalAutomations.length === 0) && (allNewAutomations.length == 0)) && ( -
- So empty! Create your own automation to get started. -
+
+
+

Automations

+
{ authenticatedData ? ( - { - setIsCreating(open); - }} - > - - - - - Create Automation - - - + {authenticatedData.email} + ) + : null + } + { + ipLocationData && ( + {ipLocationData ? `${ipLocationData.city}, ${ipLocationData.country}` : 'Unknown'} + ) + } + { + ipLocationData && ( + {ipLocationData ? `${ipLocationData.timezone}` : 'Unknown'} + ) - : ( - - ) }
- ) - } -
- { - personalAutomations && personalAutomations.map((automation) => ( - + ) + } + + + + How it works Automations help you structure your time by automating tasks you do regularly. Build your own, or try out our presets. Get results straight to your inbox. + + +
+

+ Your Creations +

+ { + authenticatedData ? ( + { + setIsCreating(open); + }} + > + + + + + Create Automation + + + + ) + : ( + + ) + } +
+ + - ))} - { - allNewAutomations.map((automation) => ( - - )) - } -
-

- Try these out -

-
- { - suggestedAutomations.map((automation) => ( - - )) - } + setNewAutomationData={setNewAutomationData} /> + + { + ((!personalAutomations || personalAutomations.length === 0) && (allNewAutomations.length == 0) && !isLoading) && ( +
+ So empty! Create your own automation to get started. +
+ { + authenticatedData ? ( + { + setIsCreating(open); + }} + > + + + + + Create Automation + + + + ) + : ( + + ) + } +
+
+ ) + } + { + isLoading && ( + + ) + } +
+ { + personalAutomations && personalAutomations.map((automation) => ( + + ))} + { + allNewAutomations.map((automation) => ( + + )) + } +
+

+ Try these out +

+
+ { + suggestedAutomations.map((automation) => ( + + )) + } +
+
-
+
); } diff --git a/src/interface/web/app/chat/chat.module.css b/src/interface/web/app/chat/chat.module.css index 08895e4f..311fc036 100644 --- a/src/interface/web/app/chat/chat.module.css +++ b/src/interface/web/app/chat/chat.module.css @@ -17,7 +17,6 @@ div.main { div.inputBox { border: 1px solid var(--border-color); border-radius: 16px; - box-shadow: 0 4px 10px var(--box-shadow-color); margin-bottom: 20px; gap: 12px; padding-left: 20px; diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index 3dca8dfc..f81287fd 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -17,6 +17,7 @@ import { StreamMessage } from '../components/chatMessage/chatMessage'; import { welcomeConsole } from '../common/utils'; import ChatInputArea, { ChatOptions } from '../components/chatInputArea/chatInputArea'; import { useAuthenticatedData } from '../common/auth'; +import { AgentData } from '../agents/page'; interface ChatBodyDataProps { chatOptionsData: ChatOptions | null; @@ -34,6 +35,7 @@ function ChatBodyData(props: ChatBodyDataProps) { const conversationId = searchParams.get('conversationId'); const [message, setMessage] = useState(''); const [processingMessage, setProcessingMessage] = useState(false); + const [agentMetadata, setAgentMetadata] = useState(null); useEffect(() => { const storedMessage = localStorage.getItem("message"); @@ -43,7 +45,7 @@ function ChatBodyData(props: ChatBodyDataProps) { }, []); useEffect(() => { - if(message){ + if (message) { setProcessingMessage(true); props.setQueryToProcess(message); } @@ -76,11 +78,14 @@ function ChatBodyData(props: ChatBodyDataProps) { + incomingMessages={props.streamedMessages} + />
-
+
setMessage(message)} sendDisabled={processingMessage} @@ -92,7 +97,6 @@ function ChatBodyData(props: ChatBodyDataProps) { ); } - export default function Chat() { const [chatOptionsData, setChatOptionsData] = useState(null); const [isLoading, setLoading] = useState(true); @@ -106,7 +110,6 @@ export default function Chat() { const [isMobileWidth, setIsMobileWidth] = useState(false); const authenticatedData = useAuthenticatedData(); - welcomeConsole(); const handleWebSocketMessage = (event: MessageEvent) => { @@ -195,9 +198,9 @@ export default function Chat() { }, []); useEffect(() => { - if (chatWS) { - chatWS.onmessage = handleWebSocketMessage; - } + if (chatWS) { + chatWS.onmessage = handleWebSocketMessage; + } }, [chatWS, messages]); //same as ChatBodyData for local storage message @@ -209,13 +212,13 @@ export default function Chat() { useEffect(() => { if (chatWS && queryToProcess) { const newStreamMessage: StreamMessage = { - rawResponse: "", - trainOfThought: [], - context: [], - onlineContext: {}, - completed: false, - timestamp: (new Date()).toISOString(), - rawQuery: queryToProcess || "", + rawResponse: "", + trainOfThought: [], + context: [], + onlineContext: {}, + completed: false, + timestamp: (new Date()).toISOString(), + rawQuery: queryToProcess || "", }; setMessages(prevMessages => [...prevMessages, newStreamMessage]); @@ -227,11 +230,11 @@ export default function Chat() { console.error("WebSocket is not open. ReadyState:", chatWS.readyState); } - setQueryToProcess(''); + setQueryToProcess(''); } }, [queryToProcess, chatWS]); - useEffect(() => { + useEffect(() => { if (processQuerySignal && chatWS && chatWS.readyState === WebSocket.OPEN) { setProcessQuerySignal(false); chatWS.onmessage = handleWebSocketMessage; @@ -242,17 +245,17 @@ export default function Chat() { useEffect(() => { const setupWebSocketConnection = async () => { - if (conversationId && (!chatWS || chatWS.readyState === WebSocket.CLOSED)) { - if(queryToProcess) { - const newWS = await setupWebSocket(conversationId, queryToProcess); - localStorage.removeItem("message"); - setChatWS(newWS); + if (conversationId && (!chatWS || chatWS.readyState === WebSocket.CLOSED)) { + if (queryToProcess) { + const newWS = await setupWebSocket(conversationId, queryToProcess); + localStorage.removeItem("message"); + setChatWS(newWS); + } + else { + const newWS = await setupWebSocket(conversationId); + setChatWS(newWS); + } } - else { - const newWS = await setupWebSocket(conversationId); - setChatWS(newWS); - } - } }; setupWebSocketConnection(); }, [conversationId]); diff --git a/src/interface/web/app/common/colorUtils.ts b/src/interface/web/app/common/colorUtils.ts index d13b202b..cc95beb0 100644 --- a/src/interface/web/app/common/colorUtils.ts +++ b/src/interface/web/app/common/colorUtils.ts @@ -1,75 +1,55 @@ +const tailwindColors = [ + 'red', + 'yellow', + 'green', + 'blue', + 'orange', + 'purple', + 'pink', + 'teal', + 'cyan', + 'lime', + 'indigo', + 'fuschia', + 'rose', + 'sky', + 'amber', + 'emerald' +]; + export function convertColorToTextClass(color: string) { - if (color === 'red') return `text-red-500`; - if (color === 'yellow') return `text-yellow-500`; - if (color === 'green') return `text-green-500`; - if (color === 'blue') return `text-blue-500`; - if (color === 'orange') return `text-orange-500`; - if (color === 'purple') return `text-purple-500`; - if (color === 'pink') return `text-pink-500`; - if (color === 'teal') return `text-teal-500`; - if (color === 'cyan') return `text-cyan-500`; - if (color === 'lime') return `text-lime-500`; - if (color === 'indigo') return `text-indigo-500`; - if (color === 'fuschia') return `text-fuschia-500`; - if (color === 'rose') return `text-rose-500`; - if (color === 'sky') return `text-sky-500`; - if (color === 'amber') return `text-amber-500`; - if (color === 'emerald') return `text-emerald-500`; + if (tailwindColors.includes(color)) { + return `text-${color}-500`; + } return `text-gray-500`; } -function convertToBGGradientClass(color: string) { - if (color === 'red') return `bg-gradient-to-b from-[hsl(var(--background))] to-red-100/70 dark:from-[hsl(var(--background))] dark:to-red-950/30 `; - if (color === 'yellow') return `bg-gradient-to-b from-[hsl(var(--background))] to-yellow-100/70 dark:from-[hsl(var(--background))] dark:to-yellow-950/30 `; - if (color === 'green') return `bg-gradient-to-b from-[hsl(var(--background))] to-green-100/90 dark:from-[hsl(var(--background))] dark:to-green-950/30 `; - if (color === 'blue') return `bg-gradient-to-b from-[hsl(var(--background))] to-blue-100/70 dark:from-[hsl(var(--background))] dark:to-blue-950/30 `; - if (color === 'orange') return `bg-gradient-to-b from-[hsl(var(--background))] to-orange-100/70 dark:from-[hsl(var(--background))] dark:to-orange-950/30 `; - if (color === 'purple') return `bg-gradient-to-b from-[hsl(var(--background))] to-purple-100/70 dark:from-[hsl(var(--background))] dark:to-purple-950/30 `; - if (color === 'pink') return `bg-gradient-to-b from-[hsl(var(--background))] to-pink-100/70 dark:from-[hsl(var(--background))] dark:to-pink-950/30 `; - if (color === 'teal') return `bg-gradient-to-b from-[hsl(var(--background))] to-teal-100/70 dark:from-[hsl(var(--background))] dark:to-teal-950/30 `; - if (color === 'cyan') return `bg-gradient-to-b from-[hsl(var(--background))] to-cyan-100/70 dark:from-[hsl(var(--background))] dark:to-cyan-950/30 `; - if (color === 'lime') return `bg-gradient-to-b from-[hsl(var(--background))] to-lime-100/70 dark:from-[hsl(var(--background))] dark:to-lime-950/30 `; - if (color === 'indigo') return `bg-gradient-to-b from-[hsl(var(--background))] to-indigo-100/70 dark:from-[hsl(var(--background))] dark:to-indigo-950/30 `; - if (color === 'fuschia') return `bg-gradient-to-b from-[hsl(var(--background))] to-fuschia-100/70 dark:from-[hsl(var(--background))] dark:to-fuschia-950/30 `; - if (color === 'rose') return `bg-gradient-to-b from-[hsl(var(--background))] to-rose-100/70 dark:from-[hsl(var(--background))] dark:to-rose-950/30 `; - if (color === 'sky') return `bg-gradient-to-b from-[hsl(var(--background))] to-sky-100/70 dark:from-[hsl(var(--background))] dark:to-sky-950/30 `; - if (color === 'amber') return `bg-gradient-to-b from-[hsl(var(--background))] to-amber-100/70 dark:from-[hsl(var(--background))] dark:to-amber-950/30 `; - if (color === 'emerald') return `bg-gradient-to-b from-[hsl(var(--background))] to-emerald-100/70 dark:from-[hsl(var(--background))] dark:to-emerald-950/30 `; +export function convertToBGGradientClass(color: string) { + if (tailwindColors.includes(color)) { + return `bg-gradient-to-b from-[hsl(var(--background))] to-${color}-100/70 dark:from-[hsl(var(--background))] dark:to-${color}-950/30 `; + } return `bg-gradient-to-b from-white to-orange-50`; } +export function convertToBGClass(color: string) { + if (tailwindColors.includes(color)) { + return `bg-${color}-500 dark:bg-${color}-900`; + } + return `bg-background`; +} + export function convertSuggestionColorToTextClass(color: string) { return `${convertToBGGradientClass(color)} dark:border dark:border-neutral-700`; } export function convertColorToBorderClass(color: string) { - if (color === 'red') return `border-red-500`; - if (color === 'yellow') return `border-yellow-500`; - if (color === 'green') return `border-green-500`; - if (color === 'blue') return `border-blue-500`; - if (color === 'orange') return `border-orange-500`; - if (color === 'purple') return `border-purple-500`; - if (color === 'pink') return `border-pink-500`; - if (color === 'teal') return `border-teal-500`; - if (color === 'cyan') return `border-cyan-500`; - if (color === 'lime') return `border-lime-500`; - if (color === 'indigo') return `border-indigo-500`; - if (color === 'fuschia') return `border-fuschia-500`; - if (color === 'rose') return `border-rose-500`; - if (color === 'sky') return `border-sky-500`; - if (color === 'amber') return `border-amber-500`; - if (color === 'emerald') return `border-emerald-500`; + if (tailwindColors.includes(color)) { + return `border-${color}-500`; + } return `border-gray-500`; } - -export const colorMap: Record = { - 'red': 'border-red-500', - 'blue': 'border-blue-500', - 'green': 'border-green-500', - 'yellow': 'border-yellow-500', - 'purple': 'border-purple-500', - 'pink': 'border-pink-500', - 'indigo': 'border-indigo-500', - 'gray': 'border-gray-500', - 'orange': 'border-orange-500', -}; +//rewrite colorMap using the convertColorToBorderClass function and iteration through the tailwindColors array +export const colorMap: Record = {}; +for (const color of tailwindColors) { + colorMap[color] = convertColorToBorderClass(color); +} diff --git a/src/interface/web/app/common/iconUtils.tsx b/src/interface/web/app/common/iconUtils.tsx index 89fbfb27..e216ce3e 100644 --- a/src/interface/web/app/common/iconUtils.tsx +++ b/src/interface/web/app/common/iconUtils.tsx @@ -46,7 +46,6 @@ function getIconFromIconName(iconName: string, color: string = 'gray', width: st const icon = iconMap[iconName]; const colorName = color.toLowerCase(); const colorClass = convertColorToTextClass(colorName); - return icon ? icon(colorClass, width, height) : null; } diff --git a/src/interface/web/app/components/chatHistory/chatHistory.module.css b/src/interface/web/app/components/chatHistory/chatHistory.module.css index c379460b..80be61a9 100644 --- a/src/interface/web/app/components/chatHistory/chatHistory.module.css +++ b/src/interface/web/app/components/chatHistory/chatHistory.module.css @@ -20,7 +20,6 @@ div.agentIndicator a { div.trainOfThought { border: 1px var(--border-color) solid; border-radius: 16px; - padding: 0 16px; + padding: 8px 16px; margin: 12px; - box-shadow: 0 4px 10px var(--box-shadow-color); } diff --git a/src/interface/web/app/components/chatHistory/chatHistory.tsx b/src/interface/web/app/components/chatHistory/chatHistory.tsx index 3fa2d91f..a6fde5c9 100644 --- a/src/interface/web/app/components/chatHistory/chatHistory.tsx +++ b/src/interface/web/app/components/chatHistory/chatHistory.tsx @@ -15,6 +15,8 @@ import { InlineLoading } from '../loading/loading'; import { Lightbulb } from "@phosphor-icons/react"; import ProfileCard from '../profileCard/profileCard'; +import { getIconFromIconName } from '@/app/common/iconUtils'; +import { AgentData } from '@/app/agents/page'; interface ChatResponse { status: string; @@ -31,26 +33,26 @@ interface ChatHistoryProps { incomingMessages?: StreamMessage[]; pendingMessage?: string; publicConversationSlug?: string; + setAgent: (agent: AgentData) => void; } -function constructTrainOfThought(trainOfThought: string[], lastMessage: boolean, key: string, completed: boolean = false) { +function constructTrainOfThought(trainOfThought: string[], lastMessage: boolean, agentColor: string, key: string, completed: boolean = false) { const lastIndex = trainOfThought.length - 1; return ( -
+
{ !completed && } {trainOfThought.map((train, index) => ( - + ))}
) } - export default function ChatHistory(props: ChatHistoryProps) { const [data, setData] = useState(null); const [currentPage, setCurrentPage] = useState(0); @@ -64,7 +66,6 @@ export default function ChatHistory(props: ChatHistoryProps) { const [fetchingData, setFetchingData] = useState(false); const [isMobileWidth, setIsMobileWidth] = useState(false); - useEffect(() => { window.addEventListener('resize', () => { setIsMobileWidth(window.innerWidth < 768); @@ -184,6 +185,7 @@ export default function ChatHistory(props: ChatHistoryProps) { setFetchingData(false); return; } + props.setAgent(chatData.response.agent); setData(chatData.response); @@ -199,6 +201,7 @@ export default function ChatHistory(props: ChatHistoryProps) { conversation_id: chatData.response.conversation_id, slug: chatData.response.slug, } + props.setAgent(chatData.response.agent); setData(chatMetadata); } @@ -208,6 +211,7 @@ export default function ChatHistory(props: ChatHistoryProps) { }) .catch(err => { console.error(err); + window.location.href = "/"; }); }; @@ -234,11 +238,6 @@ export default function ChatHistory(props: ChatHistoryProps) { return `/agents?agent=${data.agent.slug}` } - function constructAgentAvatar() { - if (!data || !data.agent || !data.agent.avatar) return `/avatar.png`; - return data.agent.avatar; - } - function constructAgentName() { if (!data || !data.agent || !data.agent.name) return `Agent`; return data.agent.name; @@ -265,7 +264,7 @@ export default function ChatHistory(props: ChatHistoryProps) { isMobileWidth={isMobileWidth} chatMessage={chatMessage} customClassName='fullHistory' - borderLeftColor='orange-500' + borderLeftColor={`${data?.agent.color}-500`} isLastMessage={index === data.chat.length - 1} /> ))} @@ -287,13 +286,14 @@ export default function ChatHistory(props: ChatHistoryProps) { } } customClassName='fullHistory' - borderLeftColor='orange-500' + borderLeftColor={`${data?.agent.color}-500`} /> { message.trainOfThought && constructTrainOfThought( message.trainOfThought, index === incompleteIncomingMessageIndex, + data?.agent.color || 'orange', `${index}trainOfThought`, message.completed) } @@ -334,7 +334,7 @@ export default function ChatHistory(props: ChatHistoryProps) { } } customClassName='fullHistory' - borderLeftColor='orange-500' + borderLeftColor={`${data?.agent.color}-500`} isLastMessage={true} /> } @@ -344,7 +344,7 @@ export default function ChatHistory(props: ChatHistoryProps) { } + avatar={getIconFromIconName(data.agent.icon, data.agent.color) || } description={constructAgentPersona()} />
diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index 80da42a9..892850b9 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -11,18 +11,16 @@ import { ArrowRight, Browser, ChatsTeardrop, - FileArrowUp, GlobeSimple, Gps, Image, Microphone, Notebook, + Paperclip, Question, Robot, Shapes, Stop, - Waveform, - WaveSine } from '@phosphor-icons/react'; import { @@ -42,17 +40,15 @@ import { AlertDialogAction, AlertDialogContent, AlertDialogDescription, - AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'; import { Popover, PopoverContent } from '@/components/ui/popover'; import { PopoverTrigger } from '@radix-ui/react-popover'; -import Link from 'next/link'; -import { AlertDialogCancel } from '@radix-ui/react-alert-dialog'; import LoginPrompt from '../loginPrompt/loginPrompt'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { InlineLoading } from '../loading/loading'; +import { convertToBGClass } from '@/app/common/colorUtils'; export interface ChatOptions { [key: string]: string @@ -66,29 +62,7 @@ interface ChatInputProps { chatOptionsData?: ChatOptions | null; isMobileWidth?: boolean; isLoggedIn: boolean; -} - -async function createNewConvo() { - try { - const response = await fetch('/api/chat/sessions', { method: "POST" }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - const conversationID = data.conversation_id; - - if (!conversationID) { - throw new Error("Conversation ID not found in response"); - } - - const url = `/chat?conversationId=${conversationID}`; - return url; - } catch (error) { - console.error("Error creating new conversation:", error); - throw error; - } + agentColor?: string; } export default function ChatInputArea(props: ChatInputProps) { @@ -280,6 +254,13 @@ export default function ChatInputArea(props: ChatInputProps) { }, [recording]); + const chatInputRef = useRef(null); + useEffect(() => { + if (!chatInputRef.current) return; + chatInputRef.current.style.height = 'auto'; + chatInputRef.current.style.height = Math.max(chatInputRef.current.scrollHeight-24, 64) + 'px'; + }, [message]); + return ( <> { @@ -391,11 +372,12 @@ export default function ChatInputArea(props: ChatInputProps) { className="!bg-none p-1 h-auto text-3xl rounded-full text-gray-300 hover:text-gray-500" disabled={props.sendDisabled} onClick={handleFileButtonClick}> - +