mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-27 17:35:07 +01:00
Show agent details card on hover on agent pill on web app home page
- Double click on agent to open edit agent card - Focus on chat input pane when agent selected/clicked for quick, smooth agent switch and message flow - Hover on agent to see agent detail card on non-mobile displays - Use debounce to only show when hover on card for a bit
This commit is contained in:
parent
220ff1df62
commit
9b554feb91
2 changed files with 110 additions and 21 deletions
|
@ -70,3 +70,19 @@ export function useIsMobileWidth() {
|
||||||
|
|
||||||
return isMobileWidth;
|
return isMobileWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useDebounce<T>(value: T, delay: number): T {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = setTimeout(() => {
|
||||||
|
setDebouncedValue(value);
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(handler);
|
||||||
|
};
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
}
|
||||||
|
|
|
@ -3,9 +3,8 @@ import "./globals.css";
|
||||||
import styles from "./page.module.css";
|
import styles from "./page.module.css";
|
||||||
import "katex/dist/katex.min.css";
|
import "katex/dist/katex.min.css";
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import Image from "next/image";
|
|
||||||
import { ArrowCounterClockwise } from "@phosphor-icons/react";
|
import { ArrowCounterClockwise } from "@phosphor-icons/react";
|
||||||
|
|
||||||
import { Card, CardTitle } from "@/components/ui/card";
|
import { Card, CardTitle } from "@/components/ui/card";
|
||||||
|
@ -16,14 +15,21 @@ import { ChatInputArea, ChatOptions } from "@/app/components/chatInputArea/chatI
|
||||||
import { Suggestion, suggestionsData } from "@/app/components/suggestions/suggestionsData";
|
import { Suggestion, suggestionsData } from "@/app/components/suggestions/suggestionsData";
|
||||||
import LoginPrompt from "@/app/components/loginPrompt/loginPrompt";
|
import LoginPrompt from "@/app/components/loginPrompt/loginPrompt";
|
||||||
|
|
||||||
import { useAuthenticatedData, UserConfig, useUserConfig } from "@/app/common/auth";
|
import {
|
||||||
|
isUserSubscribed,
|
||||||
|
useAuthenticatedData,
|
||||||
|
UserConfig,
|
||||||
|
useUserConfig,
|
||||||
|
} from "@/app/common/auth";
|
||||||
import { convertColorToBorderClass } from "@/app/common/colorUtils";
|
import { convertColorToBorderClass } from "@/app/common/colorUtils";
|
||||||
import { getIconFromIconName } from "@/app/common/iconUtils";
|
import { getIconFromIconName } from "@/app/common/iconUtils";
|
||||||
import { AgentData } from "@/app/agents/page";
|
import { AgentData } from "@/app/agents/page";
|
||||||
import { createNewConversation } from "./common/chatFunctions";
|
import { createNewConversation } from "./common/chatFunctions";
|
||||||
import { useIsMobileWidth } from "./common/utils";
|
import { useDebounce, useIsMobileWidth } from "./common/utils";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||||
|
import { AgentCard } from "@/app/components/agentCard/agentCard";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
|
||||||
interface ChatBodyDataProps {
|
interface ChatBodyDataProps {
|
||||||
chatOptionsData: ChatOptions | null;
|
chatOptionsData: ChatOptions | null;
|
||||||
|
@ -49,10 +55,15 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
const [processingMessage, setProcessingMessage] = useState(false);
|
const [processingMessage, setProcessingMessage] = useState(false);
|
||||||
const [greeting, setGreeting] = useState("");
|
const [greeting, setGreeting] = useState("");
|
||||||
const [shuffledOptions, setShuffledOptions] = useState<Suggestion[]>([]);
|
const [shuffledOptions, setShuffledOptions] = useState<Suggestion[]>([]);
|
||||||
|
const [hoveredAgent, setHoveredAgent] = useState<string | null>(null);
|
||||||
|
const debouncedHoveredAgent = useDebounce(hoveredAgent, 500);
|
||||||
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||||
const [selectedAgent, setSelectedAgent] = useState<string | null>("khoj");
|
const [selectedAgent, setSelectedAgent] = useState<string | null>("khoj");
|
||||||
const [agentIcons, setAgentIcons] = useState<JSX.Element[]>([]);
|
const [agentIcons, setAgentIcons] = useState<JSX.Element[]>([]);
|
||||||
const [agents, setAgents] = useState<AgentData[]>([]);
|
const [agents, setAgents] = useState<AgentData[]>([]);
|
||||||
|
const chatInputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
|
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const queryParam = searchParams.get("q");
|
const queryParam = searchParams.get("q");
|
||||||
|
|
||||||
|
@ -62,6 +73,12 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
}
|
}
|
||||||
}, [queryParam]);
|
}, [queryParam]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (debouncedHoveredAgent) {
|
||||||
|
setIsPopoverOpen(true);
|
||||||
|
}
|
||||||
|
}, [debouncedHoveredAgent]);
|
||||||
|
|
||||||
const onConversationIdChange = props.onConversationIdChange;
|
const onConversationIdChange = props.onConversationIdChange;
|
||||||
|
|
||||||
const agentsFetcher = () =>
|
const agentsFetcher = () =>
|
||||||
|
@ -73,6 +90,10 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const openAgentEditCard = (agentSlug: string) => {
|
||||||
|
router.push(`/agents?agent=${agentSlug}`);
|
||||||
|
};
|
||||||
|
|
||||||
function shuffleAndSetOptions() {
|
function shuffleAndSetOptions() {
|
||||||
const shuffled = FisherYatesShuffle(suggestionsData);
|
const shuffled = FisherYatesShuffle(suggestionsData);
|
||||||
setShuffledOptions(shuffled.slice(0, 3));
|
setShuffledOptions(shuffled.slice(0, 3));
|
||||||
|
@ -188,23 +209,67 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
{!props.isMobileWidth && (
|
{!props.isMobileWidth && (
|
||||||
<ScrollArea className="w-full max-w-[600px] mx-auto">
|
<ScrollArea className="w-full max-w-[600px] mx-auto">
|
||||||
<div className="flex pb-2 gap-2 items-center justify-center">
|
<div className="flex pb-2 gap-2 items-center justify-center">
|
||||||
{agentIcons.map((icon, index) => (
|
{agents.map((agent, index) => (
|
||||||
<Card
|
<Popover
|
||||||
key={`${index}-${agents[index].slug}`}
|
key={`${index}-${agent.slug}`}
|
||||||
className={`${
|
open={isPopoverOpen && debouncedHoveredAgent === agent.slug}
|
||||||
selectedAgent === agents[index].slug
|
onOpenChange={(open) => {
|
||||||
? convertColorToBorderClass(agents[index].color)
|
if (!open) {
|
||||||
: "border-stone-100 dark:border-neutral-700 text-muted-foreground"
|
setHoveredAgent(null);
|
||||||
}
|
setIsPopoverOpen(false);
|
||||||
hover:cursor-pointer rounded-lg px-2 py-2`}
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<CardTitle
|
<PopoverTrigger asChild>
|
||||||
className="text-center text-md font-medium flex justify-center items-center"
|
<Card
|
||||||
onClick={() => setSelectedAgent(agents[index].slug)}
|
className={`${
|
||||||
|
selectedAgent === agent.slug
|
||||||
|
? convertColorToBorderClass(agent.color)
|
||||||
|
: "border-stone-100 dark:border-neutral-700 text-muted-foreground"
|
||||||
|
}
|
||||||
|
hover:cursor-pointer rounded-lg px-2 py-2`}
|
||||||
|
onDoubleClick={() => openAgentEditCard(agent.slug)}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedAgent(agent.slug);
|
||||||
|
chatInputRef.current?.focus();
|
||||||
|
setHoveredAgent(null);
|
||||||
|
setIsPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => setHoveredAgent(agent.slug)}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
setHoveredAgent(null);
|
||||||
|
setIsPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardTitle className="text-center text-md font-medium flex justify-center items-center">
|
||||||
|
{agentIcons[index]} {agent.name}
|
||||||
|
</CardTitle>
|
||||||
|
</Card>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-80 p-0"
|
||||||
|
onMouseLeave={() => {
|
||||||
|
setHoveredAgent(null);
|
||||||
|
setIsPopoverOpen(false);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{icon} {agents[index].name}
|
<AgentCard
|
||||||
</CardTitle>
|
data={agent}
|
||||||
</Card>
|
userProfile={null}
|
||||||
|
isMobileWidth={props.isMobileWidth || false}
|
||||||
|
showChatButton={false}
|
||||||
|
editCard={false}
|
||||||
|
filesOptions={[]}
|
||||||
|
selectedChatModelOption=""
|
||||||
|
agentSlug=""
|
||||||
|
isSubscribed={isUserSubscribed(props.userConfig)}
|
||||||
|
setAgentChangeTriggered={() => {}}
|
||||||
|
modelOptions={[]}
|
||||||
|
inputToolOptions={{}}
|
||||||
|
outputModeOptions={{}}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
|
@ -225,6 +290,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
conversationId={null}
|
conversationId={null}
|
||||||
isMobileWidth={props.isMobileWidth}
|
isMobileWidth={props.isMobileWidth}
|
||||||
setUploadedFiles={props.setUploadedFiles}
|
setUploadedFiles={props.setUploadedFiles}
|
||||||
|
ref={chatInputRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -281,7 +347,13 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
>
|
>
|
||||||
<CardTitle
|
<CardTitle
|
||||||
className="text-center text-xs font-medium flex justify-center items-center px-1.5 py-1"
|
className="text-center text-xs font-medium flex justify-center items-center px-1.5 py-1"
|
||||||
onClick={() => setSelectedAgent(agents[index].slug)}
|
onDoubleClick={() =>
|
||||||
|
openAgentEditCard(agents[index].slug)
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedAgent(agents[index].slug);
|
||||||
|
chatInputRef.current?.focus();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{icon} {agents[index].name}
|
{icon} {agents[index].name}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
@ -299,6 +371,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
conversationId={null}
|
conversationId={null}
|
||||||
isMobileWidth={props.isMobileWidth}
|
isMobileWidth={props.isMobileWidth}
|
||||||
setUploadedFiles={props.setUploadedFiles}
|
setUploadedFiles={props.setUploadedFiles}
|
||||||
|
ref={chatInputRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
Loading…
Reference in a new issue