Add DOMPurify for rendering md text. Add a easter egg in the console

This commit is contained in:
sabaimran
2024-07-10 10:03:08 +05:30
parent e358723baa
commit e1a5c17775
7 changed files with 47 additions and 172 deletions

View File

@@ -48,6 +48,7 @@ import { StreamMessage } from '../components/chatMessage/chatMessage';
import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog';
import { Popover, PopoverContent } from '@/components/ui/popover';
import { PopoverTrigger } from '@radix-ui/react-popover';
import { welcomeConsole } from '../common/utils';
interface ChatInputProps {
sendMessage: (message: string) => void;
@@ -392,6 +393,9 @@ export default function Chat() {
const [isMobileWidth, setIsMobileWidth] = useState(false);
welcomeConsole();
const handleWebSocketMessage = (event: MessageEvent) => {
let chunk = event.data;

View File

@@ -82,8 +82,6 @@ export const setupWebSocket = async (conversationId: string) => {
webSocketUrl += `?conversation_id=${conversationId}`;
}
console.log("WebSocket URL: ", webSocketUrl);
const chatWS = new WebSocket(webSocketUrl);
chatWS.onopen = () => {

View File

@@ -0,0 +1,17 @@
export function welcomeConsole() {
console.log(`%c %s`, "font-family:monospace", `
__ __ __ __ ______ __ _____ __
/\\ \\/ / /\\ \\_\\ \\ /\\ __ \\ /\\ \\ /\\ __ \\ /\\ \\
\\ \\ _"-. \\ \\ __ \\ \\ \\ \\/\\ \\ _\\_\\ \\ \\ \\ __ \\ \\ \\ \\
\\ \\_\\ \\_\\ \\ \\_\\ \\_\\ \\ \\_____\\ /\\_____\\ \\ \\_\\ \\_\\ \\ \\_\\
\\/_/\\/_/ \\/_/\\/_/ \\/_____/ \\/_____/ \\/_/\\/_/ \\/_/
Greetings traveller,
I am ✨Khoj✨, your open-source, personal AI copilot.
See my source code at https://github.com/khoj-ai/khoj
Read my operating manual at https://docs.khoj.dev
`);
}

View File

@@ -14,6 +14,8 @@ import { TeaserReferencesSection, constructAllReferences } from '../referencePan
import { ThumbsUp, ThumbsDown, Copy, Brain, Cloud, Folder, Book, Aperture, ArrowRight, SpeakerHifi } from '@phosphor-icons/react';
import { MagnifyingGlass } from '@phosphor-icons/react/dist/ssr';
import * as DomPurify from 'dompurify';
const md = new markdownIt({
html: true,
linkify: true,
@@ -192,7 +194,7 @@ export function TrainOfThought(props: TrainOfThoughtProps) {
let header = extractedHeader ? extractedHeader[1] : "";
const iconColor = props.primary ? 'text-orange-400' : 'text-gray-500';
const icon = chooseIconFromHeader(header, iconColor);
let markdownRendered = md.render(props.message);
let markdownRendered = DomPurify.sanitize(md.render(props.message));
return (
<div className={`flex items-center ${props.primary ? 'text-gray-400' : 'text-gray-300'} ${styles.trainOfThought} ${props.primary ? styles.primary : ''}`} >
{icon}
@@ -223,7 +225,7 @@ export default function ChatMessage(props: ChatMessageProps) {
// Replace placeholders with LaTeX delimiters
markdownRendered = markdownRendered.replace(/LEFTPAREN/g, '\\(').replace(/RIGHTPAREN/g, '\\)')
.replace(/LEFTBRACKET/g, '\\[').replace(/RIGHTBRACKET/g, '\\]');
setMarkdownRendered(markdownRendered);
setMarkdownRendered(DomPurify.sanitize(markdownRendered));
}, [props.chatMessage.message]);
useEffect(() => {

View File

@@ -1,7 +1,5 @@
'use client'
import styles from "./referencePanel.module.css";
import { useEffect, useState } from "react";
import { ArrowRight, File } from "@phosphor-icons/react";
@@ -13,9 +11,8 @@ const md = new markdownIt({
typographer: true
});
import { SingleChatMessage, Context, WebPage, OnlineContextData } from "../chatMessage/chatMessage";
import { Context, WebPage, OnlineContextData } from "../chatMessage/chatMessage";
import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import {
Sheet,
@@ -26,13 +23,7 @@ import {
SheetTrigger,
} from "@/components/ui/sheet";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
interface ReferencePanelProps {
referencePanelData: SingleChatMessage | null;
setShowReferencePanel: (showReferencePanel: boolean) => void;
}
import * as DomPurify from 'dompurify';
interface NotesContextReferenceData {
title: string;
@@ -45,7 +36,7 @@ interface NotesContextReferenceCardProps extends NotesContextReferenceData {
function NotesContextReferenceCard(props: NotesContextReferenceCardProps) {
const snippet = md.render(props.content);
const snippet = props.showFullContent ? DomPurify.sanitize(md.render(props.content)) : DomPurify.sanitize(props.content);
const [isHovering, setIsHovering] = useState(false);
return (
@@ -109,12 +100,10 @@ function GenericOnlineReferenceCard(props: OnlineReferenceCardProps) {
const favicon = `https://www.google.com/s2/favicons?domain=${domain}`;
const handleMouseEnter = () => {
console.log("mouse entered card");
setIsHovering(true);
}
const handleMouseLeave = () => {
console.log("mouse left card");
setIsHovering(false);
}
@@ -122,7 +111,6 @@ function GenericOnlineReferenceCard(props: OnlineReferenceCardProps) {
<>
<Popover
open={isHovering && !props.showFullContent}
// open={true}
onOpenChange={setIsHovering}
>
<PopoverTrigger asChild>
@@ -349,156 +337,3 @@ export default function ReferencePanel(props: ReferencePanelDataProps) {
</Sheet>
);
}
function CompiledReference(props: { context: (Context | string) }) {
let snippet = "";
let file = "";
if (typeof props.context === "string") {
// Treat context as a string and get the first line for the file name
const lines = props.context.split("\n");
file = lines[0];
snippet = lines.slice(1).join("\n");
} else {
const context = props.context as Context;
snippet = context.compiled;
file = context.file;
}
const [showSnippet, setShowSnippet] = useState(false);
return (
<div className={styles.singleReference}>
<div className={styles.contextReference} onClick={() => setShowSnippet(!showSnippet)}>
<div className={styles.referencePanelTitle}>
{file}
</div>
<div className={styles.referencePanelContent} style={{ display: showSnippet ? "block" : "none" }}>
<div>
{snippet}
</div>
</div>
</div>
</div>
)
}
function WebPageReference(props: { webpages: WebPage, query: string | null }) {
let snippet = md.render(props.webpages.snippet);
const [showSnippet, setShowSnippet] = useState(false);
return (
<div className={styles.onlineReference} onClick={() => setShowSnippet(!showSnippet)}>
<div className={styles.onlineReferenceTitle}>
<a href={props.webpages.link} target="_blank" rel="noreferrer">
{
props.query ? (
<span>
{props.query}
</span>
) : <span>
{props.webpages.query}
</span>
}
</a>
</div>
<div className={styles.onlineReferenceContent} style={{ display: showSnippet ? "block" : "none" }}>
<div dangerouslySetInnerHTML={{ __html: snippet }}></div>
</div>
</div>
)
}
function OnlineReferences(props: { onlineContext: OnlineContextData, query: string }) {
const webpages = props.onlineContext.webpages;
const answerBox = props.onlineContext.answerBox;
const peopleAlsoAsk = props.onlineContext.peopleAlsoAsk;
const knowledgeGraph = props.onlineContext.knowledgeGraph;
const organic = props.onlineContext.organic;
return (
<div className={styles.singleReference}>
{
webpages && (
!Array.isArray(webpages) ? (
<WebPageReference webpages={webpages} query={props.query} />
) : (
webpages.map((webpage, index) => {
return <WebPageReference webpages={webpage} key={index} query={null} />
})
)
)
}
{
answerBox && (
<div className={styles.onlineReference}>
<div className={styles.onlineReferenceTitle}>
{answerBox.title}
</div>
<div className={styles.onlineReferenceContent}>
<div>
{answerBox.answer}
</div>
</div>
</div>
)
}
{
organic && organic.map((organicData, index) => {
return (
<div className={styles.onlineReference} key={index}>
<div className={styles.onlineReferenceTitle}>
<a href={organicData.link} target="_blank" rel="noreferrer">
{organicData.title}
</a>
</div>
<div className={styles.onlineReferenceContent}>
<div>
{organicData.snippet}
</div>
</div>
</div>
)
})
}
{
peopleAlsoAsk && peopleAlsoAsk.map((people, index) => {
return (
<div className={styles.onlineReference} key={index}>
<div className={styles.onlineReferenceTitle}>
<a href={people.link} target="_blank" rel="noreferrer">
{people.question}
</a>
</div>
<div className={styles.onlineReferenceContent}>
<div>
{people.snippet}
</div>
</div>
</div>
)
})
}
{
knowledgeGraph && (
<div className={styles.onlineReference}>
<div className={styles.onlineReferenceTitle}>
<a href={knowledgeGraph.descriptionLink} target="_blank" rel="noreferrer">
{knowledgeGraph.title}
</a>
</div>
<div className={styles.onlineReferenceContent}>
<div>
{knowledgeGraph.description}
</div>
</div>
</div>
)
}
</div>
)
}