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} -
-
-
- {snippet} -
-
-
-
- ) -} - -function WebPageReference(props: { webpages: WebPage, query: string | null }) { - - let snippet = md.render(props.webpages.snippet); - - const [showSnippet, setShowSnippet] = useState(false); - - return ( -
setShowSnippet(!showSnippet)}> -
- - { - props.query ? ( - - {props.query} - - ) : - {props.webpages.query} - - } - -
-
-
-
-
- ) -} - -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 ( -
- -
-
- {people.snippet} -
-
-
- ) - }) - } - { - 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"