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:
Robin Goetz
2023-12-18 19:41:23 +01:00
committed by robingotz
parent 39ada91fe7
commit b573e8cab1
26 changed files with 461 additions and 265 deletions

View File

@@ -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>

View File

@@ -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>
);
}

View 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>;

View File

@@ -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'] }),
});
};

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>
);

View File

@@ -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>
);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>;
};

View File

@@ -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"
/>
);

View File

@@ -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;

View File

@@ -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>
);
};

View File

@@ -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>

View File

@@ -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)} />

View File

@@ -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;

View 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);

View File

@@ -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;

View File

@@ -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}

View File

@@ -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

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

File diff suppressed because one or more lines are too long

View File

@@ -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>