mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-23 15:38:55 +01:00
Delete the fact checker application
This commit is contained in:
parent
4cad96ded6
commit
78630603f4
4 changed files with 0 additions and 891 deletions
|
@ -1,172 +0,0 @@
|
|||
input.factVerification {
|
||||
width: 100%;
|
||||
display: block;
|
||||
padding: 12px 20px;
|
||||
margin: 8px 0;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
text-align: left;
|
||||
margin: auto;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
div.factCheckerContainer {
|
||||
width: 75vw;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
input.factVerification:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
div.responseText {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
div.response {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
a.titleLink {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a.subLinks {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
font-weight: small;
|
||||
border-radius: 4px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
div.subLinks {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
div.reference {
|
||||
padding: 12px;
|
||||
margin: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
footer.footer {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div.reportActions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
button.factCheckButton {
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
margin: 8px;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
button.factCheckButton:hover {
|
||||
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
div.spinner {
|
||||
margin: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
-webkit-animation: sk-rotate 2.0s infinite linear;
|
||||
animation: sk-rotate 2.0s infinite linear;
|
||||
}
|
||||
|
||||
div.inputFields {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
/* Loading Animation */
|
||||
div.dot1,
|
||||
div.dot2 {
|
||||
width: 60%;
|
||||
height: 60%;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-radius: 100%;
|
||||
|
||||
-webkit-animation: sk-bounce 2.0s infinite ease-in-out;
|
||||
animation: sk-bounce 2.0s infinite ease-in-out;
|
||||
}
|
||||
|
||||
div.dot2 {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
-webkit-animation-delay: -1.0s;
|
||||
animation-delay: -1.0s;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
div.factCheckerContainer {
|
||||
width: 95vw;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-rotate {
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-rotate {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes sk-bounce {
|
||||
0%,
|
||||
100% {
|
||||
-webkit-transform: scale(0.0)
|
||||
}
|
||||
|
||||
50% {
|
||||
-webkit-transform: scale(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sk-bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(0.0);
|
||||
-webkit-transform: scale(0.0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.0);
|
||||
-webkit-transform: scale(1.0);
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Khoj AI - Fact Checker",
|
||||
description:
|
||||
"Use the Fact Checker with Khoj AI for verifying statements. It can research the internet for you, either refuting or confirming the statement using fresh data.",
|
||||
icons: {
|
||||
icon: "/static/assets/icons/khoj_lantern.ico",
|
||||
apple: "/static/assets/icons/khoj_lantern_256x256.png",
|
||||
},
|
||||
openGraph: {
|
||||
siteName: "Khoj AI",
|
||||
title: "Khoj AI - Fact Checker",
|
||||
description: "Your Second Brain.",
|
||||
url: "https://app.khoj.dev/factchecker",
|
||||
type: "website",
|
||||
images: [
|
||||
{
|
||||
url: "https://assets.khoj.dev/khoj_lantern_256x256.png",
|
||||
width: 256,
|
||||
height: 256,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return <div>{children}</div>;
|
||||
}
|
|
@ -1,676 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import styles from "./factChecker.module.css";
|
||||
import { useAuthenticatedData } from "@/app/common/auth";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import ChatMessage, {
|
||||
CodeContext,
|
||||
Context,
|
||||
OnlineContext,
|
||||
OnlineContextData,
|
||||
WebPage,
|
||||
} from "../components/chatMessage/chatMessage";
|
||||
import { ModelPicker, Model } from "../components/modelPicker/modelPicker";
|
||||
import ShareLink from "../components/shareLink/shareLink";
|
||||
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import Link from "next/link";
|
||||
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
|
||||
import { useIsMobileWidth } from "../common/utils";
|
||||
|
||||
const chatURL = "/api/chat";
|
||||
const verificationPrecursor =
|
||||
"Limit your search to reputable sources. Search the internet for relevant supporting or refuting information. Do not reference my notes. Refuse to answer any queries that are not falsifiable by informing me that you will not answer the question. You're not permitted to ask follow-up questions, so do the best with what you have. Respond with **TRUE** or **FALSE** or **INCONCLUSIVE**, then provide your justification. Fact Check:";
|
||||
|
||||
const LoadingSpinner = () => (
|
||||
<div className={styles.loading}>
|
||||
<div className={styles.loadingVerification}>
|
||||
Researching...
|
||||
<div className={styles.spinner}>
|
||||
<div className={`${styles.dot1} bg-blue-300`}></div>
|
||||
<div className={`${styles.dot2} bg-blue-300`}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface SupplementReferences {
|
||||
additionalLink: string;
|
||||
response: string;
|
||||
linkTitle: string;
|
||||
}
|
||||
|
||||
interface ResponseWithReferences {
|
||||
context?: Context[];
|
||||
online?: OnlineContext;
|
||||
code?: CodeContext;
|
||||
response?: string;
|
||||
}
|
||||
|
||||
function handleCompiledReferences(chunk: string, currentResponse: string) {
|
||||
const rawReference = chunk.split("### compiled references:")[1];
|
||||
const rawResponse = chunk.split("### compiled references:")[0];
|
||||
let references: ResponseWithReferences = {};
|
||||
|
||||
// Set the initial response
|
||||
references.response = currentResponse + rawResponse;
|
||||
|
||||
const rawReferenceAsJson = JSON.parse(rawReference);
|
||||
if (rawReferenceAsJson instanceof Array) {
|
||||
references.context = rawReferenceAsJson;
|
||||
} else if (typeof rawReferenceAsJson === "object" && rawReferenceAsJson !== null) {
|
||||
references.online = rawReferenceAsJson;
|
||||
}
|
||||
|
||||
return references;
|
||||
}
|
||||
|
||||
async function verifyStatement(
|
||||
message: string,
|
||||
conversationId: string,
|
||||
setIsLoading: (loading: boolean) => void,
|
||||
setInitialResponse: (response: string) => void,
|
||||
setInitialReferences: (references: ResponseWithReferences) => void,
|
||||
) {
|
||||
setIsLoading(true);
|
||||
// Construct the verification payload
|
||||
let verificationMessage = `${verificationPrecursor} ${message}`;
|
||||
const apiURL = `${chatURL}?client=web`;
|
||||
const requestBody = {
|
||||
q: verificationMessage,
|
||||
conversation_id: conversationId,
|
||||
stream: true,
|
||||
};
|
||||
|
||||
try {
|
||||
// Send a message to the chat server to verify the fact
|
||||
const response = await fetch(apiURL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
});
|
||||
if (!response.body) throw new Error("No response body found");
|
||||
|
||||
const reader = response.body?.getReader();
|
||||
let decoder = new TextDecoder();
|
||||
let result = "";
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
let chunk = decoder.decode(value, { stream: true });
|
||||
|
||||
if (chunk.includes("### compiled references:")) {
|
||||
const references = handleCompiledReferences(chunk, result);
|
||||
if (references.response) {
|
||||
result = references.response;
|
||||
setInitialResponse(references.response);
|
||||
setInitialReferences(references);
|
||||
}
|
||||
} else {
|
||||
result += chunk;
|
||||
setInitialResponse(result);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error verifying statement: ", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function spawnNewConversation(setConversationID: (conversationID: string) => void) {
|
||||
let createURL = `/api/chat/sessions?client=web`;
|
||||
|
||||
const response = await fetch(createURL, { method: "POST" });
|
||||
|
||||
const data = await response.json();
|
||||
setConversationID(data.conversation_id);
|
||||
}
|
||||
|
||||
interface ReferenceVerificationProps {
|
||||
message: string;
|
||||
additionalLink: string;
|
||||
conversationId: string;
|
||||
linkTitle: string;
|
||||
setChildReferencesCallback: (
|
||||
additionalLink: string,
|
||||
response: string,
|
||||
linkTitle: string,
|
||||
) => void;
|
||||
prefilledResponse?: string;
|
||||
}
|
||||
|
||||
function ReferenceVerification(props: ReferenceVerificationProps) {
|
||||
const [initialResponse, setInitialResponse] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const verificationStatement = `${props.message}. Use this link for reference: ${props.additionalLink}`;
|
||||
const isMobileWidth = useIsMobileWidth();
|
||||
|
||||
useEffect(() => {
|
||||
if (props.prefilledResponse) {
|
||||
setInitialResponse(props.prefilledResponse);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
verifyStatement(
|
||||
verificationStatement,
|
||||
props.conversationId,
|
||||
setIsLoading,
|
||||
setInitialResponse,
|
||||
() => {},
|
||||
);
|
||||
}
|
||||
}, [verificationStatement, props.conversationId, props.prefilledResponse]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialResponse === "") return;
|
||||
if (props.prefilledResponse) return;
|
||||
|
||||
if (!isLoading) {
|
||||
// Only set the child references when it's done loading and if the initial response is not prefilled (i.e. it was fetched from the server)
|
||||
props.setChildReferencesCallback(
|
||||
props.additionalLink,
|
||||
initialResponse,
|
||||
props.linkTitle,
|
||||
);
|
||||
}
|
||||
}, [initialResponse, isLoading, props]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <LoadingSpinner />}
|
||||
<ChatMessage
|
||||
chatMessage={{
|
||||
automationId: "",
|
||||
by: "AI",
|
||||
message: initialResponse,
|
||||
context: [],
|
||||
created: new Date().toISOString(),
|
||||
onlineContext: {},
|
||||
codeContext: {},
|
||||
conversationId: props.conversationId,
|
||||
turnId: "",
|
||||
}}
|
||||
isMobileWidth={isMobileWidth}
|
||||
onDeleteMessage={(turnId?: string) => {}}
|
||||
conversationId={props.conversationId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SupplementalReferenceProps {
|
||||
onlineData?: OnlineContextData;
|
||||
officialFactToVerify: string;
|
||||
conversationId: string;
|
||||
additionalLink: string;
|
||||
setChildReferencesCallback: (
|
||||
additionalLink: string,
|
||||
response: string,
|
||||
linkTitle: string,
|
||||
) => void;
|
||||
prefilledResponse?: string;
|
||||
linkTitle?: string;
|
||||
}
|
||||
|
||||
function SupplementalReference(props: SupplementalReferenceProps) {
|
||||
const linkTitle = props.linkTitle || props.onlineData?.organic?.[0]?.title || "Reference";
|
||||
const linkAsWebpage = { link: props.additionalLink } as WebPage;
|
||||
return (
|
||||
<Card className={`mt-2 mb-4`}>
|
||||
<CardHeader>
|
||||
<a
|
||||
className={styles.titleLink}
|
||||
href={props.additionalLink}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{linkTitle}
|
||||
</a>
|
||||
<WebPageLink {...linkAsWebpage} />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ReferenceVerification
|
||||
additionalLink={props.additionalLink}
|
||||
message={props.officialFactToVerify}
|
||||
linkTitle={linkTitle}
|
||||
conversationId={props.conversationId}
|
||||
setChildReferencesCallback={props.setChildReferencesCallback}
|
||||
prefilledResponse={props.prefilledResponse}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const WebPageLink = (webpage: WebPage) => {
|
||||
const webpageDomain = new URL(webpage.link).hostname;
|
||||
return (
|
||||
<div className={styles.subLinks}>
|
||||
<a
|
||||
className={`${styles.subLinks} bg-blue-200 px-2`}
|
||||
href={webpage.link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{webpageDomain}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function FactChecker() {
|
||||
const [factToVerify, setFactToVerify] = useState("");
|
||||
const [officialFactToVerify, setOfficialFactToVerify] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [initialResponse, setInitialResponse] = useState("");
|
||||
const [clickedVerify, setClickedVerify] = useState(false);
|
||||
const [initialReferences, setInitialReferences] = useState<ResponseWithReferences>();
|
||||
const [childReferences, setChildReferences] = useState<SupplementReferences[]>();
|
||||
const [modelUsed, setModelUsed] = useState<Model>();
|
||||
const isMobileWidth = useIsMobileWidth();
|
||||
|
||||
const [conversationID, setConversationID] = useState("");
|
||||
const [runId, setRunId] = useState("");
|
||||
const [loadedFromStorage, setLoadedFromStorage] = useState(false);
|
||||
|
||||
const [initialModel, setInitialModel] = useState<Model>();
|
||||
|
||||
function setChildReferencesCallback(
|
||||
additionalLink: string,
|
||||
response: string,
|
||||
linkTitle: string,
|
||||
) {
|
||||
const newReferences = childReferences || [];
|
||||
const exists = newReferences.find(
|
||||
(reference) => reference.additionalLink === additionalLink,
|
||||
);
|
||||
if (exists) return;
|
||||
newReferences.push({ additionalLink, response, linkTitle });
|
||||
setChildReferences(newReferences);
|
||||
}
|
||||
|
||||
let userData = useAuthenticatedData();
|
||||
|
||||
function storeData() {
|
||||
const data = {
|
||||
factToVerify,
|
||||
response: initialResponse,
|
||||
references: initialReferences,
|
||||
childReferences,
|
||||
runId,
|
||||
modelUsed,
|
||||
};
|
||||
|
||||
fetch(`/api/chat/store/factchecker`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
runId: runId,
|
||||
storeData: data,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (factToVerify) {
|
||||
document.title = `AI Fact Check: ${factToVerify}`;
|
||||
} else {
|
||||
document.title = "AI Fact Checker";
|
||||
}
|
||||
}, [factToVerify]);
|
||||
|
||||
useEffect(() => {
|
||||
const storedFact = localStorage.getItem("factToVerify");
|
||||
if (storedFact) {
|
||||
setFactToVerify(storedFact);
|
||||
}
|
||||
|
||||
// Get query params from the URL
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const factToVerifyParam = urlParams.get("factToVerify");
|
||||
|
||||
if (factToVerifyParam) {
|
||||
setFactToVerify(factToVerifyParam);
|
||||
}
|
||||
|
||||
const runIdParam = urlParams.get("runId");
|
||||
if (runIdParam) {
|
||||
setRunId(runIdParam);
|
||||
|
||||
// Define an async function to fetch data
|
||||
const fetchData = async () => {
|
||||
const storedDataURL = `/api/chat/store/factchecker?runId=${runIdParam}`;
|
||||
try {
|
||||
const response = await fetch(storedDataURL);
|
||||
if (response.status !== 200) {
|
||||
throw new Error("Failed to fetch stored data");
|
||||
}
|
||||
const storedData = JSON.parse(await response.json());
|
||||
if (storedData) {
|
||||
setOfficialFactToVerify(storedData.factToVerify);
|
||||
setInitialResponse(storedData.response);
|
||||
setInitialReferences(storedData.references);
|
||||
setChildReferences(storedData.childReferences);
|
||||
setInitialModel(storedData.modelUsed);
|
||||
}
|
||||
setLoadedFromStorage(true);
|
||||
} catch (error) {
|
||||
console.error("Error fetching stored data: ", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Call the async function
|
||||
fetchData();
|
||||
}
|
||||
}, []);
|
||||
|
||||
function onClickVerify() {
|
||||
if (clickedVerify) return;
|
||||
|
||||
// Perform validation checks on the fact to verify
|
||||
if (!factToVerify) {
|
||||
alert("Please enter a fact to verify.");
|
||||
return;
|
||||
}
|
||||
|
||||
setClickedVerify(true);
|
||||
if (!userData) {
|
||||
let currentURL = window.location.href;
|
||||
window.location.href = `/login?next=${currentURL}`;
|
||||
}
|
||||
|
||||
setInitialReferences(undefined);
|
||||
setInitialResponse("");
|
||||
|
||||
spawnNewConversation(setConversationID);
|
||||
|
||||
// Set the runId to a random 12-digit alphanumeric string
|
||||
const newRunId = [...Array(16)].map(() => Math.random().toString(36)[2]).join("");
|
||||
setRunId(newRunId);
|
||||
window.history.pushState(
|
||||
{},
|
||||
document.title,
|
||||
window.location.pathname + `?runId=${newRunId}`,
|
||||
);
|
||||
|
||||
setOfficialFactToVerify(factToVerify);
|
||||
setClickedVerify(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!conversationID) return;
|
||||
verifyStatement(
|
||||
officialFactToVerify,
|
||||
conversationID,
|
||||
setIsLoading,
|
||||
setInitialResponse,
|
||||
setInitialReferences,
|
||||
);
|
||||
}, [conversationID, officialFactToVerify]);
|
||||
|
||||
// Store factToVerify in localStorage whenever it changes
|
||||
useEffect(() => {
|
||||
localStorage.setItem("factToVerify", factToVerify);
|
||||
}, [factToVerify]);
|
||||
|
||||
// Update the meta tags for the description and og:description
|
||||
useEffect(() => {
|
||||
let metaTag = document.querySelector('meta[name="description"]');
|
||||
if (metaTag) {
|
||||
metaTag.setAttribute("content", initialResponse);
|
||||
}
|
||||
let metaOgTag = document.querySelector('meta[property="og:description"]');
|
||||
if (!metaOgTag) {
|
||||
metaOgTag = document.createElement("meta");
|
||||
metaOgTag.setAttribute("property", "og:description");
|
||||
document.getElementsByTagName("head")[0].appendChild(metaOgTag);
|
||||
}
|
||||
metaOgTag.setAttribute("content", initialResponse);
|
||||
}, [initialResponse]);
|
||||
|
||||
const renderReferences = (
|
||||
conversationId: string,
|
||||
initialReferences: ResponseWithReferences,
|
||||
officialFactToVerify: string,
|
||||
loadedFromStorage: boolean,
|
||||
childReferences?: SupplementReferences[],
|
||||
) => {
|
||||
if (loadedFromStorage && childReferences) {
|
||||
return renderSupplementalReferences(childReferences);
|
||||
}
|
||||
|
||||
const seenLinks = new Set();
|
||||
|
||||
// Any links that are present in webpages should not be searched again
|
||||
Object.entries(initialReferences.online || {}).map(([key, onlineData], index) => {
|
||||
const webpages = onlineData?.webpages || [];
|
||||
// Webpage can be a list or a single object
|
||||
if (webpages instanceof Array) {
|
||||
for (let i = 0; i < webpages.length; i++) {
|
||||
const webpage = webpages[i];
|
||||
const additionalLink = webpage.link || "";
|
||||
if (seenLinks.has(additionalLink)) {
|
||||
return null;
|
||||
}
|
||||
seenLinks.add(additionalLink);
|
||||
}
|
||||
} else {
|
||||
let singleWebpage = webpages as WebPage;
|
||||
const additionalLink = singleWebpage.link || "";
|
||||
if (seenLinks.has(additionalLink)) {
|
||||
return null;
|
||||
}
|
||||
seenLinks.add(additionalLink);
|
||||
}
|
||||
});
|
||||
|
||||
return Object.entries(initialReferences.online || {})
|
||||
.map(([key, onlineData], index) => {
|
||||
let additionalLink = "";
|
||||
|
||||
// Loop through organic links until we find one that hasn't been searched
|
||||
for (let i = 0; i < onlineData?.organic?.length; i++) {
|
||||
const webpage = onlineData?.organic?.[i];
|
||||
additionalLink = webpage.link || "";
|
||||
|
||||
if (!seenLinks.has(additionalLink)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
seenLinks.add(additionalLink);
|
||||
|
||||
if (additionalLink === "") return null;
|
||||
|
||||
return (
|
||||
<SupplementalReference
|
||||
key={index}
|
||||
onlineData={onlineData}
|
||||
officialFactToVerify={officialFactToVerify}
|
||||
conversationId={conversationId}
|
||||
additionalLink={additionalLink}
|
||||
setChildReferencesCallback={setChildReferencesCallback}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const renderSupplementalReferences = (references: SupplementReferences[]) => {
|
||||
return references.map((reference, index) => {
|
||||
return (
|
||||
<SupplementalReference
|
||||
key={index}
|
||||
additionalLink={reference.additionalLink}
|
||||
officialFactToVerify={officialFactToVerify}
|
||||
conversationId={conversationID}
|
||||
linkTitle={reference.linkTitle}
|
||||
setChildReferencesCallback={setChildReferencesCallback}
|
||||
prefilledResponse={reference.response}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const renderWebpages = (webpages: WebPage[] | WebPage) => {
|
||||
if (webpages instanceof Array) {
|
||||
return webpages.map((webpage, index) => {
|
||||
return WebPageLink(webpage);
|
||||
});
|
||||
} else {
|
||||
return WebPageLink(webpages);
|
||||
}
|
||||
};
|
||||
|
||||
function constructShareUrl() {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("runId", runId);
|
||||
return url.href;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative md:fixed h-full">
|
||||
<SidePanel conversationId={null} uploadedFiles={[]} isMobileWidth={isMobileWidth} />
|
||||
</div>
|
||||
<div className={styles.factCheckerContainer}>
|
||||
<h1
|
||||
className={`${styles.response} pt-8 md:pt-4 font-large outline-slate-800 dark:outline-slate-200`}
|
||||
>
|
||||
AI Fact Checker
|
||||
</h1>
|
||||
<footer className={`${styles.footer} mt-4`}>
|
||||
This is an experimental AI tool. It may make mistakes.
|
||||
</footer>
|
||||
{initialResponse && initialReferences && childReferences ? (
|
||||
<div className={styles.reportActions}>
|
||||
<Button asChild variant="secondary">
|
||||
<Link href="/factchecker" target="_blank" rel="noopener noreferrer">
|
||||
Try Another
|
||||
</Link>
|
||||
</Button>
|
||||
<ShareLink
|
||||
buttonTitle="Share report"
|
||||
title="AI Fact Checking Report"
|
||||
description="Share this fact checking report with others. Anyone who has this link will be able to view the report."
|
||||
url={constructShareUrl()}
|
||||
onShare={loadedFromStorage ? () => {} : storeData}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.newReportActions}>
|
||||
<div className={`${styles.inputFields} mt-4`}>
|
||||
<Input
|
||||
type="text"
|
||||
maxLength={200}
|
||||
placeholder="Enter a falsifiable statement to verify"
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setFactToVerify(e.target.value)}
|
||||
value={factToVerify}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
onClickVerify();
|
||||
}
|
||||
}}
|
||||
onFocus={(e) => (e.target.placeholder = "")}
|
||||
onBlur={(e) =>
|
||||
(e.target.placeholder =
|
||||
"Enter a falsifiable statement to verify")
|
||||
}
|
||||
/>
|
||||
<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 />
|
||||
</div>
|
||||
)}
|
||||
{initialResponse && (
|
||||
<Card className={`mt-4`}>
|
||||
<CardHeader>
|
||||
<CardTitle>{officialFactToVerify}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className={styles.responseText}>
|
||||
<ChatMessage
|
||||
chatMessage={{
|
||||
automationId: "",
|
||||
by: "AI",
|
||||
message: initialResponse,
|
||||
context: [],
|
||||
created: new Date().toISOString(),
|
||||
onlineContext: {},
|
||||
codeContext: {},
|
||||
conversationId: conversationID,
|
||||
turnId: "",
|
||||
}}
|
||||
conversationId={conversationID}
|
||||
onDeleteMessage={(turnId?: string) => {}}
|
||||
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) => {
|
||||
const webpages = onlineData?.webpages || [];
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -51,16 +51,6 @@ def chat_page(request: Request):
|
|||
)
|
||||
|
||||
|
||||
@web_client.get("/factchecker", response_class=FileResponse)
|
||||
def fact_checker_page(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
"factchecker/index.html",
|
||||
context={
|
||||
"request": request,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@web_client.get("/login", response_class=FileResponse)
|
||||
def login_page(request: Request):
|
||||
next_url = get_next_url(request)
|
||||
|
|
Loading…
Reference in a new issue