mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 05:39:12 +00:00
Add a ride hand side bar for chat controls
This commit is contained in:
@@ -171,7 +171,7 @@ function CreateAgentCard(props: CreateAgentCardProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AgentConfigurationOptions {
|
export interface AgentConfigurationOptions {
|
||||||
input_tools: { [key: string]: string };
|
input_tools: { [key: string]: string };
|
||||||
output_modes: { [key: string]: string };
|
output_modes: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ div.inputBox:focus {
|
|||||||
div.chatBodyFull {
|
div.chatBodyFull {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
height: 100%;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.inputBox {
|
button.inputBox {
|
||||||
@@ -83,7 +83,7 @@ div.titleBar {
|
|||||||
div.chatBoxBody {
|
div.chatBoxBody {
|
||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 95%;
|
width: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/s
|
|||||||
import { AppSidebar } from "../components/appSidebar/appSidebar";
|
import { AppSidebar } from "../components/appSidebar/appSidebar";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { KhojLogoType } from "../components/logo/khojLogo";
|
import { KhojLogoType } from "../components/logo/khojLogo";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Joystick } from "@phosphor-icons/react";
|
||||||
|
import { ChatSidebar } from "../components/chatSidebar/chatSidebar";
|
||||||
|
|
||||||
interface ChatBodyDataProps {
|
interface ChatBodyDataProps {
|
||||||
chatOptionsData: ChatOptions | null;
|
chatOptionsData: ChatOptions | null;
|
||||||
@@ -43,6 +46,8 @@ interface ChatBodyDataProps {
|
|||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
setImages: (images: string[]) => void;
|
setImages: (images: string[]) => void;
|
||||||
setTriggeredAbort: (triggeredAbort: boolean) => void;
|
setTriggeredAbort: (triggeredAbort: boolean) => void;
|
||||||
|
isChatSideBarOpen: boolean;
|
||||||
|
onChatSideBarOpenChange: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatBodyData(props: ChatBodyDataProps) {
|
function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
@@ -138,7 +143,8 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-row h-full w-full">
|
||||||
|
<div className="flex flex-col h-full w-full">
|
||||||
<div className={false ? styles.chatBody : styles.chatBodyFull}>
|
<div className={false ? styles.chatBody : styles.chatBodyFull}>
|
||||||
<ChatHistory
|
<ChatHistory
|
||||||
conversationId={conversationId}
|
conversationId={conversationId}
|
||||||
@@ -168,7 +174,13 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
|||||||
setTriggeredAbort={props.setTriggeredAbort}
|
setTriggeredAbort={props.setTriggeredAbort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
|
<ChatSidebar
|
||||||
|
conversationId={conversationId}
|
||||||
|
isOpen={props.isChatSideBarOpen}
|
||||||
|
onOpenChange={props.onChatSideBarOpenChange}
|
||||||
|
isMobileWidth={props.isMobileWidth} />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,6 +211,7 @@ export default function Chat() {
|
|||||||
isLoading: authenticationLoading,
|
isLoading: authenticationLoading,
|
||||||
} = useAuthenticatedData();
|
} = useAuthenticatedData();
|
||||||
const isMobileWidth = useIsMobileWidth();
|
const isMobileWidth = useIsMobileWidth();
|
||||||
|
const [isChatSideBarOpen, setIsChatSideBarOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("/api/chat/options")
|
fetch("/api/chat/options")
|
||||||
@@ -432,6 +445,16 @@ export default function Chat() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="flex justify-end items-start gap-2 text-sm ml-auto">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-12 w-12 data-[state=open]:bg-accent"
|
||||||
|
onClick={() => setIsChatSideBarOpen(!isChatSideBarOpen)}
|
||||||
|
>
|
||||||
|
<Joystick className="w-6 h-6" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className={`${styles.main} ${styles.chatLayout}`}>
|
<div className={`${styles.main} ${styles.chatLayout}`}>
|
||||||
<title>
|
<title>
|
||||||
@@ -452,6 +475,8 @@ export default function Chat() {
|
|||||||
onConversationIdChange={handleConversationIdChange}
|
onConversationIdChange={handleConversationIdChange}
|
||||||
setImages={setImages}
|
setImages={setImages}
|
||||||
setTriggeredAbort={setTriggeredAbort}
|
setTriggeredAbort={setTriggeredAbort}
|
||||||
|
isChatSideBarOpen={isChatSideBarOpen}
|
||||||
|
onChatSideBarOpenChange={setIsChatSideBarOpen}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ export function useAuthenticatedData() {
|
|||||||
export interface ModelOptions {
|
export interface ModelOptions {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string;
|
||||||
|
strengths: string;
|
||||||
}
|
}
|
||||||
export interface SyncedContent {
|
export interface SyncedContent {
|
||||||
computer: boolean;
|
computer: boolean;
|
||||||
@@ -99,6 +101,14 @@ export function useUserConfig(detailed: boolean = false) {
|
|||||||
return { userConfig, isLoadingUserConfig };
|
return { userConfig, isLoadingUserConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useChatModelOptions() {
|
||||||
|
const { data, error, isLoading } = useSWR<ModelOptions[]>(`/api/model/chat/options`, fetcher, {
|
||||||
|
revalidateOnFocus: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { models: data, error, isLoading };
|
||||||
|
}
|
||||||
|
|
||||||
export function isUserSubscribed(userConfig: UserConfig | null): boolean {
|
export function isUserSubscribed(userConfig: UserConfig | null): boolean {
|
||||||
return (
|
return (
|
||||||
(userConfig?.subscription_state &&
|
(userConfig?.subscription_state &&
|
||||||
|
|||||||
168
src/interface/web/app/common/modelSelector.tsx
Normal file
168
src/interface/web/app/common/modelSelector.tsx
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { PopoverProps } from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { Check, CaretUpDown } from "@phosphor-icons/react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useMutationObserver } from "@/app/common/utils";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
|
import { ModelOptions, useChatModelOptions } from "./auth";
|
||||||
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
|
export function ModelSelector({ ...props }: PopoverProps) {
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
const { models, isLoading, error } = useChatModelOptions();
|
||||||
|
const [peekedModel, setPeekedModel] = useState<ModelOptions | undefined>(undefined);
|
||||||
|
const [selectedModel, setSelectedModel] = useState<ModelOptions | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (models && models.length > 0) {
|
||||||
|
setSelectedModel(models[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (models && models.length > 0 && !selectedModel) {
|
||||||
|
setSelectedModel(models[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [models]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<Skeleton className="w-full h-10" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="text-sm text-error">{error.message}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid gap-2 w-[250px]">
|
||||||
|
<Popover open={open} onOpenChange={setOpen} {...props}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
role="combobox"
|
||||||
|
aria-expanded={open}
|
||||||
|
aria-label="Select a model"
|
||||||
|
className="w-full justify-between text-left"
|
||||||
|
>
|
||||||
|
<p className="truncate">
|
||||||
|
{selectedModel ? selectedModel.name.substring(0,20) : "Select a model..."}
|
||||||
|
</p>
|
||||||
|
<CaretUpDown className="opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent align="end" className="w-[250px] p-0">
|
||||||
|
<HoverCard>
|
||||||
|
<HoverCardContent
|
||||||
|
side="left"
|
||||||
|
align="start"
|
||||||
|
forceMount
|
||||||
|
className="min-h-[280px]"
|
||||||
|
>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<h4 className="font-medium leading-none">{peekedModel?.name}</h4>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{peekedModel?.description}
|
||||||
|
</div>
|
||||||
|
{peekedModel?.strengths ? (
|
||||||
|
<div className="mt-4 grid gap-2">
|
||||||
|
<h5 className="text-sm font-medium leading-none">
|
||||||
|
Strengths
|
||||||
|
</h5>
|
||||||
|
<ul className="text-sm text-muted-foreground">
|
||||||
|
{peekedModel.strengths}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</HoverCardContent>
|
||||||
|
<div>
|
||||||
|
<HoverCardTrigger />
|
||||||
|
<Command loop>
|
||||||
|
<CommandList className="h-[var(--cmdk-list-height)]">
|
||||||
|
<CommandInput placeholder="Search Models..." />
|
||||||
|
<CommandEmpty>No Models found.</CommandEmpty>
|
||||||
|
<CommandGroup key={"models"} heading={"Models"}>
|
||||||
|
{models && models.length > 0 && models
|
||||||
|
.map((model) => (
|
||||||
|
<ModelItem
|
||||||
|
key={model.id}
|
||||||
|
model={model}
|
||||||
|
isSelected={selectedModel?.id === model.id}
|
||||||
|
onPeek={(model) => setPeekedModel(model)}
|
||||||
|
onSelect={() => {
|
||||||
|
setSelectedModel(model)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</div>
|
||||||
|
</HoverCard>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelItemProps {
|
||||||
|
model: ModelOptions,
|
||||||
|
isSelected: boolean,
|
||||||
|
onSelect: () => void,
|
||||||
|
onPeek: (model: ModelOptions) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function ModelItem({ model, isSelected, onSelect, onPeek }: ModelItemProps) {
|
||||||
|
const ref = React.useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useMutationObserver(ref, (mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (
|
||||||
|
mutation.type === "attributes" &&
|
||||||
|
mutation.attributeName === "aria-selected" &&
|
||||||
|
ref.current?.getAttribute("aria-selected") === "true"
|
||||||
|
) {
|
||||||
|
onPeek(model)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={model.id}
|
||||||
|
onSelect={onSelect}
|
||||||
|
ref={ref}
|
||||||
|
className="data-[selected=true]:bg-primary data-[selected=true]:text-primary-foreground"
|
||||||
|
>
|
||||||
|
{model.name}
|
||||||
|
<Check
|
||||||
|
className={cn("ml-auto", isSelected ? "opacity-100" : "opacity-0")}
|
||||||
|
/>
|
||||||
|
</CommandItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
export interface LocationData {
|
export interface LocationData {
|
||||||
city?: string;
|
city?: string;
|
||||||
@@ -69,6 +70,25 @@ export function useIsMobileWidth() {
|
|||||||
return isMobileWidth;
|
return isMobileWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useMutationObserver = (
|
||||||
|
ref: React.MutableRefObject<HTMLElement | null>,
|
||||||
|
callback: MutationCallback,
|
||||||
|
options = {
|
||||||
|
attributes: true,
|
||||||
|
characterData: true,
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
const observer = new MutationObserver(callback)
|
||||||
|
observer.observe(ref.current, options)
|
||||||
|
return () => observer.disconnect()
|
||||||
|
}
|
||||||
|
}, [ref, callback, options])
|
||||||
|
}
|
||||||
|
|
||||||
export const convertBytesToText = (fileSize: number) => {
|
export const convertBytesToText = (fileSize: number) => {
|
||||||
if (fileSize < 1024) {
|
if (fileSize < 1024) {
|
||||||
return `${fileSize} B`;
|
return `${fileSize} B`;
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ interface FilesMenuProps {
|
|||||||
isMobileWidth: boolean;
|
isMobileWidth: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FilesMenu(props: FilesMenuProps) {
|
export function FilesMenu(props: FilesMenuProps) {
|
||||||
// Use SWR to fetch files
|
// Use SWR to fetch files
|
||||||
const { data: files, error } = useSWR<string[]>("/api/content/computer", fetcher);
|
const { data: files, error } = useSWR<string[]>("/api/content/computer", fetcher);
|
||||||
const { data: selectedFiles, error: selectedFilesError } = useSWR(
|
const { data: selectedFiles, error: selectedFilesError } = useSWR(
|
||||||
@@ -981,13 +981,6 @@ export default function AllConversations(props: SidePanelProps) {
|
|||||||
sideBarOpen={props.sideBarOpen}
|
sideBarOpen={props.sideBarOpen}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{props.sideBarOpen && (
|
|
||||||
<FilesMenu
|
|
||||||
conversationId={props.conversationId}
|
|
||||||
uploadedFiles={props.uploadedFiles}
|
|
||||||
isMobileWidth={props.isMobileWidth}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
|
SidebarRail,
|
||||||
} from "@/components/ui/sidebar";
|
} from "@/components/ui/sidebar";
|
||||||
import {
|
import {
|
||||||
KhojAgentLogo,
|
KhojAgentLogo,
|
||||||
@@ -150,6 +151,7 @@ export function AppSidebar(props: AppSidebarProps) {
|
|||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<FooterMenu sideBarIsOpen={open} />
|
<FooterMenu sideBarIsOpen={open} />
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
|
<SidebarRail />
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,7 +314,15 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea className={`h-[73vh] relative`} ref={scrollAreaRef}>
|
<ScrollArea
|
||||||
|
className={`
|
||||||
|
h-[calc(100svh-theme(spacing.44))]
|
||||||
|
sm:h-[calc(100svh-theme(spacing.44))]
|
||||||
|
md:h-[calc(100svh-theme(spacing.44))]
|
||||||
|
lg:h-[calc(100svh-theme(spacing.72))]
|
||||||
|
`}
|
||||||
|
ref={scrollAreaRef}>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className={`${styles.chatHistory} ${props.customClassName}`}>
|
<div className={`${styles.chatHistory} ${props.customClassName}`}>
|
||||||
<div ref={sentinelRef} style={{ height: "1px" }}>
|
<div ref={sentinelRef} style={{ height: "1px" }}>
|
||||||
|
|||||||
148
src/interface/web/app/components/chatSidebar/chatSidebar.tsx
Normal file
148
src/interface/web/app/components/chatSidebar/chatSidebar.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { Bell } from "@phosphor-icons/react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
|
||||||
|
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { ModelSelector } from "@/app/common/modelSelector";
|
||||||
|
import { FilesMenu } from "../allConversations/allConversations";
|
||||||
|
import { AgentConfigurationOptions } from "@/app/agents/page";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { Sheet, SheetContent } from "@/components/ui/sheet";
|
||||||
|
|
||||||
|
interface ChatSideBarProps {
|
||||||
|
conversationId: string;
|
||||||
|
isOpen: boolean;
|
||||||
|
isMobileWidth?: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
||||||
|
|
||||||
|
export function ChatSidebar({ ...props }: ChatSideBarProps) {
|
||||||
|
const { data: agentConfigurationOptions, error: agentConfigurationOptionsError } =
|
||||||
|
useSWR<AgentConfigurationOptions>("/api/agents/options", fetcher);
|
||||||
|
|
||||||
|
if (props.isMobileWidth) {
|
||||||
|
return (
|
||||||
|
<Sheet
|
||||||
|
open={props.isOpen}
|
||||||
|
onOpenChange={props.onOpenChange}>
|
||||||
|
<SheetContent
|
||||||
|
className="w-[300px] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
|
||||||
|
>
|
||||||
|
<ChatSidebarInternal {...props} />
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChatSidebarInternal {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ChatSidebarInternal({ ...props }: ChatSideBarProps) {
|
||||||
|
const { data: agentConfigurationOptions, error: agentConfigurationOptionsError } =
|
||||||
|
useSWR<AgentConfigurationOptions>("/api/agents/options", fetcher);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sidebar
|
||||||
|
collapsible="none"
|
||||||
|
className={`ml-auto rounded-lg p-2 transition-all transform duration-300 ease-in-out
|
||||||
|
${props.isOpen
|
||||||
|
? "translate-x-0 opacity-100 w-[300px]"
|
||||||
|
: "translate-x-full opacity-0 w-0"}
|
||||||
|
`}
|
||||||
|
variant="floating">
|
||||||
|
<SidebarContent>
|
||||||
|
<SidebarHeader>
|
||||||
|
Chat Options
|
||||||
|
</SidebarHeader>
|
||||||
|
<SidebarGroup key={"test"} className="border-b last:border-none">
|
||||||
|
<SidebarGroupContent className="gap-0">
|
||||||
|
<SidebarMenu className="p-0 m-0">
|
||||||
|
<SidebarMenuItem key={"item4"} className="list-none">
|
||||||
|
<span>Custom Instructions</span>
|
||||||
|
<Textarea className="w-full h-32" />
|
||||||
|
</SidebarMenuItem>
|
||||||
|
<SidebarMenuItem key={"item"} className="list-none">
|
||||||
|
<SidebarMenuButton>
|
||||||
|
<Bell /> <span>Model</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
<ModelSelector />
|
||||||
|
</SidebarMenuItem>
|
||||||
|
<SidebarMenuItem key={"item1"} className="list-none">
|
||||||
|
<SidebarMenuButton>
|
||||||
|
<Bell /> <span>Input Tools</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline">Input Tools</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="w-56">
|
||||||
|
<DropdownMenuLabel>Input Tool Options</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
{
|
||||||
|
Object.entries(agentConfigurationOptions?.input_tools ?? {}).map(([key, value]) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={true}
|
||||||
|
onCheckedChange={() => { }}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
<SidebarMenuItem key={"item2"} className="list-none">
|
||||||
|
<SidebarMenuButton>
|
||||||
|
<Bell /> <span>Output Tools</span>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline">Output Tools</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="w-56">
|
||||||
|
<DropdownMenuLabel>Output Tool Options</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
{
|
||||||
|
Object.entries(agentConfigurationOptions?.output_modes ?? {}).map(([key, value]) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
checked={true}
|
||||||
|
onCheckedChange={() => { }}
|
||||||
|
>
|
||||||
|
{key}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
<SidebarMenuItem key={"item3"} className="list-none">
|
||||||
|
<FilesMenu
|
||||||
|
conversationId={props.conversationId}
|
||||||
|
uploadedFiles={[]}
|
||||||
|
isMobileWidth={props.isMobileWidth ?? false}
|
||||||
|
/>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
</SidebarContent>
|
||||||
|
</Sidebar>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -35,7 +35,6 @@ div.inputBox:focus {
|
|||||||
div.chatBodyFull {
|
div.chatBodyFull {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.inputBox {
|
button.inputBox {
|
||||||
@@ -78,7 +77,7 @@ div.titleBar {
|
|||||||
div.chatBoxBody {
|
div.chatBoxBody {
|
||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 95%;
|
width: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
29
src/interface/web/components/ui/hover-card.tsx
Normal file
29
src/interface/web/components/ui/hover-card.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const HoverCard = HoverCardPrimitive.Root
|
||||||
|
|
||||||
|
const HoverCardTrigger = HoverCardPrimitive.Trigger
|
||||||
|
|
||||||
|
const HoverCardContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<HoverCardPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"@radix-ui/react-collapsible": "^1.1.0",
|
"@radix-ui/react-collapsible": "^1.1.0",
|
||||||
"@radix-ui/react-dialog": "^1.1.4",
|
"@radix-ui/react-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||||
|
"@radix-ui/react-hover-card": "^1.1.4",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.0",
|
||||||
"@radix-ui/react-menubar": "^1.1.1",
|
"@radix-ui/react-menubar": "^1.1.1",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.0",
|
"@radix-ui/react-navigation-menu": "^1.2.0",
|
||||||
|
|||||||
@@ -137,12 +137,23 @@ const config = {
|
|||||||
"0%": { opacity: "0", transform: "translateY(20px)" },
|
"0%": { opacity: "0", transform: "translateY(20px)" },
|
||||||
"100%": { opacity: "1", transform: "translateY(0)" },
|
"100%": { opacity: "1", transform: "translateY(0)" },
|
||||||
},
|
},
|
||||||
|
fadeInRight: {
|
||||||
|
"0%": { opacity: "0", transform: "translateX(20px)" },
|
||||||
|
"100%": { opacity: "1", transform: "translateX(0)" },
|
||||||
|
},
|
||||||
|
fadeInLeft: {
|
||||||
|
"0%": { opacity: "0", transform: "translateX(-20px)" },
|
||||||
|
"100%": { opacity: "1", transform: "translateX(0)" },
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
||||||
"fade-in-up": "fadeInUp 0.3s ease-out",
|
"fade-in-up": "fadeInUp 0.3s ease-out",
|
||||||
|
"fade-in-right": "fadeInRight 0.3s ease-out",
|
||||||
|
"fade-in-left": "fadeInLeft 0.3s ease-out",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -716,7 +716,7 @@
|
|||||||
"@radix-ui/react-primitive" "2.0.1"
|
"@radix-ui/react-primitive" "2.0.1"
|
||||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||||
|
|
||||||
"@radix-ui/react-hover-card@^1.1.2":
|
"@radix-ui/react-hover-card@^1.1.2", "@radix-ui/react-hover-card@^1.1.4":
|
||||||
version "1.1.4"
|
version "1.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.4.tgz#f334fdc1814e14a81ecb4e88a72b92326c1f89a6"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-hover-card/-/react-hover-card-1.1.4.tgz#f334fdc1814e14a81ecb4e88a72b92326c1f89a6"
|
||||||
integrity sha512-QSUUnRA3PQ2UhvoCv3eYvMnCAgGQW+sTu86QPuNb+ZMi+ZENd6UWpiXbcWDQ4AEaKF9KKpCHBeaJz9Rw6lRlaQ==
|
integrity sha512-QSUUnRA3PQ2UhvoCv3eYvMnCAgGQW+sTu86QPuNb+ZMi+ZENd6UWpiXbcWDQ4AEaKF9KKpCHBeaJz9Rw6lRlaQ==
|
||||||
|
|||||||
Reference in New Issue
Block a user