This commit is contained in:
James Ketr 2025-05-10 16:50:01 -07:00
parent b6dd4878c8
commit 538caba9f4
12 changed files with 261 additions and 111 deletions

View File

@ -0,0 +1,21 @@
Backstory is developed using:
## Frontend
* React
* MUI
* Plotly.js
* MuiMarkdown
* Mermaid
## Backend
* Python
* FastAPI
* HuggingFace Transformers
* Ollama
* Backstory Agent Framework
* Prometheus
* Grafana
* ze-monitor
* Jupyter Notebook

View File

@ -0,0 +1,100 @@
The system follows a carefully designed pipeline with isolated stages to prevent fabrication:
## System Architecture Overview
The system uses a pipeline of isolated analysis and generation steps:
1. **Stage 1: Isolated Analysis** (three sub-stages)
- **1A: Job Analysis** - Extracts requirements from job description only
- **1B: Candidate Analysis** - Catalogs qualifications from resume/context only
- **1C: Mapping Analysis** - Identifies legitimate matches between requirements and qualifications
2. **Stage 2: Resume Generation**
- Uses mapping output to create a tailored resume with evidence-based content
3. **Stage 3: Verification**
- Performs fact-checking to catch any remaining fabrications
```mermaid
flowchart TD
subgraph "Stage 1: Isolated Analysis"
subgraph "Stage 1A: Job Analysis"
A1[Job Description Input] --> A2[Job Analysis LLM]
A2 --> A3[Job Requirements JSON]
end
subgraph "Stage 1B: Candidate Analysis"
B1[Resume & Context Input] --> B2[Candidate Analysis LLM]
B2 --> B3[Candidate Qualifications JSON]
end
subgraph "Stage 1C: Mapping Analysis"
C1[Job Requirements JSON] --> C2[Candidate Qualifications JSON]
C2 --> C3[Mapping Analysis LLM]
C3 --> C4[Skills Mapping JSON]
end
end
subgraph "Stage 2: Resume Generation"
D1[Skills Mapping JSON] --> D2[Original Resume Reference]
D2 --> D3[Resume Generation LLM]
D3 --> D4[Tailored Resume Draft]
end
subgraph "Stage 3: Verification"
E1[Skills Mapping JSON] --> E2[Original Materials]
E2 --> E3[Tailored Resume Draft]
E3 --> E4[Verification LLM]
E4 --> E5{Verification Check}
E5 -->|PASS| E6[Approved Resume]
E5 -->|FAIL| E7[Correction Instructions]
E7 --> D3
end
A3 --> C1
B3 --> C2
C4 --> D1
D4 --> E3
style A2 fill:#f9d77e,stroke:#333,stroke-width:2px
style B2 fill:#f9d77e,stroke:#333,stroke-width:2px
style C3 fill:#f9d77e,stroke:#333,stroke-width:2px
style D3 fill:#f9d77e,stroke:#333,stroke-width:2px
style E4 fill:#f9d77e,stroke:#333,stroke-width:2px
style E5 fill:#a3e4d7,stroke:#333,stroke-width:2px
style E6 fill:#aed6f1,stroke:#333,stroke-width:2px
style E7 fill:#f5b7b1,stroke:#333,stroke-width:2px
```
## Stage 1: Isolated Analysis (three separate sub-stages)
1. **Job Analysis**: Extracts requirements from just the job description
2. **Candidate Analysis**: Catalogs qualifications from just the resume/context
3. **Mapping Analysis**: Identifies legitimate matches between requirements and qualifications
## Stage 2: Resume Generation
Creates a tailored resume using only verified information from the mapping
## Stage 3: Verification
1. Performs fact-checking to catch any remaining fabrications
2. Corrects issues if needed and re-verifies
### Key Anti-Fabrication Mechanisms
The system uses several techniques to prevent fabrication:
* **Isolation of Analysis Stages**: By analyzing the job and candidate separately, the system prevents the LLM from prematurely creating connections that might lead to fabrication.
* **Evidence Requirements**: Each qualification included must have explicit evidence from the original materials.
* **Conservative Transferability**: The system is instructed to be conservative when claiming skills are transferable.
* **Verification Layer**: A dedicated verification step acts as a safety check to catch any remaining fabrications.
* **Strict JSON Structures**: Using structured JSON formats ensures information flows properly between stages.
## Implementation Details
* **Prompt Engineering**: Each stage has carefully designed prompts with clear instructions and output formats.
* **Error Handling**: Comprehensive validation and error handling throughout the pipeline.
* **Correction Loop**: If verification fails, the system attempts to correct issues and re-verify.
* **Traceability**: Information in the final resume can be traced back to specific evidence in the original materials.

