Fix plotly resize
@ -8,5 +8,16 @@ module.exports = {
|
|||||||
// key: '/path/to/key.pem',
|
// key: '/path/to/key.pem',
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
webpack: {
|
||||||
|
configure: (webpackConfig) => {
|
||||||
|
// Add .ts and .tsx to resolve.extensions
|
||||||
|
webpackConfig.resolve.extensions = [
|
||||||
|
...webpackConfig.resolve.extensions,
|
||||||
|
'.ts',
|
||||||
|
'.tsx',
|
||||||
|
];
|
||||||
|
return webpackConfig;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 17 KiB |
@ -1,8 +1,8 @@
|
|||||||
import React, { useRef, useCallback } from 'react';
|
import React, { useRef, useCallback } from 'react';
|
||||||
import { BrowserRouter as Router, Routes, Route, useLocation } from "react-router-dom";
|
import { BrowserRouter as Router, Routes, Route, useLocation } from "react-router-dom";
|
||||||
import { SessionWrapper } from "./SessionWrapper";
|
import { SessionWrapper } from "./App/SessionWrapper";
|
||||||
import { Main } from "./Main";
|
import { Main } from "./App/Main";
|
||||||
import { Snack, SeverityType } from './Snack';
|
import { Snack, SeverityType } from './Components/Snack';
|
||||||
|
|
||||||
export function PathRouter({ setSnack }: { setSnack: any }) {
|
export function PathRouter({ setSnack }: { setSnack: any }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -16,21 +16,20 @@ 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 { ConversationHandle } from './Conversation';
|
import { ConversationHandle } from '../Components/Conversation';
|
||||||
import { Query } from './ChatQuery';
|
import { Query } from '../Components/ChatQuery';
|
||||||
import { Scrollable } from './Scrollable';
|
import { Scrollable } from '../Components/Scrollable';
|
||||||
import { BackstoryPage, BackstoryTabProps } from './BackstoryTab';
|
import { BackstoryPage, BackstoryTabProps } from '../Components/BackstoryTab';
|
||||||
|
|
||||||
import { HomePage } from './HomePage';
|
import { HomePage } from '../Pages/HomePage';
|
||||||
import { LoadingPage } from './LoadingPage';
|
import { LoadingPage } from '../Pages/LoadingPage';
|
||||||
import { ResumeBuilderPage } from './ResumeBuilderPage';
|
import { ResumeBuilderPage } from '../Pages/ResumeBuilderPage';
|
||||||
import { VectorVisualizerPage } from './VectorVisualizer';
|
import { VectorVisualizerPage } from '../Pages/VectorVisualizerPage';
|
||||||
import { AboutPage } from './AboutPage';
|
import { AboutPage } from '../Pages/AboutPage';
|
||||||
import { ControlsPage } from './ControlsPage';
|
import { ControlsPage } from '../Pages/ControlsPage';
|
||||||
import { SetSnackType } from './Snack';
|
import { SetSnackType } from '../Components/Snack';
|
||||||
|
|
||||||
import './Main.css';
|
import './Main.css';
|
||||||
import './Conversation.css';
|
|
||||||
|
|
||||||
import '@fontsource/roboto/300.css';
|
import '@fontsource/roboto/300.css';
|
||||||
import '@fontsource/roboto/400.css';
|
import '@fontsource/roboto/400.css';
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { useNavigate, useLocation } from "react-router-dom";
|
import { useNavigate, useLocation } from "react-router-dom";
|
||||||
import { connectionBase } from './Global';
|
import { connectionBase } from '../Global';
|
||||||
import { SetSnackType } from './Snack';
|
import { SetSnackType } from '../Components/Snack';
|
||||||
|
|
||||||
const getSessionId = async () => {
|
const getSessionId = async () => {
|
||||||
const response = await fetch(connectionBase + `/api/context`, {
|
const response = await fetch(connectionBase + `/api/context`, {
|
@ -17,7 +17,7 @@ import { Query } from './ChatQuery';
|
|||||||
import './Conversation.css';
|
import './Conversation.css';
|
||||||
import { BackstoryTextField, BackstoryTextFieldRef } from './BackstoryTextField';
|
import { BackstoryTextField, BackstoryTextFieldRef } from './BackstoryTextField';
|
||||||
import { BackstoryElementProps } from './BackstoryTab';
|
import { BackstoryElementProps } from './BackstoryTab';
|
||||||
import { connectionBase } from './Global';
|
import { connectionBase } from '../Global';
|
||||||
|
|
||||||
const loadingMessage: BackstoryMessage = { "role": "status", "content": "Establishing connection with server..." };
|
const loadingMessage: BackstoryMessage = { "role": "status", "content": "Establishing connection with server..." };
|
||||||
|
|
@ -17,7 +17,7 @@ import TableContainer from '@mui/material/TableContainer';
|
|||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
|
|
||||||
import { Scrollable } from './Scrollable';
|
import { Scrollable } from './Scrollable';
|
||||||
import { connectionBase } from './Global';
|
import { connectionBase } from '../Global';
|
||||||
|
|
||||||
import './VectorVisualizer.css';
|
import './VectorVisualizer.css';
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from './BackstoryTab';
|
||||||
@ -66,14 +66,10 @@ interface PlotData {
|
|||||||
const config: Partial<Plotly.Config> = {
|
const config: Partial<Plotly.Config> = {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
autosizable: true,
|
autosizable: true,
|
||||||
// displayModeBar: false,
|
|
||||||
displaylogo: false,
|
displaylogo: false,
|
||||||
showSendToCloud: false,
|
showSendToCloud: false,
|
||||||
staticPlot: false,
|
staticPlot: false,
|
||||||
//fillFrame: true,
|
|
||||||
/** if we DO autosize, set the frame margins in percents of plot size */
|
|
||||||
frameMargins: 0,
|
frameMargins: 0,
|
||||||
/** mousewheel or two-finger scroll zooms the plot */
|
|
||||||
scrollZoom: true,
|
scrollZoom: true,
|
||||||
doubleClick: "reset+autosize",
|
doubleClick: "reset+autosize",
|
||||||
// | "lasso2d"
|
// | "lasso2d"
|
||||||
@ -119,7 +115,7 @@ const config: Partial<Plotly.Config> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const layout: Partial<Plotly.Layout> = {
|
const layout: Partial<Plotly.Layout> = {
|
||||||
autosize: true,
|
autosize: false,
|
||||||
paper_bgcolor: '#FFFFFF', // white
|
paper_bgcolor: '#FFFFFF', // white
|
||||||
plot_bgcolor: '#FFFFFF', // white plot background
|
plot_bgcolor: '#FFFFFF', // white plot background
|
||||||
font: {
|
font: {
|
||||||
@ -200,6 +196,22 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
const [node, setNode] = useState<Node | null>(null);
|
const [node, setNode] = useState<Node | null>(null);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
const [plotDimensions, setPlotDimensions] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
/* Force resize of Plotly as it tends to not be the correct size if it is initially rendered
|
||||||
|
* off screen (eg., the VectorVisualizer is not on the tab the app loads to) */
|
||||||
|
useEffect(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const plotContainer = document.querySelector('.plot-container') as HTMLElement;
|
||||||
|
const svgContainer = document?.querySelector('.svg-container') as HTMLElement;
|
||||||
|
if ( plotContainer && svgContainer) {
|
||||||
|
const plotContainerRect = plotContainer.getBoundingClientRect();
|
||||||
|
svgContainer.style.width = `${plotContainerRect.width}px`;
|
||||||
|
svgContainer.style.height = `${plotContainerRect.height}px`;
|
||||||
|
setPlotDimensions({ width: plotContainerRect.width, height: plotContainerRect.height});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Get the collection to visualize
|
// Get the collection to visualize
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -509,7 +521,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${view2D ? '
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
layout={layout}
|
layout={{...layout, width: plotDimensions.width, height: plotDimensions.height }}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
@ -611,21 +623,8 @@ The scatter graph shows the query in N-dimensional space, mapped to ${view2D ? '
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const VectorVisualizerPage: React.FC<VectorVisualizerProps> = (props: VectorVisualizerProps) => {
|
|
||||||
return <Scrollable
|
|
||||||
autoscroll={false}
|
|
||||||
sx={{
|
|
||||||
maxWidth: "1024px",
|
|
||||||
height: "calc(100vh - 72px)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VectorVisualizer {...props} />
|
|
||||||
</Scrollable>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type { VectorVisualizerProps };
|
export type { VectorVisualizerProps };
|
||||||
|
|
||||||
export {
|
export {
|
||||||
VectorVisualizer,
|
VectorVisualizer,
|
||||||
VectorVisualizerPage
|
|
||||||
};
|
};
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { Scrollable } from './Scrollable';
|
import { Scrollable } from '../Components/Scrollable';
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from '../Components/BackstoryTab';
|
||||||
import { Document } from './Document';
|
import { Document } from '../Components/Document';
|
||||||
|
|
||||||
const AboutPage = (props: BackstoryPageProps) => {
|
const AboutPage = (props: BackstoryPageProps) => {
|
||||||
const { sessionId, submitQuery, setSnack, route, setRoute } = props;
|
const { sessionId, submitQuery, setSnack, route, setRoute } = props;
|
@ -14,9 +14,8 @@ import Typography from '@mui/material/Typography';
|
|||||||
// import ResetIcon from '@mui/icons-material/History';
|
// import ResetIcon from '@mui/icons-material/History';
|
||||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||||
|
|
||||||
import { connectionBase } from './Global';
|
import { connectionBase } from '../Global';
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from '../Components/BackstoryTab';
|
||||||
import { restyle } from 'plotly.js';
|
|
||||||
|
|
||||||
interface ServerTunables {
|
interface ServerTunables {
|
||||||
system_prompt: string,
|
system_prompt: string,
|
@ -4,10 +4,10 @@ import Box from '@mui/material/Box';
|
|||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import MuiMarkdown from 'mui-markdown';
|
import MuiMarkdown from 'mui-markdown';
|
||||||
|
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from '../Components//BackstoryTab';
|
||||||
import { Conversation, ConversationHandle } from './Conversation';
|
import { Conversation, ConversationHandle } from '../Components/Conversation';
|
||||||
import { ChatQuery } from './ChatQuery';
|
import { ChatQuery } from '../Components/ChatQuery';
|
||||||
import { MessageList } from './Message';
|
import { MessageList } from '../Components/Message';
|
||||||
|
|
||||||
const HomePage = forwardRef<ConversationHandle, BackstoryPageProps>((props: BackstoryPageProps, ref) => {
|
const HomePage = forwardRef<ConversationHandle, BackstoryPageProps>((props: BackstoryPageProps, ref) => {
|
||||||
const { sessionId, setSnack, submitQuery } = props;
|
const { sessionId, setSnack, submitQuery } = props;
|
@ -1,6 +1,6 @@
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from '../Components/BackstoryTab';
|
||||||
import { BackstoryMessage, Message } from './Message';
|
import { BackstoryMessage, Message } from '../Components/Message';
|
||||||
|
|
||||||
const LoadingPage = (props: BackstoryPageProps) => {
|
const LoadingPage = (props: BackstoryPageProps) => {
|
||||||
const backstoryPreamble: BackstoryMessage = {
|
const backstoryPreamble: BackstoryMessage = {
|
@ -6,10 +6,10 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { SxProps } from '@mui/material';
|
import { SxProps } from '@mui/material';
|
||||||
|
|
||||||
import { ChatQuery, Query } from './ChatQuery';
|
import { ChatQuery, Query } from '../Components/ChatQuery';
|
||||||
import { MessageList, BackstoryMessage } from './Message';
|
import { MessageList, BackstoryMessage } from '../Components/Message';
|
||||||
import { Conversation } from './Conversation';
|
import { Conversation } from '../Components/Conversation';
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from '../Components/BackstoryTab';
|
||||||
|
|
||||||
import './ResumeBuilderPage.css';
|
import './ResumeBuilderPage.css';
|
||||||
|
|
28
frontend/src/Pages/VectorVisualizerPage.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
|
|
||||||
|
import { Scrollable } from '../Components/Scrollable';
|
||||||
|
import { VectorVisualizer } from '../Components/VectorVisualizer';
|
||||||
|
import { BackstoryPageProps } from '../Components/BackstoryTab';
|
||||||
|
|
||||||
|
interface VectorVisualizerProps extends BackstoryPageProps {
|
||||||
|
inline?: boolean;
|
||||||
|
rag?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VectorVisualizerPage: React.FC<VectorVisualizerProps> = (props: VectorVisualizerProps) => {
|
||||||
|
return <Scrollable
|
||||||
|
autoscroll={false}
|
||||||
|
sx={{
|
||||||
|
maxWidth: "1024px",
|
||||||
|
height: "calc(100vh - 72px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VectorVisualizer {...props} />
|
||||||
|
</Scrollable>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { VectorVisualizerProps };
|
||||||
|
|
||||||
|
export {
|
||||||
|
VectorVisualizerPage
|
||||||
|
};
|
1
frontend/src/declarations.d.ts
vendored
@ -1,2 +1 @@
|
|||||||
declare module 'tsne-js';
|
|
||||||
declare module 'react-plotly.js';
|
declare module 'react-plotly.js';
|
@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { ThemeProvider } from '@mui/material/styles';
|
import { ThemeProvider } from '@mui/material/styles';
|
||||||
import { backstoryTheme } from './BackstoryTheme'; // Adjust path as needed
|
import { backstoryTheme } from './BackstoryTheme';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import reportWebVitals from './reportWebVitals';
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById('root') as HTMLElement
|
||||||
@ -16,8 +15,3 @@ root.render(
|
|||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
||||||
reportWebVitals();
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import { ReportHandler } from 'web-vitals';
|
|
||||||
|
|
||||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
|
||||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
||||||
getCLS(onPerfEntry);
|
|
||||||
getFID(onPerfEntry);
|
|
||||||
getFCP(onPerfEntry);
|
|
||||||
getLCP(onPerfEntry);
|
|
||||||
getTTFB(onPerfEntry);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default reportWebVitals;
|
|
@ -1,5 +0,0 @@
|
|||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import '@testing-library/jest-dom';
|
|
@ -18,9 +18,10 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx",
|
||||||
|
"baseUrl": "src",
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
60
src/utils/check_serializable.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
from pydantic import BaseModel, Field # type: ignore
|
||||||
|
import json
|
||||||
|
from typing import Any, List, Set
|
||||||
|
|
||||||
|
def check_serializable(obj: Any, path: str = "", errors: List[str] = [], visited: Set[int] = set()) -> List[str]:
|
||||||
|
"""
|
||||||
|
Recursively check all fields in an object for non-JSON-serializable types, avoiding infinite recursion.
|
||||||
|
Skips fields in Pydantic models marked with Field(..., exclude=True).
|
||||||
|
Args:
|
||||||
|
obj: The object to inspect (Pydantic model, dict, list, or other).
|
||||||
|
path: The current field path (e.g., 'field1.nested_field').
|
||||||
|
errors: List to collect error messages.
|
||||||
|
visited: Set of object IDs to track visited objects and prevent infinite recursion.
|
||||||
|
Returns:
|
||||||
|
List of error messages for non-serializable fields.
|
||||||
|
"""
|
||||||
|
# Check for circular reference by object ID
|
||||||
|
obj_id = id(obj)
|
||||||
|
if obj_id in visited:
|
||||||
|
errors.append(f"Field '{path}' contains a circular reference, skipping further inspection")
|
||||||
|
return errors
|
||||||
|
|
||||||
|
# Add current object to visited set
|
||||||
|
visited.add(obj_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Handle Pydantic models
|
||||||
|
if isinstance(obj, BaseModel):
|
||||||
|
for field_name, field_info in obj.model_fields.items():
|
||||||
|
# Skip fields marked with exclude=True
|
||||||
|
if field_info.exclude:
|
||||||
|
continue
|
||||||
|
value = getattr(obj, field_name)
|
||||||
|
new_path = f"{path}.{field_name}" if path else field_name
|
||||||
|
check_serializable(value, new_path, errors, visited)
|
||||||
|
|
||||||
|
# Handle dictionaries
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
for key, value in obj.items():
|
||||||
|
new_path = f"{path}[{key}]" if path else str(key)
|
||||||
|
check_serializable(value, new_path, errors, visited)
|
||||||
|
|
||||||
|
# Handle lists, tuples, or other iterables
|
||||||
|
elif isinstance(obj, (list, tuple)):
|
||||||
|
for i, value in enumerate(obj):
|
||||||
|
new_path = f"{path}[{i}]" if path else str(i)
|
||||||
|
check_serializable(value, new_path, errors, visited)
|
||||||
|
|
||||||
|
# Handle other types (check for JSON serializability)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
json.dumps(obj)
|
||||||
|
except (TypeError, OverflowError, ValueError) as e:
|
||||||
|
errors.append(f"Field '{path}' contains non-serializable type: {type(obj)} ({str(e)})")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Remove the current object from visited to allow processing in other branches
|
||||||
|
visited.discard(obj_id)
|
||||||
|
|
||||||
|
return errors
|