feat: Next iteration of ChatUI (#847)

Co-authored-by: Charles Packer <packercharles@gmail.com>
This commit is contained in:
Robin Goetz
2024-01-21 01:28:31 +01:00
committed by GitHub
parent 3f877af66f
commit f285f8601e
31 changed files with 437 additions and 223 deletions

View File

@@ -54,6 +54,7 @@
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7",
"tslib": "^2.3.0",
"use-debounce": "^10.0.0",
"zod": "^3.22.4",
"zustand": "^4.4.6"
},
@@ -16296,6 +16297,17 @@
}
}
},
"node_modules/use-debounce": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.0.tgz",
"integrity": "sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A==",
"engines": {
"node": ">= 16.0.0"
},
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/use-sidecar": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",

View File

@@ -55,6 +55,7 @@
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7",
"tslib": "^2.3.0",
"use-debounce": "^10.0.0",
"zod": "^3.22.4",
"zustand": "^4.4.6"
},

15
chatui/src/app/auth.tsx Normal file
View File

@@ -0,0 +1,15 @@
import { PropsWithChildren } from 'react';
import { useAuthStoreActions, useAuthStoreState } from './libs/auth/auth.store';
import { useAuthQuery } from './libs/auth/use-auth.query';
const Auth = (props: PropsWithChildren) => {
const result = useAuthQuery();
const { uuid } = useAuthStoreState();
const { setAsAuthenticated } = useAuthStoreActions();
if (result.isSuccess && uuid !== result.data.uuid) {
setAsAuthenticated(result.data.uuid);
}
return props.children;
};
export default Auth;

View File

@@ -2,15 +2,16 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { API_BASE_URL } from '../constants';
import { AgentMemoryUpdate } from './agent-memory-update';
export const useAgentMemoryUpdateMutation = () => {
export const useAgentMemoryUpdateMutation = (userId: string | null | undefined) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (params: AgentMemoryUpdate) =>
await fetch(API_BASE_URL + `/agents/memory`, {
await fetch(API_BASE_URL + `/agents/memory?${userId}`, {
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'] }),
onSuccess: (res, { agent_id }) =>
queryClient.invalidateQueries({ queryKey: [userId, 'agents', 'entry', agent_id, 'memory'] }),
});
};

View File

@@ -2,12 +2,12 @@ import { useQuery } from '@tanstack/react-query';
import { API_BASE_URL } from '../constants';
import { AgentMemory } from './agent-memory';
export const useAgentMemoryQuery = (agent_id: string | null | undefined) =>
export const useAgentMemoryQuery = (userId: string | null | undefined, agentId: string | null | undefined) =>
useQuery({
queryKey: ['agents', agent_id, 'memory'],
queryKey: [userId, 'agents', 'entry', agentId, 'memory'],
queryFn: async () =>
(await fetch(API_BASE_URL + `/agents/memory?agent_id=${agent_id}&user_id=null`).then((res) =>
(await fetch(API_BASE_URL + `/agents/memory?agent_id=${agentId}&user_id=${userId}`).then((res) =>
res.json()
)) as Promise<AgentMemory>,
enabled: !!agent_id,
enabled: !!userId && !!agentId,
});

View File

@@ -2,18 +2,15 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { API_BASE_URL } from '../constants';
import { Agent } from './agent';
export const useAgentsCreateMutation = () => {
export const useAgentsCreateMutation = (userId: string | null | undefined) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (params: {
user_id: string;
config: { name: string; human: string; persona: string; model: string };
}) =>
mutationFn: async (params: { name: string; human: string; persona: string; model: string }) =>
(await fetch(API_BASE_URL + '/agents', {
method: 'POST',
headers: { 'Content-Type': ' application/json' },
body: JSON.stringify(params),
body: JSON.stringify({ config: params, user_id: userId }),
}).then((res) => res.json())) as Promise<Agent>,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['agents'] }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: [userId, 'agents', 'list'] }),
});
};

View File

@@ -2,11 +2,12 @@ import { useQuery } from '@tanstack/react-query';
import { API_BASE_URL } from '../constants';
import { Agent } from './agent';
export const useAgentsQuery = () =>
export const useAgentsQuery = (userId: string | null | undefined) =>
useQuery({
queryKey: ['agents'],
queryKey: [userId, 'agents', 'list'],
enabled: !!userId,
queryFn: async () =>
(await fetch(API_BASE_URL + '/agents?user_id=null').then((res) => res.json())) as Promise<{
(await fetch(API_BASE_URL + `/agents?user_id=${userId}`).then((res) => res.json())) as Promise<{
num_agents: number;
agents: Agent[];
}>,

View File

@@ -0,0 +1,32 @@
import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
export type AuthState = {
uuid: string | null;
};
export type AuthActions = { setAsAuthenticated: (uuid: string) => void };
const useAuthStore = create(
persist<{ auth: AuthState; actions: AuthActions }>(
(set, get) => ({
auth: { uuid: null },
actions: {
setAsAuthenticated: (uuid: string) =>
set((prev) => ({
...prev,
auth: {
uuid,
},
})),
},
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => localStorage),
partialize: ({ actions, ...rest }: any) => rest,
}
)
);
export const useAuthStoreState = () => useAuthStore().auth;
export const useAuthStoreActions = () => useAuthStore().actions;

View File

@@ -0,0 +1,9 @@
import { useQuery } from '@tanstack/react-query';
import { API_BASE_URL } from '../constants';
export type AuthResponse = { uuid: string };
export const useAuthQuery = () =>
useQuery({
queryKey: ['auth'],
queryFn: async () => (await fetch(API_BASE_URL + `/auth`).then((res) => res.json())) as Promise<AuthResponse>,
});

View File

@@ -25,7 +25,17 @@ const useMessageStreamStore = create(
},
(set, get) => ({
actions: {
sendMessage: ({ agentId, message, role }: { agentId: string; message: string; role?: 'user' | 'system' }) => {
sendMessage: ({
userId,
agentId,
message,
role,
}: {
userId: string;
agentId: string;
message: string;
role?: 'user' | 'system';
}) => {
const abortController = new AbortController();
set((state) => ({ ...state, abortController, readyState: ReadyState.LOADING }));
const onMessageCallback = get().onMessageCallback;
@@ -41,7 +51,7 @@ const useMessageStreamStore = create(
method: 'POST',
headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify({
user_id: 'null',
user_id: userId,
agent_id: agentId,
message,
role: role ?? 'user',

View File

@@ -0,0 +1,19 @@
import { useQuery } from '@tanstack/react-query';
import { API_BASE_URL } from '../constants';
export const useMessagesQuery = (
userId: string | null | undefined,
agentId: string | null | undefined,
start = 0,
count = 10
) =>
useQuery({
queryKey: [userId, 'agents', 'item', agentId, 'messages', 'list', start, count],
queryFn: async () =>
(await fetch(
API_BASE_URL + `/agents/message?agent_id=${agentId}&user_id=${userId}&start=${start}&count=${count}`
).then((res) => res.json())) as Promise<{
messages: { role: string; name: string; content: string }[];
}>,
enabled: !!userId && !!agentId,
});

View File

@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef } from 'react';
import { useAgentActions, useCurrentAgent, useLastAgentInitMessage } from '../../libs/agents/agent.store';
import { useAuthStoreState } from '../../libs/auth/auth.store';
import { useMessageHistoryActions, useMessagesForKey } from '../../libs/messages/message-history.store';
import {
ReadyState,
@@ -11,7 +12,7 @@ import UserInput from './user-input';
const Chat = () => {
const initialized = useRef(false);
const auth = useAuthStoreState();
const currentAgent = useCurrentAgent();
const lastAgentInitMessage = useLastAgentInitMessage();
const messages = useMessagesForKey(currentAgent?.id ?? '');
@@ -21,12 +22,11 @@ const Chat = () => {
const { addMessage } = useMessageHistoryActions();
const { setLastAgentInitMessage } = useAgentActions();
// eslint-disable-next-line react-hooks/exhaustive-deps
const sendMessageAndAddToHistory = useCallback(
(message: string, role: 'user' | 'system' = 'user') => {
if (!currentAgent) return;
if (!currentAgent || !auth.uuid) return;
const date = new Date();
sendMessage({ agentId: currentAgent.id, message, role });
sendMessage({ userId: auth.uuid, agentId: currentAgent.id, message, role });
addMessage(currentAgent.id, {
type: role === 'user' ? 'user_message' : 'system_message',
message_type: 'user_message',
@@ -34,7 +34,7 @@ const Chat = () => {
date,
});
},
[currentAgent, sendMessage, addMessage]
[currentAgent, auth.uuid, sendMessage, addMessage]
);
useEffect(() => {
@@ -64,7 +64,7 @@ const Chat = () => {
return (
<div className="mx-auto max-w-screen-xl p-4">
<MessageContainer agentSet={!!currentAgent} readyState={readyState} messages={messages} />
<MessageContainer currentAgent={currentAgent} readyState={readyState} previousMessages={[]} messages={messages} />
<UserInput enabled={readyState !== ReadyState.LOADING} onSend={sendMessageAndAddToHistory} />
</div>
);

View File

@@ -10,23 +10,26 @@ import {
FormMessage,
} from '@memgpt/components/form';
import { Textarea } from '@memgpt/components/textarea';
import { cnMuted } from '@memgpt/components/typography';
import { cn } from '@memgpt/utils';
import { Loader2 } from 'lucide-react';
import { Loader2, LucideCheckCheck } from 'lucide-react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { AgentMemory } from '../../../libs/agents/agent-memory';
import { AgentMemoryUpdateSchema } from '../../../libs/agents/agent-memory-update';
import { useAgentMemoryUpdateMutation } from '../../../libs/agents/use-agent-memory.mutation';
import { useAuthStoreState } from '../../../libs/auth/auth.store';
export function MemoryForm({ className, data, agentId }: { className?: string; data: AgentMemory; agentId: string }) {
const mutation = useAgentMemoryUpdateMutation();
const auth = useAuthStoreState();
const mutation = useAgentMemoryUpdateMutation(auth.uuid);
const form = useForm<z.infer<typeof AgentMemoryUpdateSchema>>({
resolver: zodResolver(AgentMemoryUpdateSchema),
defaultValues: {
persona: data?.core_memory?.persona,
human: data?.core_memory?.human,
user_id: 'null',
user_id: auth.uuid ?? undefined,
agent_id: agentId,
},
});
@@ -68,10 +71,23 @@ export function MemoryForm({ className, data, agentId }: { className?: string; d
</FormItem>
)}
/>
<Button className="mt-4" type="submit" disabled={mutation.isPending}>
{mutation.isPending ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : undefined}
{mutation.isPending ? 'Saving Changes' : 'Save Changes'}
</Button>
<div className="mt-4 flex items-center justify-end">
{mutation.isPending && (
<span className={cnMuted('mr-6 flex items-center animate-in slide-in-from-bottom')}>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Saving Memory...
</span>
)}
{mutation.isSuccess && (
<span className={cnMuted('mr-6 flex items-center text-emerald-600 animate-in slide-in-from-bottom')}>
<LucideCheckCheck className="mr-2 h-4 w-4" />
New Memory Saved
</span>
)}
<Button type="submit" disabled={mutation.isPending}>
Save Memory
</Button>
</div>
</form>
</Form>
);

View File

@@ -1,11 +1,13 @@
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@memgpt/components/dialog';
import { useCurrentAgent } from '../../../libs/agents/agent.store';
import { useAgentMemoryQuery } from '../../../libs/agents/use-agent-memory.query';
import { useAuthStoreState } from '../../../libs/auth/auth.store';
import { MemoryForm } from './memory-form';
const MemoryView = ({ open, onOpenChange }: { open: boolean; onOpenChange: (open: boolean) => void }) => {
const auth = useAuthStoreState();
const currentAgent = useCurrentAgent();
const { data, isLoading } = useAgentMemoryQuery(currentAgent?.id);
const { data, isLoading } = useAgentMemoryQuery(auth.uuid, currentAgent?.id);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-2xl">

View File

@@ -1,4 +1,6 @@
import { Badge } from '@memgpt/components/badge';
import { useEffect, useRef } from 'react';
import { Agent } from '../../../libs/agents/agent';
import { Message } from '../../../libs/messages/message';
import { ReadyState } from '../../../libs/messages/message-stream.store';
import MessageContainerLayout from './message-container-layout';
@@ -7,18 +9,20 @@ import SelectAgentForm from './select-agent-form';
import ThinkingIndicator from './thinking-indicator';
const MessageContainer = ({
agentSet,
currentAgent,
messages,
readyState,
previousMessages,
}: {
agentSet: boolean;
currentAgent: Agent | null;
messages: Message[];
readyState: ReadyState;
previousMessages: { role: string; content: string; name: string; function_call: { arguments: string } }[];
}) => {
const messageBox = useRef<HTMLDivElement>(null);
useEffect(() => messageBox.current?.scrollIntoView(false), [messages]);
if (!agentSet) {
if (!currentAgent) {
return (
<MessageContainerLayout>
<SelectAgentForm />
@@ -28,7 +32,18 @@ const MessageContainer = ({
return (
<MessageContainerLayout>
<Badge
className="sticky left-1/2 top-2 z-10 mx-auto origin-center -translate-x-1/2 bg-background py-1 px-4"
variant="outline"
>
<span>{currentAgent.name}</span>
</Badge>
<div className="flex flex-1 flex-col space-y-4 px-4 py-6" ref={messageBox}>
{previousMessages.map((m) => (
<p>
{m.name} | {m.role} | {m.content} | {m.function_call?.arguments}
</p>
))}
{messages.map((message, i) => pickMessageElement(message, i))}
{readyState === ReadyState.LOADING ? <ThinkingIndicator className="flex items-center py-3 px-3" /> : undefined}
</div>

View File

@@ -6,9 +6,11 @@ import { useState } from 'react';
import { Agent } from '../../../libs/agents/agent';
import { useAgentActions } from '../../../libs/agents/agent.store';
import { useAgentsQuery } from '../../../libs/agents/use-agents.query';
import { useAuthStoreState } from '../../../libs/auth/auth.store';
const SelectAgentForm = () => {
const { data } = useAgentsQuery();
const { uuid } = useAuthStoreState();
const { data } = useAgentsQuery(uuid);
const [newAgent, setNewAgent] = useState<Agent | null>(null);
const { setAgent } = useAgentActions();
return (

View File

@@ -11,26 +11,26 @@ import { Input } from '@memgpt/components/input';
import { Label } from '@memgpt/components/label';
import { Loader2 } from 'lucide-react';
import { useAgentsCreateMutation } from '../../libs/agents/use-agents.mutation';
import { useAuthStoreState } from '../../libs/auth/auth.store';
const CreateAgentDialog = (props: { open: boolean; onOpenChange: (open: boolean) => void }) => {
const createAgent = useAgentsCreateMutation();
const auth = useAuthStoreState();
const createAgent = useAgentsCreateMutation(auth.uuid);
return (
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
<DialogContent className="sm:max-w-[425px]">
<form
onSubmit={(event) => {
event.preventDefault();
if (!auth.uuid) return;
const formData = new FormData(event.currentTarget);
// ✅ mutation is invoked when the form is submitted
createAgent.mutate(
{
user_id: 'placeholder',
config: {
name: `${formData.get('name')}`,
human: `${formData.get('human')}`,
persona: `${formData.get('persona')}`,
model: `${formData.get('model')}`,
},
name: `${formData.get('name')}`,
human: `${formData.get('human')}`,
persona: `${formData.get('persona')}`,
model: `${formData.get('model')}`,
},
{
onSuccess: () => props.onOpenChange(false),

View File

@@ -1,22 +1,28 @@
import { Button } from '@memgpt/components/button';
import { Input } from '@memgpt/components/input';
import { Skeleton } from '@memgpt/components/skeleton';
import { cnH1, cnLead } from '@memgpt/components/typography';
import { LucidePlus } from 'lucide-react';
import { useState } from 'react';
import { cnH1, cnH3, cnLead, cnMuted } from '@memgpt/components/typography';
import { LucidePlus, LucideSearch } from 'lucide-react';
import { useRef, useState } from 'react';
import { useDebounce } from 'use-debounce';
import { useAgentActions, useCurrentAgent } from '../../libs/agents/agent.store';
import { useAgentsQuery } from '../../libs/agents/use-agents.query';
import { useAuthStoreState } from '../../libs/auth/auth.store';
import AgentCard from './agent-card';
import CreateAgentDialog from './create-agent-dialog';
const Home = () => {
const { data, isLoading } = useAgentsQuery();
const { uuid } = useAuthStoreState();
const { data, isSuccess, isLoading } = useAgentsQuery(uuid);
const { setAgent } = useAgentActions();
const currentAgent = useCurrentAgent();
const [showingAgentCreation, setShowingAgentCreation] = useState(false);
const [searchInput, setSearchInput] = useState('');
const [debouncedInput] = useDebounce(searchInput, 300);
const filteredAgents = (data?.agents ?? []).filter((a) => a.name.includes(debouncedInput));
const agentsOrSkeletons = isLoading
? [1, 2, 3, 4, 5, 6, 7, 8].map((_, i) => <Skeleton key={i} className="h-52 w-full flex-none opacity-30 sm:w-96" />)
: (data?.agents ?? []).map((a) => (
: filteredAgents.map((a) => (
<AgentCard
className="flex h-52 w-full flex-none flex-col justify-between shadow-md sm:w-96"
key={a.name}
@@ -29,6 +35,7 @@ const Home = () => {
/>
));
const inputRef = useRef<HTMLInputElement>(null);
return (
<>
<div className="flex h-full flex-col items-center overflow-y-scroll">
@@ -36,16 +43,35 @@ 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="mx-auto flex w-full max-w-screen-2xl flex-wrap gap-12 px-8 pb-20">
<Button
variant="outline"
onClick={() => setShowingAgentCreation(true)}
className="flex h-52 w-full flex-none flex-col items-center justify-center sm:w-96"
>
<LucidePlus className="h-8 w-8" />
<span className="mt-2">Add New</span>
<div className="mx-auto mb-12 flex w-full max-w-screen-lg justify-between">
<div className="relative">
<Input
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
ref={inputRef}
placeholder="Search for Agent"
className="w-full pl-12 sm:w-80"
/>
<LucideSearch
onClick={() => inputRef.current?.focus()}
className="absolute top-1/2 left-3 z-0 h-5 w-5 -translate-y-1/2"
/>
</div>
<Button onClick={() => setShowingAgentCreation(true)}>
<LucidePlus className="h-5 w-5" />
<span className="ml-2">Add New</span>
</Button>
</div>
<div className="mx-auto flex w-full max-w-screen-2xl flex-wrap gap-12 px-8 pb-20">
{agentsOrSkeletons}
{isSuccess && data?.num_agents === 0 ? (
<div className="flex w-full flex-col items-center justify-center p-20">
<h3 className={cnH3()}>No Agents exist</h3>
<p className={cnMuted('mt-4')}>
Create your first agent and start chatting by clicking the Add New button.
</p>
</div>
) : undefined}
</div>
</div>
<CreateAgentDialog open={showingAgentCreation} onOpenChange={(open) => setShowingAgentCreation(open)} />

View File

@@ -15,6 +15,7 @@ import { useForm } from 'react-hook-form';
import * as z from 'zod';
import { useAgentActions, useCurrentAgent } from '../../../libs/agents/agent.store';
import { useAgentsQuery } from '../../../libs/agents/use-agents.query';
import { useAuthStoreState } from '../../../libs/auth/auth.store';
import { SettingsLayout } from '../layout';
const agentsFormSchema = z.object({
@@ -33,7 +34,8 @@ const getDefaultValues: (initialName: string | null | undefined) => Partial<Agen
});
export function AgentsForm() {
const { data, isLoading } = useAgentsQuery();
const auth = useAuthStoreState();
const { data, isLoading } = useAgentsQuery(auth.uuid);
const currentAgent = useCurrentAgent();
const { setAgent } = useAgentActions();

View File

@@ -1,24 +1,26 @@
import { createBrowserRouter, Outlet } from 'react-router-dom';
import Auth from './auth';
import { chatRoute } from './modules/chat/chat.routes';
import Home from './modules/home/home';
import { settingsRoute } from './modules/settings/settings.routes';
import Footer from './shared/layout/footer';
import Header from './shared/layout/header';
const rootRoute = () => (
<>
<Header />
<div className="h-full">
<Outlet />
</div>
<Footer />
</>
);
const RootRoute = () => {
return (
<Auth>
<Header />
<div className="h-full">
<Outlet />
</div>
<Footer />
</Auth>
);
};
export const router = createBrowserRouter([
{
path: '/',
element: rootRoute(),
element: RootRoute(),
children: [
{
path: '',

View File

@@ -253,6 +253,19 @@ class ServerChoice(Enum):
ws_api = "websocket"
def create_default_user_or_exit(config: MemGPTConfig, ms: MetadataStore):
user_id = uuid.UUID(config.anon_clientid)
user = ms.get_user(user_id=user_id)
if user is None:
ms.create_user(User(id=user_id))
user = ms.get_user(user_id=user_id)
if user is None:
typer.secho(f"Failed to create default user in database.", fg=typer.colors.RED)
sys.exit(1)
else:
return user
def server(
type: ServerChoice = typer.Option("rest", help="Server to run"),
port: int = typer.Option(None, help="Port to run the server on"),
@@ -278,13 +291,21 @@ def server(
import uvicorn
from memgpt.server.rest_api.server import app
if MemGPTConfig.exists():
config = MemGPTConfig.load()
ms = MetadataStore(config)
create_default_user_or_exit(config, ms)
else:
typer.secho(f"No configuration exists. Run memgpt configure before starting the server.", fg=typer.colors.RED)
sys.exit(1)
try:
# Start the subprocess in a new session
uvicorn.run(app, host=host or "localhost", port=port or REST_DEFAULT_PORT)
except KeyboardInterrupt:
# Handle CTRL-C
print("Terminating the server...")
typer.secho("Terminating the server...")
sys.exit(0)
elif type == ServerChoice.ws_api:
@@ -299,7 +320,7 @@ def server(
command = f"python server.py {port}"
# Run the command
print(f"Running WS (websockets) server: {command} (inside {server_directory})")
typer.secho(f"Running WS (websockets) server: {command} (inside {server_directory})")
try:
# Start the subprocess in a new session
@@ -307,13 +328,13 @@ def server(
process.wait()
except KeyboardInterrupt:
# Handle CTRL-C
print("Terminating the server...")
typer.secho("Terminating the server...")
process.terminate()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
print("Server terminated with kill()")
typer.secho("Server terminated with kill()")
sys.exit(0)
@@ -435,17 +456,7 @@ def run(
# read user id from config
ms = MetadataStore(config)
user_id = uuid.UUID(config.anon_clientid)
user = ms.get_user(user_id=user_id)
if user is None:
print("Creating user", user_id)
ms.create_user(User(id=user_id))
user = ms.get_user(user_id=user_id)
if user is None:
typer.secho(f"Failed to create default user in database.", fg=typer.colors.RED)
sys.exit(1)
else:
print("existing user", user, user_id)
user = create_default_user_or_exit(config, ms)
# override with command line arguments
if debug:

View File

@@ -3,12 +3,13 @@ from asyncio import AbstractEventLoop
from enum import Enum
import json
import uuid
from typing import List, Optional
from typing import List
from fastapi import APIRouter, Depends, Body, HTTPException, Query
from pydantic import BaseModel, Field, constr, validator
from fastapi import APIRouter, Body, HTTPException, Query
from pydantic import BaseModel, Field
from starlette.responses import StreamingResponse
from memgpt.constants import JSON_ENSURE_ASCII
from memgpt.server.rest_api.interface import QueuingInterface
from memgpt.server.server import SyncServer
@@ -112,7 +113,7 @@ def setup_agents_message_router(server: SyncServer, interface: QueuingInterface)
async def formatted_message_generator():
async for message in interface.message_generator():
formatted_message = f"data: {json.dumps(message)}\n\n"
formatted_message = f"data: {json.dumps(message, ensure_ascii=JSON_ENSURE_ASCII)}\n\n"
yield formatted_message
await asyncio.sleep(1)

View File

View File

@@ -0,0 +1,33 @@
from uuid import UUID
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
from memgpt.server.rest_api.interface import QueuingInterface
from memgpt.server.server import SyncServer
router = APIRouter()
class AuthResponse(BaseModel):
uuid: UUID = Field(..., description="UUID of the user")
def setup_auth_router(server: SyncServer, interface: QueuingInterface):
@router.get("/auth", tags=["auth"], response_model=AuthResponse)
def authenticate_user():
"""
Authenticates the user and sends response with User related data.
Currently, this is a placeholder that simply returns a UUID placeholder
"""
interface.clear()
try:
response = server.authenticate_user()
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"{e}")
return AuthResponse(uuid=response)
return router

View File

@@ -11,6 +11,7 @@ from memgpt.server.rest_api.agents.command import setup_agents_command_router
from memgpt.server.rest_api.agents.config import setup_agents_config_router
from memgpt.server.rest_api.agents.memory import setup_agents_memory_router
from memgpt.server.rest_api.agents.message import setup_agents_message_router
from memgpt.server.rest_api.auth.index import setup_auth_router
from memgpt.server.rest_api.config.index import setup_config_index_router
from memgpt.server.server import SyncServer
from memgpt.server.rest_api.interface import QueuingInterface
@@ -48,6 +49,8 @@ app.add_middleware(
allow_methods=["*"],
allow_headers=["*"],
)
# /api/auth endpoints
app.include_router(setup_auth_router(server, interface), prefix=API_PREFIX)
# /api/agents endpoints
app.include_router(setup_agents_command_router(server, interface), prefix=API_PREFIX)
app.include_router(setup_agents_config_router(server, interface), prefix=API_PREFIX)

View File

@@ -876,3 +876,7 @@ class SyncServer(LockingServer):
"new_core_memory": new_core_memory,
"modified": modified,
}
def authenticate_user(self) -> uuid.UUID:
# TODO: Implement actual authentication to enable multi user setup
return uuid.UUID(int=uuid.getnode())

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

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<title>MemGPT Chat UI</title>
<title>MemgptFrontend</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -29,8 +29,8 @@
}
}
</script>
<script type="module" crossorigin src="/assets/index-c3a527b1.js"></script>
<link rel="stylesheet" href="/assets/index-27e4af26.css">
<script type="module" crossorigin src="/assets/index-273ebfe0.js"></script>
<link rel="stylesheet" href="/assets/index-9ace7bf7.css">
</head>
<body>
<div class="h-full w-full" id="root"></div>