From caf2c6481f9ae88aa398f2e5a283dc3bed17ecb6 Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Mon, 8 Dec 2025 13:51:29 -0600 Subject: [PATCH] host of Quality of life fixes --- llm-fe/src/App.tsx | 4 + .../src/llm-fe/components/Tracker/Tracker.tsx | 23 ++++ .../src/llm-fe/contexts/WebSocketContext.js | 10 +- .../pages/AsyncDashboard2/AsyncDashboard2.tsx | 118 +++++++++++++++--- 4 files changed, 137 insertions(+), 18 deletions(-) create mode 100644 llm-fe/src/llm-fe/components/Tracker/Tracker.tsx diff --git a/llm-fe/src/App.tsx b/llm-fe/src/App.tsx index a4ecc67..5466fcd 100644 --- a/llm-fe/src/App.tsx +++ b/llm-fe/src/App.tsx @@ -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 } + + class App extends Component { render() { return ( +
diff --git a/llm-fe/src/llm-fe/components/Tracker/Tracker.tsx b/llm-fe/src/llm-fe/components/Tracker/Tracker.tsx new file mode 100644 index 0000000..a5ec347 --- /dev/null +++ b/llm-fe/src/llm-fe/components/Tracker/Tracker.tsx @@ -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; diff --git a/llm-fe/src/llm-fe/contexts/WebSocketContext.js b/llm-fe/src/llm-fe/contexts/WebSocketContext.js index 4f5d579..b86292d 100644 --- a/llm-fe/src/llm-fe/contexts/WebSocketContext.js +++ b/llm-fe/src/llm-fe/contexts/WebSocketContext.js @@ -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 ( {children} diff --git a/llm-fe/src/llm-fe/pages/AsyncDashboard2/AsyncDashboard2.tsx b/llm-fe/src/llm-fe/pages/AsyncDashboard2/AsyncDashboard2.tsx index e094490..8c9aa1c 100644 --- a/llm-fe/src/llm-fe/pages/AsyncDashboard2/AsyncDashboard2.tsx +++ b/llm-fe/src/llm-fe/pages/AsyncDashboard2/AsyncDashboard2.tsx @@ -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([]); - 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(null); async function GetAnnouncements() { const response: AxiosResponse = @@ -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 ( @@ -350,13 +394,24 @@ const AsyncDashboardInner = ({ }): JSX.Element => { prompt: "", file: null, fileType: null, - modelName: "", + modelName: "FAST", }} validationSchema={validationSchema} onSubmit={handlePromptSubmit} > {(formik) => (
+ {!isConnected && ( +
+ Connection to the LLM is not active +
+ )} {formik.values.file && (