mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 13:25:11 +00:00
References, mobile friendly chat sessions and file filter
This commit is contained in:
@@ -10,7 +10,8 @@ import NavMenu from '../components/navMenu/navMenu';
|
|||||||
import { useSearchParams } from 'next/navigation'
|
import { useSearchParams } from 'next/navigation'
|
||||||
import Loading from '../components/loading/loading';
|
import Loading from '../components/loading/loading';
|
||||||
|
|
||||||
import { handleCompiledReferences, handleImageResponse, setupWebSocket } from '../common/chatFunctions';
|
import { handleCompiledReferences, handleImageResponse, setupWebSocket, uploadDataForIndexing } from '../common/chatFunctions';
|
||||||
|
import { Progress } from "@/components/ui/progress"
|
||||||
|
|
||||||
import 'katex/dist/katex.min.css';
|
import 'katex/dist/katex.min.css';
|
||||||
import { Lightbulb, ArrowCircleUp, FileArrowUp, Microphone } from '@phosphor-icons/react';
|
import { Lightbulb, ArrowCircleUp, FileArrowUp, Microphone } from '@phosphor-icons/react';
|
||||||
@@ -18,15 +19,25 @@ import { Lightbulb, ArrowCircleUp, FileArrowUp, Microphone } from '@phosphor-ico
|
|||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Context, OnlineContextData, StreamMessage } from '../components/chatMessage/chatMessage';
|
import { StreamMessage } from '../components/chatMessage/chatMessage';
|
||||||
|
import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
|
||||||
|
|
||||||
interface ChatInputProps {
|
interface ChatInputProps {
|
||||||
sendMessage: (message: string) => void;
|
sendMessage: (message: string) => void;
|
||||||
sendDisabled: boolean;
|
sendDisabled: boolean;
|
||||||
|
setUploadedFiles?: (files: string[]) => void;
|
||||||
|
conversationId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatInputArea(props: ChatInputProps) {
|
function ChatInputArea(props: ChatInputProps) {
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const [warning, setWarning] = useState<string | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
|
||||||
|
const [progressValue, setProgressValue] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (message.startsWith('/')) {
|
if (message.startsWith('/')) {
|
||||||
@@ -35,17 +46,106 @@ function ChatInputArea(props: ChatInputProps) {
|
|||||||
}
|
}
|
||||||
}, [message]);
|
}, [message]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!uploading) {
|
||||||
|
setProgressValue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploading) {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setProgressValue((prev) => {
|
||||||
|
const increment = Math.floor(Math.random() * 5) + 1; // Generates a random number between 1 and 5
|
||||||
|
const nextValue = prev + increment;
|
||||||
|
return nextValue < 100 ? nextValue : 100; // Ensures progress does not exceed 100
|
||||||
|
});
|
||||||
|
}, 800);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, [uploading]);
|
||||||
|
|
||||||
function onSendMessage() {
|
function onSendMessage() {
|
||||||
props.sendMessage(message);
|
props.sendMessage(message);
|
||||||
setMessage('');
|
setMessage('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleFileButtonClick() {
|
||||||
|
if (!fileInputRef.current) return;
|
||||||
|
fileInputRef.current.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
if (!event.target.files) return;
|
||||||
|
|
||||||
|
uploadDataForIndexing(
|
||||||
|
event.target.files,
|
||||||
|
setWarning,
|
||||||
|
setUploading,
|
||||||
|
setError,
|
||||||
|
props.setUploadedFiles,
|
||||||
|
props.conversationId);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{
|
||||||
|
uploading && (
|
||||||
|
<AlertDialog
|
||||||
|
open={uploading}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Uploading data. Please wait.</AlertDialogTitle>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
<Progress
|
||||||
|
indicatorColor='bg-slate-500'
|
||||||
|
className='w-full h-2 rounded-full'
|
||||||
|
value={progressValue} />
|
||||||
|
</AlertDialogDescription>
|
||||||
|
<AlertDialogAction className='bg-slate-400 hover:bg-slate-500' onClick={() => setUploading(false)}>Dismiss</AlertDialogAction>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)}
|
||||||
|
{
|
||||||
|
warning && (
|
||||||
|
<AlertDialog
|
||||||
|
open={warning !== null}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Data Upload Warning</AlertDialogTitle>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogDescription>{warning}</AlertDialogDescription>
|
||||||
|
<AlertDialogAction className='bg-slate-400 hover:bg-slate-500' onClick={() => setWarning(null)}>Close</AlertDialogAction>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
error && (
|
||||||
|
<AlertDialog
|
||||||
|
open={error !== null}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Oh no!</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>Something went wrong while uploading your data</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogDescription>{error}</AlertDialogDescription>
|
||||||
|
<AlertDialogAction className='bg-slate-400 hover:bg-slate-500' onClick={() => setError(null)}>Close</AlertDialogAction>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
multiple={true}
|
||||||
|
ref={fileInputRef}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant={'ghost'}
|
variant={'ghost'}
|
||||||
className="!bg-none p-1 h-auto text-3xl rounded-full text-gray-300 hover:text-gray-500"
|
className="!bg-none p-1 h-auto text-3xl rounded-full text-gray-300 hover:text-gray-500"
|
||||||
disabled={props.sendDisabled}>
|
disabled={props.sendDisabled}
|
||||||
|
onClick={handleFileButtonClick}>
|
||||||
<FileArrowUp weight='fill' />
|
<FileArrowUp weight='fill' />
|
||||||
</Button>
|
</Button>
|
||||||
<div className="grid w-full gap-1.5">
|
<div className="grid w-full gap-1.5">
|
||||||
@@ -91,6 +191,7 @@ interface ChatBodyDataProps {
|
|||||||
onConversationIdChange?: (conversationId: string) => void;
|
onConversationIdChange?: (conversationId: string) => void;
|
||||||
setQueryToProcess: (query: string) => void;
|
setQueryToProcess: (query: string) => void;
|
||||||
streamedMessages: StreamMessage[];
|
streamedMessages: StreamMessage[];
|
||||||
|
setUploadedFiles: (files: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -153,10 +254,18 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={false ? styles.chatBody : styles.chatBodyFull}>
|
<div className={false ? styles.chatBody : styles.chatBodyFull}>
|
||||||
<ChatHistory conversationId={conversationId} setTitle={props.setTitle} pendingMessage={processingMessage ? message : ''} incomingMessages={props.streamedMessages} />
|
<ChatHistory
|
||||||
|
conversationId={conversationId}
|
||||||
|
setTitle={props.setTitle}
|
||||||
|
pendingMessage={processingMessage ? message : ''}
|
||||||
|
incomingMessages={props.streamedMessages} />
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles.inputBox} bg-background align-middle items-center justify-center`}>
|
<div className={`${styles.inputBox} bg-background align-middle items-center justify-center`}>
|
||||||
<ChatInputArea sendMessage={(message) => setMessage(message)} sendDisabled={processingMessage} />
|
<ChatInputArea
|
||||||
|
sendMessage={(message) => setMessage(message)}
|
||||||
|
sendDisabled={processingMessage}
|
||||||
|
conversationId={conversationId}
|
||||||
|
setUploadedFiles={props.setUploadedFiles} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -171,6 +280,7 @@ export default function Chat() {
|
|||||||
const [messages, setMessages] = useState<StreamMessage[]>([]);
|
const [messages, setMessages] = useState<StreamMessage[]>([]);
|
||||||
const [queryToProcess, setQueryToProcess] = useState<string>('');
|
const [queryToProcess, setQueryToProcess] = useState<string>('');
|
||||||
const [processQuerySignal, setProcessQuerySignal] = useState(false);
|
const [processQuerySignal, setProcessQuerySignal] = useState(false);
|
||||||
|
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
|
||||||
|
|
||||||
|
|
||||||
const handleWebSocketMessage = (event: MessageEvent) => {
|
const handleWebSocketMessage = (event: MessageEvent) => {
|
||||||
@@ -318,7 +428,7 @@ export default function Chat() {
|
|||||||
{title}
|
{title}
|
||||||
</title>
|
</title>
|
||||||
<div className={styles.sidePanel}>
|
<div className={styles.sidePanel}>
|
||||||
<SidePanel webSocketConnected={chatWS !== null} conversationId={conversationId} />
|
<SidePanel webSocketConnected={chatWS !== null} conversationId={conversationId} uploadedFiles={uploadedFiles} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.chatBox}>
|
<div className={styles.chatBox}>
|
||||||
<NavMenu selected="Chat" title={title} />
|
<NavMenu selected="Chat" title={title} />
|
||||||
@@ -329,6 +439,7 @@ export default function Chat() {
|
|||||||
chatOptionsData={chatOptionsData}
|
chatOptionsData={chatOptionsData}
|
||||||
setTitle={setTitle}
|
setTitle={setTitle}
|
||||||
setQueryToProcess={setQueryToProcess}
|
setQueryToProcess={setQueryToProcess}
|
||||||
|
setUploadedFiles={setUploadedFiles}
|
||||||
onConversationIdChange={handleConversationIdChange} />
|
onConversationIdChange={handleConversationIdChange} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -143,3 +143,156 @@ export function handleImageResponse(imageJson: any) {
|
|||||||
reference.response = rawResponse;
|
reference.response = rawResponse;
|
||||||
return reference;
|
return reference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function modifyFileFilterForConversation(
|
||||||
|
conversationId: string | null,
|
||||||
|
filenames: string[],
|
||||||
|
setAddedFiles: (files: string[]) => void,
|
||||||
|
mode: 'add' | 'remove') {
|
||||||
|
|
||||||
|
if (!conversationId) {
|
||||||
|
console.error("No conversation ID provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const method = mode === 'add' ? 'POST' : 'DELETE';
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
conversation_id: conversationId,
|
||||||
|
filenames: filenames,
|
||||||
|
}
|
||||||
|
const addUrl = `/api/chat/conversation/file-filters/bulk`;
|
||||||
|
|
||||||
|
fetch(addUrl, {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log("ADDEDFILES DATA: ", data);
|
||||||
|
setAddedFiles(data);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uploadDataForIndexing(
|
||||||
|
files: FileList,
|
||||||
|
setWarning: (warning: string) => void,
|
||||||
|
setUploading: (uploading: boolean) => void,
|
||||||
|
setError: (error: string) => void,
|
||||||
|
setUploadedFiles?: (files: string[]) => void,
|
||||||
|
conversationId?: string | null) {
|
||||||
|
|
||||||
|
const allowedExtensions = ['text/org', 'text/markdown', 'text/plain', 'text/html', 'application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
|
||||||
|
const allowedFileEndings = ['org', 'md', 'txt', 'html', 'pdf', 'docx'];
|
||||||
|
const badFiles: string[] = [];
|
||||||
|
const goodFiles: File[] = [];
|
||||||
|
|
||||||
|
const uploadedFiles: string[] = [];
|
||||||
|
|
||||||
|
for (let file of files) {
|
||||||
|
const fileEnding = file.name.split('.').pop();
|
||||||
|
if (!file || !file.name || !fileEnding) {
|
||||||
|
if (file) {
|
||||||
|
badFiles.push(file.name);
|
||||||
|
}
|
||||||
|
} else if ((!allowedExtensions.includes(file.type) && !allowedFileEndings.includes(fileEnding.toLowerCase()))) {
|
||||||
|
badFiles.push(file.name);
|
||||||
|
} else {
|
||||||
|
goodFiles.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (goodFiles.length === 0) {
|
||||||
|
setWarning("No supported files found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (badFiles.length > 0) {
|
||||||
|
setWarning("The following files are not supported yet:\n" + badFiles.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
// Create an array of Promises for file reading
|
||||||
|
const fileReadPromises = Array.from(goodFiles).map(file => {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = function (event) {
|
||||||
|
|
||||||
|
if (event.target === null) {
|
||||||
|
reject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileContents = event.target.result;
|
||||||
|
let fileType = file.type;
|
||||||
|
let fileName = file.name;
|
||||||
|
if (fileType === "") {
|
||||||
|
let fileExtension = fileName.split('.').pop();
|
||||||
|
if (fileExtension === "org") {
|
||||||
|
fileType = "text/org";
|
||||||
|
} else if (fileExtension === "md") {
|
||||||
|
fileType = "text/markdown";
|
||||||
|
} else if (fileExtension === "txt") {
|
||||||
|
fileType = "text/plain";
|
||||||
|
} else if (fileExtension === "html") {
|
||||||
|
fileType = "text/html";
|
||||||
|
} else if (fileExtension === "pdf") {
|
||||||
|
fileType = "application/pdf";
|
||||||
|
} else {
|
||||||
|
// Skip this file if its type is not supported
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileContents === null) {
|
||||||
|
reject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileObj = new Blob([fileContents], { type: fileType });
|
||||||
|
formData.append("files", fileObj, file.name);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setUploading(true);
|
||||||
|
|
||||||
|
// Wait for all files to be read before making the fetch request
|
||||||
|
Promise.all(fileReadPromises)
|
||||||
|
.then(() => {
|
||||||
|
return fetch("/api/v1/index/update?force=false&client=web", {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
for (let file of goodFiles) {
|
||||||
|
uploadedFiles.push(file.name);
|
||||||
|
if (conversationId && setUploadedFiles) {
|
||||||
|
modifyFileFilterForConversation(conversationId, [file.name], setUploadedFiles, 'add');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (setUploadedFiles) setUploadedFiles(uploadedFiles);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
setError(`Error uploading file: ${error}`);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setUploading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
import styles from './chatHistory.module.css';
|
import styles from './chatHistory.module.css';
|
||||||
import { useRef, useEffect, useState } from 'react';
|
import { useRef, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import ChatMessage, { ChatHistoryData, SingleChatMessage, StreamMessage, TrainOfThought } from '../chatMessage/chatMessage';
|
import ChatMessage, { ChatHistoryData, StreamMessage, TrainOfThought } from '../chatMessage/chatMessage';
|
||||||
|
|
||||||
import ReferencePanel, { hasValidReferences } from '../referencePanel/referencePanel';
|
|
||||||
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
|
|
||||||
@@ -61,10 +59,19 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
const chatHistoryRef = useRef<HTMLDivElement | null>(null);
|
const chatHistoryRef = useRef<HTMLDivElement | null>(null);
|
||||||
const sentinelRef = useRef<HTMLDivElement | null>(null);
|
const sentinelRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const [showReferencePanel, setShowReferencePanel] = useState(true);
|
|
||||||
const [referencePanelData, setReferencePanelData] = useState<SingleChatMessage | null>(null);
|
|
||||||
const [incompleteIncomingMessageIndex, setIncompleteIncomingMessageIndex] = useState<number | null>(null);
|
const [incompleteIncomingMessageIndex, setIncompleteIncomingMessageIndex] = useState<number | null>(null);
|
||||||
const [fetchingData, setFetchingData] = useState(false);
|
const [fetchingData, setFetchingData] = useState(false);
|
||||||
|
const [isMobileWidth, setIsMobileWidth] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
setIsMobileWidth(window.innerWidth < 768);
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsMobileWidth(window.innerWidth < 768);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This function ensures that scrolling to bottom happens after the data (chat messages) has been updated and rendered the first time.
|
// This function ensures that scrolling to bottom happens after the data (chat messages) has been updated and rendered the first time.
|
||||||
@@ -116,7 +123,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
fetch(`/api/chat/history?client=web&conversation_id=${props.conversationId}&n=${10 * nextPage}`)
|
fetch(`/api/chat/history?client=web&conversation_id=${props.conversationId}&n=${10 * nextPage}`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then((chatData: ChatResponse) => {
|
.then((chatData: ChatResponse) => {
|
||||||
console.log(chatData);
|
props.setTitle(chatData.response.slug);
|
||||||
if (chatData && chatData.response && chatData.response.chat.length > 0) {
|
if (chatData && chatData.response && chatData.response.chat.length > 0) {
|
||||||
|
|
||||||
if (chatData.response.chat.length === data?.chat.length) {
|
if (chatData.response.chat.length === data?.chat.length) {
|
||||||
@@ -229,9 +236,8 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
{(data && data.chat) && data.chat.map((chatMessage, index) => (
|
{(data && data.chat) && data.chat.map((chatMessage, index) => (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={`${index}fullHistory`}
|
key={`${index}fullHistory`}
|
||||||
|
isMobileWidth={isMobileWidth}
|
||||||
chatMessage={chatMessage}
|
chatMessage={chatMessage}
|
||||||
setReferencePanelData={setReferencePanelData}
|
|
||||||
setShowReferencePanel={setShowReferencePanel}
|
|
||||||
customClassName='fullHistory'
|
customClassName='fullHistory'
|
||||||
borderLeftColor='orange-500'
|
borderLeftColor='orange-500'
|
||||||
/>
|
/>
|
||||||
@@ -242,6 +248,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
<>
|
<>
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={`${index}outgoing`}
|
key={`${index}outgoing`}
|
||||||
|
isMobileWidth={isMobileWidth}
|
||||||
chatMessage={
|
chatMessage={
|
||||||
{
|
{
|
||||||
message: message.rawQuery,
|
message: message.rawQuery,
|
||||||
@@ -249,20 +256,21 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
onlineContext: {},
|
onlineContext: {},
|
||||||
created: message.timestamp,
|
created: message.timestamp,
|
||||||
by: "you",
|
by: "you",
|
||||||
intent: {},
|
|
||||||
automationId: '',
|
automationId: '',
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setReferencePanelData={() => { }}
|
|
||||||
setShowReferencePanel={() => { }}
|
|
||||||
customClassName='fullHistory'
|
customClassName='fullHistory'
|
||||||
borderLeftColor='orange-500' />
|
borderLeftColor='orange-500' />
|
||||||
{
|
{
|
||||||
message.trainOfThought && constructTrainOfThought(message.trainOfThought, index === incompleteIncomingMessageIndex, `${index}trainOfThought`, message.completed)
|
message.trainOfThought &&
|
||||||
|
constructTrainOfThought(
|
||||||
|
message.trainOfThought,
|
||||||
|
index === incompleteIncomingMessageIndex,
|
||||||
|
`${index}trainOfThought`, message.completed)
|
||||||
}
|
}
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={`${index}incoming`}
|
key={`${index}incoming`}
|
||||||
|
isMobileWidth={isMobileWidth}
|
||||||
chatMessage={
|
chatMessage={
|
||||||
{
|
{
|
||||||
message: message.rawResponse,
|
message: message.rawResponse,
|
||||||
@@ -270,12 +278,10 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
onlineContext: message.onlineContext,
|
onlineContext: message.onlineContext,
|
||||||
created: message.timestamp,
|
created: message.timestamp,
|
||||||
by: "khoj",
|
by: "khoj",
|
||||||
intent: {},
|
|
||||||
automationId: '',
|
automationId: '',
|
||||||
|
rawQuery: message.rawQuery,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setReferencePanelData={setReferencePanelData}
|
|
||||||
setShowReferencePanel={setShowReferencePanel}
|
|
||||||
customClassName='fullHistory'
|
customClassName='fullHistory'
|
||||||
borderLeftColor='orange-500'
|
borderLeftColor='orange-500'
|
||||||
/>
|
/>
|
||||||
@@ -287,6 +293,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
props.pendingMessage &&
|
props.pendingMessage &&
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
key={"pendingMessage"}
|
key={"pendingMessage"}
|
||||||
|
isMobileWidth={isMobileWidth}
|
||||||
chatMessage={
|
chatMessage={
|
||||||
{
|
{
|
||||||
message: props.pendingMessage,
|
message: props.pendingMessage,
|
||||||
@@ -294,20 +301,13 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
onlineContext: {},
|
onlineContext: {},
|
||||||
created: new Date().toISOString(),
|
created: new Date().toISOString(),
|
||||||
by: "you",
|
by: "you",
|
||||||
intent: {},
|
|
||||||
automationId: '',
|
automationId: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setReferencePanelData={() => { }}
|
|
||||||
setShowReferencePanel={() => { }}
|
|
||||||
customClassName='fullHistory'
|
customClassName='fullHistory'
|
||||||
borderLeftColor='orange-500'
|
borderLeftColor='orange-500'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{
|
|
||||||
(hasValidReferences(referencePanelData) && showReferencePanel) &&
|
|
||||||
<ReferencePanel referencePanelData={referencePanelData} setShowReferencePanel={setShowReferencePanel} />
|
|
||||||
}
|
|
||||||
<div className={`${styles.agentIndicator}`}>
|
<div className={`${styles.agentIndicator}`}>
|
||||||
<a className='no-underline mx-2 flex text-muted-foreground' href={constructAgentLink()} target="_blank" rel="noreferrer">
|
<a className='no-underline mx-2 flex text-muted-foreground' href={constructAgentLink()} target="_blank" rel="noreferrer">
|
||||||
<Lightbulb color='orange' weight='fill' />
|
<Lightbulb color='orange' weight='fill' />
|
||||||
|
|||||||
@@ -61,8 +61,7 @@ div.author {
|
|||||||
|
|
||||||
div.chatFooter {
|
div.chatFooter {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: flex-end;
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.chatButtons {
|
div.chatButtons {
|
||||||
@@ -127,4 +126,8 @@ div.trainOfThought.primary p {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.chatMessageWrapper {
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,14 @@ import styles from './chatMessage.module.css';
|
|||||||
import markdownIt from 'markdown-it';
|
import markdownIt from 'markdown-it';
|
||||||
import mditHljs from "markdown-it-highlightjs";
|
import mditHljs from "markdown-it-highlightjs";
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import Image from 'next/image';
|
|
||||||
|
|
||||||
import 'katex/dist/katex.min.css';
|
import 'katex/dist/katex.min.css';
|
||||||
import 'highlight.js/styles/github.css'
|
import 'highlight.js/styles/github.css'
|
||||||
|
|
||||||
import { hasValidReferences } from '../referencePanel/referencePanel';
|
import { ReferencePanelData, TeaserReferencesSection, constructAllReferences } from '../referencePanel/referencePanel';
|
||||||
|
|
||||||
import { ThumbsUp, ThumbsDown, Copy, Brain, Cloud, Folder, Book, Aperture } from '@phosphor-icons/react';
|
import { ThumbsUp, ThumbsDown, Copy, Brain, Cloud, Folder, Book, Aperture, ArrowRight, SpeakerHifi } from '@phosphor-icons/react';
|
||||||
import { MagnifyingGlass } from '@phosphor-icons/react/dist/ssr';
|
import { MagnifyingGlass } from '@phosphor-icons/react/dist/ssr';
|
||||||
import { compare } from 'swr/_internal';
|
|
||||||
|
|
||||||
const md = new markdownIt({
|
const md = new markdownIt({
|
||||||
html: true,
|
html: true,
|
||||||
@@ -81,21 +79,22 @@ interface AgentData {
|
|||||||
|
|
||||||
interface Intent {
|
interface Intent {
|
||||||
type: string;
|
type: string;
|
||||||
|
query: string;
|
||||||
|
"memory-type": string;
|
||||||
"inferred-queries": string[];
|
"inferred-queries": string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SingleChatMessage {
|
export interface SingleChatMessage {
|
||||||
automationId: string;
|
automationId: string;
|
||||||
by: string;
|
by: string;
|
||||||
intent: {
|
|
||||||
[key: string]: string
|
|
||||||
}
|
|
||||||
message: string;
|
message: string;
|
||||||
context: Context[];
|
context: Context[];
|
||||||
created: string;
|
created: string;
|
||||||
onlineContext: {
|
onlineContext: {
|
||||||
[key: string]: OnlineContextData
|
[key: string]: OnlineContextData
|
||||||
}
|
}
|
||||||
|
rawQuery?: string;
|
||||||
|
intent?: Intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StreamMessage {
|
export interface StreamMessage {
|
||||||
@@ -118,29 +117,43 @@ export interface ChatHistoryData {
|
|||||||
slug: string;
|
slug: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FeedbackButtons() {
|
function sendFeedback(uquery: string, kquery: string, sentiment: string) {
|
||||||
|
fetch('/api/chat/feedback', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ uquery: uquery, kquery: kquery, sentiment: sentiment })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function FeedbackButtons({ uquery, kquery }: { uquery: string, kquery: string }) {
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.feedbackButtons} flex align-middle justify-center items-center`}>
|
<div className={`${styles.feedbackButtons} flex align-middle justify-center items-center`}>
|
||||||
<button className={styles.thumbsUpButton}>
|
<button className={styles.thumbsUpButton} onClick={() => sendFeedback(uquery, kquery, 'positive')}>
|
||||||
<ThumbsUp color='hsl(var(--muted-foreground))' />
|
<ThumbsUp color='hsl(var(--muted-foreground))' />
|
||||||
</button>
|
</button>
|
||||||
<button className={styles.thumbsDownButton}>
|
<button className={styles.thumbsDownButton} onClick={() => sendFeedback(uquery, kquery, 'negative')}>
|
||||||
<ThumbsDown color='hsl(var(--muted-foreground))' />
|
<ThumbsDown color='hsl(var(--muted-foreground))' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickMessage(event: React.MouseEvent<any>, chatMessage: SingleChatMessage, setReferencePanelData: Function, setShowReferencePanel: Function) {
|
// WHAT TO DO WHEN CLICK ON KHOJ MESSAGE
|
||||||
// console.log("Clicked on message", chatMessage);
|
function onClickMessage(
|
||||||
setReferencePanelData(chatMessage);
|
event: React.MouseEvent<any>,
|
||||||
|
referencePanelData: ReferencePanelData,
|
||||||
|
setReferencePanelData: Function,
|
||||||
|
setShowReferencePanel: Function) {
|
||||||
|
|
||||||
|
setReferencePanelData(referencePanelData);
|
||||||
setShowReferencePanel(true);
|
setShowReferencePanel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatMessageProps {
|
interface ChatMessageProps {
|
||||||
chatMessage: SingleChatMessage;
|
chatMessage: SingleChatMessage;
|
||||||
setReferencePanelData: Function;
|
isMobileWidth: boolean;
|
||||||
setShowReferencePanel: Function;
|
|
||||||
customClassName?: string;
|
customClassName?: string;
|
||||||
borderLeftColor?: string;
|
borderLeftColor?: string;
|
||||||
}
|
}
|
||||||
@@ -183,6 +196,7 @@ function chooseIconFromHeader(header: string, iconColor: string) {
|
|||||||
return <Brain className={`inline mr-2 ${iconColor}`} />;
|
return <Brain className={`inline mr-2 ${iconColor}`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function TrainOfThought(props: TrainOfThoughtProps) {
|
export function TrainOfThought(props: TrainOfThoughtProps) {
|
||||||
// The train of thought comes in as a markdown-formatted string. It starts with a heading delimited by two asterisks at the start and end and a colon, followed by the message. Example: **header**: status. This function will parse the message and render it as a div.
|
// The train of thought comes in as a markdown-formatted string. It starts with a heading delimited by two asterisks at the start and end and a colon, followed by the message. Example: **header**: status. This function will parse the message and render it as a div.
|
||||||
let extractedHeader = props.message.match(/\*\*(.*)\*\*/);
|
let extractedHeader = props.message.match(/\*\*(.*)\*\*/);
|
||||||
@@ -262,8 +276,6 @@ export default function ChatMessage(props: ChatMessageProps) {
|
|||||||
}
|
}
|
||||||
}, [copySuccess]);
|
}, [copySuccess]);
|
||||||
|
|
||||||
let referencesValid = hasValidReferences(props.chatMessage);
|
|
||||||
|
|
||||||
function constructClasses(chatMessage: SingleChatMessage) {
|
function constructClasses(chatMessage: SingleChatMessage) {
|
||||||
let classes = [styles.chatMessageContainer];
|
let classes = [styles.chatMessageContainer];
|
||||||
classes.push(styles[chatMessage.by]);
|
classes.push(styles[chatMessage.by]);
|
||||||
@@ -287,26 +299,30 @@ export default function ChatMessage(props: ChatMessageProps) {
|
|||||||
return classes.join(' ');
|
return classes.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allReferences = constructAllReferences(props.chatMessage.context, props.chatMessage.onlineContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={constructClasses(props.chatMessage)}
|
className={constructClasses(props.chatMessage)}
|
||||||
onClick={props.chatMessage.by === "khoj" ? (event) => onClickMessage(event, props.chatMessage, props.setReferencePanelData, props.setShowReferencePanel) : undefined}>
|
onClick={props.chatMessage.by === "khoj" ? (event) => undefined : undefined}>
|
||||||
{/* <div className={styles.chatFooter}> */}
|
|
||||||
{/* {props.chatMessage.by} */}
|
|
||||||
{/* </div> */}
|
|
||||||
<div className={chatMessageWrapperClasses(props.chatMessage)}>
|
<div className={chatMessageWrapperClasses(props.chatMessage)}>
|
||||||
<div ref={messageRef} className={styles.chatMessage} dangerouslySetInnerHTML={{ __html: markdownRendered }} />
|
<div ref={messageRef} className={styles.chatMessage} dangerouslySetInnerHTML={{ __html: markdownRendered }} />
|
||||||
{/* Add a copy button, thumbs up, and thumbs down buttons */}
|
|
||||||
<div className={styles.chatFooter}>
|
|
||||||
<div className={styles.chatTimestamp}>
|
|
||||||
{renderTimeStamp(props.chatMessage.created)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.teaserReferencesContainer}>
|
||||||
|
<TeaserReferencesSection
|
||||||
|
isMobileWidth={props.isMobileWidth}
|
||||||
|
notesReferenceCardData={allReferences.notesReferenceCardData}
|
||||||
|
onlineReferenceCardData={allReferences.onlineReferenceCardData} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.chatFooter}>
|
||||||
|
{/* <div className={styles.chatTimestamp}>
|
||||||
|
{renderTimeStamp(props.chatMessage.created)}
|
||||||
|
</div> */}
|
||||||
<div className={styles.chatButtons}>
|
<div className={styles.chatButtons}>
|
||||||
{
|
{
|
||||||
referencesValid &&
|
|
||||||
<div className={styles.referenceButton}>
|
<div className={styles.referenceButton}>
|
||||||
<button onClick={(event) => onClickMessage(event, props.chatMessage, props.setReferencePanelData, props.setShowReferencePanel)}>
|
<button onClick={(event) => console.log("speaker")}>
|
||||||
References
|
<SpeakerHifi color='hsl(var(--muted-foreground))' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -321,11 +337,19 @@ export default function ChatMessage(props: ChatMessageProps) {
|
|||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
{
|
{
|
||||||
props.chatMessage.by === "khoj" && <FeedbackButtons />
|
props.chatMessage.by === "khoj" &&
|
||||||
|
(
|
||||||
|
props.chatMessage.intent ?
|
||||||
|
<FeedbackButtons
|
||||||
|
uquery={props.chatMessage.intent.query}
|
||||||
|
kquery={props.chatMessage.message} />
|
||||||
|
: <FeedbackButtons
|
||||||
|
uquery={props.chatMessage.rawQuery || props.chatMessage.message}
|
||||||
|
kquery={props.chatMessage.message} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
div.panel {
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
background-color: var(--calm-blue);
|
|
||||||
max-height: 80vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.panel a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.onlineReference,
|
|
||||||
div.contextReference {
|
|
||||||
margin: 4px;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.contextReference:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.singleReference {
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
background-color: hsla(var(--frosted-background-color));
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
div.panel {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.singleReference {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
import styles from "./referencePanel.module.css";
|
import styles from "./referencePanel.module.css";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { ArrowRight, File } from "@phosphor-icons/react";
|
||||||
|
|
||||||
import markdownIt from "markdown-it";
|
import markdownIt from "markdown-it";
|
||||||
const md = new markdownIt({
|
const md = new markdownIt({
|
||||||
@@ -12,22 +14,335 @@ const md = new markdownIt({
|
|||||||
});
|
});
|
||||||
|
|
||||||
import { SingleChatMessage, Context, WebPage, OnlineContextData } from "../chatMessage/chatMessage";
|
import { SingleChatMessage, Context, WebPage, OnlineContextData } from "../chatMessage/chatMessage";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger,
|
||||||
|
} from "@/components/ui/sheet";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
|
|
||||||
|
|
||||||
interface ReferencePanelProps {
|
interface ReferencePanelProps {
|
||||||
referencePanelData: SingleChatMessage | null;
|
referencePanelData: SingleChatMessage | null;
|
||||||
setShowReferencePanel: (showReferencePanel: boolean) => void;
|
setShowReferencePanel: (showReferencePanel: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasValidReferences(referencePanelData: SingleChatMessage | null) {
|
|
||||||
|
interface NotesContextReferenceData {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotesContextReferenceCardProps extends NotesContextReferenceData {
|
||||||
|
showFullContent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function NotesContextReferenceCard(props: NotesContextReferenceCardProps) {
|
||||||
|
const snippet = md.render(props.content);
|
||||||
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
referencePanelData &&
|
<>
|
||||||
(
|
<Popover
|
||||||
(referencePanelData.context && referencePanelData.context.length > 0) ||
|
open={isHovering && !props.showFullContent}
|
||||||
(referencePanelData.onlineContext && Object.keys(referencePanelData.onlineContext).length > 0 &&
|
onOpenChange={setIsHovering}
|
||||||
Object.values(referencePanelData.onlineContext).some(
|
>
|
||||||
(onlineContextData) =>
|
<PopoverTrigger asChild>
|
||||||
(onlineContextData.webpages && onlineContextData.webpages.length > 0) || onlineContextData.answerBox || onlineContextData.peopleAlsoAsk || onlineContextData.knowledgeGraph || onlineContextData.organic ))
|
<Card
|
||||||
|
onMouseEnter={() => setIsHovering(true)}
|
||||||
|
onMouseLeave={() => setIsHovering(false)}
|
||||||
|
className={`${props.showFullContent ? 'w-auto' : 'w-[200px]'} overflow-hidden break-words text-balance rounded-lg p-2 bg-muted border-none`}
|
||||||
|
>
|
||||||
|
<h3 className={`${props.showFullContent ? 'block' : 'line-clamp-1'} text-muted-foreground}`}>
|
||||||
|
<File className='w-6 h-6 text-muted-foreground inline-flex' />
|
||||||
|
{props.title}
|
||||||
|
</h3>
|
||||||
|
<p className={`${props.showFullContent ? 'block' : 'overflow-hidden line-clamp-2'}`} dangerouslySetInnerHTML={{ __html: snippet }}></p>
|
||||||
|
</Card>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-[400px] mx-2">
|
||||||
|
<Card className={`w-auto overflow-hidden break-words text-balance rounded-lg p-2 border-none`}>
|
||||||
|
<h3 className={`line-clamp-2 text-muted-foreground}`}>
|
||||||
|
<File className='w-6 h-6 text-muted-foreground inline-flex' />
|
||||||
|
{props.title}
|
||||||
|
</h3>
|
||||||
|
<p className={`overflow-hidden line-clamp-3`} dangerouslySetInnerHTML={{ __html: snippet }}></p>
|
||||||
|
</Card>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReferencePanelData {
|
||||||
|
notesReferenceCardData: NotesContextReferenceData[];
|
||||||
|
onlineReferenceCardData: OnlineReferenceData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface OnlineReferenceData {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
link: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OnlineReferenceCardProps extends OnlineReferenceData {
|
||||||
|
showFullContent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GenericOnlineReferenceCard(props: OnlineReferenceCardProps) {
|
||||||
|
const domain = new URL(props.link).hostname;
|
||||||
|
const favicon = `https://www.google.com/s2/favicons?domain=${domain}`;
|
||||||
|
|
||||||
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
console.log("mouse entered card");
|
||||||
|
setIsHovering(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
console.log("mouse left card");
|
||||||
|
setIsHovering(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Popover
|
||||||
|
open={isHovering && !props.showFullContent}
|
||||||
|
// open={true}
|
||||||
|
onOpenChange={setIsHovering}
|
||||||
|
>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Card
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
className={`${props.showFullContent ? 'w-auto' : 'w-[200px]'} overflow-hidden break-words rounded-lg text-balance p-2 bg-muted border-none`}>
|
||||||
|
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<a href={props.link} target="_blank" rel="noreferrer" className='!no-underline p-2'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<img src={favicon} alt="" className='!w-4 h-4 mr-2' />
|
||||||
|
<h3 className={`overflow-hidden ${props.showFullContent ? 'block' : 'line-clamp-1'} text-muted-foreground`}>{domain}</h3>
|
||||||
|
</div>
|
||||||
|
<h3 className={`overflow-hidden ${props.showFullContent ? 'block' : 'line-clamp-1'} font-bold`}>{props.title}</h3>
|
||||||
|
<p className={`overflow-hidden ${props.showFullContent ? 'block' : 'line-clamp-2'}`}>{props.description}</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-[400px] mx-2">
|
||||||
|
<Card
|
||||||
|
className={`w-auto overflow-hidden break-words text-balance rounded-lg border-none`}>
|
||||||
|
|
||||||
|
<div className='flex flex-col'>
|
||||||
|
<a href={props.link} target="_blank" rel="noreferrer" className='!no-underline p-2'>
|
||||||
|
<div className='flex items-center'>
|
||||||
|
<img src={favicon} alt="" className='!w-4 h-4 mr-2' />
|
||||||
|
<h3 className={`overflow-hidden ${props.showFullContent ? 'block' : 'line-clamp-2'} text-muted-foreground`}>{domain}</h3>
|
||||||
|
</div>
|
||||||
|
<h3 className={`overflow-hidden ${props.showFullContent ? 'block' : 'line-clamp-2'} font-bold`}>{props.title}</h3>
|
||||||
|
<p className={`overflow-hidden ${props.showFullContent ? 'block' : 'line-clamp-3'}`}>{props.description}</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function constructAllReferences(contextData: Context[], onlineData: { [key: string]: OnlineContextData }) {
|
||||||
|
|
||||||
|
const onlineReferences: OnlineReferenceData[] = [];
|
||||||
|
const contextReferences: NotesContextReferenceData[] = [];
|
||||||
|
|
||||||
|
if (onlineData) {
|
||||||
|
let localOnlineReferences = [];
|
||||||
|
for (const [key, value] of Object.entries(onlineData)) {
|
||||||
|
if (value.answerBox) {
|
||||||
|
localOnlineReferences.push({
|
||||||
|
title: value.answerBox.title,
|
||||||
|
description: value.answerBox.answer,
|
||||||
|
link: value.answerBox.source
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (value.knowledgeGraph) {
|
||||||
|
localOnlineReferences.push({
|
||||||
|
title: value.knowledgeGraph.title,
|
||||||
|
description: value.knowledgeGraph.description,
|
||||||
|
link: value.knowledgeGraph.descriptionLink
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.webpages) {
|
||||||
|
// If webpages is of type Array, iterate through it and add each webpage to the localOnlineReferences array
|
||||||
|
if (value.webpages instanceof Array) {
|
||||||
|
let webPageResults = value.webpages.map((webPage) => {
|
||||||
|
return {
|
||||||
|
title: webPage.query,
|
||||||
|
description: webPage.snippet,
|
||||||
|
link: webPage.link
|
||||||
|
}
|
||||||
|
});
|
||||||
|
localOnlineReferences.push(...webPageResults);
|
||||||
|
} else {
|
||||||
|
let singleWebpage = value.webpages as WebPage;
|
||||||
|
|
||||||
|
// If webpages is an object, add the object to the localOnlineReferences array
|
||||||
|
localOnlineReferences.push({
|
||||||
|
title: singleWebpage.query,
|
||||||
|
description: singleWebpage.snippet,
|
||||||
|
link: singleWebpage.link
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.organic) {
|
||||||
|
let organicResults = value.organic.map((organicContext) => {
|
||||||
|
return {
|
||||||
|
title: organicContext.title,
|
||||||
|
description: organicContext.snippet,
|
||||||
|
link: organicContext.link
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
localOnlineReferences.push(...organicResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onlineReferences.push(...localOnlineReferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contextData) {
|
||||||
|
|
||||||
|
let localContextReferences = contextData.map((context) => {
|
||||||
|
if (!context.compiled) {
|
||||||
|
const fileContent = context as unknown as string;
|
||||||
|
const title = fileContent.split('\n')[0];
|
||||||
|
const content = fileContent.split('\n').slice(1).join('\n');
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
content: content
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: context.file,
|
||||||
|
content: context.compiled
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
contextReferences.push(...localContextReferences);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notesReferenceCardData: contextReferences,
|
||||||
|
onlineReferenceCardData: onlineReferences
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface TeaserReferenceSectionProps {
|
||||||
|
notesReferenceCardData: NotesContextReferenceData[];
|
||||||
|
onlineReferenceCardData: OnlineReferenceData[];
|
||||||
|
isMobileWidth: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TeaserReferencesSection(props: TeaserReferenceSectionProps) {
|
||||||
|
const [numTeaserSlots, setNumTeaserSlots] = useState(3);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setNumTeaserSlots(props.isMobileWidth ? 1 : 3);
|
||||||
|
}, [props.isMobileWidth]);
|
||||||
|
|
||||||
|
const notesDataToShow = props.notesReferenceCardData.slice(0, numTeaserSlots);
|
||||||
|
const onlineDataToShow = notesDataToShow.length < numTeaserSlots ? props.onlineReferenceCardData.slice(0, numTeaserSlots - notesDataToShow.length) : [];
|
||||||
|
|
||||||
|
const shouldShowShowMoreButton = props.notesReferenceCardData.length > 0 || props.onlineReferenceCardData.length > 0;
|
||||||
|
|
||||||
|
const numReferences = props.notesReferenceCardData.length + props.onlineReferenceCardData.length;
|
||||||
|
|
||||||
|
if (numReferences === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${props.isMobileWidth ? 'p-0' : 'p-4'}`}>
|
||||||
|
<h3 className="inline-flex items-center">
|
||||||
|
References
|
||||||
|
<p className="text-gray-400 m-2">
|
||||||
|
{numReferences} sources
|
||||||
|
</p>
|
||||||
|
</h3>
|
||||||
|
<div className={`flex ${props.isMobileWidth ? 'w-[90vw]' : 'w-auto'} space-x-4 mt-2`}>
|
||||||
|
{
|
||||||
|
notesDataToShow.map((note, index) => {
|
||||||
|
return <NotesContextReferenceCard showFullContent={false} {...note} key={`${note.title}-${index}`} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
onlineDataToShow.map((online, index) => {
|
||||||
|
return <GenericOnlineReferenceCard showFullContent={false} {...online} key={`${online.title}-${index}`} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
shouldShowShowMoreButton &&
|
||||||
|
<ReferencePanel
|
||||||
|
notesReferenceCardData={props.notesReferenceCardData}
|
||||||
|
onlineReferenceCardData={props.onlineReferenceCardData} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface ReferencePanelDataProps {
|
||||||
|
notesReferenceCardData: NotesContextReferenceData[];
|
||||||
|
onlineReferenceCardData: OnlineReferenceData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReferencePanel(props: ReferencePanelDataProps) {
|
||||||
|
|
||||||
|
if (!props.notesReferenceCardData && !props.onlineReferenceCardData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet>
|
||||||
|
<SheetTrigger className='text-balance w-[200px] overflow-hidden break-words p-0 bg-transparent border-none text-gray-400 align-middle justify-center items-center !m-0 inline-flex'
|
||||||
|
onClick={() => { console.log("showing references") }}>
|
||||||
|
View references <ArrowRight className='m-1' />
|
||||||
|
</SheetTrigger>
|
||||||
|
<SheetContent className="overflow-y-scroll">
|
||||||
|
<SheetHeader>
|
||||||
|
<SheetTitle>References</SheetTitle>
|
||||||
|
<SheetDescription>View all references for this response</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
|
<div className="flex flex-col w-auto gap-2 mt-2">
|
||||||
|
{
|
||||||
|
props.notesReferenceCardData.map((note, index) => {
|
||||||
|
return <NotesContextReferenceCard showFullContent={true} {...note} key={`${note.title}-${index}`} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
{
|
||||||
|
props.onlineReferenceCardData.map((online, index) => {
|
||||||
|
return <GenericOnlineReferenceCard showFullContent={true} {...online} key={`${online.title}-${index}`} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +407,7 @@ function WebPageReference(props: { webpages: WebPage, query: string | null }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function OnlineReferences(props: { onlineContext: OnlineContextData, query: string}) {
|
function OnlineReferences(props: { onlineContext: OnlineContextData, query: string }) {
|
||||||
|
|
||||||
const webpages = props.onlineContext.webpages;
|
const webpages = props.onlineContext.webpages;
|
||||||
const answerBox = props.onlineContext.answerBox;
|
const answerBox = props.onlineContext.answerBox;
|
||||||
@@ -183,30 +498,3 @@ function OnlineReferences(props: { onlineContext: OnlineContextData, query: stri
|
|||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ReferencePanel(props: ReferencePanelProps) {
|
|
||||||
|
|
||||||
if (!props.referencePanelData) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasValidReferences(props.referencePanelData)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${styles.panel}`}>
|
|
||||||
References <button onClick={() => props.setShowReferencePanel(false)}>Hide</button>
|
|
||||||
{
|
|
||||||
props.referencePanelData?.context.map((context, index) => {
|
|
||||||
return <CompiledReference context={context} key={index} />
|
|
||||||
})
|
|
||||||
}
|
|
||||||
{
|
|
||||||
Object.entries(props.referencePanelData?.onlineContext || {}).map(([key, onlineContextData], index) => {
|
|
||||||
return <OnlineReferences onlineContext={onlineContextData} query={key} key={index} />
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { UserProfile } 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";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
@@ -15,6 +16,18 @@ import {
|
|||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from "@/components/ui/collapsible";
|
} from "@/components/ui/collapsible";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
CommandSeparator,
|
||||||
|
CommandShortcut,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
|
||||||
import { InlineLoading } from "../loading/loading";
|
import { InlineLoading } from "../loading/loading";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -27,9 +40,21 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerTrigger,
|
||||||
|
} from "@/components/ui/drawer";
|
||||||
|
|
||||||
|
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
import { ArrowRight, ArrowLeft, ArrowDown, Spinner, Check } from "@phosphor-icons/react";
|
import { ArrowRight, ArrowLeft, ArrowDown, Spinner, Check, FolderPlus } from "@phosphor-icons/react";
|
||||||
|
|
||||||
interface ChatHistory {
|
interface ChatHistory {
|
||||||
conversation_id: string;
|
conversation_id: string;
|
||||||
@@ -37,6 +62,7 @@ interface ChatHistory {
|
|||||||
agent_name: string;
|
agent_name: string;
|
||||||
agent_avatar: string;
|
agent_avatar: string;
|
||||||
compressed: boolean;
|
compressed: boolean;
|
||||||
|
created: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -58,6 +84,8 @@ import { Button, buttonVariants } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
||||||
|
import { modifyFileFilterForConversation } from "@/app/common/chatFunctions";
|
||||||
|
import { ScrollAreaScrollbar } from "@radix-ui/react-scroll-area";
|
||||||
|
|
||||||
// Define a fetcher function
|
// Define a fetcher function
|
||||||
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
||||||
@@ -121,70 +149,42 @@ function deleteConversation(conversationId: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function modifyFileFilterForConversation(
|
|
||||||
conversationId: string | null,
|
|
||||||
filename: string,
|
|
||||||
setAddedFiles: (files: string[]) => void,
|
|
||||||
mode: 'add' | 'remove') {
|
|
||||||
|
|
||||||
if (!conversationId) {
|
|
||||||
console.error("No conversation ID provided");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const method = mode === 'add' ? 'POST' : 'DELETE';
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
conversation_id: conversationId,
|
|
||||||
filename: filename,
|
|
||||||
}
|
|
||||||
const addUrl = `/api/chat/conversation/file-filters`;
|
|
||||||
|
|
||||||
fetch(addUrl, {
|
|
||||||
method: method,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(body),
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
setAddedFiles(data);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
interface FilesMenuProps {
|
interface FilesMenuProps {
|
||||||
conversationId: string | null;
|
conversationId: string | null;
|
||||||
|
uploadedFiles: string[];
|
||||||
|
isMobileWidth: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FilesMenu(props: FilesMenuProps) {
|
function FilesMenu(props: FilesMenuProps) {
|
||||||
// Use SWR to fetch files
|
// Use SWR to fetch files
|
||||||
const { data: files, error } = useSWR(props.conversationId ? '/api/config/data/computer' : null, fetcher);
|
const { data: files, error } = useSWR<string[]>(props.conversationId ? '/api/config/data/computer' : null, fetcher);
|
||||||
const { data: selectedFiles, error: selectedFilesError } = useSWR(props.conversationId ? `/api/chat/conversation/file-filters/${props.conversationId}` : null, fetcher);
|
const { data: selectedFiles, error: selectedFilesError } = useSWR(props.conversationId ? `/api/chat/conversation/file-filters/${props.conversationId}` : null, fetcher);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [searchInput, setSearchInput] = useState('');
|
const [unfilteredFiles, setUnfilteredFiles] = useState<string[]>([]);
|
||||||
const [filteredFiles, setFilteredFiles] = useState<string[]>([]);
|
|
||||||
const [addedFiles, setAddedFiles] = useState<string[]>([]);
|
const [addedFiles, setAddedFiles] = useState<string[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!files) return;
|
if (!files) return;
|
||||||
if (searchInput === '') {
|
|
||||||
setFilteredFiles(files);
|
// First, sort lexically
|
||||||
} else {
|
files.sort();
|
||||||
let sortedFiles = files.filter((filename: string) => filename.toLowerCase().includes(searchInput.toLowerCase()));
|
let sortedFiles = files;
|
||||||
|
|
||||||
if (addedFiles) {
|
if (addedFiles) {
|
||||||
sortedFiles = addedFiles.concat(filteredFiles.filter((filename: string) => !addedFiles.includes(filename)));
|
console.log("addedFiles in useeffect hook", addedFiles);
|
||||||
|
sortedFiles = addedFiles.concat(sortedFiles.filter((filename: string) => !addedFiles.includes(filename)));
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilteredFiles(sortedFiles);
|
setUnfilteredFiles(sortedFiles);
|
||||||
|
|
||||||
|
}, [files, addedFiles]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
for (const file of props.uploadedFiles) {
|
||||||
|
setAddedFiles((addedFiles) => [...addedFiles, file]);
|
||||||
}
|
}
|
||||||
}, [searchInput, files, addedFiles]);
|
}, [props.uploadedFiles]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedFiles) {
|
if (selectedFiles) {
|
||||||
@@ -193,6 +193,14 @@ function FilesMenu(props: FilesMenuProps) {
|
|||||||
|
|
||||||
}, [selectedFiles]);
|
}, [selectedFiles]);
|
||||||
|
|
||||||
|
const removeAllFiles = () => {
|
||||||
|
modifyFileFilterForConversation(props.conversationId, addedFiles, setAddedFiles, 'remove');
|
||||||
|
}
|
||||||
|
|
||||||
|
const addAllFiles = () => {
|
||||||
|
modifyFileFilterForConversation(props.conversationId, unfilteredFiles, setAddedFiles, 'add');
|
||||||
|
}
|
||||||
|
|
||||||
if (!props.conversationId) return (<></>);
|
if (!props.conversationId) return (<></>);
|
||||||
|
|
||||||
if (error) return <div>Failed to load files</div>;
|
if (error) return <div>Failed to load files</div>;
|
||||||
@@ -200,6 +208,88 @@ function FilesMenu(props: FilesMenuProps) {
|
|||||||
if (!files) return <InlineLoading />;
|
if (!files) return <InlineLoading />;
|
||||||
if (!selectedFiles) return <InlineLoading />;
|
if (!selectedFiles) return <InlineLoading />;
|
||||||
|
|
||||||
|
const FilesMenuCommandBox = () => {
|
||||||
|
return (
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder="Find file" />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
|
<CommandGroup heading="Quick">
|
||||||
|
<CommandItem
|
||||||
|
onSelect={() => {
|
||||||
|
removeAllFiles();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash className="h-4 w-4 mr-2" />
|
||||||
|
<span>Clear all</span>
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem
|
||||||
|
onSelect={() => {
|
||||||
|
addAllFiles();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FolderPlus className="h-4 w-4 mr-2" />
|
||||||
|
<span>Select all</span>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandGroup heading="Configure files">
|
||||||
|
{unfilteredFiles.map((filename: string) => (
|
||||||
|
addedFiles && addedFiles.includes(filename) ?
|
||||||
|
<CommandItem
|
||||||
|
key={filename}
|
||||||
|
value={filename}
|
||||||
|
className="bg-accent text-accent-foreground mb-1"
|
||||||
|
onSelect={(value) => {
|
||||||
|
modifyFileFilterForConversation(props.conversationId, [value], setAddedFiles, 'remove');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4 mr-2" />
|
||||||
|
<span className="break-all">{filename}</span>
|
||||||
|
</CommandItem>
|
||||||
|
:
|
||||||
|
<CommandItem
|
||||||
|
key={filename}
|
||||||
|
className="mb-1"
|
||||||
|
value={filename}
|
||||||
|
onSelect={(value) => {
|
||||||
|
modifyFileFilterForConversation(props.conversationId, [value], setAddedFiles, 'add');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="break-all">{filename}</span>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.isMobileWidth) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Drawer>
|
||||||
|
<DrawerTrigger className="bg-background border border-muted p-4 rounded-2xl my-8 text-left inline-flex items-center justify-between w-full">
|
||||||
|
Manage Files <ArrowRight className="h-4 w-4 mx-2" />
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Files</DrawerTitle>
|
||||||
|
<DrawerDescription>Manage files for this conversation</DrawerDescription>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div className={`${styles.panelWrapper}`}>
|
||||||
|
<FilesMenuCommandBox />
|
||||||
|
</div>
|
||||||
|
<DrawerFooter>
|
||||||
|
<DrawerClose>
|
||||||
|
<Button variant="outline">Done</Button>
|
||||||
|
</DrawerClose>
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popover
|
<Popover
|
||||||
@@ -207,10 +297,10 @@ function FilesMenu(props: FilesMenuProps) {
|
|||||||
onOpenChange={setIsOpen}>
|
onOpenChange={setIsOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<div
|
<div
|
||||||
className="w-auto bg-background border border-muted p-4 drop-shadow-sm rounded-2xl">
|
className="w-auto bg-background border border-muted p-4 drop-shadow-sm rounded-2xl my-8">
|
||||||
<div className="flex items-center justify-between space-x-4">
|
<div className="flex items-center justify-between space-x-4">
|
||||||
<h4 className="text-sm font-semibold">
|
<h4 className="text-sm font-semibold">
|
||||||
Manage Files
|
Manage Context
|
||||||
<p>
|
<p>
|
||||||
<span className="text-muted-foreground text-xs">Using {addedFiles.length == 0 ? files.length : addedFiles.length} files</span>
|
<span className="text-muted-foreground text-xs">Using {addedFiles.length == 0 ? files.length : addedFiles.length} files</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -228,40 +318,8 @@ function FilesMenu(props: FilesMenuProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-80 mx-2">
|
<PopoverContent className={`mx-2`}>
|
||||||
<Input
|
<FilesMenuCommandBox />
|
||||||
placeholder="Find file"
|
|
||||||
className="rounded-md border-none py-2 text-sm text-wrap break-words my-2 bg-accent text-accent-foreground"
|
|
||||||
value={searchInput}
|
|
||||||
onChange={(e) => setSearchInput(e.target.value)} />
|
|
||||||
{
|
|
||||||
filteredFiles.length === 0 && (
|
|
||||||
<div className="rounded-md border-none py-2 text-sm text-wrap break-words">
|
|
||||||
No files found
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
filteredFiles.map((filename: string) => (
|
|
||||||
addedFiles && addedFiles.includes(filename) ?
|
|
||||||
<Button
|
|
||||||
variant={'ghost'}
|
|
||||||
key={filename}
|
|
||||||
className="rounded-md border-none py-2 text-sm text-wrap break-words bg-accent text-accent-foreground text-left"
|
|
||||||
onClick={() => modifyFileFilterForConversation(props.conversationId, filename, setAddedFiles, 'remove')}>
|
|
||||||
{filename}
|
|
||||||
<Check className="h-4 w-4 ml-2" />
|
|
||||||
</Button>
|
|
||||||
:
|
|
||||||
<Button
|
|
||||||
variant={'ghost'}
|
|
||||||
key={filename}
|
|
||||||
className="rounded-md border-none py-2 text-sm text-wrap break-words text-left"
|
|
||||||
onClick={() => modifyFileFilterForConversation(props.conversationId, filename, setAddedFiles, 'add')}>
|
|
||||||
{filename}
|
|
||||||
</Button>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</>
|
</>
|
||||||
@@ -277,29 +335,24 @@ interface SessionsAndFilesProps {
|
|||||||
data: ChatHistory[] | null;
|
data: ChatHistory[] | null;
|
||||||
userProfile: UserProfile | null;
|
userProfile: UserProfile | null;
|
||||||
conversationId: string | null;
|
conversationId: string | null;
|
||||||
|
uploadedFiles: string[];
|
||||||
|
isMobileWidth: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SessionsAndFiles(props: SessionsAndFilesProps) {
|
function SessionsAndFiles(props: SessionsAndFilesProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`${styles.expanded}`}>
|
<ScrollArea className="h-[40vh]">
|
||||||
<button className={styles.button} onClick={() => props.setEnabled(false)}>
|
<ScrollAreaScrollbar orientation="vertical" className="h-full w-2.5 border-l border-l-transparent p-[1px]" />
|
||||||
<ArrowLeft />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<ScrollArea className="h-[40vh] w-[14rem]">
|
|
||||||
<div className={styles.sessionsList}>
|
<div className={styles.sessionsList}>
|
||||||
{props.subsetOrganizedData != null && Object.keys(props.subsetOrganizedData).map((agentName) => (
|
{props.subsetOrganizedData != null && Object.keys(props.subsetOrganizedData).map((timeGrouping) => (
|
||||||
<div key={agentName} className={`my-4`}>
|
<div key={timeGrouping} className={`my-4`}>
|
||||||
{/* <h3 className={`grid grid-flow-col auto-cols-max gap-2 my-4 font-bold text-sm`}>
|
<div className={`text-muted-foreground text-sm font-bold p-[0.5rem] `}>
|
||||||
{
|
{timeGrouping}
|
||||||
props.subsetOrganizedData &&
|
</div>
|
||||||
<img src={props.subsetOrganizedData[agentName][0].agent_avatar} alt={agentName} width={24} height={24} />
|
{props.subsetOrganizedData && props.subsetOrganizedData[timeGrouping].map((chatHistory) => (
|
||||||
}
|
|
||||||
{agentName}
|
|
||||||
</h3> */}
|
|
||||||
{props.subsetOrganizedData && props.subsetOrganizedData[agentName].map((chatHistory) => (
|
|
||||||
<ChatSession
|
<ChatSession
|
||||||
|
created={chatHistory.created}
|
||||||
compressed={true}
|
compressed={true}
|
||||||
key={chatHistory.conversation_id}
|
key={chatHistory.conversation_id}
|
||||||
conversation_id={chatHistory.conversation_id}
|
conversation_id={chatHistory.conversation_id}
|
||||||
@@ -316,7 +369,7 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
|
|||||||
<ChatSessionsModal data={props.organizedData} />
|
<ChatSessionsModal data={props.organizedData} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<FilesMenu conversationId={props.conversationId} />
|
<FilesMenu conversationId={props.conversationId} uploadedFiles={props.uploadedFiles} isMobileWidth={props.isMobileWidth} />
|
||||||
{props.userProfile &&
|
{props.userProfile &&
|
||||||
<UserProfileComponent userProfile={props.userProfile} webSocketConnected={props.webSocketConnected} collapsed={false} />
|
<UserProfileComponent userProfile={props.userProfile} webSocketConnected={props.webSocketConnected} collapsed={false} />
|
||||||
}</>
|
}</>
|
||||||
@@ -499,22 +552,23 @@ function ChatSessionsModal({ data }: ChatSessionsModalProps) {
|
|||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger
|
<DialogTrigger
|
||||||
className="flex text-left text-medium text-gray-500 hover:text-gray-900 cursor-pointer my-4 text-sm">
|
className="flex text-left text-medium text-gray-500 hover:text-gray-900 cursor-pointer my-4 text-sm p-[0.5rem]">
|
||||||
Show All
|
See All
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>All Conversations</DialogTitle>
|
<DialogTitle>All Conversations</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription
|
||||||
<ScrollArea className="h-[500px] w-[450px] p-4">
|
className="p-0">
|
||||||
{data && Object.keys(data).map((agentName) => (
|
<ScrollArea className="h-[500px] p-4">
|
||||||
<div key={agentName}>
|
{data && Object.keys(data).map((timeGrouping) => (
|
||||||
<div className={`grid grid-flow-col auto-cols-max gap-2`}>
|
<div key={timeGrouping}>
|
||||||
<img src={data[agentName][0].agent_avatar} alt={agentName} width={24} height={24} />
|
<div className={`text-muted-foreground text-sm font-bold p-[0.5rem] `}>
|
||||||
{agentName}
|
{timeGrouping}
|
||||||
</div>
|
</div>
|
||||||
{data[agentName].map((chatHistory) => (
|
{data[timeGrouping].map((chatHistory) => (
|
||||||
<ChatSession
|
<ChatSession
|
||||||
|
created={chatHistory.created}
|
||||||
compressed={false}
|
compressed={false}
|
||||||
key={chatHistory.conversation_id}
|
key={chatHistory.conversation_id}
|
||||||
conversation_id={chatHistory.conversation_id}
|
conversation_id={chatHistory.conversation_id}
|
||||||
@@ -600,6 +654,7 @@ export const useChatHistoryRecentFetchRequest = (url: string) => {
|
|||||||
interface SidePanelProps {
|
interface SidePanelProps {
|
||||||
webSocketConnected?: boolean;
|
webSocketConnected?: boolean;
|
||||||
conversationId: string | null;
|
conversationId: string | null;
|
||||||
|
uploadedFiles: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -614,6 +669,8 @@ export default function SidePanel(props: SidePanelProps) {
|
|||||||
|
|
||||||
const { data: chatHistory } = useChatHistoryRecentFetchRequest('/api/chat/sessions');
|
const { data: chatHistory } = useChatHistoryRecentFetchRequest('/api/chat/sessions');
|
||||||
|
|
||||||
|
const [isMobileWidth, setIsMobileWidth] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chatHistory) {
|
if (chatHistory) {
|
||||||
setData(chatHistory);
|
setData(chatHistory);
|
||||||
@@ -621,27 +678,44 @@ export default function SidePanel(props: SidePanelProps) {
|
|||||||
const groupedData: GroupedChatHistory = {};
|
const groupedData: GroupedChatHistory = {};
|
||||||
const subsetOrganizedData: GroupedChatHistory = {};
|
const subsetOrganizedData: GroupedChatHistory = {};
|
||||||
let numAdded = 0;
|
let numAdded = 0;
|
||||||
|
|
||||||
|
const currentDate = new Date();
|
||||||
|
|
||||||
chatHistory.forEach((chatHistory) => {
|
chatHistory.forEach((chatHistory) => {
|
||||||
if (!groupedData[chatHistory.agent_name]) {
|
const chatDate = new Date(chatHistory.created);
|
||||||
groupedData[chatHistory.agent_name] = [];
|
const diffTime = Math.abs(currentDate.getTime() - chatDate.getTime());
|
||||||
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
const timeGrouping = diffDays < 7 ? 'Recent' : diffDays < 30 ? 'Last Month' : 'All Time';
|
||||||
|
if (!groupedData[timeGrouping]) {
|
||||||
|
groupedData[timeGrouping] = [];
|
||||||
}
|
}
|
||||||
groupedData[chatHistory.agent_name].push(chatHistory);
|
groupedData[timeGrouping].push(chatHistory);
|
||||||
|
|
||||||
// Add to subsetOrganizedData if less than 8
|
// Add to subsetOrganizedData if less than 8
|
||||||
if (numAdded < 8) {
|
if (numAdded < 8) {
|
||||||
if (!subsetOrganizedData[chatHistory.agent_name]) {
|
if (!subsetOrganizedData[timeGrouping]) {
|
||||||
subsetOrganizedData[chatHistory.agent_name] = [];
|
subsetOrganizedData[timeGrouping] = [];
|
||||||
}
|
}
|
||||||
subsetOrganizedData[chatHistory.agent_name].push(chatHistory);
|
subsetOrganizedData[timeGrouping].push(chatHistory);
|
||||||
numAdded++;
|
numAdded++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
setSubsetOrganizedData(subsetOrganizedData);
|
setSubsetOrganizedData(subsetOrganizedData);
|
||||||
setOrganizedData(groupedData);
|
setOrganizedData(groupedData);
|
||||||
}
|
}
|
||||||
}, [chatHistory]);
|
}, [chatHistory]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (window.innerWidth < 768) {
|
||||||
|
setIsMobileWidth(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
setIsMobileWidth(window.innerWidth < 768);
|
||||||
|
});
|
||||||
|
|
||||||
fetch('/api/v1/user', { method: 'GET' })
|
fetch('/api/v1/user', { method: 'GET' })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
@@ -655,9 +729,25 @@ export default function SidePanel(props: SidePanelProps) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.panel}`}>
|
<div className={`${styles.panel} ${enabled ? styles.expanded : styles.collapsed}`}>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<Image src="khoj-logo.svg"
|
||||||
|
alt="logo"
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
|
/>
|
||||||
|
{/* <button className={styles.button} onClick={() => setEnabled(!enabled)}>
|
||||||
|
{enabled ? <ArrowLeft className="h-4 w-4" /> : <ArrowRight className="h-4 w-4 mx-2" />}
|
||||||
|
</button> */}
|
||||||
{
|
{
|
||||||
enabled ?
|
isMobileWidth ?
|
||||||
|
<Drawer>
|
||||||
|
<DrawerTrigger><ArrowRight className="h-4 w-4 mx-2" /></DrawerTrigger>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader>
|
||||||
|
<DrawerTitle>Sessions and Files</DrawerTitle>
|
||||||
|
<DrawerDescription>View all conversation sessions and manage conversation file filters</DrawerDescription>
|
||||||
|
</DrawerHeader>
|
||||||
<div className={`${styles.panelWrapper}`}>
|
<div className={`${styles.panelWrapper}`}>
|
||||||
<SessionsAndFiles
|
<SessionsAndFiles
|
||||||
webSocketConnected={props.webSocketConnected}
|
webSocketConnected={props.webSocketConnected}
|
||||||
@@ -665,20 +755,39 @@ export default function SidePanel(props: SidePanelProps) {
|
|||||||
subsetOrganizedData={subsetOrganizedData}
|
subsetOrganizedData={subsetOrganizedData}
|
||||||
organizedData={organizedData}
|
organizedData={organizedData}
|
||||||
data={data}
|
data={data}
|
||||||
|
uploadedFiles={props.uploadedFiles}
|
||||||
userProfile={userProfile}
|
userProfile={userProfile}
|
||||||
conversationId={props.conversationId}
|
conversationId={props.conversationId}
|
||||||
|
isMobileWidth={isMobileWidth}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<DrawerFooter>
|
||||||
|
<DrawerClose>
|
||||||
|
<Button variant="outline">Done</Button>
|
||||||
|
</DrawerClose>
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
:
|
:
|
||||||
<div>
|
<button className={styles.button} onClick={() => setEnabled(!enabled)}>
|
||||||
<div className={`${styles.collapsed}`}>
|
{enabled ? <ArrowLeft className="h-4 w-4" /> : <ArrowRight className="h-4 w-4 mx-2" />}
|
||||||
<button className={styles.button} onClick={() => setEnabled(true)}>
|
|
||||||
<ArrowRight />
|
|
||||||
</button>
|
</button>
|
||||||
{userProfile &&
|
|
||||||
<UserProfileComponent userProfile={userProfile} webSocketConnected={props.webSocketConnected} collapsed={true} />
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
{
|
||||||
|
enabled &&
|
||||||
|
<div className={`${styles.panelWrapper}`}>
|
||||||
|
<SessionsAndFiles
|
||||||
|
webSocketConnected={props.webSocketConnected}
|
||||||
|
setEnabled={setEnabled}
|
||||||
|
subsetOrganizedData={subsetOrganizedData}
|
||||||
|
organizedData={organizedData}
|
||||||
|
data={data}
|
||||||
|
uploadedFiles={props.uploadedFiles}
|
||||||
|
userProfile={userProfile}
|
||||||
|
conversationId={props.conversationId}
|
||||||
|
isMobileWidth={isMobileWidth}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,19 +41,18 @@ button.showMoreButton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.panel {
|
div.panel {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-auto-flow: row;
|
flex-direction: column;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: hsla(var(--muted));
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-width: auto;
|
max-width: auto;
|
||||||
|
transition: background-color 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.expanded {
|
div.expanded {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr auto;
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
background-color: hsla(var(--muted));
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.collapsed {
|
div.collapsed {
|
||||||
@@ -83,8 +82,10 @@ div.profile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.panelWrapper {
|
div.panelWrapper {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows: auto 1fr auto auto;
|
||||||
|
width: 70%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -119,9 +120,29 @@ div.modalSessionsList div.session {
|
|||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
div.panel {
|
div.panel {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.expanded {
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.singleReference {
|
div.singleReference {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.panelWrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.session.compressed {
|
||||||
|
max-width: 100%;
|
||||||
|
grid-template-columns: minmax(auto, 350px) 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.session {
|
||||||
|
max-width: 100%;
|
||||||
|
grid-template-columns: 200px 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
155
src/interface/web/components/ui/command.tsx
Normal file
155
src/interface/web/components/ui/command.tsx
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||||
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
|
import { Search } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||||
|
|
||||||
|
const Command = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Command.displayName = CommandPrimitive.displayName
|
||||||
|
|
||||||
|
interface CommandDialogProps extends DialogProps {}
|
||||||
|
|
||||||
|
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandInput = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||||
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandInput.displayName = CommandPrimitive.Input.displayName
|
||||||
|
|
||||||
|
const CommandList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandList.displayName = CommandPrimitive.List.displayName
|
||||||
|
|
||||||
|
const CommandEmpty = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||||
|
>((props, ref) => (
|
||||||
|
<CommandPrimitive.Empty
|
||||||
|
ref={ref}
|
||||||
|
className="py-6 text-center text-sm"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
||||||
|
|
||||||
|
const CommandGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
||||||
|
|
||||||
|
const CommandSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const CommandItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandItem.displayName = CommandPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const CommandShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CommandShortcut.displayName = "CommandShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandShortcut,
|
||||||
|
CommandSeparator,
|
||||||
|
}
|
||||||
118
src/interface/web/components/ui/drawer.tsx
Normal file
118
src/interface/web/components/ui/drawer.tsx
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Drawer = ({
|
||||||
|
shouldScaleBackground = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||||
|
<DrawerPrimitive.Root
|
||||||
|
shouldScaleBackground={shouldScaleBackground}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
Drawer.displayName = "Drawer"
|
||||||
|
|
||||||
|
const DrawerTrigger = DrawerPrimitive.Trigger
|
||||||
|
|
||||||
|
const DrawerPortal = DrawerPrimitive.Portal
|
||||||
|
|
||||||
|
const DrawerClose = DrawerPrimitive.Close
|
||||||
|
|
||||||
|
const DrawerOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const DrawerContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DrawerPortal>
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||||
|
{children}
|
||||||
|
</DrawerPrimitive.Content>
|
||||||
|
</DrawerPortal>
|
||||||
|
))
|
||||||
|
DrawerContent.displayName = "DrawerContent"
|
||||||
|
|
||||||
|
const DrawerHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DrawerHeader.displayName = "DrawerHeader"
|
||||||
|
|
||||||
|
const DrawerFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DrawerFooter.displayName = "DrawerFooter"
|
||||||
|
|
||||||
|
const DrawerTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const DrawerDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DrawerPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Drawer,
|
||||||
|
DrawerPortal,
|
||||||
|
DrawerOverlay,
|
||||||
|
DrawerTrigger,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerDescription,
|
||||||
|
}
|
||||||
32
src/interface/web/components/ui/progress.tsx
Normal file
32
src/interface/web/components/ui/progress.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
interface ProgressProps extends React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> {
|
||||||
|
indicatorColor?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
ProgressProps
|
||||||
|
>(({ className, value, indicatorColor, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className={`h-full w-full flex-1 bg-primary transition-all ${indicatorColor}`}
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
))
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Progress }
|
||||||
140
src/interface/web/components/ui/sheet.tsx
Normal file
140
src/interface/web/components/ui/sheet.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { X } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Sheet = SheetPrimitive.Root
|
||||||
|
|
||||||
|
const SheetTrigger = SheetPrimitive.Trigger
|
||||||
|
|
||||||
|
const SheetClose = SheetPrimitive.Close
|
||||||
|
|
||||||
|
const SheetPortal = SheetPrimitive.Portal
|
||||||
|
|
||||||
|
const SheetOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const sheetVariants = cva(
|
||||||
|
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||||
|
bottom:
|
||||||
|
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||||
|
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||||
|
right:
|
||||||
|
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
side: "right",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
interface SheetContentProps
|
||||||
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||||
|
VariantProps<typeof sheetVariants> {}
|
||||||
|
|
||||||
|
const SheetContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||||
|
SheetContentProps
|
||||||
|
>(({ side = "right", className, children, ...props }, ref) => (
|
||||||
|
<SheetPortal>
|
||||||
|
<SheetOverlay />
|
||||||
|
<SheetPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(sheetVariants({ side }), className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</SheetPrimitive.Close>
|
||||||
|
</SheetPrimitive.Content>
|
||||||
|
</SheetPortal>
|
||||||
|
))
|
||||||
|
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SheetHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetHeader.displayName = "SheetHeader"
|
||||||
|
|
||||||
|
const SheetFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
SheetFooter.displayName = "SheetFooter"
|
||||||
|
|
||||||
|
const SheetTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const SheetDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SheetPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Sheet,
|
||||||
|
SheetPortal,
|
||||||
|
SheetOverlay,
|
||||||
|
SheetTrigger,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetHeader,
|
||||||
|
SheetFooter,
|
||||||
|
SheetTitle,
|
||||||
|
SheetDescription,
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"@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",
|
||||||
"@radix-ui/react-popover": "^1.1.1",
|
"@radix-ui/react-popover": "^1.1.1",
|
||||||
|
"@radix-ui/react-progress": "^1.1.0",
|
||||||
"@radix-ui/react-scroll-area": "^1.1.0",
|
"@radix-ui/react-scroll-area": "^1.1.0",
|
||||||
"@radix-ui/react-slot": "^1.1.0",
|
"@radix-ui/react-slot": "^1.1.0",
|
||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.0.0",
|
||||||
"katex": "^0.16.10",
|
"katex": "^0.16.10",
|
||||||
"lucide-react": "^0.397.0",
|
"lucide-react": "^0.397.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
@@ -47,7 +49,8 @@
|
|||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
"tailwind-merge": "^2.3.0",
|
"tailwind-merge": "^2.3.0",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.4",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"vaul": "^0.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
],
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|||||||
@@ -245,7 +245,7 @@
|
|||||||
"@babel/helper-plugin-utils" "^7.24.7"
|
"@babel/helper-plugin-utils" "^7.24.7"
|
||||||
"@babel/plugin-syntax-typescript" "^7.24.7"
|
"@babel/plugin-syntax-typescript" "^7.24.7"
|
||||||
|
|
||||||
"@babel/runtime@^7.23.2", "@babel/runtime@^7.24.1":
|
"@babel/runtime@^7.13.10", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.1":
|
||||||
version "7.24.7"
|
version "7.24.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
|
||||||
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
|
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
|
||||||
@@ -501,6 +501,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46"
|
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46"
|
||||||
integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==
|
integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==
|
||||||
|
|
||||||
|
"@radix-ui/primitive@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd"
|
||||||
|
integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/primitive@1.1.0":
|
"@radix-ui/primitive@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2"
|
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2"
|
||||||
@@ -559,17 +566,52 @@
|
|||||||
"@radix-ui/react-primitive" "2.0.0"
|
"@radix-ui/react-primitive" "2.0.0"
|
||||||
"@radix-ui/react-slot" "1.1.0"
|
"@radix-ui/react-slot" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-compose-refs@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989"
|
||||||
|
integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-compose-refs@1.1.0":
|
"@radix-ui/react-compose-refs@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74"
|
||||||
integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==
|
integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==
|
||||||
|
|
||||||
|
"@radix-ui/react-context@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c"
|
||||||
|
integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-context@1.1.0":
|
"@radix-ui/react-context@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8"
|
||||||
integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==
|
integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==
|
||||||
|
|
||||||
"@radix-ui/react-dialog@1.1.1", "@radix-ui/react-dialog@^1.1.1":
|
"@radix-ui/react-dialog@1.0.5":
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300"
|
||||||
|
integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/primitive" "1.0.1"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.1"
|
||||||
|
"@radix-ui/react-context" "1.0.1"
|
||||||
|
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||||
|
"@radix-ui/react-focus-guards" "1.0.1"
|
||||||
|
"@radix-ui/react-focus-scope" "1.0.4"
|
||||||
|
"@radix-ui/react-id" "1.0.1"
|
||||||
|
"@radix-ui/react-portal" "1.0.4"
|
||||||
|
"@radix-ui/react-presence" "1.0.1"
|
||||||
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
"@radix-ui/react-slot" "1.0.2"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||||
|
aria-hidden "^1.1.1"
|
||||||
|
react-remove-scroll "2.5.5"
|
||||||
|
|
||||||
|
"@radix-ui/react-dialog@1.1.1", "@radix-ui/react-dialog@^1.0.4", "@radix-ui/react-dialog@^1.1.1":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44"
|
||||||
integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==
|
integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==
|
||||||
@@ -594,6 +636,18 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc"
|
||||||
integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==
|
integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==
|
||||||
|
|
||||||
|
"@radix-ui/react-dismissable-layer@1.0.5":
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4"
|
||||||
|
integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/primitive" "1.0.1"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.1"
|
||||||
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||||
|
"@radix-ui/react-use-escape-keydown" "1.0.3"
|
||||||
|
|
||||||
"@radix-ui/react-dismissable-layer@1.1.0":
|
"@radix-ui/react-dismissable-layer@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz#2cd0a49a732372513733754e6032d3fb7988834e"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz#2cd0a49a732372513733754e6032d3fb7988834e"
|
||||||
@@ -618,11 +672,28 @@
|
|||||||
"@radix-ui/react-primitive" "2.0.0"
|
"@radix-ui/react-primitive" "2.0.0"
|
||||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-focus-guards@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad"
|
||||||
|
integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-focus-guards@1.1.0":
|
"@radix-ui/react-focus-guards@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz#8e9abb472a9a394f59a1b45f3dd26cfe3fc6da13"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz#8e9abb472a9a394f59a1b45f3dd26cfe3fc6da13"
|
||||||
integrity sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==
|
integrity sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==
|
||||||
|
|
||||||
|
"@radix-ui/react-focus-scope@1.0.4":
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525"
|
||||||
|
integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.1"
|
||||||
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||||
|
|
||||||
"@radix-ui/react-focus-scope@1.1.0":
|
"@radix-ui/react-focus-scope@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz#ebe2891a298e0a33ad34daab2aad8dea31caf0b2"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz#ebe2891a298e0a33ad34daab2aad8dea31caf0b2"
|
||||||
@@ -632,6 +703,14 @@
|
|||||||
"@radix-ui/react-primitive" "2.0.0"
|
"@radix-ui/react-primitive" "2.0.0"
|
||||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-id@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0"
|
||||||
|
integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||||
|
|
||||||
"@radix-ui/react-id@1.1.0":
|
"@radix-ui/react-id@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed"
|
||||||
@@ -743,6 +822,14 @@
|
|||||||
"@radix-ui/react-use-size" "1.1.0"
|
"@radix-ui/react-use-size" "1.1.0"
|
||||||
"@radix-ui/rect" "1.1.0"
|
"@radix-ui/rect" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-portal@1.0.4":
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15"
|
||||||
|
integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
|
||||||
"@radix-ui/react-portal@1.1.1":
|
"@radix-ui/react-portal@1.1.1":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.1.tgz#1957f1eb2e1aedfb4a5475bd6867d67b50b1d15f"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.1.tgz#1957f1eb2e1aedfb4a5475bd6867d67b50b1d15f"
|
||||||
@@ -751,6 +838,15 @@
|
|||||||
"@radix-ui/react-primitive" "2.0.0"
|
"@radix-ui/react-primitive" "2.0.0"
|
||||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-presence@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba"
|
||||||
|
integrity sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.1"
|
||||||
|
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||||
|
|
||||||
"@radix-ui/react-presence@1.1.0":
|
"@radix-ui/react-presence@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.0.tgz#227d84d20ca6bfe7da97104b1a8b48a833bfb478"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.0.tgz#227d84d20ca6bfe7da97104b1a8b48a833bfb478"
|
||||||
@@ -759,6 +855,14 @@
|
|||||||
"@radix-ui/react-compose-refs" "1.1.0"
|
"@radix-ui/react-compose-refs" "1.1.0"
|
||||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-primitive@1.0.3":
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0"
|
||||||
|
integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-slot" "1.0.2"
|
||||||
|
|
||||||
"@radix-ui/react-primitive@2.0.0":
|
"@radix-ui/react-primitive@2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884"
|
||||||
@@ -766,6 +870,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@radix-ui/react-slot" "1.1.0"
|
"@radix-ui/react-slot" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-progress@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.1.0.tgz#28c267885ec154fc557ec7a66cb462787312f7e2"
|
||||||
|
integrity sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-context" "1.1.0"
|
||||||
|
"@radix-ui/react-primitive" "2.0.0"
|
||||||
|
|
||||||
"@radix-ui/react-roving-focus@1.1.0":
|
"@radix-ui/react-roving-focus@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e"
|
||||||
@@ -796,6 +908,14 @@
|
|||||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||||
"@radix-ui/react-use-layout-effect" "1.1.0"
|
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-slot@1.0.2":
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab"
|
||||||
|
integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.1"
|
||||||
|
|
||||||
"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0":
|
"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84"
|
||||||
@@ -812,11 +932,26 @@
|
|||||||
"@radix-ui/react-primitive" "2.0.0"
|
"@radix-ui/react-primitive" "2.0.0"
|
||||||
"@radix-ui/react-use-controllable-state" "1.1.0"
|
"@radix-ui/react-use-controllable-state" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-callback-ref@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"
|
||||||
|
integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-use-callback-ref@1.1.0":
|
"@radix-ui/react-use-callback-ref@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1"
|
||||||
integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==
|
integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==
|
||||||
|
|
||||||
|
"@radix-ui/react-use-controllable-state@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286"
|
||||||
|
integrity sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||||
|
|
||||||
"@radix-ui/react-use-controllable-state@1.1.0":
|
"@radix-ui/react-use-controllable-state@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0"
|
||||||
@@ -824,6 +959,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-escape-keydown@1.0.3":
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755"
|
||||||
|
integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||||
|
|
||||||
"@radix-ui/react-use-escape-keydown@1.1.0":
|
"@radix-ui/react-use-escape-keydown@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754"
|
||||||
@@ -831,6 +974,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@radix-ui/react-use-callback-ref" "1.1.0"
|
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||||
|
|
||||||
|
"@radix-ui/react-use-layout-effect@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399"
|
||||||
|
integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@radix-ui/react-use-layout-effect@1.1.0":
|
"@radix-ui/react-use-layout-effect@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
|
||||||
@@ -1424,6 +1574,14 @@ clsx@^2.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||||
|
|
||||||
|
cmdk@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-1.0.0.tgz#0a095fdafca3dfabed82d1db78a6262fb163ded9"
|
||||||
|
integrity sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-dialog" "1.0.5"
|
||||||
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
|
||||||
code-block-writer@^12.0.0:
|
code-block-writer@^12.0.0:
|
||||||
version "12.0.0"
|
version "12.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770"
|
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770"
|
||||||
@@ -3427,7 +3585,7 @@ react-is@^16.13.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
react-remove-scroll-bar@^2.3.4:
|
react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c"
|
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c"
|
||||||
integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==
|
integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==
|
||||||
@@ -3435,6 +3593,17 @@ react-remove-scroll-bar@^2.3.4:
|
|||||||
react-style-singleton "^2.2.1"
|
react-style-singleton "^2.2.1"
|
||||||
tslib "^2.0.0"
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
react-remove-scroll@2.5.5:
|
||||||
|
version "2.5.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77"
|
||||||
|
integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==
|
||||||
|
dependencies:
|
||||||
|
react-remove-scroll-bar "^2.3.3"
|
||||||
|
react-style-singleton "^2.2.1"
|
||||||
|
tslib "^2.1.0"
|
||||||
|
use-callback-ref "^1.3.0"
|
||||||
|
use-sidecar "^1.1.2"
|
||||||
|
|
||||||
react-remove-scroll@2.5.7:
|
react-remove-scroll@2.5.7:
|
||||||
version "2.5.7"
|
version "2.5.7"
|
||||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz#15a1fd038e8497f65a695bf26a4a57970cac1ccb"
|
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz#15a1fd038e8497f65a695bf26a4a57970cac1ccb"
|
||||||
@@ -4147,6 +4316,13 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||||
|
|
||||||
|
vaul@^0.9.1:
|
||||||
|
version "0.9.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.9.1.tgz#3640198e04636b209b1f907fcf3079bec6ecc66b"
|
||||||
|
integrity sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-dialog" "^1.0.4"
|
||||||
|
|
||||||
wcwidth@^1.0.1:
|
wcwidth@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
|
||||||
|
|||||||
@@ -909,6 +909,34 @@ class ConversationAdapters:
|
|||||||
async def aget_text_to_image_model_config():
|
async def aget_text_to_image_model_config():
|
||||||
return await TextToImageModelConfig.objects.filter().afirst()
|
return await TextToImageModelConfig.objects.filter().afirst()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def add_files_to_filter(user: KhojUser, conversation_id: int, files: List[str]):
|
||||||
|
conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
|
||||||
|
file_list = EntryAdapters.get_all_filenames_by_source(user, "computer")
|
||||||
|
for filename in files:
|
||||||
|
if filename in file_list and filename not in conversation.file_filters:
|
||||||
|
conversation.file_filters.append(filename)
|
||||||
|
conversation.save()
|
||||||
|
|
||||||
|
# remove files from conversation.file_filters that are not in file_list
|
||||||
|
conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
|
||||||
|
conversation.save()
|
||||||
|
return conversation.file_filters
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_files_from_filter(user: KhojUser, conversation_id: int, files: List[str]):
|
||||||
|
conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
|
||||||
|
for filename in files:
|
||||||
|
if filename in conversation.file_filters:
|
||||||
|
conversation.file_filters.remove(filename)
|
||||||
|
conversation.save()
|
||||||
|
|
||||||
|
# remove files from conversation.file_filters that are not in file_list
|
||||||
|
file_list = EntryAdapters.get_all_filenames_by_source(user, "computer")
|
||||||
|
conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
|
||||||
|
conversation.save()
|
||||||
|
return conversation.file_filters
|
||||||
|
|
||||||
|
|
||||||
class FileObjectAdapters:
|
class FileObjectAdapters:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ from khoj.utils.helpers import (
|
|||||||
get_device,
|
get_device,
|
||||||
is_none_or_empty,
|
is_none_or_empty,
|
||||||
)
|
)
|
||||||
from khoj.utils.rawconfig import FilterRequest, LocationData
|
from khoj.utils.rawconfig import FileFilterRequest, FilesFilterRequest, LocationData
|
||||||
|
|
||||||
# Initialize Router
|
# Initialize Router
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -94,21 +94,36 @@ def get_file_filter(request: Request, conversation_id: str) -> Response:
|
|||||||
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
|
|
||||||
|
|
||||||
|
@api_chat.delete("/conversation/file-filters/bulk", response_class=Response)
|
||||||
|
@requires(["authenticated"])
|
||||||
|
def remove_files_filter(request: Request, filter: FilesFilterRequest) -> Response:
|
||||||
|
conversation_id = int(filter.conversation_id)
|
||||||
|
files_filter = filter.filenames
|
||||||
|
file_filters = ConversationAdapters.remove_files_from_filter(request.user.object, conversation_id, files_filter)
|
||||||
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
|
|
||||||
|
|
||||||
|
@api_chat.post("/conversation/file-filters/bulk", response_class=Response)
|
||||||
|
@requires(["authenticated"])
|
||||||
|
def add_files_filter(request: Request, filter: FilesFilterRequest):
|
||||||
|
try:
|
||||||
|
conversation_id = int(filter.conversation_id)
|
||||||
|
files_filter = filter.filenames
|
||||||
|
file_filters = ConversationAdapters.add_files_to_filter(request.user.object, conversation_id, files_filter)
|
||||||
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error adding file filter {filter.filename}: {e}", exc_info=True)
|
||||||
|
raise HTTPException(status_code=422, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
@api_chat.post("/conversation/file-filters", response_class=Response)
|
@api_chat.post("/conversation/file-filters", response_class=Response)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def add_file_filter(request: Request, filter: FilterRequest):
|
def add_file_filter(request: Request, filter: FileFilterRequest):
|
||||||
try:
|
try:
|
||||||
conversation = ConversationAdapters.get_conversation_by_user(
|
conversation_id = int(filter.conversation_id)
|
||||||
request.user.object, conversation_id=int(filter.conversation_id)
|
files_filter = [filter.filename]
|
||||||
)
|
file_filters = ConversationAdapters.add_files_to_filter(request.user.object, conversation_id, files_filter)
|
||||||
file_list = EntryAdapters.get_all_filenames_by_source(request.user.object, "computer")
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
if filter.filename in file_list and filter.filename not in conversation.file_filters:
|
|
||||||
conversation.file_filters.append(filter.filename)
|
|
||||||
conversation.save()
|
|
||||||
# remove files from conversation.file_filters that are not in file_list
|
|
||||||
conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
|
|
||||||
conversation.save()
|
|
||||||
return Response(content=json.dumps(conversation.file_filters), media_type="application/json", status_code=200)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error adding file filter {filter.filename}: {e}", exc_info=True)
|
logger.error(f"Error adding file filter {filter.filename}: {e}", exc_info=True)
|
||||||
raise HTTPException(status_code=422, detail=str(e))
|
raise HTTPException(status_code=422, detail=str(e))
|
||||||
@@ -116,18 +131,11 @@ def add_file_filter(request: Request, filter: FilterRequest):
|
|||||||
|
|
||||||
@api_chat.delete("/conversation/file-filters", response_class=Response)
|
@api_chat.delete("/conversation/file-filters", response_class=Response)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def remove_file_filter(request: Request, filter: FilterRequest) -> Response:
|
def remove_file_filter(request: Request, filter: FileFilterRequest) -> Response:
|
||||||
conversation = ConversationAdapters.get_conversation_by_user(
|
conversation_id = int(filter.conversation_id)
|
||||||
request.user.object, conversation_id=int(filter.conversation_id)
|
files_filter = [filter.filename]
|
||||||
)
|
file_filters = ConversationAdapters.remove_files_from_filter(request.user.object, conversation_id, files_filter)
|
||||||
if filter.filename in conversation.file_filters:
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
conversation.file_filters.remove(filter.filename)
|
|
||||||
conversation.save()
|
|
||||||
# remove files from conversation.file_filters that are not in file_list
|
|
||||||
file_list = EntryAdapters.get_all_filenames_by_source(request.user.object, "computer")
|
|
||||||
conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
|
|
||||||
conversation.save()
|
|
||||||
return Response(content=json.dumps(conversation.file_filters), media_type="application/json", status_code=200)
|
|
||||||
|
|
||||||
|
|
||||||
class FeedbackData(BaseModel):
|
class FeedbackData(BaseModel):
|
||||||
@@ -379,7 +387,9 @@ def chat_sessions(
|
|||||||
if recent:
|
if recent:
|
||||||
conversations = conversations[:8]
|
conversations = conversations[:8]
|
||||||
|
|
||||||
sessions = conversations.values_list("id", "slug", "title", "agent__slug", "agent__name", "agent__avatar")
|
sessions = conversations.values_list(
|
||||||
|
"id", "slug", "title", "agent__slug", "agent__name", "agent__avatar", "created_at"
|
||||||
|
)
|
||||||
|
|
||||||
session_values = [
|
session_values = [
|
||||||
{
|
{
|
||||||
@@ -387,6 +397,7 @@ def chat_sessions(
|
|||||||
"slug": session[2] or session[1],
|
"slug": session[2] or session[1],
|
||||||
"agent_name": session[4],
|
"agent_name": session[4],
|
||||||
"agent_avatar": session[5],
|
"agent_avatar": session[5],
|
||||||
|
"created": session[6].strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
}
|
}
|
||||||
for session in sessions
|
for session in sessions
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ templates = Jinja2Templates([constants.web_directory, constants.next_js_director
|
|||||||
@web_client.get("/", response_class=FileResponse)
|
@web_client.get("/", response_class=FileResponse)
|
||||||
@requires(["authenticated"], redirect="login_page")
|
@requires(["authenticated"], redirect="login_page")
|
||||||
def index(request: Request):
|
def index(request: Request):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"chat/index.html",
|
||||||
|
context={
|
||||||
|
"request": request,
|
||||||
|
},
|
||||||
|
)
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
user_picture = request.session.get("user", {}).get("picture")
|
user_picture = request.session.get("user", {}).get("picture")
|
||||||
has_documents = EntryAdapters.user_has_entries(user=user)
|
has_documents = EntryAdapters.user_has_entries(user=user)
|
||||||
@@ -101,6 +107,12 @@ def search_page(request: Request):
|
|||||||
@web_client.get("/chat", response_class=FileResponse)
|
@web_client.get("/chat", response_class=FileResponse)
|
||||||
@requires(["authenticated"], redirect="login_page")
|
@requires(["authenticated"], redirect="login_page")
|
||||||
def chat_page(request: Request):
|
def chat_page(request: Request):
|
||||||
|
return templates.TemplateResponse(
|
||||||
|
"chat/index.html",
|
||||||
|
context={
|
||||||
|
"request": request,
|
||||||
|
},
|
||||||
|
)
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
user_picture = request.session.get("user", {}).get("picture")
|
user_picture = request.session.get("user", {}).get("picture")
|
||||||
has_documents = EntryAdapters.user_has_entries(user=user)
|
has_documents = EntryAdapters.user_has_entries(user=user)
|
||||||
|
|||||||
@@ -27,11 +27,16 @@ class LocationData(BaseModel):
|
|||||||
country: Optional[str]
|
country: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class FilterRequest(BaseModel):
|
class FileFilterRequest(BaseModel):
|
||||||
filename: str
|
filename: str
|
||||||
conversation_id: str
|
conversation_id: str
|
||||||
|
|
||||||
|
|
||||||
|
class FilesFilterRequest(BaseModel):
|
||||||
|
filenames: List[str]
|
||||||
|
conversation_id: str
|
||||||
|
|
||||||
|
|
||||||
class TextConfigBase(ConfigBase):
|
class TextConfigBase(ConfigBase):
|
||||||
compressed_jsonl: Path
|
compressed_jsonl: Path
|
||||||
embeddings_file: Path
|
embeddings_file: Path
|
||||||
|
|||||||
Reference in New Issue
Block a user