Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
nfcampos committed Apr 15, 2024
1 parent 0dab4d6 commit 38d4488
Show file tree
Hide file tree
Showing 5 changed files with 549 additions and 36 deletions.
104 changes: 78 additions & 26 deletions frontend/src/components/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { StreamStateProps } from "../hooks/useStreamState";
import { useChatMessages } from "../hooks/useChatMessages";
import TypingBox from "./TypingBox";
import { MessageViewer } from "./Message";
import { ArrowDownCircleIcon } from "@heroicons/react/24/outline";
import {
ArrowDownCircleIcon,
CheckCircleIcon,
} 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";
import { useMessageEditing } from "../hooks/useMessageEditing.ts";
import { MessageEditor } from "./MessageEditor.tsx";
import { Message } from "../types.ts";

interface ChatProps
extends Pick<
Expand All @@ -30,15 +33,49 @@ function usePrevious<T>(value: T): T | undefined {
return ref.current;
}

function CommitEdits(props: {
editing: Record<string, Message>;
commitEdits: () => Promise<void>;
}) {
const [inflight, setInflight] = useState(false);
return (
<div className="bg-blue-50 text-blue-800 rounded-md ring-1 ring-inset ring-blue-800/60 flex flex-row h-9 items-center">
<div className="flex-1 rounded-l-md pl-4">
{Object.keys(props.editing).length} message(s) edited.
</div>
<button
onClick={async () => {
setInflight(true);
await props.commitEdits();
setInflight(false);
}}
className={
"self-stretch -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 " +
"text-sm font-semibold ring-1 ring-inset ring-blue-800/60 hover:bg-blue-100 "
}
>
<CheckCircleIcon
className="w-6 h-6 cursor-pointer stroke-2 opacity-80 hover:opacity-100 transition-opacity duration-100"
onMouseUp={props.commitEdits}
/>

{inflight ? "Saving..." : "Save"}
</button>
</div>
);
}

export function Chat(props: ChatProps) {
const { chatId } = useParams();
const { messages, next } = useChatMessages(
chatId ?? null,
props.stream,
props.stopStream,
);

const { currentChat, assistantConfig, isLoading } = useThreadAndAssistant();
const { editing, recordEdits, commitEdits, abandonEdits } = useMessageEditing(
currentChat?.thread_id,
);

const prevMessages = usePrevious(messages);
useEffect(() => {
Expand All @@ -57,17 +94,28 @@ export function Chat(props: ChatProps) {

return (
<div className="flex-1 flex flex-col items-stretch pb-[76px] pt-2">
{messages?.map((msg, i) => (
<MessageViewer
{...msg}
key={msg.id}
runId={
i === messages.length - 1 && props.stream?.status === "done"
? props.stream?.run_id
: undefined
}
/>
))}
{messages?.map((msg, i) =>
editing[msg.id] ? (
<MessageEditor
{...msg}
key={msg.id}
onUpdate={recordEdits}
abandonEdits={() => abandonEdits(msg)}
/>
) : (
<MessageViewer
{...msg}
key={msg.id}
runId={
i === messages.length - 1 && props.stream?.status === "done"
? props.stream?.run_id
: undefined
}
startEditing={() => recordEdits(msg)}
alwaysShowControls={i === messages.length - 1}
/>
),
)}
{(props.stream?.status === "inflight" || messages === null) && (
<div className="leading-6 mb-2 animate-pulse font-black text-gray-400 text-lg">
...
Expand All @@ -89,15 +137,19 @@ export function Chat(props: ChatProps) {
</div>
)}
<div className="fixed left-0 lg:left-72 bottom-0 right-0 p-4">
<TypingBox
onSubmit={(msg) => props.startStream(msg, currentChat.thread_id)}
onInterrupt={
props.stream?.status === "inflight" ? props.stopStream : undefined
}
inflight={props.stream?.status === "inflight"}
currentConfig={assistantConfig}
currentChat={currentChat}
/>
{commitEdits && Object.keys(editing).length > 0 ? (
<CommitEdits editing={editing} commitEdits={commitEdits} />
) : (
<TypingBox
onSubmit={(msg) => props.startStream(msg, currentChat.thread_id)}
onInterrupt={
props.stream?.status === "inflight" ? props.stopStream : undefined
}
inflight={props.stream?.status === "inflight"}
currentConfig={assistantConfig}
currentChat={currentChat}
/>
)}
</div>
</div>
);
Expand Down
35 changes: 26 additions & 9 deletions frontend/src/components/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { memo, useState } from "react";
import { MessageDocument, Message as MessageType, ToolCall } from "../types";
import { str } from "../utils/str";
import { cn } from "../utils/cn";
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { ChevronDownIcon, PencilSquareIcon } from "@heroicons/react/24/outline";
import { LangSmithActions } from "./LangSmithActions";
import { DocumentList } from "./Document";
import { omit } from "lodash";
Expand Down Expand Up @@ -136,7 +136,11 @@ export function MessageContent(props: { content: MessageType["content"] }) {
}

export const MessageViewer = memo(function (
props: MessageType & { runId?: string },
props: MessageType & {
runId?: string;
startEditing?: () => void;
alwaysShowControls?: boolean;
},
) {
const [open, setOpen] = useState(false);
const contentIsDocuments =
Expand All @@ -147,15 +151,28 @@ export const MessageViewer = memo(function (
? open
: true;
return (
<div className="flex flex-col mb-8">
<div className="flex flex-col mb-8 group">
<div className="leading-6 flex flex-row">
<div
className={cn(
"font-medium text-sm text-gray-400 uppercase mr-2 mt-1 w-28 flex flex-col",
props.type === "function" && "mt-2",
<div className="flex flex-row space-between mr-4">
<div
className={cn(
"font-medium text-sm text-gray-400 uppercase mt-1 w-28 flex flex-col",
props.type === "function" && "mt-2",
)}
>
{props.type}
</div>
{props.startEditing && (
<PencilSquareIcon
className={cn(
"w-6 h-6 cursor-pointer shrink-0 transition-opacity duration-100",
props.alwaysShowControls
? "opacity-40 hover:opacity-90"
: "opacity-0 group-hover:opacity-40 group-hover:hover:opacity-90",
)}
onMouseUp={props.startEditing}
/>
)}
>
{props.type}
</div>
<div className="flex-1">
{["function", "tool"].includes(props.type) && (
Expand Down
Loading

0 comments on commit 38d4488

Please sign in to comment.