mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 14:01:25 +00:00
chore: fix bad merge
This commit is contained in:
parent
1dd45908a3
commit
94353f7696
205 changed files with 19244 additions and 14866 deletions
|
|
@ -1,23 +1,22 @@
|
|||
import { getAvatarLabel, getMessageClass } from "./messageUtils";
|
||||
import renderContentPart from "./renderContentPart";
|
||||
import type { TimelineEntry } from "./types";
|
||||
import { formatJson } from "../../utils/format";
|
||||
|
||||
const ChatMessages = ({
|
||||
entries,
|
||||
sessionError,
|
||||
eventError,
|
||||
messagesEndRef
|
||||
}: {
|
||||
entries: TimelineEntry[];
|
||||
sessionError: string | null;
|
||||
eventError: string | null;
|
||||
messagesEndRef: React.RefObject<HTMLDivElement>;
|
||||
}) => {
|
||||
return (
|
||||
<div className="messages">
|
||||
{entries.map((entry) => {
|
||||
const messageClass = getMessageClass(entry);
|
||||
|
||||
if (entry.kind === "meta") {
|
||||
const messageClass = entry.meta?.severity === "error" ? "error" : "system";
|
||||
return (
|
||||
<div key={entry.id} className={`message ${messageClass}`}>
|
||||
<div className="avatar">{getAvatarLabel(messageClass)}</div>
|
||||
|
|
@ -31,53 +30,73 @@ const ChatMessages = ({
|
|||
);
|
||||
}
|
||||
|
||||
const item = entry.item;
|
||||
if (!item) return null;
|
||||
const hasParts = (item.content ?? []).length > 0;
|
||||
const isInProgress = item.status === "in_progress";
|
||||
const isFailed = item.status === "failed";
|
||||
const messageClass = getMessageClass(item);
|
||||
const statusValue = item.status ?? "";
|
||||
const statusLabel =
|
||||
statusValue && statusValue !== "completed" ? statusValue.replace("_", " ") : "";
|
||||
const kindLabel = item.kind.replace("_", " ");
|
||||
|
||||
return (
|
||||
<div key={entry.id} className={`message ${messageClass} ${isFailed ? "error" : ""}`}>
|
||||
<div className="avatar">{getAvatarLabel(isFailed ? "error" : messageClass)}</div>
|
||||
<div className="message-content">
|
||||
{(item.kind !== "message" || item.status !== "completed") && (
|
||||
if (entry.kind === "reasoning") {
|
||||
return (
|
||||
<div key={entry.id} className="message assistant">
|
||||
<div className="avatar">AI</div>
|
||||
<div className="message-content">
|
||||
<div className="message-meta">
|
||||
<span>{kindLabel}</span>
|
||||
{statusLabel && (
|
||||
<span className={`pill ${item.status === "failed" ? "danger" : "accent"}`}>
|
||||
{statusLabel}
|
||||
<span>reasoning - {entry.reasoning?.visibility ?? "public"}</span>
|
||||
</div>
|
||||
<div className="part-body muted">{entry.reasoning?.text ?? ""}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (entry.kind === "tool") {
|
||||
const isComplete = entry.toolStatus === "completed" || entry.toolStatus === "failed";
|
||||
const isFailed = entry.toolStatus === "failed";
|
||||
return (
|
||||
<div key={entry.id} className={`message tool ${isFailed ? "error" : ""}`}>
|
||||
<div className="avatar">{getAvatarLabel(isFailed ? "error" : "tool")}</div>
|
||||
<div className="message-content">
|
||||
<div className="message-meta">
|
||||
<span>tool call - {entry.toolName}</span>
|
||||
{entry.toolStatus && entry.toolStatus !== "completed" && (
|
||||
<span className={`pill ${isFailed ? "danger" : "accent"}`}>
|
||||
{entry.toolStatus.replace("_", " ")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{hasParts ? (
|
||||
(item.content ?? []).map(renderContentPart)
|
||||
) : entry.deltaText ? (
|
||||
<span>
|
||||
{entry.deltaText}
|
||||
{isInProgress && <span className="cursor" />}
|
||||
</span>
|
||||
) : isInProgress ? (
|
||||
{entry.toolInput && <pre className="code-block">{entry.toolInput}</pre>}
|
||||
{isComplete && entry.toolOutput && (
|
||||
<div className="part">
|
||||
<div className="part-title">result</div>
|
||||
<pre className="code-block">{entry.toolOutput}</pre>
|
||||
</div>
|
||||
)}
|
||||
{!isComplete && !entry.toolInput && (
|
||||
<span className="thinking-indicator">
|
||||
<span className="thinking-dot" />
|
||||
<span className="thinking-dot" />
|
||||
<span className="thinking-dot" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Message (user or assistant)
|
||||
return (
|
||||
<div key={entry.id} className={`message ${messageClass}`}>
|
||||
<div className="avatar">{getAvatarLabel(messageClass)}</div>
|
||||
<div className="message-content">
|
||||
{entry.text ? (
|
||||
<div className="part-body">{entry.text}</div>
|
||||
) : (
|
||||
<span className="thinking-indicator">
|
||||
<span className="thinking-dot" />
|
||||
<span className="thinking-dot" />
|
||||
<span className="thinking-dot" />
|
||||
</span>
|
||||
) : (
|
||||
<span className="muted">No content yet.</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{sessionError && <div className="message-error">{sessionError}</div>}
|
||||
{eventError && <div className="message-error">{eventError}</div>}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
import { MessageSquare, Plus, Square, Terminal } from "lucide-react";
|
||||
import { CheckSquare, MessageSquare, Plus, Square, Terminal } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { McpServerEntry } from "../../App";
|
||||
import type {
|
||||
AgentInfo,
|
||||
AgentModelInfo,
|
||||
AgentModeInfo,
|
||||
PermissionEventData,
|
||||
QuestionEventData,
|
||||
SkillSource
|
||||
} from "../../types/legacyApi";
|
||||
import ApprovalsTab from "../debug/ApprovalsTab";
|
||||
import type { AgentInfo } from "sandbox-agent";
|
||||
|
||||
type AgentModeInfo = { id: string; name: string; description: string };
|
||||
type AgentModelInfo = { id: string; name?: string };
|
||||
import SessionCreateMenu, { type SessionConfig } from "../SessionCreateMenu";
|
||||
import ChatInput from "./ChatInput";
|
||||
import ChatMessages from "./ChatMessages";
|
||||
|
|
@ -31,32 +25,11 @@ const ChatPanel = ({
|
|||
messagesEndRef,
|
||||
agentLabel,
|
||||
currentAgentVersion,
|
||||
sessionModel,
|
||||
sessionVariant,
|
||||
sessionPermissionMode,
|
||||
sessionMcpServerCount,
|
||||
sessionSkillSourceCount,
|
||||
sessionEnded,
|
||||
onEndSession,
|
||||
eventError,
|
||||
questionRequests,
|
||||
permissionRequests,
|
||||
questionSelections,
|
||||
onSelectQuestionOption,
|
||||
onAnswerQuestion,
|
||||
onRejectQuestion,
|
||||
onReplyPermission,
|
||||
modesByAgent,
|
||||
modelsByAgent,
|
||||
defaultModelByAgent,
|
||||
modesLoadingByAgent,
|
||||
modelsLoadingByAgent,
|
||||
modesErrorByAgent,
|
||||
modelsErrorByAgent,
|
||||
mcpServers,
|
||||
onMcpServersChange,
|
||||
mcpConfigError,
|
||||
skillSources,
|
||||
onSkillSourcesChange
|
||||
}: {
|
||||
sessionId: string;
|
||||
transcriptEntries: TimelineEntry[];
|
||||
|
|
@ -66,39 +39,18 @@ const ChatPanel = ({
|
|||
onSendMessage: () => void;
|
||||
onKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
||||
onCreateSession: (agentId: string, config: SessionConfig) => void;
|
||||
onSelectAgent: (agentId: string) => void;
|
||||
onSelectAgent: (agentId: string) => Promise<void>;
|
||||
agents: AgentInfo[];
|
||||
agentsLoading: boolean;
|
||||
agentsError: string | null;
|
||||
messagesEndRef: React.RefObject<HTMLDivElement>;
|
||||
agentLabel: string;
|
||||
currentAgentVersion?: string | null;
|
||||
sessionModel?: string | null;
|
||||
sessionVariant?: string | null;
|
||||
sessionPermissionMode?: string | null;
|
||||
sessionMcpServerCount: number;
|
||||
sessionSkillSourceCount: number;
|
||||
sessionEnded: boolean;
|
||||
onEndSession: () => void;
|
||||
eventError: string | null;
|
||||
questionRequests: QuestionEventData[];
|
||||
permissionRequests: PermissionEventData[];
|
||||
questionSelections: Record<string, string[][]>;
|
||||
onSelectQuestionOption: (requestId: string, optionLabel: string) => void;
|
||||
onAnswerQuestion: (request: QuestionEventData) => void;
|
||||
onRejectQuestion: (requestId: string) => void;
|
||||
onReplyPermission: (requestId: string, reply: "once" | "always" | "reject") => void;
|
||||
modesByAgent: Record<string, AgentModeInfo[]>;
|
||||
modelsByAgent: Record<string, AgentModelInfo[]>;
|
||||
defaultModelByAgent: Record<string, string>;
|
||||
modesLoadingByAgent: Record<string, boolean>;
|
||||
modelsLoadingByAgent: Record<string, boolean>;
|
||||
modesErrorByAgent: Record<string, string | null>;
|
||||
modelsErrorByAgent: Record<string, string | null>;
|
||||
mcpServers: McpServerEntry[];
|
||||
onMcpServersChange: (servers: McpServerEntry[]) => void;
|
||||
mcpConfigError: string | null;
|
||||
skillSources: SkillSource[];
|
||||
onSkillSourcesChange: (sources: SkillSource[]) => void;
|
||||
}) => {
|
||||
const [showAgentMenu, setShowAgentMenu] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement | null>(null);
|
||||
|
|
@ -115,8 +67,6 @@ const ChatPanel = ({
|
|||
return () => document.removeEventListener("mousedown", handler);
|
||||
}, [showAgentMenu]);
|
||||
|
||||
const hasApprovals = questionRequests.length > 0 || permissionRequests.length > 0;
|
||||
|
||||
return (
|
||||
<div className="chat-panel">
|
||||
<div className="panel-header">
|
||||
|
|
@ -127,15 +77,22 @@ const ChatPanel = ({
|
|||
</div>
|
||||
<div className="panel-header-right">
|
||||
{sessionId && (
|
||||
<button
|
||||
type="button"
|
||||
className="button ghost small"
|
||||
onClick={onEndSession}
|
||||
title="End session"
|
||||
>
|
||||
<Square size={12} />
|
||||
End
|
||||
</button>
|
||||
sessionEnded ? (
|
||||
<span className="button ghost small" style={{ opacity: 0.5, cursor: "default" }} title="Session ended">
|
||||
<CheckSquare size={12} />
|
||||
Ended
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="button ghost small"
|
||||
onClick={onEndSession}
|
||||
title="End session"
|
||||
>
|
||||
<Square size={12} />
|
||||
End
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -161,17 +118,8 @@ const ChatPanel = ({
|
|||
modesByAgent={modesByAgent}
|
||||
modelsByAgent={modelsByAgent}
|
||||
defaultModelByAgent={defaultModelByAgent}
|
||||
modesLoadingByAgent={modesLoadingByAgent}
|
||||
modelsLoadingByAgent={modelsLoadingByAgent}
|
||||
modesErrorByAgent={modesErrorByAgent}
|
||||
modelsErrorByAgent={modelsErrorByAgent}
|
||||
mcpServers={mcpServers}
|
||||
onMcpServersChange={onMcpServersChange}
|
||||
mcpConfigError={mcpConfigError}
|
||||
skillSources={skillSources}
|
||||
onSkillSourcesChange={onSkillSourcesChange}
|
||||
onSelectAgent={onSelectAgent}
|
||||
onCreateSession={onCreateSession}
|
||||
onSelectAgent={onSelectAgent}
|
||||
open={showAgentMenu}
|
||||
onClose={() => setShowAgentMenu(false)}
|
||||
/>
|
||||
|
|
@ -187,27 +135,11 @@ const ChatPanel = ({
|
|||
<ChatMessages
|
||||
entries={transcriptEntries}
|
||||
sessionError={sessionError}
|
||||
eventError={eventError}
|
||||
messagesEndRef={messagesEndRef}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasApprovals && (
|
||||
<div className="approvals-inline">
|
||||
<div className="approvals-inline-header">Approvals</div>
|
||||
<ApprovalsTab
|
||||
questionRequests={questionRequests}
|
||||
permissionRequests={permissionRequests}
|
||||
questionSelections={questionSelections}
|
||||
onSelectQuestionOption={onSelectQuestionOption}
|
||||
onAnswerQuestion={onAnswerQuestion}
|
||||
onRejectQuestion={onRejectQuestion}
|
||||
onReplyPermission={onReplyPermission}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ChatInput
|
||||
message={message}
|
||||
onMessageChange={onMessageChange}
|
||||
|
|
@ -223,26 +155,12 @@ const ChatPanel = ({
|
|||
<span className="session-config-label">Agent</span>
|
||||
<span className="session-config-value">{agentLabel}</span>
|
||||
</div>
|
||||
<div className="session-config-field">
|
||||
<span className="session-config-label">Model</span>
|
||||
<span className="session-config-value">{sessionModel || "-"}</span>
|
||||
</div>
|
||||
<div className="session-config-field">
|
||||
<span className="session-config-label">Variant</span>
|
||||
<span className="session-config-value">{sessionVariant || "-"}</span>
|
||||
</div>
|
||||
<div className="session-config-field">
|
||||
<span className="session-config-label">Permission</span>
|
||||
<span className="session-config-value">{sessionPermissionMode || "-"}</span>
|
||||
</div>
|
||||
<div className="session-config-field">
|
||||
<span className="session-config-label">MCP Servers</span>
|
||||
<span className="session-config-value">{sessionMcpServerCount}</span>
|
||||
</div>
|
||||
<div className="session-config-field">
|
||||
<span className="session-config-label">Skills</span>
|
||||
<span className="session-config-value">{sessionSkillSourceCount}</span>
|
||||
</div>
|
||||
{currentAgentVersion && (
|
||||
<div className="session-config-field">
|
||||
<span className="session-config-label">Version</span>
|
||||
<span className="session-config-value">{currentAgentVersion}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import type { UniversalItem } from "../../types/legacyApi";
|
||||
import type { TimelineEntry } from "./types";
|
||||
|
||||
export const getMessageClass = (item: UniversalItem) => {
|
||||
if (item.kind === "tool_call" || item.kind === "tool_result") return "tool";
|
||||
if (item.kind === "system" || item.kind === "status") return "system";
|
||||
if (item.role === "user") return "user";
|
||||
if (item.role === "tool") return "tool";
|
||||
if (item.role === "system") return "system";
|
||||
export const getMessageClass = (entry: TimelineEntry) => {
|
||||
if (entry.kind === "tool") return "tool";
|
||||
if (entry.kind === "meta") return entry.meta?.severity === "error" ? "error" : "system";
|
||||
if (entry.kind === "reasoning") return "assistant";
|
||||
if (entry.role === "user") return "user";
|
||||
return "assistant";
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,93 +0,0 @@
|
|||
import type { ContentPart } from "../../types/legacyApi";
|
||||
import { formatJson } from "../../utils/format";
|
||||
|
||||
const renderContentPart = (part: ContentPart, index: number) => {
|
||||
const partType = (part as { type?: string }).type ?? "unknown";
|
||||
const key = `${partType}-${index}`;
|
||||
switch (partType) {
|
||||
case "text":
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-body">{(part as { text: string }).text}</div>
|
||||
</div>
|
||||
);
|
||||
case "json":
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-title">json</div>
|
||||
<pre className="code-block">{formatJson((part as { json: unknown }).json)}</pre>
|
||||
</div>
|
||||
);
|
||||
case "tool_call": {
|
||||
const { name, arguments: args, call_id } = part as {
|
||||
name: string;
|
||||
arguments: string;
|
||||
call_id: string;
|
||||
};
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-title">
|
||||
tool call - {name}
|
||||
{call_id ? ` - ${call_id}` : ""}
|
||||
</div>
|
||||
{args ? <pre className="code-block">{args}</pre> : <div className="muted">No arguments</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "tool_result": {
|
||||
const { call_id, output } = part as { call_id: string; output: string };
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-title">tool result - {call_id}</div>
|
||||
{output ? <pre className="code-block">{output}</pre> : <div className="muted">No output</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "file_ref": {
|
||||
const { path, action, diff } = part as { path: string; action: string; diff?: string | null };
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-title">file - {action}</div>
|
||||
<div className="part-body mono">{path}</div>
|
||||
{diff && <pre className="code-block">{diff}</pre>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "reasoning": {
|
||||
const { text, visibility } = part as { text: string; visibility: string };
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-title">reasoning - {visibility}</div>
|
||||
<div className="part-body muted">{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "image": {
|
||||
const { path, mime } = part as { path: string; mime?: string | null };
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-title">image {mime ? `- ${mime}` : ""}</div>
|
||||
<div className="part-body mono">{path}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
case "status": {
|
||||
const { label, detail } = part as { label: string; detail?: string | null };
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-title">status - {label}</div>
|
||||
{detail && <div className="part-body">{detail}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
default:
|
||||
return (
|
||||
<div key={key} className="part">
|
||||
<div className="part-title">unknown</div>
|
||||
<pre className="code-block">{formatJson(part)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default renderContentPart;
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
import type { UniversalItem } from "../../types/legacyApi";
|
||||
|
||||
export type TimelineEntry = {
|
||||
id: string;
|
||||
kind: "item" | "meta";
|
||||
kind: "message" | "tool" | "meta" | "reasoning";
|
||||
time: string;
|
||||
item?: UniversalItem;
|
||||
deltaText?: string;
|
||||
meta?: {
|
||||
title: string;
|
||||
detail?: string;
|
||||
severity?: "info" | "error";
|
||||
};
|
||||
// For messages:
|
||||
role?: "user" | "assistant";
|
||||
text?: string;
|
||||
// For tool calls:
|
||||
toolName?: string;
|
||||
toolInput?: string;
|
||||
toolOutput?: string;
|
||||
toolStatus?: string;
|
||||
// For reasoning:
|
||||
reasoning?: { text: string; visibility?: string };
|
||||
// For meta:
|
||||
meta?: { title: string; detail?: string; severity?: "info" | "error" };
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue