open-swe/apps/web/src/components/thread/messages/human.tsx

160 lines
4.5 KiB
TypeScript
Raw Normal View History

2025-05-28 12:26:47 -07: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";
import { MultimodalPreview } from "@/components/thread/MultimodalPreview";
import { isBase64ContentBlock } from "@/lib/multimodal-utils";
feat: Update chat UI to be Open SWE specific (#76) (#91) * init repo-selector * add branch selector UI, extend GH hook w branch info * add reconnectOnMount: true resumeStream, remove repo description from selector, fix styling * styling repo + branch * udpate getBranch calls to include targetRepo arg * format * add search to repo + branch selectors * add settings icon link to /github for dev convenience * add default branch selection * format * minor cleanup * init task list UI demo * individual task component * tasks real data * task visual improvement * persisting tasks, navigate to thread onClick * task UI * TaskSidebar, Navigate to tasks * org tasks by thread * group by thread * improve sidebar, task-list, rm github modal on refresh * collapse sidebar works when viewing a thread * remove micro-tasks expansion from threads component, change taskId to taskIndex for persistence + simplicity * init config sidebar ui * open and close config sidebar * add ref for getAllTasks + add graph_id metadata * format * create threadItem component * format * add threadUtils * centralize + refactor types * format * improve thread status, realtime updates * fix scroll issue * remove temperature from config-sidebar * Change model via sidebar * fix flashing on updates * init plan UI * centralize types * self CR: refactoring, cleanup, remove comments, add todos * CR: clean comments + logs, fix repo selector bug * fix: Update example env file in web (#77) * disable auto scroll on pageload * reposelector: use default branch not main master * remove archived tab temp, default branch listed first in branchSelector dropdown * auto select first repo * improve status indicator * refactor status indicator * simplify status implementation to use Langgraph * util moved from provider to thread-utils * linting, improve task completion logic * feat: Support followup requests (#60) * feat: Support followup requests * cr * cr * fix * account for pr already exists * remove task provider * fix: Better prompting and context provided for codebase structure awareness (#85) * fix: Better prompting and context provided for codebase structure awareness * cr * linting + skeleton loader * fix: branch selector, add github api pagination * small branch update * update task completion status realtime * threadItem stream remove flashing/polling * feat: Create a shared package (#89) * feat: Create a shared package * formatting * fix: drop passing target repo to getBranchName * drop docs * use shared package for config fields in config sidebar * fix: styling and structure for repo/branch selectors * fix: use shared types/objects for config fields * default key for configs * fix: improved styling for threads * fix renering num tasks * disable repo branch selector when chat started * cr * cr * bump deps * fix: rendering plan in interrupt * cr * cr --------- Co-authored-by: Dylan Boudro <121908331+starmorph@users.noreply.github.com>
2025-06-08 17:29:40 -07:00
import { BaseMessage } from "@langchain/core/messages";
2025-05-28 12:26:47 -07:00
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}
className="focus-visible:ring-0"
/>
);
}
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);
const newMessage = {
type: "human",
content: value,
} as unknown as BaseMessage;
2025-05-28 12:26:47 -07:00
thread.submit(
{ messages: [newMessage], internalMessages: [newMessage] },
2025-05-28 12:26:47 -07:00
{
checkpoint: parentCheckpoint,
streamMode: ["values"],
optimisticValues: (prev) => {
const values = meta?.firstSeenState?.values;
if (!values) return prev;
return {
...values,
feat: Update chat UI to be Open SWE specific (#76) (#91) * init repo-selector * add branch selector UI, extend GH hook w branch info * add reconnectOnMount: true resumeStream, remove repo description from selector, fix styling * styling repo + branch * udpate getBranch calls to include targetRepo arg * format * add search to repo + branch selectors * add settings icon link to /github for dev convenience * add default branch selection * format * minor cleanup * init task list UI demo * individual task component * tasks real data * task visual improvement * persisting tasks, navigate to thread onClick * task UI * TaskSidebar, Navigate to tasks * org tasks by thread * group by thread * improve sidebar, task-list, rm github modal on refresh * collapse sidebar works when viewing a thread * remove micro-tasks expansion from threads component, change taskId to taskIndex for persistence + simplicity * init config sidebar ui * open and close config sidebar * add ref for getAllTasks + add graph_id metadata * format * create threadItem component * format * add threadUtils * centralize + refactor types * format * improve thread status, realtime updates * fix scroll issue * remove temperature from config-sidebar * Change model via sidebar * fix flashing on updates * init plan UI * centralize types * self CR: refactoring, cleanup, remove comments, add todos * CR: clean comments + logs, fix repo selector bug * fix: Update example env file in web (#77) * disable auto scroll on pageload * reposelector: use default branch not main master * remove archived tab temp, default branch listed first in branchSelector dropdown * auto select first repo * improve status indicator * refactor status indicator * simplify status implementation to use Langgraph * util moved from provider to thread-utils * linting, improve task completion logic * feat: Support followup requests (#60) * feat: Support followup requests * cr * cr * fix * account for pr already exists * remove task provider * fix: Better prompting and context provided for codebase structure awareness (#85) * fix: Better prompting and context provided for codebase structure awareness * cr * linting + skeleton loader * fix: branch selector, add github api pagination * small branch update * update task completion status realtime * threadItem stream remove flashing/polling * feat: Create a shared package (#89) * feat: Create a shared package * formatting * fix: drop passing target repo to getBranchName * drop docs * use shared package for config fields in config sidebar * fix: styling and structure for repo/branch selectors * fix: use shared types/objects for config fields * default key for configs * fix: improved styling for threads * fix renering num tasks * disable repo branch selector when chat started * cr * cr * bump deps * fix: rendering plan in interrupt * cr * cr --------- Co-authored-by: Dylan Boudro <121908331+starmorph@users.noreply.github.com>
2025-06-08 17:29:40 -07:00
messages: [
...(values.messages ?? []),
newMessage,
] as unknown as BaseMessage[],
2025-05-28 12:26:47 -07:00
};
},
2025-05-28 13:25:30 -07:00
config: {
recursion_limit: 400,
},
2025-05-28 12:26:47 -07:00
},
);
};
return (
<div
className={cn(
"group ml-auto flex items-center gap-2",
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}
/>
) : (
<div className="flex flex-col gap-2">
{/* Render images and files if no text */}
{Array.isArray(message.content) && message.content.length > 0 && (
<div className="flex flex-wrap items-end justify-end gap-2">
{message.content.reduce<React.ReactNode[]>(
(acc, block, idx) => {
if (isBase64ContentBlock(block)) {
acc.push(
<MultimodalPreview
key={idx}
block={block}
size="md"
/>,
);
}
return acc;
},
[],
)}
</div>
)}
{/* Render text if present, otherwise fallback to file/image name */}
{contentString ? (
<p className="bg-muted ml-auto w-fit rounded-3xl px-4 py-2 whitespace-pre-wrap">
2025-05-28 12:26:47 -07:00
{contentString}
</p>
) : null}
</div>
)}
<div
className={cn(
"ml-auto flex items-center gap-2 transition-opacity",
"opacity-0 group-focus-within:opacity-100 group-hover:opacity-100",
isEditing && "opacity-100",
)}
>
<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>
);
}