Add a fact checker feature with updated styling (#835)

- Add an experimental feature used for fact-checking falsifiable statements with customizable models. See attached screenshot for example. Once you input a statement that needs to be fact-checked, Khoj goes on a research spree to verify or refute it.
- Integrate frontend libraries for [Tailwind](https://tailwindcss.com/) and [ShadCN](https://ui.shadcn.com/) for easier UI development. Update corresponding styling for some existing UI components. 
- Add component for model selection 
- Add backend support for sharing arbitrary packets of data that will be consumed by specific front-end views in shareable scenarios
This commit is contained in:
sabaimran
2024-06-27 06:15:38 -07:00
committed by GitHub
parent 3b7a9358c3
commit 870d9ecdbf
35 changed files with 3294 additions and 223 deletions

View File

@@ -40,6 +40,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
setLoading(false);
// Render chat options, if any
if (chatData) {
console.log(chatData);
setData(chatData.response);
}
})

View File

@@ -1,11 +1,6 @@
div.chatMessageContainer {
display: flex;
flex-direction: column;
margin: 0.5rem;
padding: 0.5rem;
border-radius: 0.5rem;
border: 1px solid black;
/* max-width: 80%; */
}
div.you {
@@ -81,5 +76,5 @@ button.codeCopyButton:hover {
div.feedbackButtons img,
button.copyButton img {
width: auto;
width: 24px;
}

View File

@@ -0,0 +1,23 @@
select.modelPicker {
font-size: small;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 0.75rem;
padding-right: 0.75rem;
border: none;
border-width: 1px;
display: flex;
align-items: center;
height: 2.5rem;
justify-content: space-between;
border-radius: calc(0.5rem - 2px);
}
select.modelPicker:after {
grid-area: select;
justify-self: end;
}
div.modelPicker {
margin-top: 8px;
}

View File

@@ -0,0 +1,152 @@
import { useAuthenticatedData } from '@/app/common/auth';
import React, { useEffect } from 'react';
import useSWR from 'swr';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import styles from './modelPicker.module.css';
export interface Model {
id: number;
chat_model: string;
}
// Custom fetcher function to fetch options
const fetchOptionsRequest = async (url: string) => {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
return response.json();
};
export const useOptionsRequest = (url: string) => {
const { data, error } = useSWR<Model[]>(url, fetchOptionsRequest);
return {
data,
isLoading: !error && !data,
isError: error,
};
};
const fetchSelectedModel = async (url: string) => {
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
return response.json();
}
export const useSelectedModel = (url: string) => {
const { data, error } = useSWR<Model>(url, fetchSelectedModel);
return {
data,
isLoading: !error && !data,
isError: error,
}
}
interface ModelPickerProps {
disabled?: boolean;
setModelUsed?: (model: Model) => void;
initialModel?: Model;
}
export const ModelPicker: React.FC<any> = (props: ModelPickerProps) => {
const { data: models } = useOptionsRequest('/api/config/data/conversation/model/options');
const { data: selectedModel } = useSelectedModel('/api/config/data/conversation/model');
const [openLoginDialog, setOpenLoginDialog] = React.useState(false);
let userData = useAuthenticatedData();
useEffect(() => {
if (props.setModelUsed && selectedModel) {
props.setModelUsed(selectedModel);
}
}, [selectedModel]);
if (!models) {
return <div>Loading...</div>;
}
function onSelect(model: Model) {
if (!userData) {
setOpenLoginDialog(true);
return;
}
if (props.setModelUsed) {
props.setModelUsed(model);
}
fetch('/api/config/data/conversation/model' + '?id=' + String(model.id), { method: 'POST', body: JSON.stringify(model) })
.then((response) => {
if (!response.ok) {
throw new Error('Failed to select model');
}
})
.catch((error) => {
console.error('Failed to select model', error);
});
}
function isSelected(model: Model) {
if (props.initialModel) {
return model.id === props.initialModel.id;
}
return selectedModel?.id === model.id;
}
return (
<div className={styles.modelPicker}>
<select className={styles.modelPicker} onChange={(e) => {
const selectedModelId = Number(e.target.value);
const selectedModel = models.find((model) => model.id === selectedModelId);
if (selectedModel) {
onSelect(selectedModel);
} else {
console.error('Selected model not found', e.target.value);
}
}} disabled={props.disabled}>
{models?.map((model) => (
<option key={model.id} value={model.id} selected={isSelected(model)}>
{model.chat_model}
</option>
))}
</select>
<AlertDialog open={openLoginDialog} onOpenChange={setOpenLoginDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>You must be logged in to configure your model.</AlertDialogTitle>
<AlertDialogDescription>Once you create an account with Khoj, you can configure your model and use a whole suite of other features. Check out our <a href="https://docs.khoj.dev/">documentation</a> to learn more.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
window.location.href = window.location.origin + '/login';
}}>
Sign in
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};

View File

@@ -1,5 +1,4 @@
menu.menu a {
color: var(--main-text-color);
text-decoration: none;
font-size: medium;
font-weight: normal;
@@ -51,7 +50,6 @@ div.settingsMenuProfile img {
}
div.settingsMenu {
color: var(--main-text-color);
padding: 0 4px;
border-radius: 4px;
display: flex;

View File

@@ -2,7 +2,6 @@ div.panel {
padding: 1rem;
border-radius: 1rem;
background-color: var(--calm-blue);
color: var(--main-text-color);
max-height: 80vh;
overflow-y: auto;
max-width: auto;

View File

@@ -0,0 +1,63 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
interface ShareLinkProps {
buttonTitle: string;
title: string;
description: string;
url: string;
onShare: () => void;
}
function copyToClipboard(text: string) {
const clipboard = navigator.clipboard;
if (!clipboard) {
return;
}
clipboard.writeText(text);
}
export default function ShareLink(props: ShareLinkProps) {
return (
<Dialog>
<DialogTrigger
className="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2"
onClick={props.onShare}>
{props.buttonTitle}
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>
{props.description}
</DialogDescription>
</DialogHeader>
<div className="flex items-center space-x-2">
<div className="grid flex-1 gap-2">
<Label htmlFor="link" className="sr-only">
Link
</Label>
<Input
id="link"
defaultValue={props.url}
readOnly
/>
</div>
<Button type="submit" size="sm" className="px-3" onClick={() => copyToClipboard(props.url)}>
<span>Copy</span>
</Button>
</div>
</DialogContent>
</Dialog>
);
}