2025-05-28 12:26:47 -07:00
|
|
|
import { parsePartialJson } from "@langchain/core/output_parsers";
|
2025-06-17 09:37:52 -07:00
|
|
|
import {
|
|
|
|
|
AIMessage,
|
|
|
|
|
Checkpoint,
|
|
|
|
|
Message,
|
|
|
|
|
ToolMessage,
|
|
|
|
|
} from "@langchain/langgraph-sdk";
|
2025-05-28 12:26:47 -07:00
|
|
|
import { getContentString } from "../utils";
|
|
|
|
|
import { BranchSwitcher, CommandBar } from "./shared";
|
|
|
|
|
import { MarkdownText } from "../markdown-text";
|
2025-06-18 11:03:39 -07:00
|
|
|
import {
|
|
|
|
|
LoadExternalComponent,
|
|
|
|
|
UIMessage,
|
|
|
|
|
} from "@langchain/langgraph-sdk/react-ui";
|
2025-05-28 12:26:47 -07:00
|
|
|
import { cn } from "@/lib/utils";
|
|
|
|
|
import { ToolCalls, ToolResult } from "./tool-calls";
|
|
|
|
|
import { MessageContentComplex } from "@langchain/core/messages";
|
|
|
|
|
import { Fragment } from "react/jsx-runtime";
|
|
|
|
|
import { useQueryState, parseAsBoolean } from "nuqs";
|
2025-06-06 17:14:15 -07:00
|
|
|
import { Interrupt } from "./interrupt";
|
2025-06-22 12:14:56 -07:00
|
|
|
import { ActionStep, ActionItemProps } from "@/components/gen-ui/action-step";
|
2025-06-22 12:51:57 -07:00
|
|
|
import { TaskSummary } from "@/components/gen-ui/task-summary";
|
2025-06-17 09:37:52 -07:00
|
|
|
import { ToolCall } from "@langchain/core/messages/tool";
|
|
|
|
|
import {
|
|
|
|
|
createApplyPatchToolFields,
|
|
|
|
|
createShellToolFields,
|
2025-06-22 12:51:57 -07:00
|
|
|
createSetTaskStatusToolFields,
|
2025-06-22 14:04:58 -07:00
|
|
|
createRgToolFields,
|
2025-06-22 15:47:29 -07:00
|
|
|
createInstallDependenciesToolFields,
|
2025-06-17 09:37:52 -07:00
|
|
|
} from "@open-swe/shared/open-swe/tools";
|
|
|
|
|
import { z } from "zod";
|
|
|
|
|
import { isAIMessageSDK, isToolMessageSDK } from "@/lib/langchain-messages";
|
2025-06-18 11:03:39 -07:00
|
|
|
import { useStream } from "@langchain/langgraph-sdk/react";
|
2025-06-17 09:37:52 -07:00
|
|
|
|
|
|
|
|
// Used only for Zod type inference.
|
|
|
|
|
const dummyRepo = { owner: "dummy", repo: "dummy" };
|
|
|
|
|
const shellTool = createShellToolFields(dummyRepo);
|
|
|
|
|
type ShellToolArgs = z.infer<typeof shellTool.schema>;
|
|
|
|
|
const applyPatchTool = createApplyPatchToolFields(dummyRepo);
|
|
|
|
|
type ApplyPatchToolArgs = z.infer<typeof applyPatchTool.schema>;
|
2025-06-22 12:51:57 -07:00
|
|
|
const setTaskStatusTool = createSetTaskStatusToolFields();
|
|
|
|
|
type SetTaskStatusToolArgs = z.infer<typeof setTaskStatusTool.schema>;
|
2025-06-22 14:04:58 -07:00
|
|
|
const rgTool = createRgToolFields(dummyRepo);
|
|
|
|
|
type RgToolArgs = z.infer<typeof rgTool.schema>;
|
2025-06-22 15:47:29 -07:00
|
|
|
const installDependenciesTool = createInstallDependenciesToolFields(dummyRepo);
|
|
|
|
|
type InstallDependenciesToolArgs = z.infer<
|
|
|
|
|
typeof installDependenciesTool.schema
|
|
|
|
|
>;
|
2025-05-28 12:26:47 -07:00
|
|
|
|
|
|
|
|
function CustomComponent({
|
|
|
|
|
message,
|
|
|
|
|
thread,
|
|
|
|
|
}: {
|
|
|
|
|
message: Message;
|
2025-06-18 11:03:39 -07:00
|
|
|
thread: ReturnType<typeof useStream>;
|
2025-05-28 12:26:47 -07:00
|
|
|
}) {
|
2025-06-18 11:03:39 -07:00
|
|
|
const values = thread.values;
|
|
|
|
|
const customComponents =
|
|
|
|
|
"ui" in values
|
|
|
|
|
? (values.ui as UIMessage[]).filter(
|
|
|
|
|
(ui) => ui.metadata?.message_id === message.id,
|
|
|
|
|
)
|
|
|
|
|
: [];
|
2025-05-28 12:26:47 -07:00
|
|
|
|
|
|
|
|
if (!customComponents?.length) return null;
|
|
|
|
|
return (
|
|
|
|
|
<Fragment key={message.id}>
|
|
|
|
|
{customComponents.map((customComponent) => (
|
|
|
|
|
<LoadExternalComponent
|
|
|
|
|
key={customComponent.id}
|
|
|
|
|
stream={thread}
|
|
|
|
|
message={customComponent}
|
2025-06-18 11:03:39 -07:00
|
|
|
meta={{ ui: customComponent }}
|
2025-05-28 12:26:47 -07:00
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</Fragment>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseAnthropicStreamedToolCalls(
|
|
|
|
|
content: MessageContentComplex[],
|
|
|
|
|
): AIMessage["tool_calls"] {
|
|
|
|
|
const toolCallContents = content.filter((c) => c.type === "tool_use" && c.id);
|
|
|
|
|
|
|
|
|
|
return toolCallContents.map((tc) => {
|
|
|
|
|
const toolCall = tc as Record<string, any>;
|
|
|
|
|
let json: Record<string, any> = {};
|
|
|
|
|
if (toolCall?.input) {
|
|
|
|
|
try {
|
|
|
|
|
json = parsePartialJson(toolCall.input) ?? {};
|
|
|
|
|
} catch {
|
|
|
|
|
// Pass
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
name: toolCall.name ?? "",
|
|
|
|
|
id: toolCall.id ?? "",
|
|
|
|
|
args: json,
|
|
|
|
|
type: "tool_call",
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-17 09:37:52 -07:00
|
|
|
export function mapToolMessageToActionStepProps(
|
|
|
|
|
message: ToolMessage,
|
|
|
|
|
thread: { messages: Message[] },
|
2025-06-22 12:14:56 -07:00
|
|
|
): ActionItemProps {
|
2025-06-17 09:37:52 -07:00
|
|
|
const toolCall: ToolCall | undefined = thread.messages
|
|
|
|
|
.filter(isAIMessageSDK)
|
|
|
|
|
.flatMap((m) => m.tool_calls ?? [])
|
|
|
|
|
.find((tc) => tc.id === message.tool_call_id);
|
|
|
|
|
|
|
|
|
|
const aiMessage = thread.messages
|
|
|
|
|
.filter(isAIMessageSDK)
|
|
|
|
|
.find((m) => m.tool_calls?.some((tc) => tc.id === message.tool_call_id));
|
|
|
|
|
const reasoningText = aiMessage
|
|
|
|
|
? getContentString(aiMessage.content)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
2025-06-22 12:14:56 -07:00
|
|
|
const status: ActionItemProps["status"] = "done";
|
2025-06-17 09:37:52 -07:00
|
|
|
const success = message.status === "success";
|
|
|
|
|
|
|
|
|
|
if (toolCall?.name === shellTool.name) {
|
|
|
|
|
const args = toolCall.args as ShellToolArgs;
|
|
|
|
|
return {
|
|
|
|
|
actionType: shellTool.name as "shell",
|
|
|
|
|
status,
|
|
|
|
|
success,
|
|
|
|
|
command: args.command || [],
|
|
|
|
|
workdir: args.workdir,
|
|
|
|
|
output: getContentString(message.content),
|
|
|
|
|
reasoningText,
|
|
|
|
|
};
|
|
|
|
|
} else if (toolCall?.name === applyPatchTool.name) {
|
|
|
|
|
const args = toolCall.args as ApplyPatchToolArgs;
|
|
|
|
|
return {
|
|
|
|
|
actionType: "apply-patch",
|
|
|
|
|
status,
|
|
|
|
|
success,
|
2025-06-19 12:57:27 -07:00
|
|
|
file_path: args.file_path || "",
|
2025-06-17 09:37:52 -07:00
|
|
|
diff: args.diff,
|
|
|
|
|
reasoningText,
|
|
|
|
|
errorMessage: !success ? getContentString(message.content) : undefined,
|
|
|
|
|
};
|
2025-06-22 14:04:58 -07:00
|
|
|
} else if (toolCall?.name === rgTool.name) {
|
|
|
|
|
const args = toolCall.args as RgToolArgs;
|
|
|
|
|
return {
|
|
|
|
|
actionType: "rg",
|
|
|
|
|
status,
|
|
|
|
|
success,
|
|
|
|
|
pattern: args.pattern || "",
|
|
|
|
|
paths: args.paths || [],
|
|
|
|
|
output: getContentString(message.content),
|
|
|
|
|
reasoningText,
|
|
|
|
|
};
|
2025-06-22 15:47:29 -07:00
|
|
|
} else if (toolCall?.name === installDependenciesTool.name) {
|
|
|
|
|
const args = toolCall.args as InstallDependenciesToolArgs;
|
|
|
|
|
return {
|
|
|
|
|
actionType: "install_dependencies",
|
|
|
|
|
status,
|
|
|
|
|
success,
|
|
|
|
|
command: args.command || "",
|
|
|
|
|
workdir: args.workdir || "",
|
|
|
|
|
output: getContentString(message.content),
|
|
|
|
|
reasoningText,
|
|
|
|
|
};
|
2025-06-17 09:37:52 -07:00
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
status: "loading",
|
|
|
|
|
summaryText: reasoningText,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-28 12:26:47 -07:00
|
|
|
export function AssistantMessage({
|
|
|
|
|
message,
|
|
|
|
|
isLoading,
|
|
|
|
|
handleRegenerate,
|
2025-06-06 17:14:15 -07:00
|
|
|
forceRenderInterrupt = false,
|
2025-06-18 11:03:39 -07:00
|
|
|
thread,
|
2025-05-28 12:26:47 -07:00
|
|
|
}: {
|
|
|
|
|
message: Message | undefined;
|
|
|
|
|
isLoading: boolean;
|
|
|
|
|
handleRegenerate: (parentCheckpoint: Checkpoint | null | undefined) => void;
|
2025-06-06 17:14:15 -07:00
|
|
|
forceRenderInterrupt?: boolean;
|
2025-06-18 11:03:39 -07:00
|
|
|
thread: ReturnType<typeof useStream<Record<string, unknown>>>;
|
2025-05-28 12:26:47 -07:00
|
|
|
}) {
|
|
|
|
|
const content = message?.content ?? [];
|
|
|
|
|
const contentString = getContentString(content);
|
|
|
|
|
const [hideToolCalls] = useQueryState(
|
|
|
|
|
"hideToolCalls",
|
|
|
|
|
parseAsBoolean.withDefault(false),
|
|
|
|
|
);
|
|
|
|
|
|
2025-06-17 09:37:52 -07:00
|
|
|
const messages = thread.messages;
|
|
|
|
|
const idx = message ? messages.findIndex((m) => m.id === message.id) : -1;
|
|
|
|
|
|
2025-05-28 12:26:47 -07:00
|
|
|
const meta = message ? thread.getMessagesMetadata(message) : undefined;
|
|
|
|
|
const threadInterrupt = thread.interrupt;
|
|
|
|
|
const parentCheckpoint = meta?.firstSeenState?.parent_checkpoint;
|
|
|
|
|
const anthropicStreamedToolCalls = Array.isArray(content)
|
|
|
|
|
? parseAnthropicStreamedToolCalls(content)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
2025-06-22 12:14:56 -07:00
|
|
|
const aiToolCalls: ToolCall[] = (() => {
|
2025-06-17 09:37:52 -07:00
|
|
|
if (message && isAIMessageSDK(message)) {
|
2025-06-22 12:14:56 -07:00
|
|
|
return message.tool_calls || [];
|
2025-06-17 09:37:52 -07:00
|
|
|
}
|
|
|
|
|
if (anthropicStreamedToolCalls?.length) {
|
2025-06-22 12:14:56 -07:00
|
|
|
return anthropicStreamedToolCalls;
|
2025-06-17 09:37:52 -07:00
|
|
|
}
|
2025-06-22 12:14:56 -07:00
|
|
|
return [];
|
2025-06-17 09:37:52 -07:00
|
|
|
})();
|
|
|
|
|
|
2025-06-22 12:14:56 -07:00
|
|
|
const toolResults = aiToolCalls
|
|
|
|
|
.map((toolCall) => {
|
|
|
|
|
const matchingToolMessage = messages.find(
|
|
|
|
|
(m) => isToolMessageSDK(m) && m.tool_call_id === toolCall.id,
|
|
|
|
|
);
|
2025-06-17 09:37:52 -07:00
|
|
|
|
2025-06-22 12:14:56 -07:00
|
|
|
return matchingToolMessage as ToolMessage | undefined;
|
|
|
|
|
})
|
|
|
|
|
.filter((m): m is ToolMessage => !!m);
|
2025-06-17 09:37:52 -07:00
|
|
|
|
2025-06-22 14:04:58 -07:00
|
|
|
const actionableToolCalls = message
|
2025-06-22 12:14:56 -07:00
|
|
|
? aiToolCalls.filter(
|
2025-06-22 14:04:58 -07:00
|
|
|
(tc) =>
|
|
|
|
|
tc.name === shellTool.name ||
|
|
|
|
|
tc.name === applyPatchTool.name ||
|
2025-06-22 15:47:29 -07:00
|
|
|
tc.name === rgTool.name ||
|
|
|
|
|
tc.name === installDependenciesTool.name,
|
2025-06-22 12:14:56 -07:00
|
|
|
)
|
|
|
|
|
: [];
|
|
|
|
|
|
2025-06-22 12:51:57 -07:00
|
|
|
const taskStatusToolCall = message
|
|
|
|
|
? aiToolCalls.find((tc) => tc.name === setTaskStatusTool.name)
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
// We can be sure that if the task status tool call is present, it will be the
|
|
|
|
|
// only tool call/result we need to render for this message.
|
|
|
|
|
if (taskStatusToolCall) {
|
|
|
|
|
const args = taskStatusToolCall.args as SetTaskStatusToolArgs;
|
|
|
|
|
const correspondingToolResult = toolResults.find(
|
|
|
|
|
(tr) => tr && tr.tool_call_id === taskStatusToolCall.id,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const status = correspondingToolResult ? "done" : "generating";
|
|
|
|
|
const completed = args.task_status === "completed";
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
<TaskSummary
|
|
|
|
|
status={status}
|
|
|
|
|
completed={completed}
|
|
|
|
|
summaryText={args.reasoning}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-22 14:04:58 -07:00
|
|
|
if (actionableToolCalls.length > 0) {
|
|
|
|
|
const actionItems = actionableToolCalls.map((toolCall): ActionItemProps => {
|
2025-06-22 12:14:56 -07:00
|
|
|
const correspondingToolResult = toolResults.find(
|
|
|
|
|
(tr) => tr && tr.tool_call_id === toolCall.id,
|
2025-06-17 09:37:52 -07:00
|
|
|
);
|
2025-06-22 12:14:56 -07:00
|
|
|
|
|
|
|
|
const isShellTool = toolCall.name === shellTool.name;
|
2025-06-22 14:04:58 -07:00
|
|
|
const isRgTool = toolCall.name === rgTool.name;
|
2025-06-22 15:47:29 -07:00
|
|
|
const isInstallDependenciesTool =
|
|
|
|
|
toolCall.name === installDependenciesTool.name;
|
2025-06-22 12:14:56 -07:00
|
|
|
|
|
|
|
|
if (correspondingToolResult) {
|
|
|
|
|
// If we have a tool result, map it to action props
|
|
|
|
|
return mapToolMessageToActionStepProps(correspondingToolResult, thread);
|
2025-06-22 14:04:58 -07:00
|
|
|
} else if (isRgTool) {
|
|
|
|
|
const args = toolCall.args as RgToolArgs;
|
|
|
|
|
return {
|
|
|
|
|
actionType: "rg",
|
|
|
|
|
status: "generating",
|
|
|
|
|
pattern: args?.pattern || "",
|
|
|
|
|
paths: args?.paths || [],
|
|
|
|
|
output: "",
|
|
|
|
|
} as ActionItemProps;
|
2025-06-22 15:47:29 -07:00
|
|
|
} else if (isInstallDependenciesTool) {
|
|
|
|
|
const args = toolCall.args as InstallDependenciesToolArgs;
|
|
|
|
|
return {
|
|
|
|
|
actionType: "install_dependencies",
|
|
|
|
|
status: "generating",
|
|
|
|
|
command: args?.command || "",
|
|
|
|
|
workdir: args?.workdir || "",
|
|
|
|
|
output: "",
|
|
|
|
|
} as ActionItemProps;
|
2025-06-22 12:14:56 -07:00
|
|
|
} else {
|
|
|
|
|
if (isShellTool) {
|
|
|
|
|
const args = toolCall.args as ShellToolArgs;
|
|
|
|
|
return {
|
|
|
|
|
actionType: "shell",
|
|
|
|
|
status: "generating",
|
|
|
|
|
command: args?.command || [],
|
|
|
|
|
workdir: args?.workdir,
|
|
|
|
|
timeout: args?.timeout,
|
|
|
|
|
} as ActionItemProps;
|
|
|
|
|
} else {
|
2025-06-22 14:04:58 -07:00
|
|
|
// Must be apply_patch tool
|
|
|
|
|
const patchArgs = toolCall.args as ApplyPatchToolArgs;
|
2025-06-22 12:14:56 -07:00
|
|
|
return {
|
|
|
|
|
actionType: "apply-patch",
|
|
|
|
|
status: "generating",
|
2025-06-22 14:04:58 -07:00
|
|
|
file_path: patchArgs?.file_path || "",
|
|
|
|
|
diff: patchArgs?.diff || "",
|
2025-06-22 12:14:56 -07:00
|
|
|
} as ActionItemProps;
|
2025-06-17 09:37:52 -07:00
|
|
|
}
|
2025-06-22 12:14:56 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
<ActionStep
|
2025-06-22 14:04:58 -07:00
|
|
|
actions={actionItems.filter(
|
|
|
|
|
(item): item is ActionItemProps => item !== undefined,
|
|
|
|
|
)}
|
2025-06-22 12:14:56 -07:00
|
|
|
reasoningText={contentString}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-06-17 09:37:52 -07:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-22 12:14:56 -07:00
|
|
|
if (message?.type === "tool" && idx > 0) {
|
|
|
|
|
const isPreviousToolCall = messages.slice(0, idx).some((prevMessage) => {
|
|
|
|
|
if (isAIMessageSDK(prevMessage) && prevMessage.tool_calls) {
|
|
|
|
|
return prevMessage.tool_calls.some(
|
|
|
|
|
(tc) => tc.id === (message as ToolMessage).tool_call_id,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(prevMessage.content)) {
|
|
|
|
|
const toolCalls = parseAnthropicStreamedToolCalls(
|
|
|
|
|
prevMessage.content as MessageContentComplex[],
|
|
|
|
|
);
|
|
|
|
|
return toolCalls?.some(
|
|
|
|
|
(tc) => tc.id === (message as ToolMessage).tool_call_id,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (isPreviousToolCall) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2025-06-17 09:37:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isLastMessage =
|
|
|
|
|
thread.messages[thread.messages.length - 1].id === message?.id;
|
|
|
|
|
const hasNoAIOrToolMessages = !thread.messages.find(
|
|
|
|
|
(m) => m.type === "ai" || m.type === "tool",
|
|
|
|
|
);
|
2025-05-28 12:26:47 -07:00
|
|
|
const isToolResult = message?.type === "tool";
|
|
|
|
|
|
|
|
|
|
if (isToolResult && hideToolCalls) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2025-06-18 11:03:39 -07:00
|
|
|
<div className="group mr-auto flex w-full items-start gap-2">
|
2025-05-28 15:18:58 -07:00
|
|
|
<div className="flex w-full flex-col gap-2">
|
2025-05-28 12:26:47 -07:00
|
|
|
{isToolResult ? (
|
2025-05-28 15:18:58 -07:00
|
|
|
<span>
|
2025-05-28 12:26:47 -07:00
|
|
|
<ToolResult message={message} />
|
|
|
|
|
<Interrupt
|
|
|
|
|
interruptValue={threadInterrupt?.value}
|
|
|
|
|
isLastMessage={isLastMessage}
|
|
|
|
|
hasNoAIOrToolMessages={hasNoAIOrToolMessages}
|
2025-06-06 17:14:15 -07:00
|
|
|
forceRenderInterrupt={forceRenderInterrupt}
|
2025-06-18 11:03:39 -07:00
|
|
|
thread={thread}
|
2025-05-28 12:26:47 -07:00
|
|
|
/>
|
2025-05-28 15:18:58 -07:00
|
|
|
</span>
|
2025-05-28 12:26:47 -07:00
|
|
|
) : (
|
2025-05-28 15:18:58 -07:00
|
|
|
<span>
|
2025-05-28 12:26:47 -07:00
|
|
|
{contentString.length > 0 && (
|
|
|
|
|
<div className="py-1">
|
|
|
|
|
<MarkdownText>{contentString}</MarkdownText>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-06-22 12:14:56 -07:00
|
|
|
{!hideToolCalls && aiToolCalls.length > 0 && (
|
2025-05-28 15:18:58 -07:00
|
|
|
<span>
|
2025-06-22 12:14:56 -07:00
|
|
|
<ToolCalls toolCalls={aiToolCalls} />
|
2025-05-28 15:18:58 -07:00
|
|
|
</span>
|
2025-05-28 12:26:47 -07:00
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{message && (
|
|
|
|
|
<CustomComponent
|
|
|
|
|
message={message}
|
|
|
|
|
thread={thread}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
<Interrupt
|
|
|
|
|
interruptValue={threadInterrupt?.value}
|
|
|
|
|
isLastMessage={isLastMessage}
|
|
|
|
|
hasNoAIOrToolMessages={hasNoAIOrToolMessages}
|
2025-06-06 17:14:15 -07:00
|
|
|
forceRenderInterrupt={forceRenderInterrupt}
|
2025-06-18 11:03:39 -07:00
|
|
|
thread={thread}
|
2025-05-28 12:26:47 -07:00
|
|
|
/>
|
|
|
|
|
<div
|
|
|
|
|
className={cn(
|
|
|
|
|
"mr-auto flex items-center gap-2 transition-opacity",
|
|
|
|
|
"opacity-0 group-focus-within:opacity-100 group-hover:opacity-100",
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<BranchSwitcher
|
|
|
|
|
branch={meta?.branch}
|
|
|
|
|
branchOptions={meta?.branchOptions}
|
|
|
|
|
onSelect={(branch) => thread.setBranch(branch)}
|
|
|
|
|
isLoading={isLoading}
|
|
|
|
|
/>
|
|
|
|
|
<CommandBar
|
|
|
|
|
content={contentString}
|
|
|
|
|
isLoading={isLoading}
|
|
|
|
|
isAiMessage={true}
|
|
|
|
|
handleRegenerate={() => handleRegenerate(parentCheckpoint)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-05-28 15:18:58 -07:00
|
|
|
</span>
|
2025-05-28 12:26:47 -07:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function AssistantMessageLoading() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="mr-auto flex items-start gap-2">
|
|
|
|
|
<div className="bg-muted flex h-8 items-center gap-1 rounded-2xl px-4 py-2">
|
|
|
|
|
<div className="bg-foreground/50 h-1.5 w-1.5 animate-[pulse_1.5s_ease-in-out_infinite] rounded-full"></div>
|
|
|
|
|
<div className="bg-foreground/50 h-1.5 w-1.5 animate-[pulse_1.5s_ease-in-out_0.5s_infinite] rounded-full"></div>
|
|
|
|
|
<div className="bg-foreground/50 h-1.5 w-1.5 animate-[pulse_1.5s_ease-in-out_1s_infinite] rounded-full"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|