2025-03-03 12:31:27 -08:00
|
|
|
import { useStreamContext } from "@/providers/Stream";
|
|
|
|
|
import { Message } from "@langchain/langgraph-sdk";
|
|
|
|
|
import { useState } from "react";
|
|
|
|
|
import { getContentString } from "../utils";
|
|
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
|
|
|
import { BranchSwitcher, CommandBar } from "./shared";
|
|
|
|
|
|
|
|
|
|
function EditableContent({
|
|
|
|
|
value,
|
|
|
|
|
setValue,
|
|
|
|
|
onSubmit,
|
|
|
|
|
}: {
|
|
|
|
|
value: string;
|
|
|
|
|
setValue: React.Dispatch<React.SetStateAction<string>>;
|
|
|
|
|
onSubmit: () => void;
|
|
|
|
|
}) {
|
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
|
|
|
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
onSubmit();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Textarea
|
|
|
|
|
value={value}
|
|
|
|
|
onChange={(e) => setValue(e.target.value)}
|
|
|
|
|
onKeyDown={handleKeyDown}
|
2025-03-04 14:37:39 +01:00
|
|
|
className="focus-visible:ring-0"
|
2025-03-03 12:31:27 -08:00
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function HumanMessage({
|
|
|
|
|
message,
|
|
|
|
|
isLoading,
|
|
|
|
|
}: {
|
|
|
|
|
message: Message;
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
}) {
|
|
|
|
|
const thread = useStreamContext();
|
|
|
|
|
const meta = thread.getMessagesMetadata(message);
|
|
|
|
|
const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
|
|
|
|
|
|
|
|
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
|
|
|
const [value, setValue] = useState("");
|
|
|
|
|
const contentString = getContentString(message.content);
|
|
|
|
|
|
|
|
|
|
const handleSubmitEdit = () => {
|
|
|
|
|
setIsEditing(false);
|
2025-03-04 14:47:16 +01:00
|
|
|
|
|
|
|
|
const newMessage: Message = { type: "human", content: value };
|
2025-03-03 12:31:27 -08:00
|
|
|
thread.submit(
|
2025-03-04 14:47:16 +01:00
|
|
|
{ messages: [newMessage] },
|
|
|
|
|
{
|
|
|
|
|
checkpoint: parentCheckpoint,
|
|
|
|
|
streamMode: ["values"],
|
|
|
|
|
optimisticValues: (prev) => {
|
|
|
|
|
const values = meta?.firstSeenState?.values;
|
|
|
|
|
if (!values) return prev;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...values,
|
|
|
|
|
messages: [...(values.messages ?? []), newMessage],
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
},
|
2025-03-03 12:31:27 -08:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
2025-03-04 14:37:39 +01:00
|
|
|
"flex items-center ml-auto gap-2 group",
|
2025-03-03 12:31:27 -08:00
|
|
|
isEditing && "w-full max-w-xl",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<div className={cn("flex flex-col gap-2", isEditing && "w-full")}>
|
|
|
|
|
{isEditing ? (
|
|
|
|
|
<EditableContent
|
|
|
|
|
value={value}
|
|
|
|
|
setValue={setValue}
|
|
|
|
|
onSubmit={handleSubmitEdit}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
2025-03-04 14:37:39 +01:00
|
|
|
<p className="text-right py-1">{contentString}</p>
|
2025-03-03 12:31:27 -08:00
|
|
|
)}
|
2025-03-04 14:37:39 +01:00
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"flex gap-2 items-center ml-auto transition-opacity",
|
|
|
|
|
"opacity-0 group-focus-within:opacity-100 group-hover:opacity-100",
|
|
|
|
|
isEditing && "opacity-100",
|
|
|
|
|
)}
|
|
|
|
|
>
|
2025-03-03 12:31:27 -08:00
|
|
|
<BranchSwitcher
|
|
|
|
|
branch={meta?.branch}
|
|
|
|
|
branchOptions={meta?.branchOptions}
|
|
|
|
|
onSelect={(branch) => thread.setBranch(branch)}
|
|
|
|
|
isLoading={isLoading}
|
|
|
|
|
/>
|
|
|
|
|
<CommandBar
|
|
|
|
|
isLoading={isLoading}
|
|
|
|
|
content={contentString}
|
|
|
|
|
isEditing={isEditing}
|
|
|
|
|
setIsEditing={(c) => {
|
|
|
|
|
if (c) {
|
|
|
|
|
setValue(contentString);
|
|
|
|
|
}
|
|
|
|
|
setIsEditing(c);
|
|
|
|
|
}}
|
|
|
|
|
handleSubmitEdit={handleSubmitEdit}
|
|
|
|
|
isHumanMessage={true}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|