Lots of UI fixes
This commit is contained in:
parent
889a3f9e3b
commit
5f184b4a1d
@ -1,5 +1,3 @@
|
|||||||
# About Backstory
|
|
||||||
|
|
||||||
The backstory about Backstory...
|
The backstory about Backstory...
|
||||||
|
|
||||||
## Backstory is two things
|
## Backstory is two things
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
import React, { ReactElement, JSXElementConstructor, useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
@ -15,6 +15,7 @@ import Box from '@mui/material/Box';
|
|||||||
import CssBaseline from '@mui/material/CssBaseline';
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { SxProps } from '@mui/material';
|
||||||
|
|
||||||
|
|
||||||
import { ResumeBuilder } from './ResumeBuilder';
|
import { ResumeBuilder } from './ResumeBuilder';
|
||||||
@ -24,6 +25,7 @@ import { VectorVisualizer } from './VectorVisualizer';
|
|||||||
import { Controls } from './Controls';
|
import { Controls } from './Controls';
|
||||||
import { Conversation, ConversationHandle } from './Conversation';
|
import { Conversation, ConversationHandle } from './Conversation';
|
||||||
import { Scrollable } from './AutoScroll';
|
import { Scrollable } from './AutoScroll';
|
||||||
|
import { BackstoryTab } from './BackstoryTab';
|
||||||
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import './Conversation.css';
|
import './Conversation.css';
|
||||||
@ -43,34 +45,20 @@ const getConnectionBase = (loc: any): string => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TabProps {
|
||||||
interface TabPanelProps {
|
label?: string,
|
||||||
children?: React.ReactNode;
|
path: string,
|
||||||
index: number;
|
tabProps?: {
|
||||||
tab: number;
|
label?: string,
|
||||||
}
|
sx?: SxProps,
|
||||||
|
icon?: string | ReactElement<unknown, string | JSXElementConstructor<any>> | undefined,
|
||||||
function CustomTabPanel(props: TabPanelProps) {
|
iconPosition?: "bottom" | "top" | "start" | "end" | undefined
|
||||||
const { children, tab, index, ...other } = props;
|
}
|
||||||
|
};
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="TabPanel"
|
|
||||||
role="tabpanel"
|
|
||||||
style={{ "display": tab === index ? "flex": "none" }}
|
|
||||||
id={`tabpanel-${index}`}
|
|
||||||
aria-labelledby={`tab-${index}`}
|
|
||||||
{...other}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
|
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
|
||||||
const [connectionBase,] = useState<string>(getConnectionBase(window.location))
|
const [connectionBase,] = useState<string>(getConnectionBase(window.location))
|
||||||
const [selectedPath, setSelectedPath] = useState<string>("");
|
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const [isMenuClosing, setIsMenuClosing] = useState(false);
|
const [isMenuClosing, setIsMenuClosing] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState<number>(0);
|
const [activeTab, setActiveTab] = useState<number>(0);
|
||||||
@ -132,15 +120,154 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Extract the sessionId from the URL if present, otherwise
|
const tabs: TabProps[] = useMemo(() => {
|
||||||
// request a sessionId from the server.
|
const backstoryPreamble: MessageList = [
|
||||||
const validPaths = useMemo(() => ['chat', 'notes', 'tasks'], []); // allowed paths
|
{
|
||||||
|
role: 'content',
|
||||||
|
title: 'Welcome to Backstory',
|
||||||
|
content: `
|
||||||
|
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 backstoryQuestions = [
|
||||||
|
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
|
||||||
|
<ChatQuery text="What is James Ketrenos' work history?" submitQuery={handleSubmitChatQuery} />
|
||||||
|
<ChatQuery text="What programming languages has James used?" submitQuery={handleSubmitChatQuery} />
|
||||||
|
<ChatQuery text="What are James' professional strengths?" submitQuery={handleSubmitChatQuery} />
|
||||||
|
<ChatQuery text="What are today's headlines on CNBC.com?" submitQuery={handleSubmitChatQuery} />
|
||||||
|
</Box>,
|
||||||
|
<Box sx={{ p: 1 }}>
|
||||||
|
<MuiMarkdown>
|
||||||
|
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**.
|
||||||
|
</MuiMarkdown>
|
||||||
|
</Box>
|
||||||
|
];
|
||||||
|
|
||||||
|
const tabSx = { flexGrow: 1, fontSize: '1rem' };
|
||||||
|
|
||||||
|
return [{
|
||||||
|
label: "",
|
||||||
|
path: "",
|
||||||
|
tabProps: {
|
||||||
|
label: "Backstory",
|
||||||
|
sx: tabSx,
|
||||||
|
icon:
|
||||||
|
<Avatar sx={{
|
||||||
|
width: 24,
|
||||||
|
height: 24
|
||||||
|
}}
|
||||||
|
variant="rounded"
|
||||||
|
alt="Backstory logo"
|
||||||
|
src="/logo192.png" />,
|
||||||
|
iconPosition: "start"
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<Scrollable
|
||||||
|
sx={{
|
||||||
|
maxWidth: "1024px",
|
||||||
|
height: "calc(100vh - 72px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Conversation
|
||||||
|
ref={chatRef}
|
||||||
|
{...{
|
||||||
|
type: "chat",
|
||||||
|
prompt: "What would you like to know about James?",
|
||||||
|
sessionId,
|
||||||
|
connectionBase,
|
||||||
|
setSnack,
|
||||||
|
preamble: backstoryPreamble,
|
||||||
|
defaultPrompts: backstoryQuestions
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Scrollable>
|
||||||
|
)
|
||||||
|
}, {
|
||||||
|
label: "Resume Builder",
|
||||||
|
path: "resume-builder",
|
||||||
|
children: (
|
||||||
|
<ResumeBuilder sx={{
|
||||||
|
margin: "0 auto",
|
||||||
|
height: "calc(100vh - 72px)",
|
||||||
|
overflow: "auto",
|
||||||
|
backgroundColor: "#F5F5F5",
|
||||||
|
display: "flex",
|
||||||
|
flexGrow: 1
|
||||||
|
}} {...{ setSnack, connectionBase, sessionId }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}, {
|
||||||
|
label: "Context Visualizer",
|
||||||
|
path: "context-visualizer",
|
||||||
|
children:
|
||||||
|
<Scrollable
|
||||||
|
sx={{
|
||||||
|
maxWidth: "1024px",
|
||||||
|
height: "calc(100vh - 72px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VectorVisualizer sx={{ p: 1 }} {...{ connectionBase, sessionId, setSnack }} />
|
||||||
|
</Scrollable>
|
||||||
|
}, {
|
||||||
|
label: "About",
|
||||||
|
path: "about",
|
||||||
|
children: (
|
||||||
|
<Scrollable
|
||||||
|
sx={{
|
||||||
|
maxWidth: "1024px",
|
||||||
|
height: "calc(100vh - 72px)",
|
||||||
|
flexDirection: "column",
|
||||||
|
margin: "0 auto",
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Message
|
||||||
|
{...{
|
||||||
|
sx: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
p: 1,
|
||||||
|
m: 0,
|
||||||
|
flexGrow: 0,
|
||||||
|
},
|
||||||
|
message: { role: 'content', title: "About Backstory", content: about },
|
||||||
|
submitQuery: handleSubmitChatQuery,
|
||||||
|
connectionBase,
|
||||||
|
sessionId,
|
||||||
|
setSnack
|
||||||
|
}} />
|
||||||
|
<Box sx={{ display: "flex", flexGrow: 1, p: 0, m: 0 }} />
|
||||||
|
</Scrollable>
|
||||||
|
)
|
||||||
|
}, {
|
||||||
|
path: "settings",
|
||||||
|
tabProps: {
|
||||||
|
sx: { flexShrink: 1, flexGrow: 0, fontSize: '1rem' },
|
||||||
|
icon: <SettingsIcon />
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<Box className="ChatBox">
|
||||||
|
{sessionId !== undefined &&
|
||||||
|
<Controls {...{ sessionId, setSnack, connectionBase }} />
|
||||||
|
}
|
||||||
|
</Box >
|
||||||
|
)
|
||||||
|
}];
|
||||||
|
}, [about, connectionBase, sessionId, setSnack, isMobile]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
const pathParts = url.pathname.split('/').filter(Boolean); // [path, sessionId]
|
const pathParts = url.pathname.split('/').filter(Boolean); // [path, sessionId]
|
||||||
|
|
||||||
const fetchSession = async (pathOverride?: string) => {
|
const fetchSession = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(connectionBase + `/api/context`, {
|
const response = await fetch(connectionBase + `/api/context`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -155,30 +282,29 @@ const App = () => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setSessionId(data.id);
|
setSessionId(data.id);
|
||||||
|
|
||||||
const newPath = pathOverride || 'chat'; // default fallback
|
const newPath = `/${data.id}`;
|
||||||
window.history.replaceState({}, '', `/${newPath}/${data.id}`);
|
window.history.replaceState({}, '', newPath);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
setSnack("Server is temporarily down", "error");
|
setSnack("Server is temporarily down", "error");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (pathParts.length < 2) {
|
if (pathParts.length < 1) {
|
||||||
console.log("No session id or path -- creating new session");
|
console.log("No session id or path -- creating new session");
|
||||||
fetchSession();
|
fetchSession();
|
||||||
} else {
|
} else {
|
||||||
const currentPath = pathParts[0];
|
const currentPath = pathParts.length < 2 ? '' : pathParts[0];
|
||||||
const session = pathParts[1];
|
const session = pathParts.length < 2 ? pathParts[0] : pathParts[1];
|
||||||
|
let tabIndex = tabs.findIndex((tab) => tab.path === currentPath);
|
||||||
if (!validPaths.includes(currentPath)) {
|
if (-1 === tabIndex) {
|
||||||
console.log(`Invalid path "${currentPath}" -- redirecting to default`);
|
console.log(`Invalid path "${currentPath}" -- redirecting to default`);
|
||||||
fetchSession(); // or you could window.location.replace if you want
|
tabIndex = 0;
|
||||||
} else {
|
|
||||||
console.log(`Path: ${currentPath}, Session id: ${session}`);
|
|
||||||
setSessionId(session);
|
|
||||||
setSelectedPath(currentPath);
|
|
||||||
}
|
}
|
||||||
|
setSessionId(session);
|
||||||
|
setActiveTab(tabIndex);
|
||||||
}
|
}
|
||||||
}, [setSessionId, setSelectedPath, connectionBase, setSnack, validPaths]);
|
}, [setSessionId, connectionBase, setSnack, tabs]);
|
||||||
|
|
||||||
const handleMenuClose = () => {
|
const handleMenuClose = () => {
|
||||||
setIsMenuClosing(true);
|
setIsMenuClosing(true);
|
||||||
@ -196,159 +322,38 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||||
|
if (newValue > tabs.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setActiveTab(newValue);
|
setActiveTab(newValue);
|
||||||
|
const tabPath = tabs[newValue].path;
|
||||||
|
if (tabPath) {
|
||||||
|
window.history.pushState({}, '', `/${tabPath}/${sessionId}`);
|
||||||
|
} else {
|
||||||
|
window.history.pushState({}, '', `/${sessionId}`);
|
||||||
|
}
|
||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabSelect = (newPath: string) => {
|
|
||||||
if (!sessionId) return; // safety
|
|
||||||
setSelectedPath(newPath);
|
|
||||||
window.history.pushState({}, '', `/${newPath}/${sessionId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handlePopState = () => {
|
const handlePopState = () => {
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||||
|
const currentPath = pathParts.length < 2 ? '' : pathParts[0];
|
||||||
|
const session = pathParts.length < 2 ? pathParts[0] : pathParts[1];
|
||||||
|
|
||||||
if (pathParts.length >= 2) {
|
let tabIndex = tabs.findIndex((tab) => tab.path === currentPath);
|
||||||
const path = pathParts[0];
|
if (-1 === tabIndex) {
|
||||||
const session = pathParts[1];
|
console.log(`Invalid path "${currentPath}" -- redirecting to default`);
|
||||||
|
tabIndex = 0;
|
||||||
if (validPaths.includes(path)) {
|
|
||||||
setSelectedPath(path);
|
|
||||||
setSessionId(session);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
setSessionId(session);
|
||||||
|
setActiveTab(tabIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('popstate', handlePopState);
|
window.addEventListener('popstate', handlePopState);
|
||||||
return () => window.removeEventListener('popstate', handlePopState);
|
return () => window.removeEventListener('popstate', handlePopState);
|
||||||
}, [setSelectedPath, setSessionId, validPaths]);
|
}, [setSessionId, tabs]);
|
||||||
|
|
||||||
const menuDrawer = (
|
|
||||||
<Card className="MenuCard">
|
|
||||||
<Tabs sx={{ display: "flex", flexGrow: 1 }}
|
|
||||||
orientation="vertical"
|
|
||||||
value={activeTab}
|
|
||||||
indicatorColor="secondary"
|
|
||||||
textColor="inherit"
|
|
||||||
variant="scrollable"
|
|
||||||
allowScrollButtonsMobile
|
|
||||||
onChange={handleTabChange}
|
|
||||||
aria-label="Backstory navigation">
|
|
||||||
<Tab sx={{ fontSize: '1rem' }} label="Backstory"
|
|
||||||
value={0}
|
|
||||||
icon={
|
|
||||||
<Avatar sx={{
|
|
||||||
width: 24,
|
|
||||||
height: 24
|
|
||||||
}}
|
|
||||||
variant="rounded"
|
|
||||||
alt="Backstory logo"
|
|
||||||
src="/logo192.png" />
|
|
||||||
}
|
|
||||||
iconPosition="start" />
|
|
||||||
<Tab
|
|
||||||
value={1}
|
|
||||||
sx={{ fontSize: '1rem' }} wrapped
|
|
||||||
label="Resume Builder"
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
value={2}
|
|
||||||
sx={{ fontSize: '1rem' }} wrapped
|
|
||||||
label="Context Visualizer"
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
value={3}
|
|
||||||
sx={{ fontSize: '1rem' }} label="About" />
|
|
||||||
<Tab
|
|
||||||
value={4}
|
|
||||||
sx={{ fontSize: '1rem' }} icon={<SettingsIcon />} />
|
|
||||||
</Tabs>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
|
|
||||||
const tabs = useMemo(() => {
|
|
||||||
const chatPreamble: MessageList = [
|
|
||||||
{
|
|
||||||
role: 'content',
|
|
||||||
title: 'Welcome to Backstory',
|
|
||||||
content: `
|
|
||||||
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 = [
|
|
||||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
|
|
||||||
<ChatQuery text="What is James Ketrenos' work history?" submitQuery={handleSubmitChatQuery} />
|
|
||||||
<ChatQuery text="What programming languages has James used?" submitQuery={handleSubmitChatQuery} />
|
|
||||||
<ChatQuery text="What are James' professional strengths?" submitQuery={handleSubmitChatQuery} />
|
|
||||||
<ChatQuery text="What are today's headlines on CNBC.com?" submitQuery={handleSubmitChatQuery} />
|
|
||||||
</Box>,
|
|
||||||
<Box sx={{ p: 1 }}>
|
|
||||||
<MuiMarkdown>
|
|
||||||
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**.
|
|
||||||
</MuiMarkdown>
|
|
||||||
</Box>
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
|
||||||
<Scrollable
|
|
||||||
sx={{
|
|
||||||
maxWidth: "1024px",
|
|
||||||
height: "calc(100vh - 72px)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Conversation
|
|
||||||
ref={chatRef}
|
|
||||||
{...{
|
|
||||||
type: "chat",
|
|
||||||
prompt: "What would you like to know about James?",
|
|
||||||
sessionId,
|
|
||||||
connectionBase,
|
|
||||||
setSnack,
|
|
||||||
preamble: chatPreamble,
|
|
||||||
defaultPrompts: chatQuestions
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Scrollable>,
|
|
||||||
<ResumeBuilder sx={{
|
|
||||||
margin: "0 auto",
|
|
||||||
height: "calc(100vh - 72px)",
|
|
||||||
overflow: "auto",
|
|
||||||
backgroundColor: "#F5F5F5",
|
|
||||||
display: "flex",
|
|
||||||
flexGrow: 1
|
|
||||||
}} {...{ setSnack, connectionBase, sessionId }} />,
|
|
||||||
<Scrollable
|
|
||||||
sx={{
|
|
||||||
maxWidth: "1024px",
|
|
||||||
height: "calc(100vh - 72px)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VectorVisualizer {...{ connectionBase, sessionId, setSnack }} />
|
|
||||||
</Scrollable>,
|
|
||||||
<Box className="ChatBox">
|
|
||||||
<Box className="Conversation">
|
|
||||||
<Message {...{ message: { role: 'content', content: about }, submitQuery: handleSubmitChatQuery, connectionBase, sessionId, setSnack }} />
|
|
||||||
</Box>
|
|
||||||
</Box>,
|
|
||||||
<Box className="ChatBox">
|
|
||||||
{sessionId !== undefined &&
|
|
||||||
<Controls {...{ sessionId, setSnack, connectionBase }} />
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
];
|
|
||||||
}, [about, connectionBase, sessionId, setSnack, isMobile]);
|
|
||||||
|
|
||||||
|
|
||||||
/* toolbar height is 64px + 8px margin-top */
|
/* toolbar height is 64px + 8px margin-top */
|
||||||
const Offset = styled('div')(() => ({ minHeight: '72px', height: '72px' }));
|
const Offset = styled('div')(() => ({ minHeight: '72px', height: '72px' }));
|
||||||
@ -406,34 +411,7 @@ const App = () => {
|
|||||||
allowScrollButtonsMobile
|
allowScrollButtonsMobile
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
aria-label="Backstory navigation">
|
aria-label="Backstory navigation">
|
||||||
<Tab sx={{ fontSize: '1rem' }} label="Backstory"
|
{tabs.map((tab, index) => <Tab key={index} value={index} label={tab.label} {...tab.tabProps} />)}
|
||||||
value={0}
|
|
||||||
icon={
|
|
||||||
<Avatar sx={{
|
|
||||||
width: 24,
|
|
||||||
height: 24
|
|
||||||
}}
|
|
||||||
variant="rounded"
|
|
||||||
alt="Backstory logo"
|
|
||||||
src="/logo192.png" />
|
|
||||||
}
|
|
||||||
iconPosition="start" />
|
|
||||||
<Tab
|
|
||||||
value={1}
|
|
||||||
sx={{ fontSize: '1rem' }} wrapped
|
|
||||||
label="Resume Builder"
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
value={2}
|
|
||||||
sx={{ fontSize: '1rem' }} wrapped
|
|
||||||
label="Context Visualizer"
|
|
||||||
/>
|
|
||||||
<Tab
|
|
||||||
value={3}
|
|
||||||
sx={{ fontSize: '1rem' }} label="About" />
|
|
||||||
<Tab
|
|
||||||
value={4}
|
|
||||||
sx={{ flexShrink: 1, flexGrow: 0, fontSize: '1rem' }} icon={<SettingsIcon />} />
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
@ -467,12 +445,24 @@ const App = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
{menuDrawer}
|
<Card className="MenuCard">
|
||||||
|
<Tabs sx={{ display: "flex", flexGrow: 1 }}
|
||||||
|
orientation="vertical"
|
||||||
|
value={activeTab}
|
||||||
|
indicatorColor="secondary"
|
||||||
|
textColor="inherit"
|
||||||
|
variant="scrollable"
|
||||||
|
allowScrollButtonsMobile
|
||||||
|
onChange={handleTabChange}
|
||||||
|
aria-label="Backstory navigation">
|
||||||
|
{tabs.map((tab, index) => <Tab key={index} value={index} label={tab.label} {...tab.tabProps} />)}
|
||||||
|
</Tabs>
|
||||||
|
</Card>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</Box>
|
</Box>
|
||||||
{
|
{
|
||||||
tabs.map((tab: any, i: number) =>
|
tabs.map((tab: any, i: number) =>
|
||||||
<CustomTabPanel key={i} tab={activeTab} index={i}>{tab}</CustomTabPanel>
|
<BackstoryTab key={i} active={i === activeTab}>{tab.children}</BackstoryTab>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef, useState, RefObject } from 'react';
|
import { useEffect, useRef, RefObject } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { SxProps, Theme } from '@mui/material';
|
import { SxProps, Theme } from '@mui/material';
|
||||||
|
|
||||||
@ -53,15 +53,15 @@ const useAutoScrollToBottom = (
|
|||||||
behavior: smooth ? 'smooth' : 'auto'
|
behavior: smooth ? 'smooth' : 'auto'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// console.log('Not scrolling', {
|
console.log('Not scrolling', {
|
||||||
// isNearBottom,
|
isNearBottom,
|
||||||
// isUserScrollingUp,
|
isUserScrollingUp,
|
||||||
// scrollHeight: container.scrollHeight,
|
scrollHeight: container.scrollHeight,
|
||||||
// scrollTop: container.scrollTop,
|
scrollTop: container.scrollTop,
|
||||||
// clientHeight: container.clientHeight,
|
clientHeight: container.clientHeight,
|
||||||
// threshold,
|
threshold,
|
||||||
// delta: container.scrollHeight - container.scrollTop - container.clientHeight
|
delta: container.scrollHeight - container.scrollTop - container.clientHeight
|
||||||
// });
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,8 @@ interface ChatBubbleProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatBubble({ role, isFullWidth, children, sx, className, title }: ChatBubbleProps) {
|
function ChatBubble(props: ChatBubbleProps) {
|
||||||
|
const { role, isFullWidth, children, sx, className, title } = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const defaultRadius = '16px';
|
const defaultRadius = '16px';
|
||||||
@ -122,7 +123,7 @@ function ChatBubble({ role, isFullWidth, children, sx, className, title }: ChatB
|
|||||||
<Accordion
|
<Accordion
|
||||||
defaultExpanded
|
defaultExpanded
|
||||||
className={className}
|
className={className}
|
||||||
sx={{ ...styles[role] }}
|
sx={{ ...styles[role], ...sx }}
|
||||||
>
|
>
|
||||||
<AccordionSummary
|
<AccordionSummary
|
||||||
expandIcon={<ExpandMoreIcon />}
|
expandIcon={<ExpandMoreIcon />}
|
||||||
@ -134,9 +135,7 @@ function ChatBubble({ role, isFullWidth, children, sx, className, title }: ChatB
|
|||||||
{children}
|
{children}
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -444,7 +444,8 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
|||||||
<Box className={className || "Conversation"}
|
<Box className={className || "Conversation"}
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex", flexDirection: "column", flexGrow: 1, p: 1, mt: 0,
|
p: 1,
|
||||||
|
mt: 0,
|
||||||
...sx
|
...sx
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,7 @@ import Collapse from '@mui/material/Collapse';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
import { ExpandMore } from './ExpandMore';
|
import { ExpandMore } from './ExpandMore';
|
||||||
|
import { SxProps, Theme } from '@mui/material';
|
||||||
|
|
||||||
import { ChatBubble } from './ChatBubble';
|
import { ChatBubble } from './ChatBubble';
|
||||||
import { StyledMarkdown } from './StyledMarkdown';
|
import { StyledMarkdown } from './StyledMarkdown';
|
||||||
@ -61,12 +62,14 @@ interface MessageMetaProps {
|
|||||||
type MessageList = MessageData[];
|
type MessageList = MessageData[];
|
||||||
|
|
||||||
interface MessageProps {
|
interface MessageProps {
|
||||||
|
sx?: SxProps<Theme>,
|
||||||
message?: MessageData,
|
message?: MessageData,
|
||||||
isFullWidth?: boolean,
|
isFullWidth?: boolean,
|
||||||
submitQuery?: (text: string) => void,
|
submitQuery?: (text: string) => void,
|
||||||
sessionId?: string,
|
sessionId?: string,
|
||||||
connectionBase: string,
|
connectionBase: string,
|
||||||
setSnack: SetSnackType,
|
setSnack: SetSnackType,
|
||||||
|
className?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ChatQueryInterface {
|
interface ChatQueryInterface {
|
||||||
@ -216,7 +219,8 @@ const ChatQuery = ({ text, submitQuery }: ChatQueryInterface) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Message = ({ message, submitQuery, isFullWidth, sessionId, setSnack, connectionBase }: MessageProps) => {
|
const Message = (props: MessageProps) => {
|
||||||
|
const { message, submitQuery, isFullWidth, sessionId, setSnack, connectionBase, sx, className } = props;
|
||||||
const [expanded, setExpanded] = useState<boolean>(false);
|
const [expanded, setExpanded] = useState<boolean>(false);
|
||||||
const textFieldRef = useRef(null);
|
const textFieldRef = useRef(null);
|
||||||
|
|
||||||
@ -237,7 +241,7 @@ const Message = ({ message, submitQuery, isFullWidth, sessionId, setSnack, conne
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatBubble
|
<ChatBubble
|
||||||
className="Message"
|
className={className || "Message"}
|
||||||
isFullWidth={isFullWidth}
|
isFullWidth={isFullWidth}
|
||||||
role={message.role}
|
role={message.role}
|
||||||
title={message.title}
|
title={message.title}
|
||||||
@ -246,9 +250,10 @@ const Message = ({ message, submitQuery, isFullWidth, sessionId, setSnack, conne
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
pb: message.metadata ? 0 : "8px",
|
pb: message.metadata ? 0 : "8px",
|
||||||
m: 0,
|
m: 0,
|
||||||
mb: 1,
|
|
||||||
mt: 1,
|
mt: 1,
|
||||||
|
marginBottom: "0px !important", // Remove whitespace from expanded Accordion
|
||||||
// overflowX: "auto"
|
// overflowX: "auto"
|
||||||
|
...sx,
|
||||||
}}>
|
}}>
|
||||||
<CardContent ref={textFieldRef} sx={{ position: "relative", display: "flex", flexDirection: "column", overflowX: "auto", m: 0, p: 0 }}>
|
<CardContent ref={textFieldRef} sx={{ position: "relative", display: "flex", flexDirection: "column", overflowX: "auto", m: 0, p: 0 }}>
|
||||||
<CopyBubble content={message?.content} />
|
<CopyBubble content={message?.content} />
|
||||||
|
@ -9,6 +9,7 @@ import Button from '@mui/material/Button';
|
|||||||
import SendIcon from '@mui/icons-material/Send';
|
import SendIcon from '@mui/icons-material/Send';
|
||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
import Switch from '@mui/material/Switch';
|
import Switch from '@mui/material/Switch';
|
||||||
|
import { SxProps, Theme } from '@mui/material';
|
||||||
|
|
||||||
import { SetSnackType } from './Snack';
|
import { SetSnackType } from './Snack';
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ interface VectorVisualizerProps {
|
|||||||
setSnack: SetSnackType;
|
setSnack: SetSnackType;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
rag?: any;
|
rag?: any;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChromaResult {
|
interface ChromaResult {
|
||||||
@ -98,7 +100,8 @@ const symbolMap: Record<string, string> = {
|
|||||||
'query': 'circle',
|
'query': 'circle',
|
||||||
};
|
};
|
||||||
|
|
||||||
const VectorVisualizer: React.FC<VectorVisualizerProps> = ({ setSnack, rag, inline, connectionBase, sessionId }) => {
|
const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualizerProps) => {
|
||||||
|
const { setSnack, rag, inline, connectionBase, sessionId, sx } = props;
|
||||||
const [plotData, setPlotData] = useState<PlotData | null>(null);
|
const [plotData, setPlotData] = useState<PlotData | null>(null);
|
||||||
const [newQuery, setNewQuery] = useState<string>('');
|
const [newQuery, setNewQuery] = useState<string>('');
|
||||||
const [newQueryEmbedding, setNewQueryEmbedding] = useState<ChromaResult | undefined>(undefined);
|
const [newQueryEmbedding, setNewQueryEmbedding] = useState<ChromaResult | undefined>(undefined);
|
||||||
@ -305,7 +308,8 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = ({ setSnack, rag, inli
|
|||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
flexGrow: 1
|
flexGrow: 1,
|
||||||
|
...sx
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
!inline &&
|
!inline &&
|
||||||
|
Loading…
x
Reference in New Issue
Block a user