host of Quality of life fixes

This commit is contained in:
2025-12-08 13:51:29 -06:00
parent 95f351c5aa
commit caf2c6481f
4 changed files with 137 additions and 18 deletions

View File

@@ -20,6 +20,7 @@ import Account2 from './llm-fe/pages/Account2/Account2';
import AnalyticsPage from './llm-fe/pages/Analytics/Analytics';
import PasswordResetConfirmation from './llm-fe/pages/PasswordResetConfirmation/PasswordReset';
import GlobalThemeWrapper from './llm-fe/components/GlobalThemeWrapper/GlobalThemeWrapper';
import Tracker from './llm-fe/components/Tracker/Tracker';
const ProtectedRoutes = () => {
const { authenticated, needsNewPassword, loading } = useContext(AuthContext);
@@ -34,11 +35,14 @@ const ProtectedRoutes = () => {
return <Outlet key={Date.now()} />
}
class App extends Component {
render() {
return (
<GlobalThemeWrapper>
<Tracker />
<div className='site'>
<main>
<div className="main-container">

View File

@@ -0,0 +1,23 @@
import React, { useEffect } from 'react';
const Tracker: React.FC = () => {
useEffect(() => {
if (process.env.NODE_ENV === 'production') {
const script = document.createElement('script');
script.src = "https://tianji.aimloperations.com/tracker.js";
script.async = true;
script.defer = true;
script.setAttribute('data-website-id', 'cm7x7m52m03kbddswbswrt17y');
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}
}, []);
return null;
};
export default Tracker;

View File

@@ -18,6 +18,8 @@ function WebSocketProvider({ children }) {
const channels = useRef({}); // maps each channel to the callback
const { account, setAccount } = useContext(AccountContext);
const [currentChannel, setCurrentChannel] = useState("");
const [isConnected, setIsConnected] = useState(false);
/* called from a component that registers a callback for a channel */
const subscribe = (channel, callback) => {
//console.log(`Subbing to ${channel}`)
@@ -76,8 +78,12 @@ function WebSocketProvider({ children }) {
ws.current.onopen = () => {
setSocket(ws.current);
setIsConnected(true);
};
ws.current.onclose = () => {
console.log('websocket closed');
setIsConnected(false);
};
ws.current.onclose = () => { };
ws.current.onmessage = (message) => {
const data = message.data;
// lookup for an existing chat in which this message belongs
@@ -103,7 +109,7 @@ function WebSocketProvider({ children }) {
/* subscribe and unsubscribe are the only required prop for the context */
return (
<WebSocketContext.Provider
value={[subscribe, unsubscribe, socket, sendMessage]}
value={[subscribe, unsubscribe, socket, sendMessage, isConnected]}
>
{children}
</WebSocketContext.Provider>

View File

@@ -3,6 +3,7 @@ import styled, { ThemeContext } from "styled-components";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as Yup from "yup";
import { AttachFile, Delete, Send } from "@mui/icons-material"; // Keeping icons for now, can replace later if needed
import { Tooltip } from "@mui/material";
import Markdown from "markdown-to-jsx";
import {
@@ -110,7 +111,7 @@ const StyledInputContainer = styled.div`
}
`;
const StyledInput = styled.input`
const StyledInput = styled.textarea`
flex: 1;
background: transparent;
border: none;
@@ -118,6 +119,11 @@ const StyledInput = styled.input`
font-size: 1rem;
padding: 0.8rem;
outline: none;
resize: none;
overflow-y: auto;
max-height: 150px; /* Approx 5 lines */
font-family: inherit;
line-height: 1.5;
&::placeholder {
color: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.4)'};
@@ -147,6 +153,23 @@ const IconButton = styled.button`
}
`;
const StyledSelect = styled.select`
background: transparent;
border: none;
color: ${({ theme }) => theme.colors.text};
font-size: 0.9rem;
padding: 0.5rem;
outline: none;
cursor: pointer;
margin-right: 0.5rem;
border-right: 1px solid ${({ theme }) => theme.colors.cardBorder};
option {
background: ${({ theme }) => theme.colors.background || '#1a1a1a'};
color: ${({ theme }) => theme.colors.text};
}
`;
const ConversationItem = styled.div<{ $active: boolean }>`
padding: 0.8rem 1rem;
margin-bottom: 0.5rem;
@@ -226,7 +249,7 @@ const AlwaysScrollToBottom = (): JSX.Element => {
const AsyncDashboardInner = ({ }): JSX.Element => {
const [announcements, setAnnouncement] = useState<Announcement[]>([]);
const [subscribe, unsubscribe, socket, sendMessage] =
const [subscribe, unsubscribe, socket, sendMessage, isConnected] =
useContext(WebSocketContext);
const { account } = useContext(AccountContext);
@@ -242,6 +265,7 @@ const AsyncDashboardInner = ({ }): JSX.Element => {
const conversationRef = useRef(conversationDetails);
const theme = useContext(ThemeContext);
const textareaRef = useRef<HTMLTextAreaElement>(null);
async function GetAnnouncements() {
const response: AxiosResponse<AnnouncementType[]> =
@@ -271,12 +295,32 @@ const AsyncDashboardInner = ({ }): JSX.Element => {
conversationRef.current = tempConversations;
setConversationDetails(tempConversations);
sendMessage(prompt, selectedConversation, file, fileType, modelName);
resetForm();
resetForm({
values: {
prompt: "",
file: null,
fileType: null,
modelName: modelName, // Keep the selected model
}
});
// Reset textarea height
if (textareaRef.current) {
textareaRef.current.style.height = 'auto';
}
} catch (e) {
console.log(`error ${e}`);
}
};
const adjustHeight = () => {
const textarea = textareaRef.current;
if (textarea) {
textarea.style.height = 'auto';
textarea.style.height = `${textarea.scrollHeight}px`;
}
};
return (
<PageContainer>
<ParticleBackground />
@@ -350,13 +394,24 @@ const AsyncDashboardInner = ({ }): JSX.Element => {
prompt: "",
file: null,
fileType: null,
modelName: "",
modelName: "FAST",
}}
validationSchema={validationSchema}
onSubmit={handlePromptSubmit}
>
{(formik) => (
<Form>
{!isConnected && (
<div style={{
color: '#ff4444',
textAlign: 'center',
marginBottom: '0.5rem',
fontSize: '0.9rem',
fontWeight: 500
}}>
Connection to the LLM is not active
</div>
)}
<StyledInputContainer>
<label htmlFor="file-upload" style={{ cursor: 'pointer', display: 'flex' }}>
<IconButton as="span">
@@ -380,19 +435,50 @@ const AsyncDashboardInner = ({ }): JSX.Element => {
/>
<Field
name="prompt"
as={StyledInput}
placeholder="Type a message..."
autoComplete="off"
/>
as={StyledSelect}
name="modelName"
>
<option value="FAST">FAST</option>
<option value="THINKING">THINKING</option>
</Field>
<Field name="prompt">
{({ field }: any) => (
<StyledInput
{...field}
ref={textareaRef}
placeholder="Type a message..."
rows={1}
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (formik.isValid && formik.dirty) {
formik.submitForm();
}
}
}}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
field.onChange(e);
adjustHeight();
}}
/>
)}
</Field>
<Tooltip title={!isConnected ? "Connection to the LLM is not active" : "Send message"}>
<span>
<IconButton
type="submit"
disabled={!formik.isValid || !formik.dirty}
style={{ color: formik.isValid && formik.dirty ? '#fff' : undefined, background: formik.isValid && formik.dirty ? theme?.main : undefined }}
disabled={!formik.isValid || !formik.dirty || !isConnected}
style={{
color: (formik.isValid && formik.dirty && isConnected) ? '#fff' : undefined,
background: (formik.isValid && formik.dirty && isConnected) ? theme?.main : undefined
}}
>
<Send />
</IconButton>
</span>
</Tooltip>
</StyledInputContainer>
{formik.values.file && (
<div style={{