diff --git a/src/interface/web/app/chat/chat.module.css b/src/interface/web/app/chat/chat.module.css index 2ab692ea..1e2ee8ce 100644 --- a/src/interface/web/app/chat/chat.module.css +++ b/src/interface/web/app/chat/chat.module.css @@ -121,6 +121,7 @@ div.agentIndicator { div.chatLayout { gap: 0; + grid-template-columns: 1fr; } } diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index 7a508b7d..53a7ebc3 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -141,22 +141,22 @@ export default function Chat() { return (
- {title} + Khoj AI - {title} - }>
- + }> + +
-
) } diff --git a/src/interface/web/app/components/chatHistory/chatHistory.tsx b/src/interface/web/app/components/chatHistory/chatHistory.tsx index 39462d9d..e594a824 100644 --- a/src/interface/web/app/components/chatHistory/chatHistory.tsx +++ b/src/interface/web/app/components/chatHistory/chatHistory.tsx @@ -118,7 +118,7 @@ export default function ChatHistory(props: ChatHistoryProps) { setReferencePanelData={setReferencePanelData} setShowReferencePanel={setShowReferencePanel} customClassName='fullHistory' - borderLeftColor='orange-400' + borderLeftColor='orange-500' /> ))} { diff --git a/src/interface/web/app/components/chatMessage/chatMessage.module.css b/src/interface/web/app/components/chatMessage/chatMessage.module.css index 328a1ba6..307050f5 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.module.css +++ b/src/interface/web/app/components/chatMessage/chatMessage.module.css @@ -14,7 +14,7 @@ div.chatMessageWrapper { div.khojfullHistory { border-color: var(--border-color); border-width: 1px; - padding-left: 24px; + padding-left: 4px; } div.youfullHistory { @@ -105,6 +105,7 @@ button.codeCopyButton:hover { } div.feedbackButtons img, +button.codeCopyButton img, button.copyButton img { width: 24px; } diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx index abb47233..15d6d400 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.tsx +++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx @@ -154,6 +154,7 @@ export default function ChatMessage(props: ChatMessageProps) { useEffect(() => { if (messageRef.current) { const preElements = messageRef.current.querySelectorAll('pre > .hljs'); + console.log("make copy button"); preElements.forEach((preElement) => { const copyButton = document.createElement('button'); const copyImage = document.createElement('img'); @@ -212,7 +213,8 @@ export default function ChatMessage(props: ChatMessageProps) { classes.push(styles[chatMessage.by]); if (chatMessage.by === "khoj") { - classes.push(`border-l-4 border-opacity-50 border-l-orange-500 border-l-${props.borderLeftColor}`); + const dynamicBorderColor = `border-l-${props.borderLeftColor}`; + classes.push(`border-l-4 border-opacity-50 border-l-orange-400 ${dynamicBorderColor}`); } return classes.join(' '); diff --git a/src/interface/web/app/components/loading/loading.tsx b/src/interface/web/app/components/loading/loading.tsx index 709a1eaa..4f7ed1c8 100644 --- a/src/interface/web/app/components/loading/loading.tsx +++ b/src/interface/web/app/components/loading/loading.tsx @@ -1,26 +1,17 @@ import styles from './loading.module.css'; +import { CircleNotch } from '@phosphor-icons/react'; + export default function Loading() { - // return ( - //
- // - //
- // ) return (
); +} +export function InlineLoading() { return ( -
-

- Loading... -

-
+ ) } diff --git a/src/interface/web/app/components/navMenu/navMenu.tsx b/src/interface/web/app/components/navMenu/navMenu.tsx index 5749e90c..3d829b1c 100644 --- a/src/interface/web/app/components/navMenu/navMenu.tsx +++ b/src/interface/web/app/components/navMenu/navMenu.tsx @@ -78,7 +78,7 @@ export default function NavMenu(props: NavMenuProps) { : - + Chat diff --git a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx index 5c844738..e9fa2c78 100644 --- a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx +++ b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx @@ -9,10 +9,19 @@ import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; import Link from "next/link"; import useSWR from "swr"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; + +import { InlineLoading } from "../loading/loading"; + import { Dialog, DialogContent, DialogDescription, + DialogFooter, DialogHeader, DialogTitle, DialogTrigger, @@ -20,6 +29,8 @@ import { import { ScrollArea } from "@/components/ui/scroll-area"; +import { ArrowRight, ArrowLeft, ArrowDown, Spinner } from "@phosphor-icons/react"; + interface ChatHistory { conversation_id: string; slug: string; @@ -35,9 +46,21 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + import { Pencil, Trash, Share } from "@phosphor-icons/react"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; + +// Define a fetcher function +const fetcher = (url: string) => fetch(url).then((res) => res.json()); interface GroupedChatHistory { [key: string]: ChatHistory[]; @@ -47,7 +70,7 @@ function renameConversation(conversationId: string, newTitle: string) { const editUrl = `/api/chat/title?client=web&conversation_id=${conversationId}&title=${newTitle}`; fetch(editUrl, { - method: 'POST', + method: 'PATCH', headers: { 'Content-Type': 'application/json', }, @@ -62,7 +85,7 @@ function renameConversation(conversationId: string, newTitle: string) { }); } -function shareConversation(conversationId: string) { +function shareConversation(conversationId: string, setShareUrl: (url: string) => void) { const shareUrl = `/api/chat/share?client=web&conversation_id=${conversationId}`; fetch(shareUrl, { @@ -74,6 +97,7 @@ function shareConversation(conversationId: string) { .then(response => response.json()) .then(data => { console.log(data); + setShareUrl(data.url); }) .catch(err => { console.error(err); @@ -82,7 +106,7 @@ function shareConversation(conversationId: string) { } function deleteConversation(conversationId: string) { - const deleteUrl = `/api/chat/delete?client=web&conversation_id=${conversationId}`; + const deleteUrl = `/api/chat/history?client=web&conversation_id=${conversationId}`; fetch(deleteUrl, { method: 'DELETE', @@ -100,28 +124,354 @@ function deleteConversation(conversationId: string) { }); } + +interface FilesMenuProps { + conversationId: string; +} + +function FilesMenu(props: FilesMenuProps) { + // Use SWR to fetch files + const { data: files, error } = useSWR('/api/config/data/computer', fetcher); + const { data: selectedFiles, error: selectedFilesError } = useSWR(`/api/chat/conversation/file-filters/${props.conversationId}`, fetcher); + const [isOpen, setIsOpen] = useState(false); + const [searchInput, setSearchInput] = useState(''); + const [filteredFiles, setFilteredFiles] = useState([]); + + // Function to handle file click + const handleFileClick = (filename: string) => { + console.log(`File clicked: ${filename}`); + // Implement the logic you want to execute on file click + }; + + useEffect(() => { + if (!files) return; + if (searchInput === '') { + setFilteredFiles(files); + } else { + let filteredFiles = files.filter((filename: string) => filename.toLowerCase().includes(searchInput.toLowerCase())); + setFilteredFiles(filteredFiles); + } + }, [searchInput, files]); + + if (error) return
Failed to load files
; + if (!files) return ; + + return ( + <> + {/* +
    + {files.length === 0 ? ( + + ) : ( + files.map((filename: string) => ( +
  • handleFileClick(filename)}> + {filename} +
  • + )) + )} +
+ {files.length > 0 && } +
*/} + + + +
+
+

+ Manage Files +

+ Using {files.length} files +

+

+ +
+
+
+ + setSearchInput(e.target.value)} /> + { + filteredFiles.length === 0 && ( +
+ No files found +
+ ) + } + { + filteredFiles.map((filename: string) => ( +
+ {filename} +
+ )) + } +
+
+ {/* +
+

+ Manage Files +

+ Using {files.length} files +

+

+ + + +
+ + setSearchInput(e.target.value)} /> + { + filteredFiles.length === 0 && ( +
+ No files found +
+ ) + } + { + filteredFiles.map((filename: string) => ( +
+ {filename} +
+ )) + } +
+
*/} + + ) + +} + +interface SessionsAndFilesProps { + webSocketConnected?: boolean; + setEnabled: (enabled: boolean) => void; + subsetOrganizedData: GroupedChatHistory | null; + organizedData: GroupedChatHistory | null; + data: ChatHistory[] | null; + userProfile: UserProfile | null; +} + +function SessionsAndFiles(props: SessionsAndFilesProps) { + return ( + <> +
+ +
+ +
+ {props.subsetOrganizedData != null && Object.keys(props.subsetOrganizedData).map((agentName) => ( +
+

+ { + props.subsetOrganizedData && + {agentName} + } + {agentName} +

+ {props.subsetOrganizedData && props.subsetOrganizedData[agentName].map((chatHistory) => ( + + ))} +
+ ))} +
+
+ { + (props.data && props.data.length > 5) && ( + + ) + } + + {props.userProfile && + + } + ) +} + interface ChatSessionActionMenuProps { conversationId: string; } function ChatSessionActionMenu(props: ChatSessionActionMenuProps) { + const [renamedTitle, setRenamedTitle] = useState(''); + const [isRenaming, setIsRenaming] = useState(false); + const [isSharing, setIsSharing] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const [shareUrl, setShareUrl] = useState(''); + const [showShareUrl, setShowShareUrl] = useState(false); + + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + if (isSharing) { + shareConversation(props.conversationId, setShareUrl); + setShowShareUrl(true); + setIsSharing(false); + } + }, [isSharing]); + + if (isRenaming) { + return ( + setIsRenaming(open)}> + + + Set a new title for the conversation + + This will help you identify the conversation easily, and also help you search for it later. + + setRenamedTitle(e.target.value)} + /> + + + + + + + ) + } + + if (isSharing || showShareUrl) { + if (shareUrl) { + navigator.clipboard.writeText(shareUrl); + } + return ( + { + setShowShareUrl(open) + setIsSharing(open) + }}> + + + Conversation Share URL + + Sharing this chat session will allow anyone with a link to view the conversation. + + + + + { + !showShareUrl && + + } + { + showShareUrl && + + } + + + + ) + } + + if (isDeleting) { + console.log("Deleting"); + return ( + setIsDeleting(open)}> + + + Delete Conversation + + Are you sure you want to delete this conversation? This action cannot be undone. + + + + Cancel + { + deleteConversation(props.conversationId); + setIsDeleting(false); + }} + className="bg-rose-500 hover:bg-rose-600">Delete + + + + ) + } return ( - + setIsOpen(open)} + open={isOpen}> : - - - @@ -151,27 +501,24 @@ interface ChatSessionsModalProps { data: GroupedChatHistory | null; } - -// function ConversationList() - function ChatSessionsModal({ data }: ChatSessionsModalProps) { return ( + className="flex text-left text-medium text-gray-500 hover:text-gray-900 cursor-pointer my-4 text-sm"> Show All All Conversations - + {data && Object.keys(data).map((agentName) => (
-

+
{agentName} {agentName} -

+
{data[agentName].map((chatHistory) => ( - + {props.userProfile.username[0]} @@ -316,52 +663,24 @@ export default function SidePanel(props: SidePanelProps) { { enabled ?
-
- -
- -
- {subsetOrganizedData && Object.keys(subsetOrganizedData).map((agentName) => ( -
-

- {agentName} - {agentName} -

- {subsetOrganizedData[agentName].map((chatHistory) => ( - - ))} -
- ))} -
-
- { - (data && data.length > 5) && ( - - ) - } - {userProfile && - - } +
:
- { userProfile && + + {userProfile && } -
} diff --git a/src/interface/web/app/components/sidePanel/sidePanel.module.css b/src/interface/web/app/components/sidePanel/sidePanel.module.css index f8c33aee..03b4cd1b 100644 --- a/src/interface/web/app/components/sidePanel/sidePanel.module.css +++ b/src/interface/web/app/components/sidePanel/sidePanel.module.css @@ -79,7 +79,6 @@ div.profile { grid-template-columns: auto 1fr; gap: 1rem; align-items: center; - align-self: flex-end; margin-top: auto; } diff --git a/src/interface/web/components/ui/collapsible.tsx b/src/interface/web/components/ui/collapsible.tsx new file mode 100644 index 00000000..9fa48946 --- /dev/null +++ b/src/interface/web/components/ui/collapsible.tsx @@ -0,0 +1,11 @@ +"use client" + +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/src/interface/web/components/ui/popover.tsx b/src/interface/web/components/ui/popover.tsx new file mode 100644 index 00000000..a0ec48be --- /dev/null +++ b/src/interface/web/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/src/interface/web/package.json b/src/interface/web/package.json index db1b0b3d..6ca1c013 100644 --- a/src/interface/web/package.json +++ b/src/interface/web/package.json @@ -20,11 +20,13 @@ "@phosphor-icons/react": "^2.1.7", "@radix-ui/react-alert-dialog": "^1.1.1", "@radix-ui/react-avatar": "^1.1.0", + "@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-menubar": "^1.1.1", "@radix-ui/react-navigation-menu": "^1.2.0", + "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@types/katex": "^0.16.7", diff --git a/src/interface/web/yarn.lock b/src/interface/web/yarn.lock index 40dfdd7a..e56434ad 100644 --- a/src/interface/web/yarn.lock +++ b/src/interface/web/yarn.lock @@ -535,6 +535,20 @@ "@radix-ui/react-use-callback-ref" "1.1.0" "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-collapsible@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.0.tgz#4d49ddcc7b7d38f6c82f1fd29674f6fab5353e77" + integrity sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-collection@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" @@ -692,6 +706,27 @@ "@radix-ui/react-use-previous" "1.1.0" "@radix-ui/react-visually-hidden" "1.1.0" +"@radix-ui/react-popover@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.1.tgz#604b783cdb3494ed4f16a58c17f0e81e61ab7775" + integrity sha512-3y1A3isulwnWhvTTwmIreiB8CF4L+qRjZnK1wYLO7pplddzXKby/GnZ2M7OZY3qgnl6p9AodUIHRYGXNah8Y7g== + dependencies: + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.0" + "@radix-ui/react-focus-guards" "1.1.0" + "@radix-ui/react-focus-scope" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-popper" "1.2.0" + "@radix-ui/react-portal" "1.1.1" + "@radix-ui/react-presence" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.7" + "@radix-ui/react-popper@1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz#a3e500193d144fe2d8f5d5e60e393d64111f2a7a"