mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 13:25:11 +00:00
Fix layout/styling of the factchecker app
This commit is contained in:
@@ -13,6 +13,11 @@ input.factVerification {
|
|||||||
font-size: large;
|
font-size: large;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.factCheckerContainer {
|
||||||
|
width: 75vw;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
input.factVerification:focus {
|
input.factVerification:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||||
@@ -123,6 +128,12 @@ div.dot2 {
|
|||||||
animation-delay: -1.0s;
|
animation-delay: -1.0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
div.factCheckerContainer {
|
||||||
|
width: 95vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@-webkit-keyframes sk-rotate {
|
@-webkit-keyframes sk-rotate {
|
||||||
100% {
|
100% {
|
||||||
-webkit-transform: rotate(360deg)
|
-webkit-transform: rotate(360deg)
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
.factCheckerLayout {
|
|
||||||
max-width: 70vw;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 700px) {
|
|
||||||
.factCheckerLayout {
|
|
||||||
max-width: 90vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import NavMenu from '../components/navMenu/navMenu';
|
|
||||||
import styles from './factCheckerLayout.module.css';
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Khoj AI - Fact Checker",
|
title: "Khoj AI - Fact Checker",
|
||||||
@@ -17,8 +14,7 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.factCheckerLayout}>
|
<div>
|
||||||
<NavMenu selected="none" />
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card"
|
} from "@/components/ui/card"
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import SidePanel from '../components/sidePanel/chatHistorySidePanel';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -76,41 +77,41 @@ async function verifyStatement(
|
|||||||
setIsLoading: (loading: boolean) => void,
|
setIsLoading: (loading: boolean) => void,
|
||||||
setInitialResponse: (response: string) => void,
|
setInitialResponse: (response: string) => void,
|
||||||
setInitialReferences: (references: ResponseWithReferences) => void) {
|
setInitialReferences: (references: ResponseWithReferences) => void) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
// Send a message to the chat server to verify the fact
|
// Send a message to the chat server to verify the fact
|
||||||
let verificationMessage = `${verificationPrecursor} ${message}`;
|
let verificationMessage = `${verificationPrecursor} ${message}`;
|
||||||
const apiURL = `${chatURL}?q=${encodeURIComponent(verificationMessage)}&client=web&stream=true&conversation_id=${conversationId}`;
|
const apiURL = `${chatURL}?q=${encodeURIComponent(verificationMessage)}&client=web&stream=true&conversation_id=${conversationId}`;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(apiURL);
|
const response = await fetch(apiURL);
|
||||||
if (!response.body) throw new Error("No response body found");
|
if (!response.body) throw new Error("No response body found");
|
||||||
|
|
||||||
const reader = response.body?.getReader();
|
const reader = response.body?.getReader();
|
||||||
let decoder = new TextDecoder();
|
let decoder = new TextDecoder();
|
||||||
let result = "";
|
let result = "";
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
|
|
||||||
let chunk = decoder.decode(value, { stream: true });
|
let chunk = decoder.decode(value, { stream: true });
|
||||||
|
|
||||||
if (chunk.includes("### compiled references:")) {
|
if (chunk.includes("### compiled references:")) {
|
||||||
const references = handleCompiledReferences(chunk, result);
|
const references = handleCompiledReferences(chunk, result);
|
||||||
if (references.response) {
|
if (references.response) {
|
||||||
result = references.response;
|
result = references.response;
|
||||||
setInitialResponse(references.response);
|
setInitialResponse(references.response);
|
||||||
setInitialReferences(references);
|
setInitialReferences(references);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result += chunk;
|
|
||||||
setInitialResponse(result);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
result += chunk;
|
||||||
|
setInitialResponse(result);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("Error verifying statement: ", error);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error verifying statement: ", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ function ReferenceVerification(props: ReferenceVerificationProps) {
|
|||||||
setInitialResponse(props.prefilledResponse);
|
setInitialResponse(props.prefilledResponse);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else {
|
} else {
|
||||||
verifyStatement(verificationStatement, props.conversationId, setIsLoading, setInitialResponse, () => {});
|
verifyStatement(verificationStatement, props.conversationId, setIsLoading, setInitialResponse, () => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsMobileWidth(window.innerWidth < 768);
|
setIsMobileWidth(window.innerWidth < 768);
|
||||||
@@ -454,7 +455,7 @@ export default function FactChecker() {
|
|||||||
additionalLink={additionalLink}
|
additionalLink={additionalLink}
|
||||||
setChildReferencesCallback={setChildReferencesCallback} />
|
setChildReferencesCallback={setChildReferencesCallback} />
|
||||||
);
|
);
|
||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSupplementalReferences = (references: SupplementReferences[]) => {
|
const renderSupplementalReferences = (references: SupplementReferences[]) => {
|
||||||
@@ -489,102 +490,111 @@ export default function FactChecker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.factCheckerContainer}>
|
<>
|
||||||
<h1 className={`${styles.response} font-large outline-slate-800 dark:outline-slate-200`}>
|
<div className='relative md:fixed h-full'>
|
||||||
AI Fact Checker
|
<SidePanel
|
||||||
</h1>
|
conversationId={null}
|
||||||
<footer className={`${styles.footer} mt-4`}>
|
uploadedFiles={[]}
|
||||||
This is an experimental AI tool. It may make mistakes.
|
isMobileWidth={isMobileWidth}
|
||||||
</footer>
|
/>
|
||||||
{
|
</div>
|
||||||
initialResponse && initialReferences && childReferences
|
<div className={styles.factCheckerContainer}>
|
||||||
?
|
<h1 className={`${styles.response} pt-8 md:pt-4 font-large outline-slate-800 dark:outline-slate-200`}>
|
||||||
<div className={styles.reportActions}>
|
AI Fact Checker
|
||||||
<Button asChild variant='secondary'>
|
</h1>
|
||||||
<Link href="/factchecker" target="_blank" rel="noopener noreferrer">
|
<footer className={`${styles.footer} mt-4`}>
|
||||||
Try Another
|
This is an experimental AI tool. It may make mistakes.
|
||||||
</Link>
|
</footer>
|
||||||
</Button>
|
{
|
||||||
<ShareLink
|
initialResponse && initialReferences && childReferences
|
||||||
buttonTitle='Share report'
|
?
|
||||||
title="AI Fact Checking Report"
|
<div className={styles.reportActions}>
|
||||||
description="Share this fact checking report with others. Anyone who has this link will be able to view the report."
|
<Button asChild variant='secondary'>
|
||||||
url={constructShareUrl()}
|
<Link href="/factchecker" target="_blank" rel="noopener noreferrer">
|
||||||
onShare={loadedFromStorage ? () => {} : storeData} />
|
Try Another
|
||||||
</div>
|
</Link>
|
||||||
: <div className={styles.newReportActions}>
|
</Button>
|
||||||
<div className={`${styles.inputFields} mt-4`}>
|
<ShareLink
|
||||||
<Input
|
buttonTitle='Share report'
|
||||||
type="text"
|
title="AI Fact Checking Report"
|
||||||
maxLength={200}
|
description="Share this fact checking report with others. Anyone who has this link will be able to view the report."
|
||||||
placeholder="Enter a falsifiable statement to verify"
|
url={constructShareUrl()}
|
||||||
disabled={isLoading}
|
onShare={loadedFromStorage ? () => { } : storeData} />
|
||||||
onChange={(e) => setFactToVerify(e.target.value)}
|
</div>
|
||||||
value={factToVerify}
|
: <div className={styles.newReportActions}>
|
||||||
onKeyDown={(e) => {
|
<div className={`${styles.inputFields} mt-4`}>
|
||||||
if (e.key === "Enter") {
|
<Input
|
||||||
onClickVerify();
|
type="text"
|
||||||
}
|
maxLength={200}
|
||||||
}}
|
placeholder="Enter a falsifiable statement to verify"
|
||||||
onFocus={(e) => e.target.placeholder = ""}
|
disabled={isLoading}
|
||||||
onBlur={(e) => e.target.placeholder = "Enter a falsifiable statement to verify"} />
|
onChange={(e) => setFactToVerify(e.target.value)}
|
||||||
<Button disabled={clickedVerify} onClick={() => onClickVerify()}>Verify</Button>
|
value={factToVerify}
|
||||||
</div>
|
onKeyDown={(e) => {
|
||||||
<h3 className={`mt-4 mb-4`}>
|
if (e.key === "Enter") {
|
||||||
Try with a particular model. You must be <a href="/settings" className="font-medium text-blue-600 dark:text-blue-500 hover:underline">subscribed</a> to configure the model.
|
onClickVerify();
|
||||||
</h3>
|
}
|
||||||
</div>
|
}}
|
||||||
}
|
onFocus={(e) => e.target.placeholder = ""}
|
||||||
<ModelPicker disabled={isLoading || loadedFromStorage} setModelUsed={setModelUsed} initialModel={initialModel} />
|
onBlur={(e) => e.target.placeholder = "Enter a falsifiable statement to verify"} />
|
||||||
{isLoading && <div className={styles.loading}>
|
<Button disabled={clickedVerify} onClick={() => onClickVerify()}>Verify</Button>
|
||||||
|
</div>
|
||||||
|
<h3 className={`mt-4 mb-4`}>
|
||||||
|
Try with a particular model. You must be <a href="/settings" className="font-medium text-blue-600 dark:text-blue-500 hover:underline">subscribed</a> to configure the model.
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<ModelPicker disabled={isLoading || loadedFromStorage} setModelUsed={setModelUsed} initialModel={initialModel} />
|
||||||
|
{isLoading && <div className={styles.loading}>
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
</div>}
|
</div>}
|
||||||
{
|
{
|
||||||
initialResponse &&
|
initialResponse &&
|
||||||
<Card className={`mt-4`}>
|
<Card className={`mt-4`}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>{officialFactToVerify}</CardTitle>
|
<CardTitle>{officialFactToVerify}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className={styles.responseText}>
|
<div className={styles.responseText}>
|
||||||
<ChatMessage chatMessage={
|
<ChatMessage chatMessage={
|
||||||
{
|
|
||||||
automationId: "",
|
|
||||||
by: "AI",
|
|
||||||
message: initialResponse,
|
|
||||||
context: [],
|
|
||||||
created: (new Date()).toISOString(),
|
|
||||||
onlineContext: {}
|
|
||||||
}
|
|
||||||
} isMobileWidth={isMobileWidth} />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter>
|
|
||||||
{
|
|
||||||
initialReferences && initialReferences.online && Object.keys(initialReferences.online).length > 0 && (
|
|
||||||
<div className={styles.subLinks}>
|
|
||||||
{
|
{
|
||||||
Object.entries(initialReferences.online).map(([key, onlineData], index) => {
|
automationId: "",
|
||||||
const webpages = onlineData?.webpages || [];
|
by: "AI",
|
||||||
return renderWebpages(webpages);
|
message: initialResponse,
|
||||||
})
|
context: [],
|
||||||
|
created: (new Date()).toISOString(),
|
||||||
|
onlineContext: {}
|
||||||
}
|
}
|
||||||
</div>
|
} isMobileWidth={isMobileWidth} />
|
||||||
)}
|
|
||||||
</CardFooter>
|
</div>
|
||||||
</Card>
|
</CardContent>
|
||||||
}
|
<CardFooter>
|
||||||
{
|
{
|
||||||
initialReferences &&
|
initialReferences && initialReferences.online && Object.keys(initialReferences.online).length > 0 && (
|
||||||
<div className={styles.referenceContainer}>
|
<div className={styles.subLinks}>
|
||||||
<h2 className="mt-4 mb-4">Supplements</h2>
|
{
|
||||||
<div className={styles.references}>
|
Object.entries(initialReferences.online).map(([key, onlineData], index) => {
|
||||||
{ initialReferences.online !== undefined &&
|
const webpages = onlineData?.webpages || [];
|
||||||
renderReferences(conversationID, initialReferences, officialFactToVerify, loadedFromStorage, childReferences)}
|
return renderWebpages(webpages);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
initialReferences &&
|
||||||
|
<div className={styles.referenceContainer}>
|
||||||
|
<h2 className="mt-4 mb-4">Supplements</h2>
|
||||||
|
<div className={styles.references}>
|
||||||
|
{initialReferences.online !== undefined &&
|
||||||
|
renderReferences(conversationID, initialReferences, officialFactToVerify, loadedFromStorage, childReferences)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user