@echothink-ui/agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/components/AgentApprovalGate.d.ts +10 -0
- package/dist/components/AgentContextViewer.d.ts +7 -0
- package/dist/components/AgentGeneratedArtifactPanel.d.ts +8 -0
- package/dist/components/AgentHandoffPanel.d.ts +22 -0
- package/dist/components/AgentInterruptionPanel.d.ts +13 -0
- package/dist/components/AgentMemoryPanel.d.ts +9 -0
- package/dist/components/AgentMessageList.d.ts +8 -0
- package/dist/components/AgentPlanDiff.d.ts +7 -0
- package/dist/components/AgentPlanPreview.d.ts +6 -0
- package/dist/components/AgentPromptBox.d.ts +15 -0
- package/dist/components/AgentRunControls.d.ts +10 -0
- package/dist/components/AgentSafetyPanel.d.ts +6 -0
- package/dist/components/AgentStateBadge.d.ts +6 -0
- package/dist/components/AgentThinkingChain.d.ts +8 -0
- package/dist/components/AgentThinkingPanel.d.ts +8 -0
- package/dist/components/AgentToolCallLog.d.ts +7 -0
- package/dist/components/AgentTraceViewer.d.ts +6 -0
- package/dist/components/AppDomainAgentPanel.d.ts +17 -0
- package/dist/components/ChatAgentRail.d.ts +24 -0
- package/dist/components/ScopeAttachmentPanel.d.ts +7 -0
- package/dist/components/utils.d.ts +20 -0
- package/dist/index.cjs +2709 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +2433 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +2666 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +128 -0
- package/package.json +45 -0
- package/src/components/AgentApprovalGate.tsx +165 -0
- package/src/components/AgentContextViewer.tsx +161 -0
- package/src/components/AgentGeneratedArtifactPanel.tsx +224 -0
- package/src/components/AgentHandoffPanel.tsx +154 -0
- package/src/components/AgentInterruptionPanel.tsx +85 -0
- package/src/components/AgentMemoryPanel.tsx +167 -0
- package/src/components/AgentMessageList.tsx +209 -0
- package/src/components/AgentPlanDiff.tsx +149 -0
- package/src/components/AgentPlanPreview.tsx +106 -0
- package/src/components/AgentPromptBox.tsx +163 -0
- package/src/components/AgentRunControls.tsx +221 -0
- package/src/components/AgentSafetyPanel.tsx +113 -0
- package/src/components/AgentStateBadge.tsx +30 -0
- package/src/components/AgentThinkingChain.tsx +151 -0
- package/src/components/AgentThinkingPanel.tsx +56 -0
- package/src/components/AgentToolCallLog.tsx +262 -0
- package/src/components/AgentTraceViewer.tsx +218 -0
- package/src/components/AppDomainAgentPanel.tsx +66 -0
- package/src/components/ChatAgentRail.tsx +192 -0
- package/src/components/ScopeAttachmentPanel.tsx +130 -0
- package/src/components/utils.ts +186 -0
- package/src/index.test.tsx +212 -0
- package/src/index.tsx +88 -0
- package/src/styles.css +2902 -0
- package/src/types.ts +158 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { AgentMessage, AgentRunState, ChatAgentRailAttachment } from "../types";
|
|
3
|
+
import { ChatAgentRail } from "./ChatAgentRail";
|
|
4
|
+
|
|
5
|
+
export interface AppDomainAgentPanelProps
|
|
6
|
+
extends Omit<React.HTMLAttributes<HTMLElement>, "children" | "onSubmit"> {
|
|
7
|
+
appDomainRef: string;
|
|
8
|
+
agentRef?: string;
|
|
9
|
+
projectRef?: string;
|
|
10
|
+
messages?: AgentMessage[];
|
|
11
|
+
state?: AgentRunState;
|
|
12
|
+
prompt?: string;
|
|
13
|
+
onPromptChange?: (value: string) => void;
|
|
14
|
+
onSubmit?: (prompt: string, attachments?: ChatAgentRailAttachment[]) => void;
|
|
15
|
+
attachments?: ChatAgentRailAttachment[];
|
|
16
|
+
onAttachmentRemove?: (id: string) => void;
|
|
17
|
+
runControls?: React.ReactNode;
|
|
18
|
+
children?: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function AppDomainAgentPanel({
|
|
22
|
+
appDomainRef,
|
|
23
|
+
agentRef,
|
|
24
|
+
projectRef,
|
|
25
|
+
messages = [],
|
|
26
|
+
state,
|
|
27
|
+
prompt = "",
|
|
28
|
+
onPromptChange,
|
|
29
|
+
onSubmit,
|
|
30
|
+
attachments,
|
|
31
|
+
onAttachmentRemove,
|
|
32
|
+
runControls,
|
|
33
|
+
children,
|
|
34
|
+
...props
|
|
35
|
+
}: AppDomainAgentPanelProps) {
|
|
36
|
+
return (
|
|
37
|
+
<ChatAgentRail
|
|
38
|
+
{...props}
|
|
39
|
+
agentRef={agentRef}
|
|
40
|
+
projectRef={projectRef}
|
|
41
|
+
messages={messages}
|
|
42
|
+
state={state}
|
|
43
|
+
prompt={prompt}
|
|
44
|
+
onPromptChange={onPromptChange ?? (() => undefined)}
|
|
45
|
+
onSubmit={onSubmit}
|
|
46
|
+
attachments={attachments}
|
|
47
|
+
onAttachmentRemove={onAttachmentRemove}
|
|
48
|
+
runControls={runControls}
|
|
49
|
+
scopeLabel={appDomainRef}
|
|
50
|
+
header={
|
|
51
|
+
<div className="eth-agent-app-domain-panel__header">
|
|
52
|
+
<p className="eth-agent-app-domain-panel__eyebrow">App domain</p>
|
|
53
|
+
<h2 className="eth-agent-app-domain-panel__title">{appDomainRef}</h2>
|
|
54
|
+
{agentRef || projectRef ? (
|
|
55
|
+
<div className="eth-agent-app-domain-panel__meta" aria-label="Agent panel scope">
|
|
56
|
+
{agentRef ? <span>Agent {agentRef}</span> : null}
|
|
57
|
+
{projectRef ? <span>Project {projectRef}</span> : null}
|
|
58
|
+
</div>
|
|
59
|
+
) : null}
|
|
60
|
+
{children ? <div className="eth-agent-app-domain-panel__slot">{children}</div> : null}
|
|
61
|
+
</div>
|
|
62
|
+
}
|
|
63
|
+
data-eth-component="AppDomainAgentPanel"
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { AgentMessage, AgentRunState, ChatAgentRailAttachment } from "../types";
|
|
4
|
+
import { AgentMessageList } from "./AgentMessageList";
|
|
5
|
+
import { AgentPromptBox } from "./AgentPromptBox";
|
|
6
|
+
import { AgentStateBadge } from "./AgentStateBadge";
|
|
7
|
+
import { ScopeAttachmentPanel } from "./ScopeAttachmentPanel";
|
|
8
|
+
import { isActiveAgentState, mergeStyle } from "./utils";
|
|
9
|
+
|
|
10
|
+
export interface ChatAgentRailProps extends Omit<
|
|
11
|
+
React.HTMLAttributes<HTMLElement>,
|
|
12
|
+
"children" | "onSubmit"
|
|
13
|
+
> {
|
|
14
|
+
agentRef?: string;
|
|
15
|
+
projectRef?: string;
|
|
16
|
+
messages: AgentMessage[];
|
|
17
|
+
state?: AgentRunState;
|
|
18
|
+
prompt: string;
|
|
19
|
+
onPromptChange: (value: string) => void;
|
|
20
|
+
onSubmit?: (prompt: string, attachments?: ChatAgentRailAttachment[]) => void;
|
|
21
|
+
attachments?: ChatAgentRailAttachment[];
|
|
22
|
+
onAttachmentRemove?: (id: string) => void;
|
|
23
|
+
onAttach?: () => void;
|
|
24
|
+
onSlash?: () => void;
|
|
25
|
+
autoScroll?: boolean;
|
|
26
|
+
runControls?: React.ReactNode;
|
|
27
|
+
header?: React.ReactNode;
|
|
28
|
+
scopeLabel?: React.ReactNode;
|
|
29
|
+
width?: number | string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface LeftAgentRailProps extends ChatAgentRailProps {}
|
|
33
|
+
|
|
34
|
+
interface AgentRailBaseProps extends ChatAgentRailProps {
|
|
35
|
+
componentName: "ChatAgentRail" | "LeftAgentRail";
|
|
36
|
+
railClassName: string;
|
|
37
|
+
placement: "left" | "right";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function ChatAgentRail(props: ChatAgentRailProps) {
|
|
41
|
+
return (
|
|
42
|
+
<AgentRailBase
|
|
43
|
+
{...props}
|
|
44
|
+
componentName="ChatAgentRail"
|
|
45
|
+
railClassName="eth-agent-chat-rail"
|
|
46
|
+
placement="right"
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function LeftAgentRail(props: LeftAgentRailProps) {
|
|
52
|
+
return (
|
|
53
|
+
<AgentRailBase
|
|
54
|
+
{...props}
|
|
55
|
+
componentName="LeftAgentRail"
|
|
56
|
+
railClassName="eth-left-agent-rail eth-agent-left-rail"
|
|
57
|
+
placement="left"
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function AgentRailBase({
|
|
63
|
+
agentRef,
|
|
64
|
+
projectRef,
|
|
65
|
+
messages,
|
|
66
|
+
state = "idle",
|
|
67
|
+
prompt,
|
|
68
|
+
onPromptChange,
|
|
69
|
+
onSubmit,
|
|
70
|
+
attachments = [],
|
|
71
|
+
onAttachmentRemove,
|
|
72
|
+
onAttach,
|
|
73
|
+
onSlash,
|
|
74
|
+
autoScroll = true,
|
|
75
|
+
runControls,
|
|
76
|
+
header,
|
|
77
|
+
scopeLabel,
|
|
78
|
+
width = 360,
|
|
79
|
+
className,
|
|
80
|
+
style,
|
|
81
|
+
componentName,
|
|
82
|
+
railClassName,
|
|
83
|
+
placement,
|
|
84
|
+
"aria-label": ariaLabel,
|
|
85
|
+
...props
|
|
86
|
+
}: AgentRailBaseProps) {
|
|
87
|
+
const streaming = messages.some((message) => message.streaming) || isActiveAgentState(state);
|
|
88
|
+
const border = "1px solid var(--eth-color-border-subtle)";
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<aside
|
|
92
|
+
{...props}
|
|
93
|
+
aria-label={ariaLabel ?? "Agent collaboration rail"}
|
|
94
|
+
className={clsx("eth-chat-rail", railClassName, className)}
|
|
95
|
+
data-eth-component={componentName}
|
|
96
|
+
data-placement={placement}
|
|
97
|
+
style={mergeStyle(
|
|
98
|
+
{
|
|
99
|
+
background: "var(--eth-color-layer-01)",
|
|
100
|
+
borderInlineEnd: placement === "left" ? border : 0,
|
|
101
|
+
borderInlineStart: placement === "right" ? border : 0,
|
|
102
|
+
display: "grid",
|
|
103
|
+
flex: `0 0 ${typeof width === "number" ? `${width}px` : width}`,
|
|
104
|
+
gridTemplateRows: "auto minmax(0, 1fr) auto",
|
|
105
|
+
height: "100%",
|
|
106
|
+
maxWidth: "100%",
|
|
107
|
+
minHeight: 0,
|
|
108
|
+
width
|
|
109
|
+
},
|
|
110
|
+
style
|
|
111
|
+
)}
|
|
112
|
+
>
|
|
113
|
+
<header
|
|
114
|
+
className="eth-chat-rail__header"
|
|
115
|
+
style={{
|
|
116
|
+
borderBottom: "1px solid var(--eth-color-border-subtle)",
|
|
117
|
+
display: "grid",
|
|
118
|
+
gap: "0.75rem",
|
|
119
|
+
padding: "1rem"
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<div
|
|
123
|
+
className="eth-chat-rail__header-main"
|
|
124
|
+
style={{
|
|
125
|
+
alignItems: "flex-start",
|
|
126
|
+
display: "flex",
|
|
127
|
+
gap: "0.75rem",
|
|
128
|
+
justifyContent: "space-between"
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<div style={{ minWidth: 0 }}>
|
|
132
|
+
{header ?? (
|
|
133
|
+
<>
|
|
134
|
+
<h2 className="eth-chat-rail__title" style={{ fontSize: "1rem", margin: 0 }}>
|
|
135
|
+
Agent
|
|
136
|
+
</h2>
|
|
137
|
+
{agentRef || projectRef ? (
|
|
138
|
+
<p
|
|
139
|
+
className="eth-chat-rail__meta"
|
|
140
|
+
style={{
|
|
141
|
+
color: "var(--eth-color-text-secondary)",
|
|
142
|
+
fontSize: "0.8rem",
|
|
143
|
+
margin: "0.25rem 0 0"
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
{[agentRef, projectRef].filter(Boolean).join(" · ")}
|
|
147
|
+
</p>
|
|
148
|
+
) : null}
|
|
149
|
+
</>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
<AgentStateBadge state={state} />
|
|
153
|
+
</div>
|
|
154
|
+
{runControls ? <div className="eth-chat-rail__run-controls">{runControls}</div> : null}
|
|
155
|
+
</header>
|
|
156
|
+
|
|
157
|
+
<AgentMessageList
|
|
158
|
+
className="eth-chat-rail__messages"
|
|
159
|
+
autoScroll={autoScroll}
|
|
160
|
+
messages={messages}
|
|
161
|
+
streaming={streaming}
|
|
162
|
+
style={{ minHeight: 0, padding: "1rem" }}
|
|
163
|
+
/>
|
|
164
|
+
|
|
165
|
+
<footer
|
|
166
|
+
className="eth-chat-rail__footer"
|
|
167
|
+
style={{
|
|
168
|
+
borderTop: "1px solid var(--eth-color-border-subtle)",
|
|
169
|
+
display: "grid",
|
|
170
|
+
gap: "0.75rem",
|
|
171
|
+
padding: "1rem"
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<ScopeAttachmentPanel
|
|
175
|
+
attachments={attachments.map((attachment) => ({
|
|
176
|
+
...attachment,
|
|
177
|
+
kind: attachment.kind === "file" ? "document" : attachment.kind
|
|
178
|
+
}))}
|
|
179
|
+
onRemove={onAttachmentRemove}
|
|
180
|
+
/>
|
|
181
|
+
<AgentPromptBox
|
|
182
|
+
value={prompt}
|
|
183
|
+
onChange={onPromptChange}
|
|
184
|
+
scopeLabel={scopeLabel}
|
|
185
|
+
onAttach={onAttach}
|
|
186
|
+
onSlash={onSlash}
|
|
187
|
+
onSubmit={(value) => onSubmit?.(value, attachments)}
|
|
188
|
+
/>
|
|
189
|
+
</footer>
|
|
190
|
+
</aside>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { Badge, Button, StatusDot, Surface, statusLabel } from "@echothink-ui/core";
|
|
4
|
+
import type { SurfaceComponentProps } from "@echothink-ui/core";
|
|
5
|
+
import type { AgentScopeAttachment } from "../types";
|
|
6
|
+
|
|
7
|
+
export interface ScopeAttachmentPanelProps
|
|
8
|
+
extends Omit<SurfaceComponentProps, "children" | "items" | "metadata"> {
|
|
9
|
+
attachments: AgentScopeAttachment[];
|
|
10
|
+
onRemove?: (id: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const attentionStatuses = new Set<AgentScopeAttachment["status"]>([
|
|
14
|
+
"approval-required",
|
|
15
|
+
"blocked",
|
|
16
|
+
"failed",
|
|
17
|
+
"pending-approval",
|
|
18
|
+
"stale",
|
|
19
|
+
"warning"
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function ScopeAttachmentPanel({
|
|
23
|
+
attachments,
|
|
24
|
+
onRemove,
|
|
25
|
+
title = "Attached context",
|
|
26
|
+
subtitle,
|
|
27
|
+
severity,
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}: ScopeAttachmentPanelProps) {
|
|
31
|
+
if (!attachments.length) return null;
|
|
32
|
+
|
|
33
|
+
const hasAttention = attachments.some((attachment) =>
|
|
34
|
+
attachment.status ? attentionStatuses.has(attachment.status) : false
|
|
35
|
+
);
|
|
36
|
+
const listLabel = typeof title === "string" ? `${title} items` : "Attached context items";
|
|
37
|
+
const renderedSubtitle =
|
|
38
|
+
subtitle ??
|
|
39
|
+
`${attachments.length} ${
|
|
40
|
+
attachments.length === 1 ? "scope is" : "scopes are"
|
|
41
|
+
} available to the agent run`;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Surface
|
|
45
|
+
{...props}
|
|
46
|
+
className={clsx("eth-agent-scope-attachments", className)}
|
|
47
|
+
data-eth-component="ScopeAttachmentPanel"
|
|
48
|
+
severity={severity ?? (hasAttention ? "warning" : undefined)}
|
|
49
|
+
subtitle={renderedSubtitle}
|
|
50
|
+
title={title}
|
|
51
|
+
>
|
|
52
|
+
<ul className="eth-agent-scope-attachments__list" aria-label={listLabel}>
|
|
53
|
+
{attachments.map((attachment) => (
|
|
54
|
+
<li
|
|
55
|
+
key={attachment.id}
|
|
56
|
+
className="eth-agent-scope-attachments__item"
|
|
57
|
+
data-kind={attachment.kind}
|
|
58
|
+
data-status={attachment.status}
|
|
59
|
+
>
|
|
60
|
+
<span className="eth-agent-scope-attachments__icon" aria-hidden="true">
|
|
61
|
+
{kindInitial(attachment.kind)}
|
|
62
|
+
</span>
|
|
63
|
+
<span className="eth-agent-scope-attachments__main">
|
|
64
|
+
<span className="eth-agent-scope-attachments__topline">
|
|
65
|
+
<strong className="eth-agent-scope-attachments__label">
|
|
66
|
+
{attachment.label}
|
|
67
|
+
</strong>
|
|
68
|
+
<Badge severity="neutral">{kindLabel(attachment.kind)}</Badge>
|
|
69
|
+
</span>
|
|
70
|
+
<span className="eth-agent-scope-attachments__meta">
|
|
71
|
+
{kindDescription(attachment.kind)}
|
|
72
|
+
</span>
|
|
73
|
+
</span>
|
|
74
|
+
<span className="eth-agent-scope-attachments__aside">
|
|
75
|
+
{attachment.status ? (
|
|
76
|
+
<StatusDot status={attachment.status} label={statusLabel(attachment.status)} />
|
|
77
|
+
) : (
|
|
78
|
+
<span className="eth-agent-scope-attachments__untracked">No status</span>
|
|
79
|
+
)}
|
|
80
|
+
{onRemove ? (
|
|
81
|
+
<Button
|
|
82
|
+
aria-label={`Remove ${attachment.label}`}
|
|
83
|
+
density="compact"
|
|
84
|
+
intent="ghost"
|
|
85
|
+
onClick={() => onRemove(attachment.id)}
|
|
86
|
+
>
|
|
87
|
+
Remove
|
|
88
|
+
</Button>
|
|
89
|
+
) : null}
|
|
90
|
+
</span>
|
|
91
|
+
</li>
|
|
92
|
+
))}
|
|
93
|
+
</ul>
|
|
94
|
+
</Surface>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function kindLabel(kind: AgentScopeAttachment["kind"]) {
|
|
99
|
+
switch (kind) {
|
|
100
|
+
case "app-domain":
|
|
101
|
+
return "App domain";
|
|
102
|
+
case "document":
|
|
103
|
+
return "Document";
|
|
104
|
+
default:
|
|
105
|
+
return kind.charAt(0).toUpperCase() + kind.slice(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function kindDescription(kind: AgentScopeAttachment["kind"]) {
|
|
110
|
+
switch (kind) {
|
|
111
|
+
case "app-domain":
|
|
112
|
+
return "Runnable application boundary";
|
|
113
|
+
case "document":
|
|
114
|
+
return "Source document";
|
|
115
|
+
case "project":
|
|
116
|
+
return "Project scope";
|
|
117
|
+
case "resource":
|
|
118
|
+
return "External resource";
|
|
119
|
+
case "task":
|
|
120
|
+
return "Task context";
|
|
121
|
+
case "scope":
|
|
122
|
+
return "General scope";
|
|
123
|
+
default:
|
|
124
|
+
return "Attached context";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function kindInitial(kind: AgentScopeAttachment["kind"]) {
|
|
129
|
+
return kindLabel(kind).charAt(0);
|
|
130
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import type { EthOperationalStatus, EthSeverity } from "@echothink-ui/core";
|
|
3
|
+
import type {
|
|
4
|
+
AgentDiffItem,
|
|
5
|
+
AgentPlanStep,
|
|
6
|
+
AgentRunState,
|
|
7
|
+
AgentSafetyCheck
|
|
8
|
+
} from "../types";
|
|
9
|
+
|
|
10
|
+
export const EMPTY_MARK = "—";
|
|
11
|
+
|
|
12
|
+
export function formatDateTime(value: string) {
|
|
13
|
+
const date = new Date(value);
|
|
14
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
15
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
16
|
+
dateStyle: "medium",
|
|
17
|
+
timeStyle: "short"
|
|
18
|
+
}).format(date);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function formatTime(value: string) {
|
|
22
|
+
const date = new Date(value);
|
|
23
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
24
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
25
|
+
hour: "2-digit",
|
|
26
|
+
minute: "2-digit",
|
|
27
|
+
second: "2-digit"
|
|
28
|
+
}).format(date);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function formatDuration(durationMs?: number) {
|
|
32
|
+
if (durationMs == null) return EMPTY_MARK;
|
|
33
|
+
if (durationMs < 1000) return `${Math.round(durationMs)} ms`;
|
|
34
|
+
return `${(durationMs / 1000).toFixed(durationMs < 10000 ? 1 : 0)} s`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function jsonPreview(value: unknown, redacted = false, maxLength = 120) {
|
|
38
|
+
if (redacted) return "[redacted]";
|
|
39
|
+
if (value == null) return EMPTY_MARK;
|
|
40
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
41
|
+
if (!text) return EMPTY_MARK;
|
|
42
|
+
return text.length > maxLength ? `${text.slice(0, maxLength - 1)}…` : text;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function prettyJson(value: unknown, redacted = false) {
|
|
46
|
+
if (redacted) return "[redacted]";
|
|
47
|
+
if (value == null) return EMPTY_MARK;
|
|
48
|
+
if (typeof value === "string") return value;
|
|
49
|
+
return JSON.stringify(value, null, 2);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function stateLabel(state: AgentRunState) {
|
|
53
|
+
switch (state) {
|
|
54
|
+
case "idle":
|
|
55
|
+
return "Idle";
|
|
56
|
+
case "listening":
|
|
57
|
+
return "Listening";
|
|
58
|
+
case "thinking":
|
|
59
|
+
return "Thinking";
|
|
60
|
+
case "tool-calling":
|
|
61
|
+
return "Calling tools";
|
|
62
|
+
case "waiting-for-user":
|
|
63
|
+
return "Waiting for user";
|
|
64
|
+
case "interrupted":
|
|
65
|
+
return "Interrupted";
|
|
66
|
+
case "failed":
|
|
67
|
+
return "Failed";
|
|
68
|
+
case "completed":
|
|
69
|
+
return "Completed";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function stateSeverity(state: AgentRunState): EthSeverity {
|
|
74
|
+
switch (state) {
|
|
75
|
+
case "failed":
|
|
76
|
+
return "danger";
|
|
77
|
+
case "completed":
|
|
78
|
+
return "success";
|
|
79
|
+
case "waiting-for-user":
|
|
80
|
+
case "interrupted":
|
|
81
|
+
return "warning";
|
|
82
|
+
case "listening":
|
|
83
|
+
case "thinking":
|
|
84
|
+
case "tool-calling":
|
|
85
|
+
return "info";
|
|
86
|
+
case "idle":
|
|
87
|
+
return "neutral";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function stateStatus(state: AgentRunState): EthOperationalStatus {
|
|
92
|
+
switch (state) {
|
|
93
|
+
case "completed":
|
|
94
|
+
return "completed";
|
|
95
|
+
case "failed":
|
|
96
|
+
return "failed";
|
|
97
|
+
case "waiting-for-user":
|
|
98
|
+
return "pending-approval";
|
|
99
|
+
case "interrupted":
|
|
100
|
+
return "warning";
|
|
101
|
+
case "listening":
|
|
102
|
+
case "thinking":
|
|
103
|
+
case "tool-calling":
|
|
104
|
+
return "running";
|
|
105
|
+
case "idle":
|
|
106
|
+
return "inactive";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function isActiveAgentState(state: AgentRunState) {
|
|
111
|
+
return state === "listening" || state === "thinking" || state === "tool-calling";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function statusSeverity(status: EthOperationalStatus): EthSeverity {
|
|
115
|
+
if (status === "failed" || status === "blocked" || status === "approval-required") {
|
|
116
|
+
return "danger";
|
|
117
|
+
}
|
|
118
|
+
if (status === "warning" || status === "pending-approval") return "warning";
|
|
119
|
+
if (status === "completed" || status === "succeeded" || status === "synced") return "success";
|
|
120
|
+
if (status === "running" || status === "in-progress" || status === "active") return "info";
|
|
121
|
+
return "neutral";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function riskSeverity(riskLevel: "low" | "medium" | "high" | "critical" = "low") {
|
|
125
|
+
if (riskLevel === "critical" || riskLevel === "high") return "danger" satisfies EthSeverity;
|
|
126
|
+
if (riskLevel === "medium") return "warning" satisfies EthSeverity;
|
|
127
|
+
return "info" satisfies EthSeverity;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function safetyStatus(check: AgentSafetyCheck): EthOperationalStatus {
|
|
131
|
+
if (check.status === "pass") return "completed";
|
|
132
|
+
if (check.status === "warn") return "warning";
|
|
133
|
+
return "failed";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function safetySeverity(check: AgentSafetyCheck): EthSeverity {
|
|
137
|
+
if (check.status === "pass") return "success";
|
|
138
|
+
if (check.status === "warn") return "warning";
|
|
139
|
+
return "danger";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function mergeStyle(
|
|
143
|
+
base: React.CSSProperties,
|
|
144
|
+
incoming?: React.CSSProperties
|
|
145
|
+
): React.CSSProperties {
|
|
146
|
+
return incoming ? { ...base, ...incoming } : base;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function diffPlanSteps(before: AgentPlanStep[], after: AgentPlanStep[]): AgentDiffItem[] {
|
|
150
|
+
const beforeById = new Map(before.map((step) => [step.id, step]));
|
|
151
|
+
const afterById = new Map(after.map((step) => [step.id, step]));
|
|
152
|
+
const ids = Array.from(
|
|
153
|
+
new Set([...after.map((step) => step.id), ...before.map((step) => step.id)])
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return ids.map((id) => {
|
|
157
|
+
const beforeStep = beforeById.get(id);
|
|
158
|
+
const afterStep = afterById.get(id);
|
|
159
|
+
if (!beforeStep && afterStep) {
|
|
160
|
+
return { id, after: afterStep, status: "added", severity: "success" };
|
|
161
|
+
}
|
|
162
|
+
if (beforeStep && !afterStep) {
|
|
163
|
+
return { id, before: beforeStep, status: "removed", severity: "danger" };
|
|
164
|
+
}
|
|
165
|
+
const changed = JSON.stringify(beforeStep) !== JSON.stringify(afterStep);
|
|
166
|
+
return {
|
|
167
|
+
id,
|
|
168
|
+
before: beforeStep,
|
|
169
|
+
after: afterStep,
|
|
170
|
+
status: changed ? "modified" : "unchanged",
|
|
171
|
+
severity: changed ? "warning" : "neutral"
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const visuallyHiddenStyle: React.CSSProperties = {
|
|
177
|
+
border: 0,
|
|
178
|
+
clip: "rect(0 0 0 0)",
|
|
179
|
+
height: 1,
|
|
180
|
+
margin: -1,
|
|
181
|
+
overflow: "hidden",
|
|
182
|
+
padding: 0,
|
|
183
|
+
position: "absolute",
|
|
184
|
+
whiteSpace: "nowrap",
|
|
185
|
+
width: 1
|
|
186
|
+
};
|