@actioneer/ads 1.0.7 → 1.0.8
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/DESIGN.md +80 -86
- package/README.md +6 -0
- package/components/ai-elements/chart.js +20 -20
- package/components/ai-elements/inline-citation.js +50 -50
- package/components/ai-elements/message.js +1 -1
- package/components/ai-elements/tool.js +5 -5
- package/components/alert.d.ts +3 -2
- package/components/alert.js +25 -25
- package/components/answer.js +1 -1
- package/components/assistant-ui/action-bar.js +1 -1
- package/components/assistant-ui/attachment.js +68 -49
- package/components/assistant-ui/composer-add-context.js +1 -1
- package/components/assistant-ui/composer-trigger-popover.js +1 -1
- package/components/assistant-ui/composer.js +13 -13
- package/components/assistant-ui/message-timing.js +6 -6
- package/components/assistant-ui/selection-toolbar.js +5 -5
- package/components/assistant-ui/sources.js +15 -15
- package/components/assistant-ui/tooltip-icon-button.js +18 -14
- package/components/audio-waveform.d.ts +21 -8
- package/components/audio-waveform.js +371 -94
- package/components/autocomplete.js +9 -9
- package/components/breadcrumbs.js +32 -38
- package/components/button.js +83 -73
- package/components/card.js +1 -1
- package/components/chart.js +732 -673
- package/components/chip.js +37 -35
- package/components/command-palette.js +12 -12
- package/components/connector-card.d.ts +1 -2
- package/components/connector-card.js +17 -17
- package/components/date-range-picker.js +1 -2
- package/components/drawer.js +9 -10
- package/components/dropdown.js +1 -1
- package/components/empty-state.d.ts +2 -0
- package/components/empty-state.js +17 -15
- package/components/field.js +9 -10
- package/components/file-dropzone.d.ts +6 -3
- package/components/file-dropzone.js +72 -60
- package/components/infinite-canvas.js +9 -9
- package/components/modal.js +1 -1
- package/components/page-body.js +8 -8
- package/components/popover.js +11 -11
- package/components/progress-circle.d.ts +1 -1
- package/components/progress-circle.js +57 -48
- package/components/segmented.d.ts +1 -0
- package/components/segmented.js +34 -36
- package/components/select.js +2 -2
- package/components/selection-action-bar.js +32 -31
- package/components/setting-row.js +10 -10
- package/components/slider.js +5 -5
- package/components/stepper.d.ts +1 -1
- package/components/stepper.js +1 -1
- package/components/switch.js +10 -10
- package/components/table.d.ts +2 -2
- package/components/table.js +177 -170
- package/components/tabs.js +41 -35
- package/components/timeline.js +3 -3
- package/components/toast.js +53 -56
- package/components/ui/button.js +4 -4
- package/components/ui/dialog.js +1 -1
- package/components/viz-charts.js +263 -221
- package/examples/README.md +30 -0
- package/examples/agents-screen.tsx +477 -0
- package/examples/settings-screen.tsx +90 -0
- package/examples/workspace-header.tsx +71 -0
- package/lib/icons.d.ts +8 -0
- package/lib/icons.js +171 -118
- package/llms.txt +14 -14
- package/manifest.json +77 -48
- package/package.json +1 -1
- package/styles.css +55 -68
- package/theme.css +50 -59
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ADS sample screens
|
|
2
|
+
|
|
3
|
+
Full screens built only from `@actioneer/ads` exports. Open them to see how the
|
|
4
|
+
components are meant to be composed, or copy one into your app as a starting
|
|
5
|
+
point and swap the sample data for your own.
|
|
6
|
+
|
|
7
|
+
Before rendering any of them, finish the package setup in the main README:
|
|
8
|
+
import `@actioneer/ads/styles.css` right after `tailwindcss`, and wrap your app
|
|
9
|
+
root in `MotionConfig`, `ToastProvider` and `TooltipProvider`.
|
|
10
|
+
|
|
11
|
+
| File | Export | What it shows |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `agents-screen.tsx` | `AgentsScreen` | A full list page: `PageHeader` with a primary action, a summary strip, `Tabs` with counts leading a `SearchDisclosure`, a filter `Dropdown`, a sortable `Table` with clickable rows and a pinned action column, per-row actions with `Tooltip`s, and filtered `EmptyState`s. |
|
|
14
|
+
| `settings-screen.tsx` | `SettingsScreen` | A settings card: `Card` header with a status `Badge`, an `InputField`, a `SettingList` of `SettingRow`s with `Switch` and `Button` controls, and one primary action. |
|
|
15
|
+
| `workspace-header.tsx` | `WorkspaceHeaderScreen` | A page header with a status `Badge` and trailing actions, then `Tabs` with counts whose panels hold `Card`s. |
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// app/agents/page.tsx
|
|
19
|
+
"use client";
|
|
20
|
+
|
|
21
|
+
import { AgentsScreen } from "./agents-screen"; // copied from node_modules/@actioneer/ads/examples
|
|
22
|
+
|
|
23
|
+
export default function Page() {
|
|
24
|
+
return <AgentsScreen />;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
These are the same compositions the ADS Storybook renders under
|
|
29
|
+
**Recipes**, so what you see there is what these files produce. Icons come from
|
|
30
|
+
`@actioneer/ads/lib/icons`; never install a second icon package.
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agents screen — a full list page built only from ADS exports.
|
|
3
|
+
*
|
|
4
|
+
* What it shows: a PageHeader with a primary action, a summary strip, Tabs
|
|
5
|
+
* with counts leading a trailing SearchDisclosure, a filter Dropdown, a
|
|
6
|
+
* sortable Table whose rows open a record and keep a pinned action column,
|
|
7
|
+
* per-row Dropdown actions with Tooltips, and filtered EmptyStates.
|
|
8
|
+
*
|
|
9
|
+
* Copy it into your app and replace the sample AGENTS data with your own.
|
|
10
|
+
*/
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
import {
|
|
13
|
+
Avatar,
|
|
14
|
+
Button,
|
|
15
|
+
Dropdown,
|
|
16
|
+
DropdownContent,
|
|
17
|
+
DropdownItem,
|
|
18
|
+
DropdownTrigger,
|
|
19
|
+
EmptyState,
|
|
20
|
+
PageHeader,
|
|
21
|
+
SearchDisclosure,
|
|
22
|
+
Tab,
|
|
23
|
+
TabList,
|
|
24
|
+
Table,
|
|
25
|
+
TableBody,
|
|
26
|
+
TableCell,
|
|
27
|
+
TableColumn,
|
|
28
|
+
TableHeader,
|
|
29
|
+
TableRow,
|
|
30
|
+
Tabs,
|
|
31
|
+
Tooltip,
|
|
32
|
+
type SortDescriptor,
|
|
33
|
+
} from "@actioneer/ads";
|
|
34
|
+
import {
|
|
35
|
+
Close,
|
|
36
|
+
Copy,
|
|
37
|
+
Eye,
|
|
38
|
+
Filter,
|
|
39
|
+
MoreHorizontal,
|
|
40
|
+
PhoneIncoming,
|
|
41
|
+
Search,
|
|
42
|
+
Trash,
|
|
43
|
+
} from "@actioneer/ads/lib/icons";
|
|
44
|
+
|
|
45
|
+
export type AgentStatus = "deployed" | "not-deployed";
|
|
46
|
+
export type AgentUsage = "all" | "deployed" | "not-deployed" | "hotlines" | "campaigns";
|
|
47
|
+
export type ManualFilter = "original" | "duplicated" | "has-calls" | "no-calls";
|
|
48
|
+
|
|
49
|
+
export interface AgentRecord {
|
|
50
|
+
id: string;
|
|
51
|
+
name: string;
|
|
52
|
+
type: string;
|
|
53
|
+
updated: string;
|
|
54
|
+
updatedSort: number;
|
|
55
|
+
status: AgentStatus;
|
|
56
|
+
lineage?: string;
|
|
57
|
+
hotlines: number;
|
|
58
|
+
campaigns: number;
|
|
59
|
+
steps: number;
|
|
60
|
+
calls: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const AGENTS: AgentRecord[] = [
|
|
64
|
+
{ id: "simran", name: "Simran", type: "Voice Agent", updated: "Aug 25, 2026", updatedSort: 20260825, status: "deployed", hotlines: 0, campaigns: 1, steps: 0, calls: 0 },
|
|
65
|
+
{ id: "vivaan", name: "Vivaan", type: "Voice Agent", updated: "Aug 26, 2026", updatedSort: 20260826, status: "not-deployed", lineage: "Simran", hotlines: 0, campaigns: 0, steps: 0, calls: 0 },
|
|
66
|
+
{ id: "manish", name: "Manish", type: "Voice Agent", updated: "Aug 26, 2026", updatedSort: 20260826, status: "not-deployed", lineage: "Esha", hotlines: 0, campaigns: 0, steps: 0, calls: 0 },
|
|
67
|
+
{ id: "esha", name: "Esha", type: "Voice Agent", updated: "Aug 26, 2026", updatedSort: 20260826, status: "not-deployed", lineage: "Simran", hotlines: 0, campaigns: 0, steps: 0, calls: 0 },
|
|
68
|
+
{ id: "ananya", name: "Ananya", type: "KYC completion support", updated: "Aug 6, 2026", updatedSort: 20260806, status: "deployed", hotlines: 0, campaigns: 5, steps: 28, calls: 96 },
|
|
69
|
+
{ id: "aarohi", name: "Aarohi", type: "EMI due-date reminder", updated: "Aug 6, 2026", updatedSort: 20260806, status: "deployed", hotlines: 0, campaigns: 4, steps: 28, calls: 96 },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
export const USAGE_TABS = [
|
|
73
|
+
{ value: "all", label: "All", count: 6 },
|
|
74
|
+
{ value: "deployed", label: "Deployed", count: 3 },
|
|
75
|
+
{ value: "not-deployed", label: "Not yet deployed", count: 3 },
|
|
76
|
+
{ value: "hotlines", label: "On hotlines", count: 0 },
|
|
77
|
+
{ value: "campaigns", label: "In campaigns", count: 3 },
|
|
78
|
+
] as const;
|
|
79
|
+
|
|
80
|
+
export const MANUAL_FILTERS = [
|
|
81
|
+
{ value: "original", label: "Original agents" },
|
|
82
|
+
{ value: "duplicated", label: "Duplicated agents" },
|
|
83
|
+
{ value: "has-calls", label: "Has calls" },
|
|
84
|
+
{ value: "no-calls", label: "No calls" },
|
|
85
|
+
] as const;
|
|
86
|
+
|
|
87
|
+
export function ScreenFrame({ children }: { children: React.ReactNode }) {
|
|
88
|
+
return <div className="min-h-screen bg-card p-5 text-foreground">{children}</div>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function AgentStatusAvatar({ name, status }: { name: string; status: AgentStatus }) {
|
|
92
|
+
const label = status === "deployed" ? "Deployed" : "Not yet deployed";
|
|
93
|
+
return (
|
|
94
|
+
<span className="relative inline-flex shrink-0">
|
|
95
|
+
<Avatar name={name} size="sm" />
|
|
96
|
+
<span
|
|
97
|
+
aria-hidden="true"
|
|
98
|
+
className={status === "deployed"
|
|
99
|
+
? "absolute bottom-0 right-0 size-2 rounded-full bg-success ring-1 ring-card"
|
|
100
|
+
: "absolute bottom-0 right-0 size-2 rounded-full bg-wg6 ring-1 ring-card"}
|
|
101
|
+
/>
|
|
102
|
+
<span className="sr-only">{label}</span>
|
|
103
|
+
</span>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function AgentIdentity({ agent }: { agent: AgentRecord }) {
|
|
108
|
+
return (
|
|
109
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
110
|
+
<AgentStatusAvatar name={agent.name} status={agent.status} />
|
|
111
|
+
<div className="min-w-0">
|
|
112
|
+
<div className="truncate text-m font-medium text-foreground">{agent.name}</div>
|
|
113
|
+
{agent.lineage && (
|
|
114
|
+
<div className="truncate text-s text-secondary">
|
|
115
|
+
Copy of {agent.lineage}
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function AgentSummaryStripContent() {
|
|
124
|
+
const items = [
|
|
125
|
+
{ label: "Deployed", value: "3", detail: "of 6 agents" },
|
|
126
|
+
{ label: "In campaigns", value: "3", detail: "ready to engage" },
|
|
127
|
+
{ label: "Call activity", value: "192", detail: "all-time calls" },
|
|
128
|
+
];
|
|
129
|
+
return (
|
|
130
|
+
<section
|
|
131
|
+
aria-label="Agent summary"
|
|
132
|
+
className="grid min-h-20 grid-cols-1 gap-4 rounded-m border border-table-stroke px-4 py-5 sm:grid-cols-3 sm:gap-0"
|
|
133
|
+
>
|
|
134
|
+
{items.map((item, index) => (
|
|
135
|
+
<div
|
|
136
|
+
key={item.label}
|
|
137
|
+
className={index === 0 ? "min-w-0 sm:pr-6" : "min-w-0 border-table-stroke sm:border-l sm:px-6"}
|
|
138
|
+
>
|
|
139
|
+
<p className="text-s font-medium uppercase tracking-wide text-table-header-foreground">
|
|
140
|
+
{item.label}
|
|
141
|
+
</p>
|
|
142
|
+
<p className="mt-2 flex items-baseline gap-2">
|
|
143
|
+
<span className="text-xl font-medium leading-none tabular-nums text-foreground">{item.value}</span>
|
|
144
|
+
<span className="text-m text-muted-foreground">{item.detail}</span>
|
|
145
|
+
</p>
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</section>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function AgentRowActions({
|
|
153
|
+
name,
|
|
154
|
+
defaultOpen = false,
|
|
155
|
+
focusTrigger = false,
|
|
156
|
+
}: {
|
|
157
|
+
name: string;
|
|
158
|
+
defaultOpen?: boolean;
|
|
159
|
+
focusTrigger?: boolean;
|
|
160
|
+
}) {
|
|
161
|
+
return (
|
|
162
|
+
<Dropdown defaultOpen={defaultOpen}>
|
|
163
|
+
<Tooltip content={`Actions for ${name}`}>
|
|
164
|
+
<span className="inline-flex">
|
|
165
|
+
<DropdownTrigger>
|
|
166
|
+
<Button
|
|
167
|
+
autoFocus={focusTrigger}
|
|
168
|
+
variant="ghost"
|
|
169
|
+
size="icon-sm"
|
|
170
|
+
aria-label={`Actions for ${name}`}
|
|
171
|
+
className="enabled:hover:bg-card enabled:hover:text-foreground enabled:active:bg-card enabled:active:text-foreground"
|
|
172
|
+
>
|
|
173
|
+
<MoreHorizontal aria-hidden="true" />
|
|
174
|
+
</Button>
|
|
175
|
+
</DropdownTrigger>
|
|
176
|
+
</span>
|
|
177
|
+
</Tooltip>
|
|
178
|
+
<DropdownContent align="end" autoFocusFirstItem={defaultOpen}>
|
|
179
|
+
<DropdownItem startContent={<Eye aria-hidden="true" />}>See details</DropdownItem>
|
|
180
|
+
<DropdownItem startContent={<Copy aria-hidden="true" />}>Duplicate</DropdownItem>
|
|
181
|
+
<DropdownItem danger startContent={<Trash aria-hidden="true" />}>Delete</DropdownItem>
|
|
182
|
+
</DropdownContent>
|
|
183
|
+
</Dropdown>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function matchesUsage(agent: AgentRecord, usage: AgentUsage) {
|
|
188
|
+
if (usage === "deployed") return agent.status === "deployed";
|
|
189
|
+
if (usage === "not-deployed") return agent.status === "not-deployed";
|
|
190
|
+
if (usage === "hotlines") return agent.hotlines > 0;
|
|
191
|
+
if (usage === "campaigns") return agent.campaigns > 0;
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function matchesManualFilter(agent: AgentRecord, filter: ManualFilter) {
|
|
196
|
+
if (filter === "original") return !agent.lineage;
|
|
197
|
+
if (filter === "duplicated") return Boolean(agent.lineage);
|
|
198
|
+
if (filter === "has-calls") return agent.calls > 0;
|
|
199
|
+
return agent.calls === 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function sortAgents(agents: AgentRecord[], descriptor?: SortDescriptor) {
|
|
203
|
+
if (!descriptor) return agents;
|
|
204
|
+
const direction = descriptor.direction === "ascending" ? 1 : -1;
|
|
205
|
+
const value = (agent: AgentRecord): string | number => {
|
|
206
|
+
if (descriptor.column === "type") return agent.type;
|
|
207
|
+
if (descriptor.column === "updated") return agent.updatedSort;
|
|
208
|
+
if (descriptor.column === "hotlines") return agent.hotlines;
|
|
209
|
+
if (descriptor.column === "campaigns") return agent.campaigns;
|
|
210
|
+
if (descriptor.column === "steps") return agent.steps;
|
|
211
|
+
if (descriptor.column === "calls") return agent.calls;
|
|
212
|
+
return agent.name;
|
|
213
|
+
};
|
|
214
|
+
return [...agents].sort((left, right) => {
|
|
215
|
+
const a = value(left);
|
|
216
|
+
const b = value(right);
|
|
217
|
+
const result = typeof a === "number" && typeof b === "number"
|
|
218
|
+
? a - b
|
|
219
|
+
: String(a).localeCompare(String(b));
|
|
220
|
+
return result * direction;
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function AgentsFilterMenu({
|
|
225
|
+
filters,
|
|
226
|
+
onAdd,
|
|
227
|
+
}: {
|
|
228
|
+
filters: ManualFilter[];
|
|
229
|
+
onAdd: (filter: ManualFilter) => void;
|
|
230
|
+
}) {
|
|
231
|
+
return (
|
|
232
|
+
<Dropdown>
|
|
233
|
+
<Tooltip content="Add filter">
|
|
234
|
+
<span className="inline-flex">
|
|
235
|
+
<DropdownTrigger>
|
|
236
|
+
<Button
|
|
237
|
+
variant="outline"
|
|
238
|
+
size="icon-sm"
|
|
239
|
+
aria-label="Add filter"
|
|
240
|
+
data-active={filters.length > 0 || undefined}
|
|
241
|
+
className="aria-expanded:text-action-primary-text data-[active=true]:text-action-primary-text"
|
|
242
|
+
>
|
|
243
|
+
<Filter aria-hidden="true" />
|
|
244
|
+
{filters.length > 0 && (
|
|
245
|
+
<span aria-hidden="true" className="absolute -right-1 -top-1 size-2 rounded-full bg-danger" />
|
|
246
|
+
)}
|
|
247
|
+
</Button>
|
|
248
|
+
</DropdownTrigger>
|
|
249
|
+
</span>
|
|
250
|
+
</Tooltip>
|
|
251
|
+
<DropdownContent align="end" autoFocusFirstItem>
|
|
252
|
+
{MANUAL_FILTERS.map((filter) => (
|
|
253
|
+
<DropdownItem
|
|
254
|
+
key={filter.value}
|
|
255
|
+
disabled={filters.includes(filter.value)}
|
|
256
|
+
onSelect={() => onAdd(filter.value)}
|
|
257
|
+
>
|
|
258
|
+
{filter.label}
|
|
259
|
+
</DropdownItem>
|
|
260
|
+
))}
|
|
261
|
+
</DropdownContent>
|
|
262
|
+
</Dropdown>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function AgentsEmptyState({
|
|
267
|
+
title,
|
|
268
|
+
description,
|
|
269
|
+
hotline = false,
|
|
270
|
+
action,
|
|
271
|
+
}: {
|
|
272
|
+
title: string;
|
|
273
|
+
description?: string;
|
|
274
|
+
hotline?: boolean;
|
|
275
|
+
action?: "view-all" | "clear";
|
|
276
|
+
}) {
|
|
277
|
+
return (
|
|
278
|
+
<div className="rounded-m border border-table-stroke bg-card">
|
|
279
|
+
<EmptyState
|
|
280
|
+
icon={hotline ? <PhoneIncoming aria-hidden="true" /> : <Search aria-hidden="true" />}
|
|
281
|
+
title={title}
|
|
282
|
+
description={description}
|
|
283
|
+
action={action === "view-all"
|
|
284
|
+
? <Button size="sm" variant="outline">View all agents</Button>
|
|
285
|
+
: action === "clear"
|
|
286
|
+
? <Button size="sm" variant="outline">Clear filters</Button>
|
|
287
|
+
: undefined}
|
|
288
|
+
/>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function AgentsComposition() {
|
|
294
|
+
const toolbarRef = React.useRef<HTMLDivElement>(null);
|
|
295
|
+
const [usage, setUsage] = React.useState<AgentUsage>("all");
|
|
296
|
+
const [search, setSearch] = React.useState("");
|
|
297
|
+
const [searchOpen, setSearchOpen] = React.useState(false);
|
|
298
|
+
const [manualFilters, setManualFilters] = React.useState<ManualFilter[]>([]);
|
|
299
|
+
const [sortDescriptor, setSortDescriptor] = React.useState<SortDescriptor>();
|
|
300
|
+
const [toolbarIsConstrained, setToolbarIsConstrained] = React.useState(false);
|
|
301
|
+
|
|
302
|
+
React.useEffect(() => {
|
|
303
|
+
const toolbar = toolbarRef.current;
|
|
304
|
+
if (!toolbar) return;
|
|
305
|
+
|
|
306
|
+
const update = () => setToolbarIsConstrained(toolbar.clientWidth < 820);
|
|
307
|
+
update();
|
|
308
|
+
|
|
309
|
+
const observer = new ResizeObserver(update);
|
|
310
|
+
observer.observe(toolbar);
|
|
311
|
+
return () => observer.disconnect();
|
|
312
|
+
}, []);
|
|
313
|
+
|
|
314
|
+
const filtered = AGENTS.filter((agent) => {
|
|
315
|
+
const query = search.trim().toLowerCase();
|
|
316
|
+
const matchesQuery = !query || `${agent.name} ${agent.type} ${agent.lineage ?? ""}`.toLowerCase().includes(query);
|
|
317
|
+
return matchesQuery && matchesUsage(agent, usage) && manualFilters.every((filter) => matchesManualFilter(agent, filter));
|
|
318
|
+
});
|
|
319
|
+
const rows = sortAgents(filtered, sortDescriptor);
|
|
320
|
+
const hasManualQuery = Boolean(search.trim() || manualFilters.length > 0);
|
|
321
|
+
const compactTabs = searchOpen || toolbarIsConstrained;
|
|
322
|
+
|
|
323
|
+
const clearQuery = () => {
|
|
324
|
+
setSearch("");
|
|
325
|
+
setManualFilters([]);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
let emptyTitle = "No agents match";
|
|
329
|
+
let emptyDescription: string | undefined = hasManualQuery ? "No agents match the applied search or manual filters." : undefined;
|
|
330
|
+
let emptyHotline = false;
|
|
331
|
+
if (!hasManualQuery && usage === "hotlines") {
|
|
332
|
+
emptyTitle = "No agents on hotlines";
|
|
333
|
+
emptyDescription = undefined;
|
|
334
|
+
emptyHotline = true;
|
|
335
|
+
} else if (!hasManualQuery && usage === "deployed") {
|
|
336
|
+
emptyTitle = "No deployed agents";
|
|
337
|
+
} else if (!hasManualQuery && usage === "not-deployed") {
|
|
338
|
+
emptyTitle = "All agents are deployed";
|
|
339
|
+
} else if (!hasManualQuery && usage === "campaigns") {
|
|
340
|
+
emptyTitle = "No agents in campaigns";
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<ScreenFrame>
|
|
345
|
+
<PageHeader
|
|
346
|
+
title="Agents"
|
|
347
|
+
actions={<Button size="sm">New agent</Button>}
|
|
348
|
+
/>
|
|
349
|
+
|
|
350
|
+
<div className="mt-10">
|
|
351
|
+
<AgentSummaryStripContent />
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<div className="mt-10">
|
|
355
|
+
<div ref={toolbarRef} className="flex w-full items-center gap-2">
|
|
356
|
+
<Tabs
|
|
357
|
+
className="min-w-0 flex-1 gap-0"
|
|
358
|
+
value={usage}
|
|
359
|
+
onValueChange={(value) => setUsage(value as AgentUsage)}
|
|
360
|
+
>
|
|
361
|
+
<TabList aria-label="Filter agents by usage" className="min-w-max">
|
|
362
|
+
{USAGE_TABS.map((tab, index) =>
|
|
363
|
+
compactTabs && index > 2 ? null : (
|
|
364
|
+
<Tab key={tab.value} value={tab.value} count={tab.count}>
|
|
365
|
+
{tab.label}
|
|
366
|
+
</Tab>
|
|
367
|
+
),
|
|
368
|
+
)}
|
|
369
|
+
{compactTabs && (
|
|
370
|
+
<button
|
|
371
|
+
type="button"
|
|
372
|
+
aria-label="Show 2 more usage filters"
|
|
373
|
+
onClick={() => setSearchOpen(false)}
|
|
374
|
+
className="relative -mb-px inline-flex h-8 items-center justify-center whitespace-nowrap text-l font-normal text-secondary outline-none transition-colors hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring"
|
|
375
|
+
>
|
|
376
|
+
+2 more
|
|
377
|
+
</button>
|
|
378
|
+
)}
|
|
379
|
+
</TabList>
|
|
380
|
+
</Tabs>
|
|
381
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
382
|
+
<SearchDisclosure
|
|
383
|
+
value={search}
|
|
384
|
+
onValueChange={setSearch}
|
|
385
|
+
open={searchOpen}
|
|
386
|
+
onOpenChange={setSearchOpen}
|
|
387
|
+
placeholder="Search agents"
|
|
388
|
+
inputLabel="Search agents by name or type"
|
|
389
|
+
triggerLabel="Search agents"
|
|
390
|
+
/>
|
|
391
|
+
<AgentsFilterMenu
|
|
392
|
+
filters={manualFilters}
|
|
393
|
+
onAdd={(filter) => setManualFilters((current) => current.includes(filter) ? current : [...current, filter])}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
{manualFilters.length > 0 && (
|
|
399
|
+
<div className="mt-2 flex flex-wrap items-center gap-2">
|
|
400
|
+
{manualFilters.map((filter) => {
|
|
401
|
+
const label = MANUAL_FILTERS.find((item) => item.value === filter)?.label ?? filter;
|
|
402
|
+
return (
|
|
403
|
+
<Button
|
|
404
|
+
key={filter}
|
|
405
|
+
variant="ghost"
|
|
406
|
+
size="xs"
|
|
407
|
+
aria-label={`Remove filter ${label}`}
|
|
408
|
+
onClick={() => setManualFilters((current) => current.filter((item) => item !== filter))}
|
|
409
|
+
endContent={<Close aria-hidden="true" />}
|
|
410
|
+
className="bg-action-primary-soft text-s text-action-primary-text [&>span]:gap-2"
|
|
411
|
+
>
|
|
412
|
+
{label}
|
|
413
|
+
</Button>
|
|
414
|
+
);
|
|
415
|
+
})}
|
|
416
|
+
<Button
|
|
417
|
+
variant="ghost"
|
|
418
|
+
size="xs"
|
|
419
|
+
onClick={() => setManualFilters([])}
|
|
420
|
+
className="text-s enabled:hover:bg-muted enabled:hover:text-foreground enabled:active:bg-muted enabled:active:text-foreground"
|
|
421
|
+
>
|
|
422
|
+
Clear
|
|
423
|
+
</Button>
|
|
424
|
+
</div>
|
|
425
|
+
)}
|
|
426
|
+
|
|
427
|
+
<section aria-label="Agents" className="mt-6">
|
|
428
|
+
{rows.length === 0 ? (
|
|
429
|
+
<EmptyState
|
|
430
|
+
icon={emptyHotline ? <PhoneIncoming aria-hidden="true" /> : <Search aria-hidden="true" />}
|
|
431
|
+
title={emptyTitle}
|
|
432
|
+
description={emptyDescription}
|
|
433
|
+
action={hasManualQuery
|
|
434
|
+
? <Button size="sm" variant="outline" onClick={clearQuery}>Clear filters</Button>
|
|
435
|
+
: <Button size="sm" variant="outline" onClick={() => setUsage("all")}>View all agents</Button>}
|
|
436
|
+
/>
|
|
437
|
+
) : (
|
|
438
|
+
<Table
|
|
439
|
+
aria-label="Your agents"
|
|
440
|
+
sortDescriptor={sortDescriptor}
|
|
441
|
+
onSortChange={setSortDescriptor}
|
|
442
|
+
>
|
|
443
|
+
<TableHeader>
|
|
444
|
+
<TableRow>
|
|
445
|
+
<TableColumn sortKey="agent">Agent</TableColumn>
|
|
446
|
+
<TableColumn sortKey="type">Type</TableColumn>
|
|
447
|
+
<TableColumn sortKey="updated" align="right">Updated</TableColumn>
|
|
448
|
+
<TableColumn sortKey="hotlines" align="right">Hotlines</TableColumn>
|
|
449
|
+
<TableColumn sortKey="campaigns" align="right">Campaigns</TableColumn>
|
|
450
|
+
<TableColumn sortKey="steps" align="right">Steps</TableColumn>
|
|
451
|
+
<TableColumn sortKey="calls" align="right">Calls</TableColumn>
|
|
452
|
+
<TableColumn allowsSorting={false} align="right" aria-label="Actions" />
|
|
453
|
+
</TableRow>
|
|
454
|
+
</TableHeader>
|
|
455
|
+
<TableBody>
|
|
456
|
+
{rows.map((agent) => (
|
|
457
|
+
<TableRow key={agent.id}>
|
|
458
|
+
<TableCell><AgentIdentity agent={agent} /></TableCell>
|
|
459
|
+
<TableCell className="text-secondary">{agent.type}</TableCell>
|
|
460
|
+
<TableCell align="right" className="tabular-nums text-secondary" sortValue={agent.updatedSort}>{agent.updated}</TableCell>
|
|
461
|
+
<TableCell align="right" className="tabular-nums">{agent.hotlines}</TableCell>
|
|
462
|
+
<TableCell align="right" className="tabular-nums">{agent.campaigns}</TableCell>
|
|
463
|
+
<TableCell align="right" className="tabular-nums">{agent.steps}</TableCell>
|
|
464
|
+
<TableCell align="right" className="tabular-nums">{agent.calls}</TableCell>
|
|
465
|
+
<TableCell align="right"><AgentRowActions name={agent.name} /></TableCell>
|
|
466
|
+
</TableRow>
|
|
467
|
+
))}
|
|
468
|
+
</TableBody>
|
|
469
|
+
</Table>
|
|
470
|
+
)}
|
|
471
|
+
</section>
|
|
472
|
+
</div>
|
|
473
|
+
</ScreenFrame>
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export { AgentsComposition as AgentsScreen };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings screen — a settings card built only from ADS exports.
|
|
3
|
+
*
|
|
4
|
+
* What it shows: Card header with a status Badge, an InputField, a SettingList
|
|
5
|
+
* of SettingRows whose trailing control is a Switch or Button, and a footer
|
|
6
|
+
* with one primary action.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import {
|
|
10
|
+
Badge,
|
|
11
|
+
Button,
|
|
12
|
+
Card,
|
|
13
|
+
CardBody,
|
|
14
|
+
CardDescription,
|
|
15
|
+
CardFooter,
|
|
16
|
+
CardHeader,
|
|
17
|
+
CardTitle,
|
|
18
|
+
InputField,
|
|
19
|
+
SettingList,
|
|
20
|
+
SettingRow,
|
|
21
|
+
Switch,
|
|
22
|
+
} from "@actioneer/ads";
|
|
23
|
+
import {
|
|
24
|
+
Database,
|
|
25
|
+
Settings,
|
|
26
|
+
User,
|
|
27
|
+
} from "@actioneer/ads/lib/icons";
|
|
28
|
+
|
|
29
|
+
export function SettingsScreen() {
|
|
30
|
+
const [digest, setDigest] = React.useState(false);
|
|
31
|
+
const [sharing, setSharing] = React.useState(true);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Card className="mx-auto w-full max-w-2xl">
|
|
35
|
+
<CardHeader>
|
|
36
|
+
<div className="flex items-start justify-between gap-4">
|
|
37
|
+
<div className="flex min-w-0 flex-col gap-1">
|
|
38
|
+
<CardTitle>Workspace preferences</CardTitle>
|
|
39
|
+
<CardDescription>
|
|
40
|
+
A canonical settings composition: context first, grouped controls second, one clear save action.
|
|
41
|
+
</CardDescription>
|
|
42
|
+
</div>
|
|
43
|
+
<Badge variant="dot" color="success">Saved</Badge>
|
|
44
|
+
</div>
|
|
45
|
+
</CardHeader>
|
|
46
|
+
|
|
47
|
+
<CardBody className="flex flex-col gap-5">
|
|
48
|
+
<InputField
|
|
49
|
+
label="Workspace name"
|
|
50
|
+
defaultValue="Actioneer research"
|
|
51
|
+
description="Shown to everyone in this workspace."
|
|
52
|
+
/>
|
|
53
|
+
|
|
54
|
+
<SettingList aria-label="Workspace settings">
|
|
55
|
+
<SettingRow
|
|
56
|
+
icon={<User />}
|
|
57
|
+
label="Weekly digest"
|
|
58
|
+
description="Receive a concise summary every Monday."
|
|
59
|
+
action={
|
|
60
|
+
<Switch checked={digest} onCheckedChange={setDigest}>
|
|
61
|
+
Weekly digest
|
|
62
|
+
</Switch>
|
|
63
|
+
}
|
|
64
|
+
/>
|
|
65
|
+
<SettingRow
|
|
66
|
+
icon={<Database />}
|
|
67
|
+
label="Source sharing"
|
|
68
|
+
description="Allow teammates to reuse connected sources."
|
|
69
|
+
action={
|
|
70
|
+
<Switch checked={sharing} onCheckedChange={setSharing}>
|
|
71
|
+
Source sharing
|
|
72
|
+
</Switch>
|
|
73
|
+
}
|
|
74
|
+
/>
|
|
75
|
+
<SettingRow
|
|
76
|
+
icon={<Settings />}
|
|
77
|
+
label="Advanced controls"
|
|
78
|
+
description="Manage retention and export permissions."
|
|
79
|
+
action={<Button variant="ghost" size="sm">Manage</Button>}
|
|
80
|
+
/>
|
|
81
|
+
</SettingList>
|
|
82
|
+
</CardBody>
|
|
83
|
+
|
|
84
|
+
<CardFooter className="justify-end gap-2">
|
|
85
|
+
<Button variant="ghost">Cancel</Button>
|
|
86
|
+
<Button>Save changes</Button>
|
|
87
|
+
</CardFooter>
|
|
88
|
+
</Card>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace header — a page header and tabbed body built only from ADS exports.
|
|
3
|
+
*
|
|
4
|
+
* What it shows: title with a status Badge, trailing page actions, and Tabs
|
|
5
|
+
* with counts whose panels hold Cards.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
Badge,
|
|
9
|
+
Button,
|
|
10
|
+
Card,
|
|
11
|
+
CardBody,
|
|
12
|
+
Tab,
|
|
13
|
+
TabList,
|
|
14
|
+
TabPanel,
|
|
15
|
+
Tabs,
|
|
16
|
+
} from "@actioneer/ads";
|
|
17
|
+
import {
|
|
18
|
+
ArrowUpRight,
|
|
19
|
+
Plus,
|
|
20
|
+
} from "@actioneer/ads/lib/icons";
|
|
21
|
+
|
|
22
|
+
export function WorkspaceHeaderScreen() {
|
|
23
|
+
return (
|
|
24
|
+
<section className="mx-auto flex w-full max-w-4xl flex-col gap-6">
|
|
25
|
+
<header className="flex flex-col items-start gap-5 sm:flex-row sm:justify-between">
|
|
26
|
+
<div className="flex w-full min-w-0 flex-1 flex-col gap-2">
|
|
27
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
28
|
+
<h1 className="text-2xl font-medium text-foreground">Research workspace</h1>
|
|
29
|
+
<Badge variant="dot" color="success">Connected</Badge>
|
|
30
|
+
</div>
|
|
31
|
+
<p className="max-w-2xl text-base text-muted-foreground">
|
|
32
|
+
Keep status beside the title, supporting context below, and primary actions at the far edge.
|
|
33
|
+
</p>
|
|
34
|
+
</div>
|
|
35
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
36
|
+
<Button variant="outline" endContent={<ArrowUpRight aria-hidden />}>Share</Button>
|
|
37
|
+
<Button startContent={<Plus aria-hidden />}>New board</Button>
|
|
38
|
+
</div>
|
|
39
|
+
</header>
|
|
40
|
+
|
|
41
|
+
<Tabs defaultValue="boards">
|
|
42
|
+
<TabList aria-label="Workspace sections">
|
|
43
|
+
<Tab value="boards" count={12}>Boards</Tab>
|
|
44
|
+
<Tab value="sources" count={6}>Sources</Tab>
|
|
45
|
+
<Tab value="members" count={8}>Members</Tab>
|
|
46
|
+
</TabList>
|
|
47
|
+
<TabPanel value="boards">
|
|
48
|
+
<Card>
|
|
49
|
+
<CardBody>
|
|
50
|
+
<p className="text-base text-foreground">Boards keep ongoing analysis visible and reusable.</p>
|
|
51
|
+
</CardBody>
|
|
52
|
+
</Card>
|
|
53
|
+
</TabPanel>
|
|
54
|
+
<TabPanel value="sources">
|
|
55
|
+
<Card>
|
|
56
|
+
<CardBody>
|
|
57
|
+
<p className="text-base text-foreground">Six connected sources are available to this workspace.</p>
|
|
58
|
+
</CardBody>
|
|
59
|
+
</Card>
|
|
60
|
+
</TabPanel>
|
|
61
|
+
<TabPanel value="members">
|
|
62
|
+
<Card>
|
|
63
|
+
<CardBody>
|
|
64
|
+
<p className="text-base text-foreground">Eight members can collaborate here.</p>
|
|
65
|
+
</CardBody>
|
|
66
|
+
</Card>
|
|
67
|
+
</TabPanel>
|
|
68
|
+
</Tabs>
|
|
69
|
+
</section>
|
|
70
|
+
);
|
|
71
|
+
}
|
package/lib/icons.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export type IconProps = Omit<React.ComponentProps<PhosphorIcon>, "ref"> & {
|
|
|
21
21
|
/** Legacy Hugeicons prop — `>= 2` maps to weight="bold", else ignored. */
|
|
22
22
|
strokeWidth?: number;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* Icon-only controls (Button `size="icon*"`, TooltipIconButton) provide this
|
|
26
|
+
* context. The control turns its icon teal on hover and draws no background.
|
|
27
|
+
* Only the info icon also swaps to its `fill` weight (a hidden twin that
|
|
28
|
+
* `[data-icon-hover-fill]:hover` in theme.css reveals); every other icon keeps
|
|
29
|
+
* its regular outline on hover.
|
|
30
|
+
*/
|
|
31
|
+
export declare const IconHoverFillContext: React.Context<boolean>;
|
|
24
32
|
export declare const ArrowDown: React.ForwardRefExoticComponent<Omit<import('@phosphor-icons/react').IconProps, "ref"> & {
|
|
25
33
|
/** Legacy Hugeicons prop — `>= 2` maps to weight="bold", else ignored. */
|
|
26
34
|
strokeWidth?: number;
|