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;