feat: next iteration of chatui (#642)
* feat: add dark mode & make minor UI improvements added dark mode toggle & picked a color scheme that is closer to the memgpt icons cleaned up the home page a little bit. * feat: add thinking indicator & make minor UI improvements we now show a thinking while the current message is loading. removed status indicator as we do not work with websockets anymore. also adjusted some of the chat styles to better fit the new theme. * feat: add memory viewer and allow memory edit * chore: build frontend
This commit is contained in:
@@ -8,6 +8,27 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/src/styles.css" />
|
||||
<script>
|
||||
if (localStorage.theme === 'dark') {
|
||||
if (document && document.documentElement) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
} else if (localStorage.theme === 'light') {
|
||||
if (document && document.documentElement) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('darkMode', 'light');
|
||||
}
|
||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
localStorage.setItem('darkMode', 'system');
|
||||
if (document && document.documentElement) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
} else {
|
||||
if (document && document.documentElement) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="h-full w-full" id="root"></div>
|
||||
|
||||
@@ -8,24 +8,21 @@ import { useMessageSocketActions } from './libs/messages/message-stream.store';
|
||||
import { useCurrentAgent } from './libs/agents/agent.store';
|
||||
import { useMessageHistoryActions } from './libs/messages/message-history.store';
|
||||
import { Message } from './libs/messages/message';
|
||||
import { useNextMessageLoadingActions } from './libs/messages/next-message-loading.store';
|
||||
import { useAgentMemoryQuery } from './libs/agents/use-agent-memory.query';
|
||||
import { ThemeProvider } from './shared/theme';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export function App() {
|
||||
const { setAgentParam, registerOnMessageCallback, resetSocket } = useMessageSocketActions();
|
||||
const { addMessage } =useMessageHistoryActions()
|
||||
const { setLoading } = useNextMessageLoadingActions()
|
||||
|
||||
const currentAgent = useCurrentAgent();
|
||||
|
||||
useEffect(() => registerOnMessageCallback((message: Message) => {
|
||||
setLoading(message['type'] === 'agent_response_start');
|
||||
if (currentAgent) {
|
||||
addMessage(currentAgent.name, message);
|
||||
}
|
||||
}), [registerOnMessageCallback, currentAgent, setLoading, addMessage]);
|
||||
}), [registerOnMessageCallback, currentAgent, addMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAgent) return;
|
||||
@@ -38,9 +35,11 @@ export function App() {
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
<ThemeProvider>
|
||||
<RouterProvider router={router} />
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
<Toaster />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
10
memgpt-frontend/src/app/libs/agents/agent-memory-update.ts
Normal file
10
memgpt-frontend/src/app/libs/agents/agent-memory-update.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const AgentMemoryUpdateSchema = z.object({
|
||||
persona: z.string(),
|
||||
human: z.string(),
|
||||
user_id: z.string(),
|
||||
agent_id: z.string()
|
||||
})
|
||||
|
||||
export type AgentMemoryUpdate = z.infer<typeof AgentMemoryUpdateSchema>;
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AgentMemoryUpdate } from './agent-memory-update';
|
||||
import { API_BASE_URL } from '../constants';
|
||||
|
||||
export const useAgentMemoryUpdateMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
{
|
||||
mutationFn:
|
||||
async (params: AgentMemoryUpdate) =>
|
||||
await fetch(API_BASE_URL + `/agents/memory`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': ' application/json' },
|
||||
body: JSON.stringify(params),
|
||||
})
|
||||
.then(res => res.json()),
|
||||
onSuccess: (res, { agent_id }) =>
|
||||
queryClient.invalidateQueries({ queryKey: ['agents', agent_id, 'memory'] }),
|
||||
});
|
||||
};
|
||||
@@ -22,8 +22,11 @@ const useMessageStreamStore = create(combine({
|
||||
actions: {
|
||||
setAgentParam: (agentParam: string) => set(state => ({ ...state, agentParam })),
|
||||
sendMessage: (message: string) => {
|
||||
set(state => ({ ...state, readyState: ReadyState.LOADING }))
|
||||
|
||||
const agent_id = get().agentParam;
|
||||
const onMessageCallback = get().onMessageCallback;
|
||||
const onCloseCb = () => set(state => ({ ...state, readyState: ReadyState.IDLE }));
|
||||
const onSuccessCb = () => set(state => ({ ...state, readyState: ReadyState.IDLE }))
|
||||
const onOpenCb = () => set(state => ({ ...state, readyState: ReadyState.LOADING }))
|
||||
const errorCb = () => set(state => ({ ...state, readyState: ReadyState.ERROR }))
|
||||
@@ -62,6 +65,7 @@ const useMessageStreamStore = create(combine({
|
||||
message_type: 'assistant_message',
|
||||
message: parsedData['assistant_message'],
|
||||
})
|
||||
onSuccessCb();
|
||||
} else if (parsedData['function_call'] != null) {
|
||||
onMessageCallback({
|
||||
type: 'agent_response',
|
||||
@@ -75,10 +79,10 @@ const useMessageStreamStore = create(combine({
|
||||
message: parsedData['function_return'],
|
||||
})
|
||||
}
|
||||
onSuccessCb();
|
||||
},
|
||||
onclose() {
|
||||
console.log('Connection closed by the server');
|
||||
onCloseCb();
|
||||
},
|
||||
onerror(err) {
|
||||
console.log('There was an error from server', err);
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { create } from 'zustand';
|
||||
import { combine } from 'zustand/middleware';
|
||||
|
||||
const useNextMessageLoadingStore = create(combine({
|
||||
isLoading: false,
|
||||
},
|
||||
(set) => ({
|
||||
actions: {
|
||||
setLoading: (isLoading: boolean) => set(() => ({ isLoading })),
|
||||
},
|
||||
})));
|
||||
|
||||
export const useNextMessageLoading = () => useNextMessageLoadingStore(s => s.isLoading);
|
||||
|
||||
export const useNextMessageLoadingActions = () =>
|
||||
useNextMessageLoadingStore((s) => s.actions);
|
||||
@@ -7,15 +7,12 @@ import {
|
||||
useMessageStreamReadyState,
|
||||
} from '../../libs/messages/message-stream.store';
|
||||
import {
|
||||
useMessageHistory,
|
||||
useMessageHistoryActions,
|
||||
useMessagesForKey,
|
||||
} from '../../libs/messages/message-history.store';
|
||||
import { useNextMessageLoading } from '../../libs/messages/next-message-loading.store';
|
||||
import { useCurrentAgent } from '../../libs/agents/agent.store';
|
||||
|
||||
const Chat = () => {
|
||||
const isThinking = useNextMessageLoading();
|
||||
const currentAgent = useCurrentAgent();
|
||||
const messages = useMessagesForKey(currentAgent?.name ?? '');
|
||||
|
||||
@@ -34,7 +31,7 @@ const Chat = () => {
|
||||
};
|
||||
|
||||
return (<div className='max-w-screen-xl mx-auto p-4'>
|
||||
<MessageContainer agentSet={!!currentAgent} isThinking={isThinking} readyState={readyState} messages={messages} />
|
||||
<MessageContainer agentSet={!!currentAgent} readyState={readyState} messages={messages} />
|
||||
<UserInput enabled={readyState !== ReadyState.LOADING} onSend={sendUserMessageAndAddToHistory} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import * as z from 'zod';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@memgpt/components/form';
|
||||
import { Textarea } from '@memgpt/components/textarea';
|
||||
import { Button } from '@memgpt/components/button';
|
||||
import React from 'react';
|
||||
import { AgentMemory } from '../../../libs/agents/agent-memory';
|
||||
import { cn } from '@memgpt/utils';
|
||||
import { AgentMemoryUpdateSchema } from '../../../libs/agents/agent-memory-update';
|
||||
import { useAgentMemoryUpdateMutation } from '../../../libs/agents/use-agent-memory.mutation';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
|
||||
export function MemoryForm({ data, agentId, className }: { data: AgentMemory; agentId: string, className?: string }) {
|
||||
const mutation = useAgentMemoryUpdateMutation();
|
||||
|
||||
const form = useForm<z.infer<typeof AgentMemoryUpdateSchema>>({
|
||||
resolver: zodResolver(AgentMemoryUpdateSchema),
|
||||
defaultValues: {
|
||||
persona: data.core_memory.persona,
|
||||
human: data.core_memory.human,
|
||||
user_id: 'null',
|
||||
agent_id: agentId
|
||||
}
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof AgentMemoryUpdateSchema>) {
|
||||
mutation.mutate(data)
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className={cn('flex flex-col gap-8',className)}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="persona"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Persona</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="min-h-[20rem] resize-none"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is the agents core memory. It is immediately available without querying any other resources.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="human"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Human</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="min-h-[20rem] resize-none"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is what the agent knows about you so far!
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button className="mt-4" type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? <Loader2 className="h-4 w-4 animate-spin mr-2"/> : undefined}
|
||||
{mutation.isPending ? 'Saving Changes' : 'Save Changes'}</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@memgpt/components/button';
|
||||
import { Brain } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@memgpt/components/dialog';
|
||||
import { useCurrentAgent } from '../../../libs/agents/agent.store';
|
||||
import { useAgentMemoryQuery } from '../../../libs/agents/use-agent-memory.query';
|
||||
import { MemoryForm } from './memory-form';
|
||||
|
||||
const MemoryView = ({ open, onOpenChange }: {open: boolean, onOpenChange: (open: boolean) => void}) => {
|
||||
const currentAgent = useCurrentAgent();
|
||||
const {data, isLoading} = useAgentMemoryQuery(currentAgent?.name);
|
||||
return <Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit Memory</DialogTitle>
|
||||
<DialogDescription>
|
||||
This is your agents current memory. Make changes and click save to edit it.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{isLoading || !currentAgent || !data ? <p>Loading memory</p> :
|
||||
<MemoryForm data={data} agentId={currentAgent.name} className="max-h-[80vh] overflow-auto px-1 py-4"/>}
|
||||
</DialogContent>
|
||||
</Dialog>;
|
||||
};
|
||||
|
||||
export default MemoryView;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
|
||||
const MessageContainerLayout = ({ children }: PropsWithChildren) =>
|
||||
<div className='relative mt-4 overflow-y-auto border bg-muted rounded-md h-[70svh]'>{children}</div>;
|
||||
<div className='relative mt-4 overflow-y-auto border bg-muted/50 rounded-md h-[70svh]'>{children}</div>;
|
||||
|
||||
export default MessageContainerLayout;
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import MessageContainerLayout from './message-container-layout';
|
||||
import StatusIndicator from './status-indicator';
|
||||
import ThinkingIndicator from './thinking-indicator';
|
||||
import { Message } from '../../../libs/messages/message';
|
||||
import { ReadyState } from '../../../libs/messages/message-stream.store';
|
||||
import { pickMessageElement } from './message/pick-message-element';
|
||||
import SelectAgentForm from './select-agent-form';
|
||||
|
||||
const MessageContainer = ({ agentSet, isThinking, messages, readyState }: {
|
||||
const MessageContainer = ({ agentSet, messages, readyState }: {
|
||||
agentSet: boolean;
|
||||
isThinking: boolean;
|
||||
messages: Message[];
|
||||
readyState: ReadyState
|
||||
}) => {
|
||||
@@ -22,10 +20,10 @@ const MessageContainer = ({ agentSet, isThinking, messages, readyState }: {
|
||||
</MessageContainerLayout>;
|
||||
}
|
||||
|
||||
return <MessageContainerLayout><StatusIndicator readyState={readyState} />
|
||||
return <MessageContainerLayout>
|
||||
<div className='flex flex-col flex-1 px-4 py-6 space-y-4' ref={messageBox}>
|
||||
{messages.map((message, i) => pickMessageElement(message, i))}
|
||||
{isThinking ? <ThinkingIndicator className='py-3 px-3 flex items-center' /> : undefined}
|
||||
{readyState === ReadyState.LOADING ? <ThinkingIndicator className='py-3 px-3 flex items-center' /> : undefined}
|
||||
</div>
|
||||
</MessageContainerLayout>;
|
||||
};
|
||||
|
||||
@@ -7,8 +7,8 @@ const UserMessage = (props: { message: string; date: Date }) => {
|
||||
message={props.message}
|
||||
date={props.date}
|
||||
dir="rtl"
|
||||
bg="bg-muted-foreground/40"
|
||||
fg="text-black"
|
||||
bg="bg-muted-foreground/40 dark:bg-muted-foreground/20"
|
||||
fg="text-black dark:text-white"
|
||||
initials="U"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Badge } from '@memgpt/components/badge';
|
||||
import { ReadyState } from '../../../libs/messages/message-stream.store';
|
||||
|
||||
const twPos = (extraClasses?: string) => 'absolute h-4 w-4 p-0 top-4 right-4 ' + (extraClasses ?? '');
|
||||
const StatusIndicator = ({ readyState }: {readyState: ReadyState}) => {
|
||||
if (readyState === ReadyState.IDLE) {
|
||||
return <Badge className={twPos('hover:bg-emerald-600 bg-emerald-500')}/>
|
||||
}
|
||||
if (readyState === ReadyState.ERROR) {
|
||||
return <Badge className={twPos()} variant="destructive"/>
|
||||
}
|
||||
if (readyState === ReadyState.LOADING) {
|
||||
return <Badge className={twPos('animate-pulse')} variant="outline"/>
|
||||
}
|
||||
return <></>
|
||||
};
|
||||
|
||||
export default StatusIndicator;
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { Label } from '@memgpt/components/label';
|
||||
import React, { useState } from 'react';
|
||||
import { Input } from '@memgpt/components/input';
|
||||
import { Button } from '@memgpt/components/button';
|
||||
|
||||
@@ -9,17 +8,19 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@memgpt/components/form';
|
||||
import { Brain } from 'lucide-react';
|
||||
import MemoryView from './memory-view/memory-view';
|
||||
|
||||
const formSchema = z.object({
|
||||
message: z.string().min(1, 'Message cannot be empty...'),
|
||||
});
|
||||
const UserInput = (props: { enabled: boolean; onSend: (message: string) => void }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
@@ -46,11 +47,15 @@ const UserInput = (props: { enabled: boolean; onSend: (message: string) => void
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
></FormField>
|
||||
<Button disabled={!props.enabled} className="mt-8" type="submit">
|
||||
Send
|
||||
</Button>
|
||||
</form>
|
||||
/>
|
||||
<div className="mt-8 flex gap-2">
|
||||
<Button disabled={!props.enabled}type="submit">
|
||||
Send
|
||||
</Button>
|
||||
<Button onClick={() => setOpen(true)} className="ml-1" type="button" size="icon" variant="outline"><Brain className="h-4 w-4" /></Button>
|
||||
</div>
|
||||
</form>
|
||||
<MemoryView open={open} onOpenChange={(open) => setOpen(open)}/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ const AgentCard = ({ name, persona, human, create_time, className, onBtnClick, i
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span>{name}</span>
|
||||
{isCurrentAgent && <Badge variant="secondary">Current Agent</Badge>}
|
||||
{isCurrentAgent && <Badge className="whitespace-nowrap">Current Agent</Badge>}
|
||||
</CardTitle>
|
||||
<CardDescription>{persona}</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
@@ -6,6 +6,7 @@ import AgentCard from './agent-card';
|
||||
import { LucidePlus } from 'lucide-react';
|
||||
import { Skeleton } from '@memgpt/components/skeleton';
|
||||
import CreateAgentDialog from './create-agent-dialog';
|
||||
import { Button } from '@memgpt/components/button';
|
||||
|
||||
const Home = () => {
|
||||
const { data, isLoading } = useAgentsQuery();
|
||||
@@ -13,13 +14,11 @@ const Home = () => {
|
||||
const currentAgent = useCurrentAgent();
|
||||
const [showingAgentCreation, setShowingAgentCreation] = useState(false);
|
||||
|
||||
const agentsOrSkeletons = isLoading ? <>
|
||||
<Skeleton className="flex-none opacity-30 w-full sm:w-80" />
|
||||
<Skeleton className="flex-none opacity-30 w-full sm:w-80" />
|
||||
</>
|
||||
const agentsOrSkeletons = isLoading ?
|
||||
[1,2,3,4,5,6,7,8].map((_,i) => <Skeleton key={i} className="h-52 flex-none opacity-30 w-full sm:w-96" />)
|
||||
: (data?.agents ?? [])
|
||||
.map((a) =>
|
||||
<AgentCard className="h-52 flex-none w-full sm:w-80 snap-center shadow-md snap-always" key={a.name}
|
||||
<AgentCard className="h-52 flex flex-col justify-between flex-none w-full sm:w-96 shadow-md" key={a.name}
|
||||
name={a.name} human={a.human} persona={a.persona}
|
||||
create_time={a.create_time}
|
||||
onBtnClick={() => setAgent(a)}
|
||||
@@ -33,17 +32,15 @@ const Home = () => {
|
||||
<h1 className={cnH1()}>Welcome to MemGPT</h1>
|
||||
<p className={cnLead('mt-2 mb-4')}>Select or create an agent to start your conversation...</p>
|
||||
</div>
|
||||
<div className="w-full mx-auto max-w-screen-2xl px-8">
|
||||
<div
|
||||
className="flex gap-12 flex-wrap px-8 py-4">
|
||||
<button
|
||||
<div className="w-full pb-20 mx-auto max-w-screen-2xl px-8 flex gap-12 flex-wrap">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowingAgentCreation(true)}
|
||||
className="h-52 text-muted-foreground flex-col items-center justify-center flex flex-none border snap-center snap-always rounded-md w-full sm:w-80">
|
||||
className="h-52 flex-col items-center justify-center flex flex-none w-full sm:w-96">
|
||||
<LucidePlus className="h-8 w-8" />
|
||||
<span className="mt-2">Add New</span>
|
||||
</button>
|
||||
</Button>
|
||||
{agentsOrSkeletons}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CreateAgentDialog open={showingAgentCreation} onOpenChange={(open) => setShowingAgentCreation(open)} />
|
||||
|
||||
@@ -2,18 +2,22 @@ import React from 'react';
|
||||
import { Button } from '@memgpt/components/button';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@memgpt/components/avatar';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { useTheme } from '../theme';
|
||||
import { MoonStar, Sun } from 'lucide-react';
|
||||
|
||||
const twNavLink = '[&.active]:opacity-100 opacity-60';
|
||||
const Header = () => (
|
||||
<div className='border-b sm:px-8 py-2 flex justify-between items-start'>
|
||||
const Header = () => {
|
||||
const {theme, toggleTheme} = useTheme();
|
||||
return <div className='border-b sm:px-8 py-2 flex justify-between items-start'>
|
||||
<NavLink to='/'>
|
||||
<span className='sr-only'>Home</span>
|
||||
<Avatar className='border'>
|
||||
<Avatar className='border bg-white'>
|
||||
<AvatarImage alt='MemGPT logo.' src='/memgpt_logo_transparent.png' />
|
||||
<AvatarFallback className='border'>MG</AvatarFallback>
|
||||
</Avatar>
|
||||
</NavLink>
|
||||
|
||||
|
||||
<nav className='flex space-x-4'>
|
||||
<Button size='sm' asChild variant='link'>
|
||||
<NavLink className={twNavLink} to='/'>Home</NavLink>
|
||||
@@ -25,8 +29,10 @@ const Header = () => (
|
||||
{/* @ts-ignore */}
|
||||
<NavLink className={twNavLink} to='/settings/agents'>Settings</NavLink>
|
||||
</Button>
|
||||
<Button size="icon" variant="ghost" onClick={toggleTheme}>
|
||||
{theme === 'light' ? <MoonStar className="h-4 w-4"/> : <Sun className="w-4 w-4"/>}
|
||||
</Button>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
export default Header;
|
||||
|
||||
48
memgpt-frontend/src/app/shared/theme.tsx
Normal file
48
memgpt-frontend/src/app/shared/theme.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
PropsWithChildren,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useContext, useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
const ThemeContext = createContext<{
|
||||
theme: 'light' | 'dark';
|
||||
toggleTheme: () => void;
|
||||
setTheme: Dispatch<SetStateAction<'light' | 'dark'>>;
|
||||
}>({
|
||||
setTheme(value: ((prevState: ("light" | "dark")) => ("light" | "dark")) | "light" | "dark"): void {},
|
||||
toggleTheme() {},
|
||||
theme: localStorage.getItem('theme') === 'dark' ? 'dark' : 'light'
|
||||
});
|
||||
|
||||
export function ThemeProvider({ children }: PropsWithChildren) {
|
||||
const [theme, setTheme] = useState<'light' | 'dark'>(localStorage.getItem('theme') === 'dark' ? 'dark' : 'light');
|
||||
const toggleTheme = useCallback(() => setTheme(prev => prev === 'light' ? 'dark' : 'light'), [setTheme])
|
||||
const contextValue = useMemo(() => ({
|
||||
theme,
|
||||
setTheme,
|
||||
toggleTheme,
|
||||
}), [theme, setTheme, toggleTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (theme === 'light') {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.documentElement.classList.add('light');
|
||||
} else {
|
||||
document.documentElement.classList.remove('light');
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
|
||||
localStorage.setItem('theme', theme)
|
||||
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={contextValue}>{children}</ThemeContext.Provider>);
|
||||
}
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext);
|
||||
@@ -2,70 +2,55 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
|
||||
--foreground: 224 71.4% 4.1%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
|
||||
--card-foreground: 224 71.4% 4.1%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
|
||||
--popover-foreground: 224 71.4% 4.1%;
|
||||
--primary: 220.9 39.3% 11%;
|
||||
--primary-foreground: 210 20% 98%;
|
||||
--secondary: 220 14.3% 95.9%;
|
||||
--secondary-foreground: 220.9 39.3% 11%;
|
||||
--muted: 220 14.3% 95.9%;
|
||||
--muted-foreground: 220 8.9% 46.1%;
|
||||
--accent: 220 14.3% 95.9%;
|
||||
--accent-foreground: 220.9 39.3% 11%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 10% 3.9%;
|
||||
|
||||
--destructive-foreground: 210 20% 98%;
|
||||
--border: 220 13% 91%;
|
||||
--input: 220 13% 91%;
|
||||
--ring: 224 71.4% 4.1%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--background: 224 71.4% 4.1%;
|
||||
--foreground: 210 20% 98%;
|
||||
--card: 224 71.4% 4.1%;
|
||||
--card-foreground: 210 20% 98%;
|
||||
--popover: 224 71.4% 4.1%;
|
||||
--popover-foreground: 210 20% 98%;
|
||||
--primary: 210 20% 98%;
|
||||
--primary-foreground: 220.9 39.3% 11%;
|
||||
--secondary: 215 27.9% 16.9%;
|
||||
--secondary-foreground: 210 20% 98%;
|
||||
--muted: 215 27.9% 16.9%;
|
||||
--muted-foreground: 217.9 10.6% 64.9%;
|
||||
--accent: 215 27.9% 16.9%;
|
||||
--accent-foreground: 210 20% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
--destructive-foreground: 210 20% 98%;
|
||||
--border: 215 27.9% 16.9%;
|
||||
--input: 215 27.9% 16.9%;
|
||||
--ring: 216 12.2% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
|
||||
@@ -20,7 +20,7 @@ def setup_agents_memory_router(server: SyncServer, interface: QueuingInterface):
|
||||
interface.clear()
|
||||
return server.get_agent_memory(user_id=user_id, agent_id=agent_id)
|
||||
|
||||
@router.put("/agents/memory")
|
||||
@router.post("/agents/memory")
|
||||
def get_agent_memory(body: CoreMemory):
|
||||
interface.clear()
|
||||
new_memory_contents = {"persona": body.persona, "human": body.human}
|
||||
|
||||
@@ -544,7 +544,7 @@ class SyncServer(LockingServer):
|
||||
memgpt_agent.memory.edit_persona(new_persona)
|
||||
modified = True
|
||||
|
||||
elif "human" in new_memory_contents and new_memory_contents["human"] is not None:
|
||||
if "human" in new_memory_contents and new_memory_contents["human"] is not None:
|
||||
new_human = new_memory_contents["human"]
|
||||
if old_core_memory["human"] != new_human:
|
||||
new_core_memory["human"] = new_human
|
||||
|
||||
129
memgpt/server/static_files/assets/index-156e79f0.js
Normal file
129
memgpt/server/static_files/assets/index-156e79f0.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
memgpt/server/static_files/assets/index-fc829661.css
Normal file
1
memgpt/server/static_files/assets/index-fc829661.css
Normal file
File diff suppressed because one or more lines are too long
@@ -8,8 +8,29 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
|
||||
<script type="module" crossorigin src="/assets/index-3436ea62.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-5cf8a11d.css">
|
||||
<script>
|
||||
if (localStorage.theme === 'dark') {
|
||||
if (document && document.documentElement) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
} else if (localStorage.theme === 'light') {
|
||||
if (document && document.documentElement) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('darkMode', 'light');
|
||||
}
|
||||
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
localStorage.setItem('darkMode', 'system');
|
||||
if (document && document.documentElement) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
} else {
|
||||
if (document && document.documentElement) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-156e79f0.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-fc829661.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="h-full w-full" id="root"></div>
|
||||
|
||||
Reference in New Issue
Block a user