mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-23 23:48:56 +01:00
Improve the logged out share experience
This commit is contained in:
parent
6f1d799759
commit
bea0aa5445
6 changed files with 102 additions and 51 deletions
|
@ -39,11 +39,15 @@ import {
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
AlertDialogContent,
|
AlertDialogContent,
|
||||||
AlertDialogDescription,
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle
|
AlertDialogTitle
|
||||||
} from '@/components/ui/alert-dialog';
|
} from '@/components/ui/alert-dialog';
|
||||||
import { Popover, PopoverContent } from '@/components/ui/popover';
|
import { Popover, PopoverContent } from '@/components/ui/popover';
|
||||||
import { PopoverTrigger } from '@radix-ui/react-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';
|
||||||
|
|
||||||
export interface ChatOptions {
|
export interface ChatOptions {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
|
@ -66,6 +70,8 @@ export default function ChatInputArea(props: ChatInputProps) {
|
||||||
const [warning, setWarning] = useState<string | null>(null);
|
const [warning, setWarning] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [loginRedirectMessage, setLoginRedirectMessage] = useState<string | null>(null);
|
||||||
|
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
|
||||||
|
|
||||||
const [progressValue, setProgressValue] = useState(0);
|
const [progressValue, setProgressValue] = useState(0);
|
||||||
|
|
||||||
|
@ -87,7 +93,15 @@ export default function ChatInputArea(props: ChatInputProps) {
|
||||||
}, [uploading]);
|
}, [uploading]);
|
||||||
|
|
||||||
function onSendMessage() {
|
function onSendMessage() {
|
||||||
props.sendMessage(message);
|
if (!message.trim()) return;
|
||||||
|
|
||||||
|
if (!props.isLoggedIn) {
|
||||||
|
setLoginRedirectMessage('Hey there, you need to be signed in to send messages to Khoj AI');
|
||||||
|
setShowLoginPrompt(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.sendMessage(message.trim());
|
||||||
setMessage('');
|
setMessage('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +117,12 @@ export default function ChatInputArea(props: ChatInputProps) {
|
||||||
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
if (!event.target.files) return;
|
if (!event.target.files) return;
|
||||||
|
|
||||||
|
if (!props.isLoggedIn) {
|
||||||
|
setLoginRedirectMessage('Whoa! You need to login to upload files');
|
||||||
|
setShowLoginPrompt(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
uploadDataForIndexing(
|
uploadDataForIndexing(
|
||||||
event.target.files,
|
event.target.files,
|
||||||
setWarning,
|
setWarning,
|
||||||
|
@ -153,6 +173,13 @@ export default function ChatInputArea(props: ChatInputProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{
|
||||||
|
showLoginPrompt && loginRedirectMessage && (
|
||||||
|
<LoginPrompt
|
||||||
|
onOpenChange={setShowLoginPrompt}
|
||||||
|
loginRedirectMessage={loginRedirectMessage} />
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
uploading && (
|
uploading && (
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
/* HTML: <div class="loader"></div> */
|
|
||||||
.loader {
|
|
||||||
--c: conic-gradient(from -90deg, hsla(var(--secondary)) 90deg, #0000 0);
|
|
||||||
background: var(--c), var(--c);
|
|
||||||
background-size: 40% 40%;
|
|
||||||
animation: l19 1s infinite alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes l19 {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
10% {
|
|
||||||
background-position: 0 0, 0 calc(100%/3)
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
background-position: 0 0, calc(100%/3) calc(100%/3)
|
|
||||||
}
|
|
||||||
|
|
||||||
90%,
|
|
||||||
100% {
|
|
||||||
background-position: 0 0, calc(100%/3) 0
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,11 @@
|
||||||
import styles from './loading.module.css';
|
|
||||||
|
|
||||||
import { CircleNotch } from '@phosphor-icons/react';
|
import { CircleNotch } from '@phosphor-icons/react';
|
||||||
|
|
||||||
export default function Loading() {
|
export default function Loading() {
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.loader} h-[100vh] flex items-center justify-center`}></div>
|
// NOTE: We can display usage tips here for casual learning moments.
|
||||||
|
<div className={`bg-background opacity-50 flex items-center justify-center h-screen`}>
|
||||||
|
<div>Loading <span><CircleNotch className={`inline animate-spin h-5 w-5`} /></span></div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
src/interface/web/app/components/loginPrompt/loginPrompt.tsx
Normal file
42
src/interface/web/app/components/loginPrompt/loginPrompt.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle
|
||||||
|
} from '@/components/ui/alert-dialog';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export interface LoginPromptProps {
|
||||||
|
loginRedirectMessage: string;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LoginPrompt(props: LoginPromptProps) {
|
||||||
|
return (
|
||||||
|
<AlertDialog
|
||||||
|
open={true}
|
||||||
|
onOpenChange={props.onOpenChange}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Sign in to Khoj to continue</AlertDialogTitle>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogDescription>{props.loginRedirectMessage}. By logging in, you agree to our <Link href="https://khoj.dev/terms-of-service">Terms of Service.</Link></AlertDialogDescription>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Dismiss</AlertDialogCancel>
|
||||||
|
<AlertDialogAction className='bg-slate-400 hover:bg-slate-500'
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = `/login?next=${encodeURIComponent(window.location.pathname)}`;
|
||||||
|
}}>
|
||||||
|
<Link href={`/login?next=${encodeURIComponent(window.location.pathname)}`}> {/* Redirect to login page */}
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import styles from "./sidePanel.module.css";
|
||||||
|
|
||||||
import { Suspense, useEffect, useState } from "react";
|
import { Suspense, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { UserProfile } from "@/app/common/auth";
|
import { UserProfile, useAuthenticatedData } from "@/app/common/auth";
|
||||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
@ -45,7 +45,7 @@ import {
|
||||||
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
import { ArrowRight, ArrowLeft, ArrowDown, Spinner, Check, FolderPlus, DotsThreeVertical } from "@phosphor-icons/react";
|
import { ArrowRight, ArrowLeft, ArrowDown, Spinner, Check, FolderPlus, DotsThreeVertical, House, StackPlus, UserCirclePlus } from "@phosphor-icons/react";
|
||||||
|
|
||||||
interface ChatHistory {
|
interface ChatHistory {
|
||||||
conversation_id: string;
|
conversation_id: string;
|
||||||
|
@ -496,7 +496,7 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
onOpenChange={(open) => setIsOpen(open)}
|
onOpenChange={(open) => setIsOpen(open)}
|
||||||
open={isOpen}>
|
open={isOpen}>
|
||||||
<DropdownMenuTrigger><DotsThreeVertical className="h-4 w-4"/></DropdownMenuTrigger>
|
<DropdownMenuTrigger><DotsThreeVertical className="h-4 w-4" /></DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<Button className="p-0 text-sm h-auto" variant={'ghost'} onClick={() => setIsRenaming(true)}>
|
<Button className="p-0 text-sm h-auto" variant={'ghost'} onClick={() => setIsRenaming(true)}>
|
||||||
|
@ -544,7 +544,7 @@ function ChatSessionsModal({ data }: ChatSessionsModalProps) {
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger
|
<DialogTrigger
|
||||||
className="flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-4 text-sm p-[0.5rem]">
|
className="flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-4 text-sm p-[0.5rem]">
|
||||||
<span className="mr-2">See All <ArrowRight className="h-4 w-4" /></span>
|
<span className="mr-2">See All <ArrowRight className="h-4 w-4" /></span>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
@ -656,9 +656,9 @@ export default function SidePanel(props: SidePanelProps) {
|
||||||
const [subsetOrganizedData, setSubsetOrganizedData] = useState<GroupedChatHistory | null>(null);
|
const [subsetOrganizedData, setSubsetOrganizedData] = useState<GroupedChatHistory | null>(null);
|
||||||
const [enabled, setEnabled] = useState(false);
|
const [enabled, setEnabled] = useState(false);
|
||||||
|
|
||||||
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
|
const authenticatedData = useAuthenticatedData();
|
||||||
|
const { data: chatSessions } = useChatSessionsFetchRequest(authenticatedData ? `/api/chat/sessions` : '');
|
||||||
|
|
||||||
const { data: chatSessions } = useChatSessionsFetchRequest('/api/chat/sessions');
|
|
||||||
|
|
||||||
const [isMobileWidth, setIsMobileWidth] = useState(false);
|
const [isMobileWidth, setIsMobileWidth] = useState(false);
|
||||||
|
|
||||||
|
@ -707,16 +707,6 @@ export default function SidePanel(props: SidePanelProps) {
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
setIsMobileWidth(window.innerWidth < 768);
|
setIsMobileWidth(window.innerWidth < 768);
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch('/api/v1/user', { method: 'GET' })
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data: UserProfile) => {
|
|
||||||
setUserProfile(data);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -728,7 +718,8 @@ export default function SidePanel(props: SidePanelProps) {
|
||||||
height={40}
|
height={40}
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
isMobileWidth ?
|
authenticatedData &&
|
||||||
|
isMobileWidth ?
|
||||||
<Drawer>
|
<Drawer>
|
||||||
<DrawerTrigger><ArrowRight className="h-4 w-4 mx-2" /></DrawerTrigger>
|
<DrawerTrigger><ArrowRight className="h-4 w-4 mx-2" /></DrawerTrigger>
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
|
@ -744,7 +735,7 @@ export default function SidePanel(props: SidePanelProps) {
|
||||||
organizedData={organizedData}
|
organizedData={organizedData}
|
||||||
data={data}
|
data={data}
|
||||||
uploadedFiles={props.uploadedFiles}
|
uploadedFiles={props.uploadedFiles}
|
||||||
userProfile={userProfile}
|
userProfile={authenticatedData}
|
||||||
conversationId={props.conversationId}
|
conversationId={props.conversationId}
|
||||||
isMobileWidth={isMobileWidth}
|
isMobileWidth={isMobileWidth}
|
||||||
/>
|
/>
|
||||||
|
@ -763,7 +754,7 @@ export default function SidePanel(props: SidePanelProps) {
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
enabled &&
|
authenticatedData && enabled &&
|
||||||
<div className={`${styles.panelWrapper}`}>
|
<div className={`${styles.panelWrapper}`}>
|
||||||
<SessionsAndFiles
|
<SessionsAndFiles
|
||||||
webSocketConnected={props.webSocketConnected}
|
webSocketConnected={props.webSocketConnected}
|
||||||
|
@ -772,13 +763,26 @@ export default function SidePanel(props: SidePanelProps) {
|
||||||
organizedData={organizedData}
|
organizedData={organizedData}
|
||||||
data={data}
|
data={data}
|
||||||
uploadedFiles={props.uploadedFiles}
|
uploadedFiles={props.uploadedFiles}
|
||||||
userProfile={userProfile}
|
userProfile={authenticatedData}
|
||||||
conversationId={props.conversationId}
|
conversationId={props.conversationId}
|
||||||
isMobileWidth={isMobileWidth}
|
isMobileWidth={isMobileWidth}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
!authenticatedData && enabled &&
|
||||||
|
<div className={`${styles.panelWrapper}`}>
|
||||||
|
<Link href="/">
|
||||||
|
<Button variant="ghost"><House className="h-4 w-4 mr-1" />Home</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/">
|
||||||
|
<Button variant="ghost"><StackPlus className="h-4 w-4 mr-1" />New Conversation</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href={`/login?next=${encodeURIComponent(window.location.pathname)}`}> {/* Redirect to login page */}
|
||||||
|
<Button variant="default"><UserCirclePlus className="h-4 w-4 mr-1"/>Sign Up</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||||
<ChatInputArea
|
<ChatInputArea
|
||||||
isLoggedIn={props.isLoggedIn}
|
isLoggedIn={props.isLoggedIn}
|
||||||
sendMessage={(message) => setMessage(message)}
|
sendMessage={(message) => setMessage(message)}
|
||||||
sendDisabled={!props.isLoggedIn || processingMessage}
|
sendDisabled={processingMessage}
|
||||||
chatOptionsData={props.chatOptionsData}
|
chatOptionsData={props.chatOptionsData}
|
||||||
conversationId={props.conversationId}
|
conversationId={props.conversationId}
|
||||||
isMobileWidth={props.isMobileWidth}
|
isMobileWidth={props.isMobileWidth}
|
||||||
|
@ -280,7 +280,7 @@ export default function SharedChat({ params }: { params: { slug: string } }) {
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.main} ${authenticatedData ? styles.chatLayout : ''}`}>
|
<div className={`${styles.main} ${styles.chatLayout}`}>
|
||||||
<title>
|
<title>
|
||||||
{title}
|
{title}
|
||||||
</title>
|
</title>
|
||||||
|
@ -290,6 +290,7 @@ export default function SharedChat({ params }: { params: { slug: string } }) {
|
||||||
conversationId={conversationId ?? null}
|
conversationId={conversationId ?? null}
|
||||||
uploadedFiles={uploadedFiles} />
|
uploadedFiles={uploadedFiles} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.chatBox}>
|
<div className={styles.chatBox}>
|
||||||
<NavMenu selected="Chat" title={title} />
|
<NavMenu selected="Chat" title={title} />
|
||||||
<div className={styles.chatBoxBody}>
|
<div className={styles.chatBoxBody}>
|
||||||
|
|
Loading…
Reference in a new issue