import React, { useState, useEffect, useRef, useCallback } from 'react'; import useMediaQuery from '@mui/material/useMediaQuery'; import Card from '@mui/material/Card'; import { styled } from '@mui/material/styles'; import Avatar from '@mui/material/Avatar'; import Tabs from '@mui/material/Tabs'; import Tab from '@mui/material/Tab'; import Tooltip from '@mui/material/Tooltip'; import Snackbar, { SnackbarCloseReason } from '@mui/material/Snackbar'; import Alert from '@mui/material/Alert'; import AppBar from '@mui/material/AppBar'; import Drawer from '@mui/material/Drawer'; import Toolbar from '@mui/material/Toolbar'; import SettingsIcon from '@mui/icons-material/Settings'; import IconButton from '@mui/material/IconButton'; import Box from '@mui/material/Box'; import CssBaseline from '@mui/material/CssBaseline'; import MenuIcon from '@mui/icons-material/Menu'; import { ResumeBuilder } from './ResumeBuilder'; import { Message, ChatQuery, MessageList, MessageData } from './Message'; import { SeverityType } from './Snack'; import { VectorVisualizer } from './VectorVisualizer'; import { Controls } from './Controls'; import { Conversation, ConversationHandle } from './Conversation'; import './App.css'; import '@fontsource/roboto/300.css'; import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; import MuiMarkdown from 'mui-markdown'; const getConnectionBase = (loc: any): string => { if (!loc.host.match(/.*battle-linux.*/)) { return loc.protocol + "//" + loc.host; } else { return loc.protocol + "//battle-linux.ketrenos.com:8912"; } } interface TabPanelProps { children?: React.ReactNode; index: number; tab: number; } function CustomTabPanel(props: TabPanelProps) { const { children, tab, index, ...other } = props; return (
{tab === index && children}
); } const App = () => { const conversationRef = useRef(null); const [processing, setProcessing] = useState(false); const [sessionId, setSessionId] = useState(undefined); const [connectionBase,] = useState(getConnectionBase(window.location)) const [menuOpen, setMenuOpen] = useState(false); const [isMenuClosing, setIsMenuClosing] = useState(false); const [snackOpen, setSnackOpen] = useState(false); const [snackMessage, setSnackMessage] = useState(""); const [snackSeverity, setSnackSeverity] = useState("success"); const [tab, setTab] = useState(0); const [about, setAbout] = useState(""); const [resume, setResume] = useState(undefined); const [facts, setFacts] = useState(undefined); const isDesktop = useMediaQuery('(min-width:650px)'); const prevIsDesktopRef = useRef(isDesktop); const chatRef = useRef(null); // Set the snack pop-up and open it const setSnack = useCallback((message: string, severity: SeverityType = "success") => { setSnackMessage(message); setSnackSeverity(severity); setSnackOpen(true); }, []); useEffect(() => { if (prevIsDesktopRef.current === isDesktop) return; if (menuOpen) { setMenuOpen(false); } prevIsDesktopRef.current = isDesktop; }, [isDesktop, setMenuOpen, menuOpen]) // Get the About markdown useEffect(() => { if (about !== "") { return; } const fetchAbout = async () => { try { const response = await fetch("/docs/about.md", { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { throw Error("/docs/about.md not found"); } const data = await response.text(); setAbout(data); } catch (error: any) { console.error('Error obtaining About content information:', error); setAbout("No information provided."); }; }; fetchAbout(); }, [about, setAbout]) const handleSubmitChatQuery = (query: string) => { console.log(`handleSubmitChatQuery: ${query} -- `, chatRef.current ? ' sending' : 'no handler'); chatRef.current?.submitQuery(query); }; const chatPreamble: MessageList = [ { role: 'info', content: ` # Welcome to Backstory Backstory is a RAG enabled expert system with access to real-time data running self-hosted (no cloud) versions of industry leading Large and Small Language Models (LLM/SLMs). It was written by James Ketrenos in order to provide answers to questions potential employers may have about his work history. What would you like to know about James? ` } ]; const chatQuestions = [ , As with all LLM interactions, the results may not be 100% accurate. If you have questions about my career, I'd love to hear from you. You can send me an email at **james_backstory@ketrenos.com**. ]; // Extract the sessionId from the URL if present, otherwise // request a sessionId from the server. useEffect(() => { const url = new URL(window.location.href); const pathParts = url.pathname.split('/').filter(Boolean); if (!pathParts.length) { console.log("No session id -- creating a new session") fetch(connectionBase + `/api/context`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, }) .then(response => response.json()) .then(data => { console.log(`Session id: ${data.id} -- returned from server`) setSessionId(data.id); window.history.replaceState({}, '', `/${data.id}`); }) .catch(error => console.error('Error generating session ID:', error)); } else { console.log(`Session id: ${pathParts[0]} -- existing session`) setSessionId(pathParts[0]); } }, [setSessionId, connectionBase]); const handleMenuClose = () => { setIsMenuClosing(true); setMenuOpen(false); }; const handleMenuTransitionEnd = () => { setIsMenuClosing(false); }; const handleMenuToggle = () => { if (!isMenuClosing) { setMenuOpen(!menuOpen); } }; const settingsPanel = ( <> {sessionId !== undefined && } ); const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { setTab(newValue); handleMenuClose(); }; const menuDrawer = ( } iconPosition="start" /> } /> ); const handleSnackClose = ( event: React.SyntheticEvent | Event, reason?: SnackbarCloseReason, ) => { if (reason === 'clickaway') { return; } setSnackOpen(false); }; /* toolbar height is 56px + 8px margin-top */ const Offset = styled('div')(({ theme }) => ({ ...theme.mixins.toolbar, minHeight: '64px', height: '64px' })); return ( theme.zIndex.drawer + 1, maxWidth: "100vw" }} > {!isDesktop && { setTab(0); setMenuOpen(false); }} > BACKSTORY } {menuOpen === false && isDesktop && } iconPosition="start" /> } /> } {menuDrawer} { settingsPanel } {snackMessage} ); }; export default App;