diff --git a/src/ketr-chat/package-lock.json b/src/ketr-chat/package-lock.json index 13e9047..ac4a5ee 100644 --- a/src/ketr-chat/package-lock.json +++ b/src/ketr-chat/package-lock.json @@ -21,6 +21,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", + "mui-markdown": "^1.2.6", "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", @@ -4626,6 +4627,12 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "optional": true + }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -13453,6 +13460,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/markdown-to-jsx": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.4.tgz", + "integrity": "sha512-1bSfXyBKi+EYS3YY+e0Csuxf8oZ3decdfhOav/Z7Wrk89tjudyL5FOmwZQUoy0/qVXGUl+6Q3s2SWtpDEWITfQ==", + "peer": true, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -14517,6 +14536,22 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/mui-markdown": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/mui-markdown/-/mui-markdown-1.2.6.tgz", + "integrity": "sha512-GR3+CVLDS4eSri8d4QFkcrBtdBNUCRbWeyXuf3v/t0qZoWDta1U5Wm/MHsYsWSR4HBy6BVoyxlZ0fMNGOCScyQ==", + "optionalDependencies": { + "prism-react-renderer": "^2.0.3" + }, + "peerDependencies": { + "@emotion/react": "^11.10.8", + "@emotion/styled": "^11.10.8", + "@mui/material": ">= 5.12.2", + "markdown-to-jsx": "^7.3.0", + "react": ">= 17.0.2", + "react-dom": ">= 17.0.2" + } + }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", @@ -16752,6 +16787,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "optional": true, + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/probe-image-size": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz", diff --git a/src/ketr-chat/package.json b/src/ketr-chat/package.json index 811982d..ef15fe7 100644 --- a/src/ketr-chat/package.json +++ b/src/ketr-chat/package.json @@ -16,6 +16,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", + "mui-markdown": "^1.2.6", "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", diff --git a/src/ketr-chat/public/disable-jpk.png b/src/ketr-chat/public/disable-jpk.png new file mode 100755 index 0000000..826227c Binary files /dev/null and b/src/ketr-chat/public/disable-jpk.png differ diff --git a/src/ketr-chat/public/settings.png b/src/ketr-chat/public/settings.png new file mode 100755 index 0000000..8f7a3a9 Binary files /dev/null and b/src/ketr-chat/public/settings.png differ diff --git a/src/ketr-chat/src/App.css b/src/ketr-chat/src/App.css index 0259b62..723d482 100644 --- a/src/ketr-chat/src/App.css +++ b/src/ketr-chat/src/App.css @@ -77,11 +77,10 @@ div { height: 100%; } -.user-message { +.user-message.MuiCard-root { background-color: #DCF8C6; border: 1px solid #B2E0A7; color: #333333; - padding: 0.5rem; margin-bottom: 0.75rem; margin-left: 1rem; border-radius: 0.25rem; @@ -95,23 +94,40 @@ div { flex-direction: column; align-items: self-end; align-self: end; + flex-grow: 0; } -.assistant-message { - background-color: #FFFFFF; +.assistant-message.MuiCard-root { border: 1px solid #E0E0E0; + background-color: #FFFFFF; color: #333333; - padding: 0.5rem; margin-bottom: 0.75rem; margin-right: 1rem; min-width: 70%; border-radius: 0.25rem; justify-self: left; display: flex; - /* white-space: pre-wrap; */ + white-space: pre-wrap; overflow-wrap: break-word; word-break: break-word; flex-direction: column; + flex-grow: 0; + padding: 16px 0; + font-size: 0.9rem; +} + +.assistant-message .MuiCardContent-root { + padding: 0 16px !important; + font-size: 0.9rem; +} + +.assistant-message span { + font-size: 0.9rem; +} + +.user-message .MuiCardContent-root:last-child, +.assistant-message .MuiCardContent-root:last-child { + padding: 16px; } .users > div { @@ -129,42 +145,50 @@ div { } /* Reduce general whitespace in markdown content */ -.markdown-content p { +.assistant-message p.MuiTypography-root { margin-top: 0.5rem; margin-bottom: 0.5rem; + font-size: 0.9rem; } /* Reduce space between headings and content */ -.markdown-content h1, -.markdown-content h2, -.markdown-content h3, -.markdown-content h4, -.markdown-content h5, -.markdown-content h6 { +.assistant-message h1.MuiTypography-root, +.assistant-message h2.MuiTypography-root, +.assistant-message h3.MuiTypography-root, +.assistant-message h4.MuiTypography-root, +.assistant-message h5.MuiTypography-root, +.assistant-message h6.MuiTypography-root { margin-top: 1rem; margin-bottom: 0.5rem; + font-size: 1rem; } /* Reduce space in lists */ -.markdown-content ul, -.markdown-content ol { +.assistant-message ul.MuiTypography-root, +.assistant-message ol.MuiTypography-root { margin-top: 0.5rem; margin-bottom: 0.5rem; - padding-left: 1.5rem; + font-size: 0.9rem; } -.markdown-content li { +.assistant-message li.MuiTypography-root { margin-bottom: 0.25rem; + font-size: 0.9rem; } -/* Reduce space between list items */ -.markdown-content li p { +.assistant-message .MuiTypography-root li { margin-top: 0; margin-bottom: 0; + padding: 0; + font-size: 0.9rem; } /* Reduce space around code blocks */ -.markdown-content pre { - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} \ No newline at end of file +.assistant-message .MuiTypography-root pre { + border: 1px solid #F5F5F5; + border-radius: 0.5rem; + padding: 0.5rem 0.75rem; + margin-top: 0; + margin-bottom: 0; + font-size: 0.9rem; +} diff --git a/src/ketr-chat/src/App.tsx b/src/ketr-chat/src/App.tsx index 14a9f71..11bf9cb 100644 --- a/src/ketr-chat/src/App.tsx +++ b/src/ketr-chat/src/App.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useCallback, ReactElement } from 'r import FormGroup from '@mui/material/FormGroup'; import FormControlLabel from '@mui/material/FormControlLabel'; import { useTheme } from '@mui/material'; +import { styled } from '@mui/material/styles'; import Switch from '@mui/material/Switch'; import Divider from '@mui/material/Divider'; import Tooltip from '@mui/material/Tooltip'; @@ -13,21 +14,29 @@ import AccordionActions from '@mui/material/AccordionActions'; import AccordionSummary from '@mui/material/AccordionSummary'; import AccordionDetails from '@mui/material/AccordionDetails'; import Typography from '@mui/material/Typography'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import Button from '@mui/material/Button'; import AppBar from '@mui/material/AppBar'; import Drawer from '@mui/material/Drawer'; import Toolbar from '@mui/material/Toolbar'; import MenuIcon from '@mui/icons-material/Menu'; import SettingsIcon from '@mui/icons-material/Settings'; -import IconButton from '@mui/material/IconButton'; +import IconButton, { IconButtonProps } from '@mui/material/IconButton'; import Box from '@mui/material/Box'; import CssBaseline from '@mui/material/CssBaseline'; import AddIcon from '@mui/icons-material/AddCircle'; import SendIcon from '@mui/icons-material/Send'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import Card from '@mui/material/Card'; +import CardHeader from '@mui/material/CardHeader'; +import CardMedia from '@mui/material/CardMedia'; +import CardContent from '@mui/material/CardContent'; +import CardActions from '@mui/material/CardActions'; +import Collapse from '@mui/material/Collapse'; import PropagateLoader from "react-spinners/PropagateLoader"; -import Markdown from 'react-markdown'; +// import Markdown from 'react-markdown'; +import { MuiMarkdown as Markdown } from "mui-markdown"; import './App.css'; import rehypeKatex from 'rehype-katex' import remarkMath from 'remark-math' @@ -38,19 +47,26 @@ import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; -const welcomeMessage = { "role": "assistant", "content": "Welcome to Ketr-Chat. I have real-time access to a lot of information. Ask things like 'What are the headlines from cnn.com?' or 'What is the weather in Portland, OR?'" }; +const welcomeMarkdown = ` +# Welcome to Ketr-Chat. + +This system has real-time access to weather, stocks, the current time, and can answer questions about the contents of a website. + +**NOTE**: As of right now, the LLM model being used is refusing to use enabled tools when RAG is enabled to provide context. +So, in order to use the real-time information, you need to click the Settings ![settings](settings.png) icon, open RAG, and disable JPK: ![disable JPK](disable-jpk.png). + +Ask things like: + * What are the headlines from CNBC? + * What is the weather in Portland, OR? + * What is James Ketrenos' work history? + * What are the stock value of the most traded companies? +`; + +const welcomeMessage = { + "role": "assistant", "content": welcomeMarkdown +}; const loadingMessage = { "role": "assistant", "content": "Instancing chat session..." }; -//const url: string = "https://ai.ketrenos.com" - -const getConnectionBase = (loc: any): string => { - if (!loc.host.match(/.*battle-linux.*/)) { - return loc.protocol + "//" + loc.host; - } else { - return loc.protocol + "//battle-linux.ketrenos.com:5000"; - } -} - type Tool = { type: string, function?: { @@ -88,6 +104,32 @@ type SystemInfo = { "CPU": string }; +type MessageMetadata = { + rag: any, + tools: any[] +}; + +type MessageData = { + role: string, + content: string, + user?: string, + type?: string, + id?: string, + isProcessing?: boolean, + metadata?: MessageMetadata +}; + +type MessageList = MessageData[]; + + +const getConnectionBase = (loc: any): string => { + if (!loc.host.match(/.*battle-linux.*/)) { + return loc.protocol + "//" + loc.host; + } else { + return loc.protocol + "//battle-linux.ketrenos.com:5000"; + } +} + const SystemInfoComponent: React.FC<{ systemInfo: SystemInfo }> = ({ systemInfo }) => { const [systemElements, setSystemElements] = useState([]); @@ -236,6 +278,130 @@ const Controls = ({ tools, rags, systemPrompt, toggleTool, toggleRag, setSystemP ); } +interface ExpandMoreProps extends IconButtonProps { + expand: boolean; +} + +const ExpandMore = styled((props: ExpandMoreProps) => { + const { expand, ...other } = props; + return ; +})(({ theme }) => ({ + marginLeft: 'auto', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + variants: [ + { + props: ({ expand }) => !expand, + style: { + transform: 'rotate(0deg)', + }, + }, + { + props: ({ expand }) => !!expand, + style: { + transform: 'rotate(180deg)', + }, + }, + ], +})); + +interface MessageInterface { + message: MessageData +}; + +interface MessageMetaInterface { + metadata: MessageMetadata +} +const MessageMeta = ({ metadata }: MessageMetaInterface) => { + if (metadata === undefined) { + return <> + } + + console.log(JSON.stringify(metadata.tools[0].result, null, 2)); + + return (<> + { + metadata.tools !== undefined && + +

Tools queried:

+ {metadata.tools.map((tool: any, index: number) => <> + + +
+
{tool.tool}
+
Result Len: {JSON.stringify(tool.result).length}
+
+
{JSON.stringify(tool.result, null, 2)}
+
+ )} +
+ } + { + metadata.rag.name !== undefined && + +

RAG from '{metadata.rag.name}' collection matches against embedding vector of {metadata.rag.query_embedding.length} dimensions:

+ {metadata.rag.ids.map((id: number, index: number) => <> + + +
+
Doc ID: {metadata.rag.ids[index]}
+
Similarity: {Math.round(metadata.rag.distances[index] * 100) / 100}
+
Type: {metadata.rag.metadatas[index].doc_type}
+
Chunk Len: {metadata.rag.documents[index].length}
+
+
{metadata.rag.documents[index]}
+
+ + )} +
+ } + + ); +}; + +const Message = ({ message }: MessageInterface) => { + const [expanded, setExpanded] = React.useState(false); + + const handleExpandClick = () => { + setExpanded(!expanded); + }; + + const formattedContent = message.content.trim(); + + return ( + + + {message.role === 'assistant' ? + + : + + {message.content} + + } + + {message.metadata && <> + + LLM information for this query + + + + + + + + + + } + + ); +} + const App = () => { const [query, setQuery] = useState(''); const [conversation, setConversation] = useState([]); @@ -256,8 +422,9 @@ const App = () => { // Scroll to bottom of conversation when conversation updates useEffect(() => { - if (conversationRef.current) { - conversationRef.current.scrollTop = conversationRef.current.scrollHeight; + const queryElement = document.getElementById('QueryInput'); + if (queryElement) { + queryElement.scrollIntoView(); } }, [conversation]); @@ -575,22 +742,6 @@ const App = () => { } }; - type MessageMetadata = { - title: string - }; - - type Message = { - role: string, - content: string, - user?: string, - type?: string, - id?: string, - isProcessing?: boolean, - metadata?: MessageMetadata - }; - - type MessageList = Message[]; - const onNew = async () => { reset(["history"], "New chat started."); } @@ -739,11 +890,13 @@ const App = () => { setSnackOpen(false); }; + const Offset = styled('div')(({ theme }) => theme.mixins.toolbar); + return ( theme.zIndex.drawer + 1, }} @@ -778,6 +931,8 @@ const App = () => { + + { {drawer} - - - {conversation.map((message, index) => { - const formattedContent = message.content.trim(); - - return ( -
- {message.metadata ? ( - <> -
{message.metadata.title}
- {message.user && ( -
{message.user}
- )} - {message.role === 'assistant' ? ( -
- - {/* */} -
- ) : ( -
{formattedContent}
- )} - - ) : ( - <> - {message.user && ( -
{message.user}
- )} - {message.role === 'assistant' ? ( -
- -
- ) : ( -
{formattedContent}
- )} - - )} -
- ); - })} + + + {conversation.map((message, index) => )}