diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx
index 4cfe1f5e..618d759c 100644
--- a/src/interface/web/app/chat/page.tsx
+++ b/src/interface/web/app/chat/page.tsx
@@ -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;
diff --git a/src/interface/web/app/common/chatFunctions.ts b/src/interface/web/app/common/chatFunctions.ts
index 60254692..923d06d5 100644
--- a/src/interface/web/app/common/chatFunctions.ts
+++ b/src/interface/web/app/common/chatFunctions.ts
@@ -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 = () => {
diff --git a/src/interface/web/app/common/utils.ts b/src/interface/web/app/common/utils.ts
new file mode 100644
index 00000000..5cc9d767
--- /dev/null
+++ b/src/interface/web/app/common/utils.ts
@@ -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
+ `);
+}
diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx
index dc44b7a9..c3696323 100644
--- a/src/interface/web/app/components/chatMessage/chatMessage.tsx
+++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx
@@ -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 (
{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(() => {
diff --git a/src/interface/web/app/components/referencePanel/referencePanel.tsx b/src/interface/web/app/components/referencePanel/referencePanel.tsx
index 6263adf6..aa914243 100644
--- a/src/interface/web/app/components/referencePanel/referencePanel.tsx
+++ b/src/interface/web/app/components/referencePanel/referencePanel.tsx
@@ -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) {
<>
@@ -349,156 +337,3 @@ export default function ReferencePanel(props: ReferencePanelDataProps) {
);
}
-
-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 (
-
-
setShowSnippet(!showSnippet)}>
-
- {file}
-
-
-
-
- )
-}
-
-function WebPageReference(props: { webpages: WebPage, query: string | null }) {
-
- let snippet = md.render(props.webpages.snippet);
-
- const [showSnippet, setShowSnippet] = useState(false);
-
- return (
- setShowSnippet(!showSnippet)}>
-
-
-
- )
-}
-
-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 (
-
- {
- webpages && (
- !Array.isArray(webpages) ? (
-
- ) : (
- webpages.map((webpage, index) => {
- return
- })
- )
- )
- }
- {
- answerBox && (
-
-
- {answerBox.title}
-
-
-
- {answerBox.answer}
-
-
-
- )
- }
- {
- organic && organic.map((organicData, index) => {
- return (
-
-
-
-
- {organicData.snippet}
-
-
-
- )
- })
- }
- {
- peopleAlsoAsk && peopleAlsoAsk.map((people, index) => {
- return (
-
- )
- })
- }
- {
- knowledgeGraph && (
-
-
-
-
- {knowledgeGraph.description}
-
-
-
- )
- }
-
-
- )
-}
diff --git a/src/interface/web/package.json b/src/interface/web/package.json
index 4e20159b..e762b974 100644
--- a/src/interface/web/package.json
+++ b/src/interface/web/package.json
@@ -31,12 +31,14 @@
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
+ "@types/dompurify": "^3.0.5",
"@types/katex": "^0.16.7",
"@types/markdown-it": "^14.1.1",
"autoprefixer": "^10.4.19",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
+ "dompurify": "^3.1.6",
"katex": "^0.16.10",
"lucide-react": "^0.397.0",
"markdown-it": "^14.1.0",
diff --git a/src/interface/web/yarn.lock b/src/interface/web/yarn.lock
index 54b30ab8..27c25989 100644
--- a/src/interface/web/yarn.lock
+++ b/src/interface/web/yarn.lock
@@ -1045,6 +1045,13 @@
mkdirp "^2.1.6"
path-browserify "^1.0.1"
+"@types/dompurify@^3.0.5":
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.5.tgz#02069a2fcb89a163bacf1a788f73cb415dd75cb7"
+ integrity sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==
+ dependencies:
+ "@types/trusted-types" "*"
+
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -1100,6 +1107,11 @@
"@types/prop-types" "*"
csstype "^3.0.2"
+"@types/trusted-types@*":
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
+ integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
+
"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.2.0.tgz#44356312aea8852a3a82deebdacd52ba614ec07a"
@@ -1792,6 +1804,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dompurify@^3.1.6:
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.6.tgz#43c714a94c6a7b8801850f82e756685300a027e2"
+ integrity sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==
+
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"