View File

@ -10,11 +10,8 @@ const AboutPage = (props: BackstoryPageProps) => {
const [ subRoute, setSubRoute] = useState<string>(""); const [ subRoute, setSubRoute] = useState<string>("");
useEffect(() => { useEffect(() => {
console.log(`AboutPage: ${page}`); console.log(`AboutPage: ${page} - route - ${route} - subRoute: ${subRoute}`);
}, [page]); }, [page, route, subRoute]);
useEffect(() => {
console.log(`AboutPage: ${page} - subRoute: ${subRoute}`);
}, [subRoute]);
useEffect(() => { useEffect(() => {
if (route === undefined) { return; } if (route === undefined) { return; }
@ -31,13 +28,10 @@ const AboutPage = (props: BackstoryPageProps) => {
console.log("Document expanded:", document, open); console.log("Document expanded:", document, open);
if (open) { if (open) {
setPage(document); setPage(document);
if (setRoute) setRoute(document);
} else { } else {
setPage(""); setPage("");
} if (setRoute) setRoute("");
/* This is just to quiet warnings for now...*/
if (route === "never" && subRoute && setRoute) {
setRoute(document);
setSubRoute(document);
} }
} }

View File

@ -79,14 +79,12 @@ const App = () => {
}; };
const tabs: BackstoryTabProps[] = useMemo(() => { const tabs: BackstoryTabProps[] = useMemo(() => {
const tabSx = { flexGrow: 1, fontSize: '1rem' };
const homeTab: BackstoryTabProps = { const homeTab: BackstoryTabProps = {
label: "", label: "",
path: "", path: "",
tabProps: { tabProps: {
label: "Backstory", label: "Backstory",
sx: tabSx, sx: { flexGrow: 1, fontSize: '1rem' },
icon: icon:
<Avatar sx={{ <Avatar sx={{
width: 24, width: 24,
@ -151,6 +149,20 @@ const App = () => {
]; ];
}, [sessionId, setSnack, subRoute]); }, [sessionId, setSnack, subRoute]);
useEffect(() => {
if (sessionId === undefined || activeTab > tabs.length - 1) { return; }
console.log(`route - '${tabs[activeTab].path}', subRoute - '${subRoute}'`);
let path = tabs[activeTab].path ? `/${tabs[activeTab].path}` : '';
if (subRoute) {
path += `/${subRoute}`;
}
path += `/${sessionId}`;
console.log('pushState: ', path);
// window.history.pushState({}, '', path);
}, [activeTab, sessionId, subRoute, tabs]);
const fetchSession = useCallback((async (pathParts?: string[]) => { const fetchSession = useCallback((async (pathParts?: string[]) => {
try { try {
const response = await fetch(connectionBase + `/api/context`, { const response = await fetch(connectionBase + `/api/context`, {

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React from 'react';
import { Box } from '@mui/material'; import { Box } from '@mui/material';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import { SxProps, Theme } from '@mui/material'; import { SxProps, Theme } from '@mui/material';
@ -9,7 +9,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import LocationSearchingIcon from '@mui/icons-material/LocationSearching'; import LocationSearchingIcon from '@mui/icons-material/LocationSearching';
import { MessageRoles } from './Message'; import { MessageRoles } from './Message';
import { ErrorOutline, InfoOutline, Memory, Message, Psychology, /* Stream, */ } from '@mui/icons-material'; import { ErrorOutline, InfoOutline, Memory, Psychology, /* Stream, */ } from '@mui/icons-material';
interface ChatBubbleProps { interface ChatBubbleProps {
role: MessageRoles, role: MessageRoles,
@ -24,9 +24,7 @@ interface ChatBubbleProps {
} }
function ChatBubble(props: ChatBubbleProps) { function ChatBubble(props: ChatBubbleProps) {
const { role, children, sx, className, title, onExpand, expandable, expanded }: ChatBubbleProps = props; const { role, children, sx, className, title, onExpand, expandable, expanded } = props;
console.log("ChatBubbble():", props.expanded);
const theme = useTheme(); const theme = useTheme();
@ -34,12 +32,12 @@ function ChatBubble(props: ChatBubbleProps) {
const defaultStyle = { const defaultStyle = {
padding: theme.spacing(1, 2), padding: theme.spacing(1, 2),
fontSize: '0.875rem', fontSize: '0.875rem',
alignSelf: 'flex-start', // Left-aligned is used by default alignSelf: 'flex-start',
maxWidth: '100%', maxWidth: '100%',
minWidth: '100%', minWidth: '100%',
height: 'fit-content', height: 'fit-content',
'& > *': { '& > *': {
color: 'inherit', // Children inherit 'color' from parent color: 'inherit',
overflow: 'hidden', overflow: 'hidden',
m: 0, m: 0,
}, },
@ -47,130 +45,151 @@ function ChatBubble(props: ChatBubbleProps) {
mb: 0, mb: 0,
m: 0, m: 0,
p: 0, p: 0,
} },
} };
const styles: any = { const styles: any = {
'assistant': { assistant: {
...defaultStyle, ...defaultStyle,
backgroundColor: theme.palette.primary.main, // Midnight Blue (#1A2536) backgroundColor: theme.palette.primary.main,
border: `1px solid ${theme.palette.secondary.main}`, // Dusty Teal (#4A7A7D) border: `1px solid ${theme.palette.secondary.main}`,
borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`, // Rounded, flat bottom-left for assistant borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`,
color: theme.palette.primary.contrastText, // Warm Gray (#D3CDBF) for text color: theme.palette.primary.contrastText,
}, },
'content': { content: {
...defaultStyle, ...defaultStyle,
backgroundColor: '#F5F2EA', // Light cream background for easy reading backgroundColor: '#F5F2EA',
border: `1px solid ${theme.palette.custom.highlight}`, // Golden Ochre border border: `1px solid ${theme.palette.custom.highlight}`,
borderRadius: 0, borderRadius: 0,
alignSelf: 'center', // Centered in the chat alignSelf: 'center',
color: theme.palette.text.primary, // Charcoal Black for maximum readability color: theme.palette.text.primary,
padding: '8px 8px', // More generous padding for better text framing padding: '8px 8px',
marginBottom: '0px', // Space between content and conversation marginBottom: '0px',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)', // Subtle elevation boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)',
fontSize: '0.9rem', // Slightly smaller than default fontSize: '0.9rem',
lineHeight: '1.3', // More compact line height lineHeight: '1.3',
fontFamily: theme.typography.fontFamily, // Consistent font with your theme fontFamily: theme.typography.fontFamily,
}, },
'error': { error: {
...defaultStyle, ...defaultStyle,
backgroundColor: '#F8E7E7', // Soft light red background backgroundColor: '#F8E7E7',
border: `1px solid #D83A3A`, // Prominent red border border: `1px solid #D83A3A`,
borderRadius: defaultRadius, borderRadius: defaultRadius,
maxWidth: '90%', maxWidth: '90%',
minWidth: '90%', minWidth: '90%',
alignSelf: 'center', alignSelf: 'center',
color: '#8B2525', // Deep red text for good contrast color: '#8B2525',
padding: '10px 16px', padding: '10px 16px',
boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)', // Subtle shadow with red tint boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)',
}, },
'fact-check': 'qualifications', 'fact-check': 'qualifications',
'job-description': 'content', 'job-description': 'content',
'job-requirements': 'qualifications', 'job-requirements': 'qualifications',
'info': { info: {
...defaultStyle, ...defaultStyle,
backgroundColor: '#BFD8D8', // Softened Dusty Teal backgroundColor: '#BFD8D8',
border: `1px solid ${theme.palette.secondary.main}`, // Dusty Teal border: `1px solid ${theme.palette.secondary.main}`,
borderRadius: defaultRadius, borderRadius: defaultRadius,
color: theme.palette.text.primary, // Charcoal Black (#2E2E2E) — much better contrast color: theme.palette.text.primary,
opacity: 0.95, opacity: 0.95,
}, },
'processing': "status", processing: 'status',
'qualifications': { qualifications: {
...defaultStyle, ...defaultStyle,
backgroundColor: theme.palette.primary.light, // Lighter shade, e.g., Soft Blue (#2A3B56) backgroundColor: theme.palette.primary.light,
border: `1px solid ${theme.palette.secondary.main}`, // Keep Dusty Teal (#4A7A7D) for contrast border: `1px solid ${theme.palette.secondary.main}`,
borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`, // Unchanged borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`,
color: theme.palette.primary.contrastText, // Warm Gray (#D3CDBF) for readable text color: theme.palette.primary.contrastText,
}, },
'resume': 'content', resume: 'content',
'searching': 'status', searching: 'status',
'status': { status: {
...defaultStyle, ...defaultStyle,
backgroundColor: 'rgba(74, 122, 125, 0.15)', // Translucent dusty teal backgroundColor: 'rgba(74, 122, 125, 0.15)',
border: `1px solid ${theme.palette.secondary.light}`, // Lighter dusty teal border: `1px solid ${theme.palette.secondary.light}`,
borderRadius: '4px', borderRadius: '4px',
maxWidth: '75%', maxWidth: '75%',
minWidth: '75%', minWidth: '75%',
alignSelf: 'center', alignSelf: 'center',
color: theme.palette.secondary.dark, // Darker dusty teal for text color: theme.palette.secondary.dark,
fontWeight: 500, // Slightly bolder than normal fontWeight: 500,
fontSize: '0.95rem', // Slightly smaller fontSize: '0.95rem',
padding: '8px 12px', padding: '8px 12px',
opacity: 0.9, opacity: 0.9,
transition: 'opacity 0.3s ease-in-out', // Smooth fade effect for appearing/disappearing transition: 'opacity 0.3s ease-in-out',
}, },
'streaming': "assistant", streaming: 'assistant',
'system': { system: {
...defaultStyle, ...defaultStyle,
backgroundColor: '#EDEAE0', // Soft warm gray that plays nice with #D3CDBF backgroundColor: '#EDEAE0',
border: `1px dashed ${theme.palette.custom.highlight}`, // Golden Ochre border: `1px dashed ${theme.palette.custom.highlight}`,
borderRadius: defaultRadius, borderRadius: defaultRadius,
maxWidth: '90%', maxWidth: '90%',
minWidth: '90%', minWidth: '90%',
alignSelf: 'center', alignSelf: 'center',
color: theme.palette.text.primary, // Charcoal Black color: theme.palette.text.primary,
fontStyle: 'italic', fontStyle: 'italic',
}, },
'thinking': "status", thinking: 'status',
'user': { user: {
...defaultStyle, ...defaultStyle,
backgroundColor: theme.palette.background.default, // Warm Gray (#D3CDBF) backgroundColor: theme.palette.background.default,
border: `1px solid ${theme.palette.custom.highlight}`, // Golden Ochre (#D4A017) border: `1px solid ${theme.palette.custom.highlight}`,
borderRadius: `${defaultRadius} ${defaultRadius} 0 ${defaultRadius}`, // Rounded, flat bottom-right for user borderRadius: `${defaultRadius} ${defaultRadius} 0 ${defaultRadius}`,
alignSelf: 'flex-end', // Right-aligned for user alignSelf: 'flex-end',
color: theme.palette.primary.main, // Midnight Blue (#1A2536) for text color: theme.palette.primary.main,
}, },
}; };
// Resolve string references in styles
for (const [key, value] of Object.entries(styles)) { for (const [key, value] of Object.entries(styles)) {
if (typeof (value) === "string") { if (typeof value === 'string') {
(styles as any)[key] = styles[value]; styles[key] = styles[value];
} }
} }
const icons: any = { const icons: any = {
"error": <ErrorOutline color='error' />, error: <ErrorOutline color="error" />,
"info": <InfoOutline color='info' />, info: <InfoOutline color="info" />,
"processing": <LocationSearchingIcon />, processing: <LocationSearchingIcon />,
// "streaming": <Stream />, searching: <Memory />,
"searching": <Memory />, thinking: <Psychology />,
"thinking": <Psychology />, tooling: <LocationSearchingIcon />,
"tooling": <LocationSearchingIcon />,
}; };
// Render Accordion for expandable content
if (expandable || (role === 'content' && title)) { if (expandable || (role === 'content' && title)) {
// Determine if Accordion is controlled
const isControlled = typeof expanded === 'boolean' && typeof onExpand === 'function';
return ( return (
<Accordion <Accordion
expanded={expanded} expanded={isControlled ? expanded : undefined} // Omit expanded prop for uncontrolled
defaultExpanded={expanded} // Default to collapsed for uncontrolled Accordion
className={className} className={className}
onChange={() => { console.log(`onChange(${expanded} inverse)`); onExpand && onExpand(!expanded); }} onChange={(_event, newExpanded) => {
if (isControlled && onExpand) {
onExpand(newExpanded); // Call onExpand with new state
}
}}
sx={{ ...styles[role], ...sx }} sx={{ ...styles[role], ...sx }}
> >
<AccordionSummary <AccordionSummary
expandIcon={<ExpandMoreIcon />} expandIcon={<ExpandMoreIcon />}
slotProps={{ content: { sx: { fontWeight: 'bold', fontSize: '1.1rem', m: 0, p: 0, display: 'flex', justifyItems: 'center' } } }} slotProps={{
content: {
sx: {
fontWeight: 'bold',
fontSize: '1.1rem',
m: 0,
p: 0,
display: 'flex',
justifyItems: 'center',
},
},
}}
> >
{title || ""} {title || ''}
</AccordionSummary> </AccordionSummary>
<AccordionDetails sx={{ mt: 0, mb: 0, p: 0, pl: 2, pr: 2 }}> <AccordionDetails sx={{ mt: 0, mb: 0, p: 0, pl: 2, pr: 2 }}>
{children} {children}
@ -179,10 +198,20 @@ function ChatBubble(props: ChatBubbleProps) {
); );
} }
// Render non-expandable content
return ( return (
<Box className={className} sx={{ ...(role in styles ? styles[role] : styles["status"]), gap: 1, display: "flex", ...sx, flexDirection: "row" }}> <Box
className={className}
sx={{
...(role in styles ? styles[role] : styles['status']),
gap: 1,
display: 'flex',
...sx,
flexDirection: 'row',
}}
>
{icons[role] !== undefined && icons[role]} {icons[role] !== undefined && icons[role]}
<Box sx={{ p: 0, m: 0, gap: 0, display: "flex", flexGrow: 1, flexDirection: "column" }}> <Box sx={{ p: 0, m: 0, gap: 0, display: 'flex', flexGrow: 1, flexDirection: 'column' }}>
{children} {children}
</Box> </Box>
</Box> </Box>

View File

@ -511,7 +511,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
> >
{ {
filteredConversation.map((message, index) => filteredConversation.map((message, index) =>
<Message key={index} {...{ sendQuery, message, connectionBase, sessionId, setSnack, submitQuery }} /> <Message key={index} expanded={message.expanded === undefined ? true : message.expanded} {...{ sendQuery, message, connectionBase, sessionId, setSnack, submitQuery }} />
) )
} }
{ {

View File

@ -15,8 +15,6 @@ interface DocumentProps extends BackstoryElementProps {
const Document = (props: DocumentProps) => { const Document = (props: DocumentProps) => {
const { setSnack, submitQuery, filepath, content, title, expanded, disableCopy, onExpand, sessionId } = props; const { setSnack, submitQuery, filepath, content, title, expanded, disableCopy, onExpand, sessionId } = props;
console.log(`${filepath} expanded: ${expanded}`);
const [document, setDocument] = useState<string>(""); const [document, setDocument] = useState<string>("");
// Get the markdown // Get the markdown

View File

@ -1,5 +1,4 @@
const getConnectionBase = (loc: any): string => { const getConnectionBase = (loc: any): string => {
console.log(`getConnectionBase(${loc})`)
if (!loc.host.match(/.*battle-linux.*/)) { if (!loc.host.match(/.*battle-linux.*/)) {
return loc.protocol + "//" + loc.host; return loc.protocol + "//" + loc.host;
} else { } else {

View File

@ -26,7 +26,7 @@ It was written by James Ketrenos in order to provide answers to
questions potential employers may have about his work history. questions potential employers may have about his work history.
What would you like to know about James? What would you like to know about James?
` `,
} }
]; ];

View File

@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef } from 'react';
import Divider from '@mui/material/Divider'; import Divider from '@mui/material/Divider';
import Accordion from '@mui/material/Accordion'; import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary'; import AccordionSummary from '@mui/material/AccordionSummary';
@ -237,8 +237,8 @@ const MessageMeta = (props: MessageMetaProps) => {
}; };
const Message = (props: MessageProps) => { const Message = (props: MessageProps) => {
const { message, submitQuery, sx, className, onExpand, expanded } = props; const { message, submitQuery, sx, className, onExpand, expanded, sessionId, setSnack } = props;
const [metaExpanded, setMetaExpanded] = useState<boolean>(props.expanded || false); const [metaExpanded, setMetaExpanded] = useState<boolean>(false);
const textFieldRef = useRef(null); const textFieldRef = useRef(null);
const handleMetaExpandClick = () => { const handleMetaExpandClick = () => {
@ -287,7 +287,7 @@ const Message = (props: MessageProps) => {
overflow: "auto", /* Handles scrolling for the div */ overflow: "auto", /* Handles scrolling for the div */
}} }}
> >
<StyledMarkdown {...{ content: formattedContent, submitQuery }} /> <StyledMarkdown {...{ content: formattedContent, submitQuery, sessionId, setSnack }} />
</Scrollable> </Scrollable>
: :
<Typography <Typography
@ -318,7 +318,7 @@ const Message = (props: MessageProps) => {
)} )}
</CardActions> </CardActions>
{message.metadata && <> {message.metadata && <>
<Collapse in={expanded} timeout="auto" unmountOnExit> <Collapse in={metaExpanded} timeout="auto" unmountOnExit>
<CardContent> <CardContent>
<MessageMeta messageProps={props} metadata={message.metadata} /> <MessageMeta messageProps={props} metadata={message.metadata} />
</CardContent> </CardContent>

View File

@ -2,7 +2,7 @@ import React from 'react';
import { MuiMarkdown } from 'mui-markdown'; import { MuiMarkdown } from 'mui-markdown';
import { SxProps, useTheme } from '@mui/material/styles'; import { SxProps, useTheme } from '@mui/material/styles';
import { Link } from '@mui/material'; import { Link } from '@mui/material';
import { ChatQuery, QueryOptions } from './ChatQuery'; import { ChatQuery } from './ChatQuery';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import JsonView from '@uiw/react-json-view'; import JsonView from '@uiw/react-json-view';
import { vscodeTheme } from '@uiw/react-json-view/vscode'; import { vscodeTheme } from '@uiw/react-json-view/vscode';
@ -11,12 +11,12 @@ import { Scrollable } from './Scrollable';
import { jsonrepair } from 'jsonrepair'; import { jsonrepair } from 'jsonrepair';
import './StyledMarkdown.css'; import './StyledMarkdown.css';
import { BackstoryElementProps } from './BackstoryTab';
interface StyledMarkdownProps { interface StyledMarkdownProps extends BackstoryElementProps {
className?: string, className?: string,
content: string, content: string,
sx?: SxProps, sx?: SxProps,
submitQuery?: (prompt: string, tunables?: QueryOptions) => void,
}; };
const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProps) => { const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProps) => {
@ -53,6 +53,7 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
displayDataTypes={false} displayDataTypes={false}
objectSortKeys={false} objectSortKeys={false}
collapsed={false} collapsed={false}
shortenTextAfterLength={100}
value={JSON.parse(fixed)}> value={JSON.parse(fixed)}>
<JsonView.String <JsonView.String
render={({ children, ...reset }) => { render={({ children, ...reset }) => {
@ -84,17 +85,13 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
} }
} }
}, },
chatQuery: undefined ChatQuery: {
};
if (submitQuery) {
overrides.ChatQuery = {
component: ChatQuery, component: ChatQuery,
props: { props: {
submitQuery, submitQuery,
}, },
};
} }
};
return <Box return <Box
className={`MuiMarkdown ${className || ""}`} className={`MuiMarkdown ${className || ""}`}

View File

@ -108,7 +108,7 @@ const symbolMap: Record<string, string> = {
}; };
const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualizerProps) => { const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualizerProps) => {
const { setSnack, rag, inline, sessionId, sx } = props; const { setSnack, rag, inline, sessionId, sx, submitQuery } = 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);
@ -436,7 +436,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
wordBreak: 'break-all', wordBreak: 'break-all',
}} }}
> >
<StyledMarkdown sx={{ p: 1, pt: 0 }} content={tooltip?.content || "Select a node in the visualization."} /> <StyledMarkdown sx={{ p: 1, pt: 0 }} content={tooltip?.content || "Select a node in the visualization."} {...{ sessionId, setSnack, submitQuery }} />
</Scrollable> </Scrollable>
} }
</Card> </Card>