Skip to content

Commit

Permalink
Start of frontend changes
Browse files Browse the repository at this point in the history
Time travel, use checkpoint as primary source of truth

Refactor state management for chat window

Add support for state graph

Fixes

Pare down unneeded functionality, frontend updates

Fix repeated history fetches

Add basic state graph support, many other fixes

Revise state graph time travel flow

Use message graph as default

Fix flashing messages in UI on send

Allow adding and deleting tool calls

Hacks!

Only accept module paths

More logs

add env

add built ui files

Build ui files

Update cli

Delete .github/workflows/build_deploy_image.yml

Update path

Update ui files

Move migrations

Move ui files

0.0.5

Allow resume execution for tool messages (#2)

Undo

Undo

Remove cli

Undo

Undo

Update storage/threads

Undo ui

Undo

Lint

Undo

Rm

Undo

Rm

Update api

Undo

WIP
  • Loading branch information
jacoblee93 authored and nfcampos committed Apr 6, 2024
1 parent d9699a6 commit b67e318
Show file tree
Hide file tree
Showing 17 changed files with 1,164 additions and 153 deletions.
4 changes: 2 additions & 2 deletions backend/app/api/runs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, Sequence
from typing import Dict, Optional, Sequence, Union

import langsmith.client
from fastapi import APIRouter, BackgroundTasks, HTTPException
Expand All @@ -24,8 +24,8 @@ class CreateRunPayload(BaseModel):
"""Payload for creating a run."""

thread_id: str
input: Optional[Sequence[AnyMessage]] = Field(default_factory=list)
config: Optional[RunnableConfig] = None
input: Optional[Union[Sequence[AnyMessage], Dict]] = Field(default_factory=list)


async def _run_input_and_config(
Expand Down
23 changes: 13 additions & 10 deletions backend/app/api/threads.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Annotated, List, Sequence
from typing import Annotated, Any, Dict, List, Optional, Sequence, Union
from uuid import uuid4

from fastapi import APIRouter, HTTPException, Path
Expand All @@ -21,10 +21,11 @@ class ThreadPutRequest(BaseModel):
assistant_id: str = Field(..., description="The ID of the assistant to use.")


class ThreadMessagesPostRequest(BaseModel):
class ThreadPostRequest(BaseModel):
"""Payload for adding messages to a thread."""

messages: Sequence[AnyMessage]
values: Optional[Union[Dict[str, Any], Sequence[AnyMessage]]]
config: Optional[Dict[str, Any]] = None


@router.get("/")
Expand All @@ -33,23 +34,25 @@ async def list_threads(opengpts_user_id: OpengptsUserId) -> List[Thread]:
return await storage.list_threads(opengpts_user_id)


@router.get("/{tid}/messages")
async def get_thread_messages(
@router.get("/{tid}/state")
async def get_thread_state(
opengpts_user_id: OpengptsUserId,
tid: ThreadID,
):
"""Get all messages for a thread."""
return await storage.get_thread_messages(opengpts_user_id, tid)
return await storage.get_thread_state(opengpts_user_id, tid)


@router.post("/{tid}/messages")
async def add_thread_messages(
@router.post("/{tid}/state")
async def update_thread_state(
payload: ThreadPostRequest,
opengpts_user_id: OpengptsUserId,
tid: ThreadID,
payload: ThreadMessagesPostRequest,
):
"""Add messages to a thread."""
return await storage.post_thread_messages(opengpts_user_id, tid, payload.messages)
return await storage.update_thread_state(
payload.config or {"configurable": {"thread_id": tid}}, payload.values
)


@router.get("/{tid}/history")
Expand Down
22 changes: 11 additions & 11 deletions backend/app/storage.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import datetime, timezone
from typing import List, Optional, Sequence
from typing import Any, List, Optional, Sequence, Union

from langchain_core.messages import AnyMessage
from langchain_core.runnables import RunnableConfig

from app.agent import AgentType, get_agent_executor
from app.lifespan import get_pg_pool
Expand Down Expand Up @@ -98,37 +99,36 @@ async def get_thread(user_id: str, thread_id: str) -> Optional[Thread]:
)


async def get_thread_messages(user_id: str, thread_id: str):
async def get_thread_state(user_id: str, thread_id: str):
"""Get all messages for a thread."""
app = get_agent_executor([], AgentType.GPT_35_TURBO, "", False)
state = await app.aget_state({"configurable": {"thread_id": thread_id}})
return {
"messages": state.values,
"resumeable": bool(state.next),
"values": state.values,
"next": state.next,
}


async def post_thread_messages(
user_id: str, thread_id: str, messages: Sequence[AnyMessage]
async def update_thread_state(
config: RunnableConfig, messages: Union[Sequence[AnyMessage], dict[str, Any]]
):
"""Add messages to a thread."""
app = get_agent_executor([], AgentType.GPT_35_TURBO, "", False)
await app.aupdate_state({"configurable": {"thread_id": thread_id}}, messages)
return await app.aupdate_state(config, messages)


async def get_thread_history(user_id: str, thread_id: str):
"""Get the history of a thread."""
app = get_agent_executor([], AgentType.GPT_35_TURBO, "", False)
config = {"configurable": {"thread_id": thread_id}}
return [
{
"values": c.values,
"resumeable": bool(c.next),
"next": c.next,
"config": c.config,
"parent": c.parent_config,
}
async for c in app.aget_state_history(
{"configurable": {"thread_id": thread_id}}
)
async for c in app.aget_state_history(config)
]


Expand Down
4 changes: 2 additions & 2 deletions backend/tests/unit_tests/app/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,9 @@ async def test_threads() -> None:
)
assert response.status_code == 200, response.text

response = await client.get(f"/threads/{tid}/messages", headers=headers)
response = await client.get(f"/threads/{tid}/state", headers=headers)
assert response.status_code == 200
assert response.json() == {"messages": [], "resumeable": False}
assert response.json() == {"values": [], "resumeable": False}

response = await client.get("/threads/", headers=headers)

Expand Down
4 changes: 4 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "frontend",
"private": true,
"version": "0.0.0",
"packageManager": "yarn@1.22.19",
"type": "module",
"scripts": {
"dev": "vite --host",
Expand All @@ -11,9 +12,12 @@
"format": "prettier -w src"
},
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"@microsoft/fetch-event-source": "^2.0.1",
"@mui/material": "^5.15.14",
"@tailwindcss/forms": "^0.5.6",
"@tailwindcss/typography": "^0.5.10",
"clsx": "^2.0.0",
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ function App(props: { edit?: boolean }) {
const { currentChat, assistantConfig, isLoading } = useThreadAndAssistant();

const startTurn = useCallback(
async (message: MessageWithFiles | null, thread_id: string) => {
async (
message: MessageWithFiles | null,
thread_id: string,
config?: Record<string, unknown>,
) => {
const files = message?.files || [];
if (files.length > 0) {
const formData = files.reduce((formData, file) => {
Expand Down Expand Up @@ -56,6 +60,7 @@ function App(props: { edit?: boolean }) {
]
: null,
thread_id,
config,
);
},
[startStream],
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/assets/EmptyState.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions frontend/src/components/AutosizeTextarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Ref } from "react";
import { cn } from "../utils/cn";

const COMMON_CLS = cn(
"text-sm col-[1] row-[1] m-0 resize-none overflow-hidden whitespace-pre-wrap break-words bg-transparent px-2 py-1 rounded shadow-none",
);

export function AutosizeTextarea(props: {
id?: string;
inputRef?: Ref<HTMLTextAreaElement>;
value?: string | null | undefined;
placeholder?: string;
className?: string;
onChange?: (e: string) => void;
onFocus?: () => void;
onBlur?: () => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
autoFocus?: boolean;
readOnly?: boolean;
cursorPointer?: boolean;
disabled?: boolean;
fullHeight?: boolean;
}) {
return (
<div
className={
cn("grid w-full", props.className) +
(props.fullHeight ? "" : " max-h-80 overflow-auto ")
}
>
<textarea
ref={props.inputRef}
id={props.id}
className={cn(
COMMON_CLS,
"text-transparent caret-black rounded focus:outline-0 focus:ring-0",
)}
disabled={props.disabled}
value={props.value ?? ""}
rows={1}
onChange={(e) => {
const target = e.target as HTMLTextAreaElement;
props.onChange?.(target.value);
}}
onFocus={props.onFocus}
onBlur={props.onBlur}
placeholder={props.placeholder}
readOnly={props.readOnly}
autoFocus={props.autoFocus && !props.readOnly}
onKeyDown={props.onKeyDown}
/>
<div
aria-hidden
className={cn(COMMON_CLS, "pointer-events-none select-none")}
>
{props.value}{" "}
</div>
</div>
);
}
18 changes: 13 additions & 5 deletions frontend/src/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ import { ArrowDownCircleIcon } from "@heroicons/react/24/outline";
import { MessageWithFiles } from "../utils/formTypes.ts";
import { useParams } from "react-router-dom";
import { useThreadAndAssistant } from "../hooks/useThreadAndAssistant.ts";
// import { useHistories } from "../hooks/useHistories.ts";
// import { Timeline } from "./Timeline.tsx";
// import { deepEquals } from "../utils/equals.ts";

interface ChatProps extends Pick<StreamStateProps, "stream" | "stopStream"> {
interface ChatProps
extends Pick<
StreamStateProps,
"stream" | "stopStream" | "streamErrorMessage"
> {
startStream: (
message: MessageWithFiles | null,
thread_id: string,
Expand All @@ -26,7 +33,7 @@ function usePrevious<T>(value: T): T | undefined {

export function Chat(props: ChatProps) {
const { chatId } = useParams();
const { messages, resumeable } = useChatMessages(
const { messages, next } = useChatMessages(
chatId ?? null,
props.stream,
props.stopStream,
Expand Down Expand Up @@ -67,12 +74,13 @@ export function Chat(props: ChatProps) {
...
</div>
)}
{props.stream?.status === "error" && (
{(props.streamErrorMessage || props.stream?.status === "error") && (
<div className="flex items-center rounded-md bg-yellow-50 px-2 py-1 text-xs font-medium text-yellow-800 ring-1 ring-inset ring-yellow-600/20">
An error has occurred. Please try again.
{props.streamErrorMessage ??
"An error has occurred. Please try again."}
</div>
)}
{resumeable && props.stream?.status !== "inflight" && (
{next.length && props.stream?.status !== "inflight" && (
<div
className="flex items-center rounded-md bg-blue-50 px-2 py-1 text-xs font-medium text-blue-800 ring-1 ring-inset ring-yellow-600/20 cursor-pointer"
onClick={() =>
Expand Down
Loading

0 comments on commit b67e318

Please sign in to comment.