Files
wehub-resource-sync cb15c5e0d8
Release / Check for new version (push) Has been cancelled
Release / Build macOS ARM64 (push) Has been cancelled
Release / Build macOS x64 (push) Has been cancelled
Release / Build Linux ARM64 (push) Has been cancelled
Release / Build Linux musl ARM64 (push) Has been cancelled
Release / Build Linux musl x64 (push) Has been cancelled
Release / Build Linux x64 (push) Has been cancelled
Release / Build Windows x64 (push) Has been cancelled
Release / Publish to npm (push) Has been cancelled
Release / Publish sandbox package to npm (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled
CI / Rust (windows-latest - x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Native E2E Tests (push) Has been cancelled
CI / Windows Integration Test (push) Has been cancelled
CI / Global Install (macos-latest) (push) Has been cancelled
CI / Global Install (ubuntu-latest) (push) Has been cancelled
CI / Version Sync Check (push) Has been cancelled
CI / Rust (push) Has been cancelled
CI / Dashboard (push) Has been cancelled
CI / Sandbox Package (push) Has been cancelled
CI / Rust (macos-latest - aarch64-apple-darwin) (push) Has been cancelled
CI / Rust (macos-latest - x86_64-apple-darwin) (push) Has been cancelled
CI / Global Install (windows-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:58 +08:00

170 lines
4.6 KiB
TypeScript

"use client";
import type { EveDynamicToolPart, EveMessage, EveMessagePart } from "eve/react";
import { Message, MessageContent, MessageResponse } from "@/components/ai-elements/message";
import { Reasoning, ReasoningContent, ReasoningTrigger } from "@/components/ai-elements/reasoning";
import {
Tool,
ToolContent,
ToolHeader,
ToolInput,
ToolOutput,
} from "@/components/ai-elements/tool";
import { Button } from "@/components/ui/button";
export type AgentInputResponse = {
readonly optionId?: string;
readonly requestId: string;
readonly text?: string;
};
export function AgentMessage({
canRespond,
isStreaming,
message,
onInputResponses,
}: {
readonly canRespond: boolean;
readonly isStreaming: boolean;
readonly message: EveMessage;
readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;
}) {
const lastTextIndex = message.parts.reduce(
(last, part, index) => (part.type === "text" ? index : last),
-1,
);
return (
<Message
data-optimistic={message.metadata?.optimistic ? "true" : undefined}
from={message.role}
>
<MessageContent>
{message.parts.map((part, index) => (
<AgentMessagePart
canRespond={canRespond}
key={partKey(part, index)}
onInputResponses={onInputResponses}
part={part}
showCaret={isStreaming && message.role === "assistant" && index === lastTextIndex}
/>
))}
</MessageContent>
</Message>
);
}
function AgentMessagePart({
canRespond,
onInputResponses,
part,
showCaret,
}: {
readonly canRespond: boolean;
readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;
readonly part: EveMessagePart;
readonly showCaret: boolean;
}) {
switch (part.type) {
case "step-start":
return null;
case "text":
return (
<MessageResponse caret="block" isAnimating={showCaret}>
{part.text}
</MessageResponse>
);
case "reasoning":
return (
<Reasoning defaultOpen isStreaming={part.state === "streaming"}>
<ReasoningTrigger />
<ReasoningContent>{part.text}</ReasoningContent>
</Reasoning>
);
case "dynamic-tool":
return (
<Tool
defaultOpen={part.state === "approval-requested" || part.state === "approval-responded"}
>
<ToolHeader
state={part.state}
title={part.toolName}
toolName={part.toolName}
type="dynamic-tool"
/>
<ToolContent>
<ToolInput input={part.input} />
<InputRequestActions
canRespond={canRespond}
part={part}
onInputResponses={onInputResponses}
/>
<ToolOutput errorText={part.errorText} output={part.output} />
</ToolContent>
</Tool>
);
}
}
function InputRequestActions({
canRespond,
onInputResponses,
part,
}: {
readonly canRespond: boolean;
readonly onInputResponses: (responses: readonly AgentInputResponse[]) => void | Promise<void>;
readonly part: EveDynamicToolPart;
}) {
const inputRequest = part.toolMetadata?.eve?.inputRequest;
if (!inputRequest) {
return null;
}
const inputResponse = part.toolMetadata?.eve?.inputResponse;
const selectedOption = inputRequest.options?.find(
(option) => option.id === inputResponse?.optionId,
);
return (
<div className="space-y-3 rounded-md border border-yellow-500/30 bg-yellow-500/5 p-3">
<p className="text-muted-foreground text-sm">{inputRequest.prompt}</p>
{inputResponse ? (
<p className="font-medium text-sm">
Responded: {selectedOption?.label ?? inputResponse.text ?? inputResponse.optionId}
</p>
) : (
<div className="flex flex-wrap gap-2">
{inputRequest.options?.map((option) => (
<Button
disabled={!canRespond}
key={option.id}
onClick={() => {
void onInputResponses([
{
optionId: option.id,
requestId: inputRequest.requestId,
},
]);
}}
size="sm"
type="button"
variant={option.style === "danger" ? "destructive" : "default"}
>
{option.label}
</Button>
))}
</div>
)}
</div>
);
}
function partKey(part: EveMessagePart, index: number): string {
switch (part.type) {
case "dynamic-tool":
return part.toolCallId;
default:
return `${part.type}:${index}`;
}
}