host of Quality of life fixes
This commit is contained in:
@@ -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">
|
||||
|
||||
23
llm-fe/src/llm-fe/components/Tracker/Tracker.tsx
Normal file
23
llm-fe/src/llm-fe/components/Tracker/Tracker.tsx
Normal 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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
type="submit"
|
||||
disabled={!formik.isValid || !formik.dirty}
|
||||
style={{ color: formik.isValid && formik.dirty ? '#fff' : undefined, background: formik.isValid && formik.dirty ? theme?.main : undefined }}
|
||||
as={StyledSelect}
|
||||
name="modelName"
|
||||
>
|
||||
<Send />
|
||||
</IconButton>
|
||||
<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 || !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={{
|
||||
|
||||
Reference in New Issue
Block a user