@utopia-studio-design/design-system-cli 0.4.1 → 0.5.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 +23 -0
- package/bin/utopia-ds-mcp.mjs +9 -2
- package/bin/utopia-ds.mjs +106 -8
- package/data/blocks/activity-feed/ActivityFeedBlock.tsx +255 -0
- package/data/blocks/activity-feed/README.md +16 -0
- package/data/blocks/activity-feed/activity-feed-contract.ts +96 -0
- package/data/blocks/activity-feed/activity-feed-fixture.ts +22 -0
- package/data/blocks/activity-feed/activity-feed.css +154 -0
- package/data/blocks/agent-status/AgentStatusBlock.tsx +313 -0
- package/data/blocks/agent-status/README.md +15 -0
- package/data/blocks/agent-status/agent-status-contract.ts +133 -0
- package/data/blocks/agent-status/agent-status-fixture.ts +79 -0
- package/data/blocks/agent-status/agent-status.css +216 -0
- package/data/blocks/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
- package/data/blocks/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
- package/data/blocks/analytics-dashboard/README.md +17 -0
- package/data/blocks/analytics-dashboard/analytics-dashboard.css +92 -0
- package/data/blocks/calendar-application/CalendarApplicationBlocks.tsx +519 -0
- package/data/blocks/calendar-application/README.md +20 -0
- package/data/blocks/calendar-application/calendar-application.css +216 -0
- package/data/blocks/calendar-application/calendar-localization.ts +90 -0
- package/data/blocks/calendar-application/calendar-panel-contract.ts +72 -0
- package/data/blocks/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
- package/data/blocks/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
- package/data/blocks/dashboard-composer/README.md +16 -0
- package/data/blocks/dashboard-composer/RegistryDataBlocks.tsx +238 -0
- package/data/blocks/dashboard-composer/dashboard-composer.css +328 -0
- package/data/blocks/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
- package/data/blocks/database-workspace/README.md +9 -0
- package/data/blocks/database-workspace/database-workspace.css +107 -0
- package/data/blocks/interaction-workbench/InteractionWorkbench.tsx +271 -0
- package/data/blocks/interaction-workbench/README.md +9 -0
- package/data/blocks/interaction-workbench/interaction-workbench.css +226 -0
- package/data/docs/calendar.md +164 -0
- package/data/docs/charts.md +95 -0
- package/data/docs/composition-registry.md +174 -0
- package/data/docs/dashboard-composer.md +94 -0
- package/data/docs/database.md +68 -0
- package/data/docs/examples/dashboard-layout.v1.json +65 -0
- package/data/docs/foundations.md +1 -1
- package/data/docs/interaction-foundation.md +109 -0
- package/data/docs/quick-start-ai.md +23 -1
- package/data/manifests/blocks.json +392 -0
- package/data/manifests/catalog.json +7 -4
- package/data/manifests/components.json +179 -0
- package/data/manifests/composition-registry.json +427 -0
- package/data/manifests/motion-profiles.json +1 -1
- package/data/manifests/patterns.json +113 -1
- package/data/manifests/templates.json +346 -0
- package/data/manifests/theme-renacore.json +142 -0
- package/data/manifests/themes.json +314 -28
- package/data/templates/analytics-dashboard/AnalyticsDashboardBlocks.tsx +107 -0
- package/data/templates/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
- package/data/templates/analytics-dashboard/README.md +6 -0
- package/data/templates/analytics-dashboard/analytics-dashboard.css +92 -0
- package/data/templates/analytics-dashboard/index.html +13 -0
- package/data/templates/analytics-dashboard/main.tsx +196 -0
- package/data/templates/analytics-dashboard/styles.css +120 -0
- package/data/templates/analytics-dashboard/template.manifest.json +17 -0
- package/data/templates/calendar-application/CalendarApplicationBlocks.tsx +519 -0
- package/data/templates/calendar-application/README.md +10 -0
- package/data/templates/calendar-application/calendar-application.css +216 -0
- package/data/templates/calendar-application/calendar-localization.ts +90 -0
- package/data/templates/calendar-application/index.html +14 -0
- package/data/templates/calendar-application/main.tsx +409 -0
- package/data/templates/calendar-application/styles.css +248 -0
- package/data/templates/calendar-application/template.manifest.json +27 -0
- package/data/templates/composition-registry-lab/README.md +9 -0
- package/data/templates/composition-registry-lab/index.html +14 -0
- package/data/templates/composition-registry-lab/main.tsx +217 -0
- package/data/templates/composition-registry-lab/sample-layout.json +63 -0
- package/data/templates/composition-registry-lab/styles.css +343 -0
- package/data/templates/composition-registry-lab/template.manifest.json +34 -0
- package/data/templates/dashboard-composer/ActivityFeedBlock.tsx +255 -0
- package/data/templates/dashboard-composer/AgentStatusBlock.tsx +313 -0
- package/data/templates/dashboard-composer/AnalyticsDashboardBlocks.tsx +107 -0
- package/data/templates/dashboard-composer/AnalyticsMetricBlocks.tsx +87 -0
- package/data/templates/dashboard-composer/CalendarApplicationBlocks.tsx +519 -0
- package/data/templates/dashboard-composer/CeramicPuckAdapter.tsx +380 -0
- package/data/templates/dashboard-composer/DashboardComposerBlocks.tsx +337 -0
- package/data/templates/dashboard-composer/DatabaseWorkspaceBlocks.tsx +240 -0
- package/data/templates/dashboard-composer/README.md +53 -0
- package/data/templates/dashboard-composer/RegistryDataBlocks.tsx +238 -0
- package/data/templates/dashboard-composer/activity-feed-contract.ts +96 -0
- package/data/templates/dashboard-composer/activity-feed-fixture.ts +22 -0
- package/data/templates/dashboard-composer/activity-feed.css +154 -0
- package/data/templates/dashboard-composer/agent-status-contract.ts +133 -0
- package/data/templates/dashboard-composer/agent-status-fixture.ts +79 -0
- package/data/templates/dashboard-composer/agent-status-lab/index.html +19 -0
- package/data/templates/dashboard-composer/agent-status-lab/main.tsx +151 -0
- package/data/templates/dashboard-composer/agent-status-lab/styles.css +139 -0
- package/data/templates/dashboard-composer/agent-status.css +216 -0
- package/data/templates/dashboard-composer/analytics-dashboard.css +92 -0
- package/data/templates/dashboard-composer/calendar-application.css +216 -0
- package/data/templates/dashboard-composer/calendar-localization.ts +90 -0
- package/data/templates/dashboard-composer/calendar-panel-contract.ts +72 -0
- package/data/templates/dashboard-composer/dashboard-composer.css +328 -0
- package/data/templates/dashboard-composer/database-workspace.css +107 -0
- package/data/templates/dashboard-composer/index.html +14 -0
- package/data/templates/dashboard-composer/main.tsx +550 -0
- package/data/templates/dashboard-composer/sample-layout.json +147 -0
- package/data/templates/dashboard-composer/styles.css +198 -0
- package/data/templates/dashboard-composer/template.manifest.json +34 -0
- package/data/templates/dashboard-composer/visual-editor/PuckEditorSurface.tsx +132 -0
- package/data/templates/dashboard-composer/visual-editor/VisualEditorApp.tsx +343 -0
- package/data/templates/dashboard-composer/visual-editor/index.html +17 -0
- package/data/templates/dashboard-composer/visual-editor/main.tsx +95 -0
- package/data/templates/dashboard-composer/visual-editor/styles.css +310 -0
- package/data/templates/database-workspace/DatabaseWorkspaceBlocks.tsx +240 -0
- package/data/templates/database-workspace/README.md +5 -0
- package/data/templates/database-workspace/database-workspace.css +107 -0
- package/data/templates/database-workspace/index.html +14 -0
- package/data/templates/database-workspace/main.tsx +247 -0
- package/data/templates/database-workspace/styles.css +121 -0
- package/data/templates/database-workspace/template.manifest.json +19 -0
- package/data/templates/interaction-lab/InteractionWorkbench.tsx +271 -0
- package/data/templates/interaction-lab/README.md +7 -0
- package/data/templates/interaction-lab/index.html +14 -0
- package/data/templates/interaction-lab/interaction-workbench.css +226 -0
- package/data/templates/interaction-lab/main.tsx +104 -0
- package/data/templates/interaction-lab/styles.css +224 -0
- package/data/templates/interaction-lab/template.manifest.json +36 -0
- package/lib/api.mjs +72 -5
- package/lib/composition-runtime.mjs +382 -0
- package/package.json +4 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
.ceramic-activity-feed {
|
|
2
|
+
display: grid;
|
|
3
|
+
align-content: start;
|
|
4
|
+
gap: var(--space-3);
|
|
5
|
+
min-inline-size: 0;
|
|
6
|
+
block-size: 100%;
|
|
7
|
+
padding: var(--space-4);
|
|
8
|
+
background: var(--surface);
|
|
9
|
+
color: var(--foreground);
|
|
10
|
+
text-align: start;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.ceramic-activity-feed > header,
|
|
14
|
+
.ceramic-activity-feed > header > div,
|
|
15
|
+
.ceramic-activity-items > li,
|
|
16
|
+
.ceramic-activity-copy > div,
|
|
17
|
+
.ceramic-activity-feed > footer {
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ceramic-activity-feed > header {
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
gap: var(--space-3);
|
|
25
|
+
padding-block-end: var(--space-3);
|
|
26
|
+
border-block-end: var(--border-width-hairline) solid var(--border);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.ceramic-activity-feed > header > div {
|
|
30
|
+
align-items: start;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
gap: var(--space-1);
|
|
33
|
+
min-inline-size: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ceramic-activity-feed :is(h2, h3, p) {
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ceramic-activity-feed > header span {
|
|
41
|
+
color: var(--muted-foreground);
|
|
42
|
+
font-size: var(--font-size-eyebrow);
|
|
43
|
+
font-weight: var(--font-weight-semibold);
|
|
44
|
+
letter-spacing: var(--tracking-eyebrow);
|
|
45
|
+
text-transform: uppercase;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.ceramic-activity-groups {
|
|
49
|
+
display: grid;
|
|
50
|
+
gap: var(--space-4);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.ceramic-activity-groups > section {
|
|
54
|
+
display: grid;
|
|
55
|
+
gap: var(--space-2);
|
|
56
|
+
min-inline-size: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.ceramic-activity-groups h3 {
|
|
60
|
+
color: var(--muted-foreground);
|
|
61
|
+
font-size: var(--font-size-small);
|
|
62
|
+
font-weight: var(--font-weight-semibold);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.ceramic-activity-items {
|
|
66
|
+
display: grid;
|
|
67
|
+
gap: 0;
|
|
68
|
+
margin: 0;
|
|
69
|
+
padding: 0;
|
|
70
|
+
list-style: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.ceramic-activity-items > li {
|
|
74
|
+
position: relative;
|
|
75
|
+
align-items: start;
|
|
76
|
+
gap: var(--space-3);
|
|
77
|
+
min-inline-size: 0;
|
|
78
|
+
padding-block: var(--space-2);
|
|
79
|
+
border-block-end: var(--border-width-hairline) solid var(--border);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ceramic-activity-feed[data-density='compact'] .ceramic-activity-items > li {
|
|
83
|
+
padding-block: var(--space-1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ceramic-activity-feed[data-density='comfortable'] .ceramic-activity-items > li {
|
|
87
|
+
padding-block: var(--space-3);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.ceramic-activity-marker {
|
|
91
|
+
flex: 0 0 auto;
|
|
92
|
+
margin-block-start: var(--space-1);
|
|
93
|
+
background: var(--muted-foreground);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.ceramic-activity-marker[data-tone='info'] {
|
|
97
|
+
background: var(--primary);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.ceramic-activity-marker[data-tone='positive'] {
|
|
101
|
+
background: var(--success);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ceramic-activity-marker[data-tone='warning'] {
|
|
105
|
+
background: var(--warning);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.ceramic-activity-marker[data-tone='critical'] {
|
|
109
|
+
background: var(--destructive);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.ceramic-activity-copy {
|
|
113
|
+
display: grid;
|
|
114
|
+
gap: var(--space-1);
|
|
115
|
+
min-inline-size: 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.ceramic-activity-copy p {
|
|
119
|
+
overflow-wrap: anywhere;
|
|
120
|
+
line-height: var(--line-height-body);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.ceramic-activity-copy > div {
|
|
124
|
+
flex-wrap: wrap;
|
|
125
|
+
gap: var(--space-2);
|
|
126
|
+
color: var(--muted-foreground);
|
|
127
|
+
font-size: var(--font-size-small);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.ceramic-activity-state {
|
|
131
|
+
display: grid;
|
|
132
|
+
align-content: center;
|
|
133
|
+
justify-items: start;
|
|
134
|
+
gap: var(--space-2);
|
|
135
|
+
min-block-size: 10rem;
|
|
136
|
+
color: var(--muted-foreground);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.ceramic-activity-state strong {
|
|
140
|
+
color: var(--foreground);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.ceramic-activity-feed > footer {
|
|
144
|
+
justify-content: center;
|
|
145
|
+
min-block-size: var(--control-size-sm);
|
|
146
|
+
color: var(--muted-foreground);
|
|
147
|
+
font-size: var(--font-size-small);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@media (max-width: 28rem) {
|
|
151
|
+
.ceramic-activity-feed {
|
|
152
|
+
padding: var(--space-3);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { Badge } from '@utopia-studio-design/design-system/Badge'
|
|
3
|
+
import { Button } from '@utopia-studio-design/design-system/Button'
|
|
4
|
+
import {
|
|
5
|
+
ProgressBar,
|
|
6
|
+
StatusDot,
|
|
7
|
+
} from '@utopia-studio-design/design-system/DataDisplay'
|
|
8
|
+
import './agent-status.css'
|
|
9
|
+
|
|
10
|
+
export type AgentExecutionState =
|
|
11
|
+
| 'queued'
|
|
12
|
+
| 'running'
|
|
13
|
+
| 'waiting-approval'
|
|
14
|
+
| 'blocked'
|
|
15
|
+
| 'failed'
|
|
16
|
+
| 'completed'
|
|
17
|
+
|
|
18
|
+
export type AgentRisk = 'none' | 'low' | 'medium' | 'high'
|
|
19
|
+
export type AgentStatusDensity = 'compact' | 'default' | 'comfortable'
|
|
20
|
+
export type AgentStatusFilter = 'all' | 'active' | 'attention' | 'completed'
|
|
21
|
+
export type AgentStatusState = 'loading' | 'empty' | 'error' | 'ready'
|
|
22
|
+
|
|
23
|
+
export interface AgentApprovalRequest {
|
|
24
|
+
id: string
|
|
25
|
+
description?: string
|
|
26
|
+
label: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AgentStatusItem {
|
|
30
|
+
id: string
|
|
31
|
+
approval?: AgentApprovalRequest
|
|
32
|
+
name: string
|
|
33
|
+
progress?: number
|
|
34
|
+
risk?: AgentRisk
|
|
35
|
+
state: AgentExecutionState
|
|
36
|
+
summary?: string
|
|
37
|
+
taskLabel: string
|
|
38
|
+
updatedAt?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AgentStatusLabels {
|
|
42
|
+
approve: string
|
|
43
|
+
approvalRequested: string
|
|
44
|
+
count: (visible: string, total: string) => string
|
|
45
|
+
emptyDescription: string
|
|
46
|
+
emptyTitle: string
|
|
47
|
+
errorDescription: string
|
|
48
|
+
errorTitle: string
|
|
49
|
+
eyebrow: string
|
|
50
|
+
loading: string
|
|
51
|
+
noMatchingDescription: string
|
|
52
|
+
noMatchingTitle: string
|
|
53
|
+
progress: string
|
|
54
|
+
reject: string
|
|
55
|
+
retry: string
|
|
56
|
+
risk: Record<Exclude<AgentRisk, 'none'>, string>
|
|
57
|
+
state: Record<AgentExecutionState, string>
|
|
58
|
+
timeUnavailable: string
|
|
59
|
+
title: string
|
|
60
|
+
updated: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AgentStatusBlockProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title'> {
|
|
64
|
+
density?: AgentStatusDensity
|
|
65
|
+
direction?: 'ltr' | 'rtl'
|
|
66
|
+
filter?: AgentStatusFilter
|
|
67
|
+
items?: AgentStatusItem[]
|
|
68
|
+
labels: AgentStatusLabels
|
|
69
|
+
locale?: string
|
|
70
|
+
maxItems?: number
|
|
71
|
+
onApprove?: (item: AgentStatusItem, request: AgentApprovalRequest) => void
|
|
72
|
+
onReject?: (item: AgentStatusItem, request: AgentApprovalRequest) => void
|
|
73
|
+
onRetry?: () => void
|
|
74
|
+
showApprovals?: boolean
|
|
75
|
+
state?: AgentStatusState
|
|
76
|
+
timeZone?: string
|
|
77
|
+
title?: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const attentionStates = new Set<AgentExecutionState>([
|
|
81
|
+
'waiting-approval',
|
|
82
|
+
'blocked',
|
|
83
|
+
'failed',
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
function filterItems(items: AgentStatusItem[], filter: AgentStatusFilter) {
|
|
87
|
+
if (filter === 'active') {
|
|
88
|
+
return items.filter((item) => item.state === 'queued' || item.state === 'running')
|
|
89
|
+
}
|
|
90
|
+
if (filter === 'attention') {
|
|
91
|
+
return items.filter((item) => attentionStates.has(item.state))
|
|
92
|
+
}
|
|
93
|
+
if (filter === 'completed') {
|
|
94
|
+
return items.filter((item) => item.state === 'completed')
|
|
95
|
+
}
|
|
96
|
+
return items
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function formatNumber(locale: string, value: number) {
|
|
100
|
+
try {
|
|
101
|
+
return new Intl.NumberFormat(locale).format(value)
|
|
102
|
+
} catch {
|
|
103
|
+
return String(value)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatPercent(locale: string, value: number) {
|
|
108
|
+
try {
|
|
109
|
+
return new Intl.NumberFormat(locale, {
|
|
110
|
+
maximumFractionDigits: 0,
|
|
111
|
+
style: 'percent',
|
|
112
|
+
}).format(value / 100)
|
|
113
|
+
} catch {
|
|
114
|
+
return `${Math.round(value)}%`
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatUpdatedAt(locale: string, timeZone: string, value?: string) {
|
|
119
|
+
if (!value) return null
|
|
120
|
+
const date = new Date(value)
|
|
121
|
+
if (Number.isNaN(date.valueOf())) return null
|
|
122
|
+
try {
|
|
123
|
+
return {
|
|
124
|
+
date,
|
|
125
|
+
label: new Intl.DateTimeFormat(locale, {
|
|
126
|
+
dateStyle: 'medium',
|
|
127
|
+
timeStyle: 'short',
|
|
128
|
+
timeZone,
|
|
129
|
+
}).format(date),
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
return {
|
|
133
|
+
date,
|
|
134
|
+
label: new Intl.DateTimeFormat(locale, {
|
|
135
|
+
dateStyle: 'medium',
|
|
136
|
+
timeStyle: 'short',
|
|
137
|
+
timeZone: 'UTC',
|
|
138
|
+
}).format(date),
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function statusVariant(state: AgentExecutionState) {
|
|
144
|
+
if (state === 'failed') return 'destructive' as const
|
|
145
|
+
if (state === 'completed') return 'secondary' as const
|
|
146
|
+
return 'outline' as const
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function progressStatus(state: AgentExecutionState) {
|
|
150
|
+
if (state === 'failed') return 'destructive' as const
|
|
151
|
+
if (state === 'blocked' || state === 'waiting-approval') return 'warning' as const
|
|
152
|
+
if (state === 'completed') return 'success' as const
|
|
153
|
+
return 'default' as const
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function AgentStatusBlock({
|
|
157
|
+
density = 'default',
|
|
158
|
+
direction,
|
|
159
|
+
filter = 'all',
|
|
160
|
+
items = [],
|
|
161
|
+
labels,
|
|
162
|
+
locale = 'en-US',
|
|
163
|
+
maxItems = 6,
|
|
164
|
+
onApprove,
|
|
165
|
+
onReject,
|
|
166
|
+
onRetry,
|
|
167
|
+
showApprovals = true,
|
|
168
|
+
state: stateProp,
|
|
169
|
+
timeZone = 'UTC',
|
|
170
|
+
title,
|
|
171
|
+
...props
|
|
172
|
+
}: AgentStatusBlockProps) {
|
|
173
|
+
const titleId = React.useId()
|
|
174
|
+
const filteredItems = filterItems(items, filter)
|
|
175
|
+
const visibleItems = filteredItems.slice(0, maxItems)
|
|
176
|
+
const state = stateProp ?? (items.length ? 'ready' : 'empty')
|
|
177
|
+
const visibleCount = formatNumber(locale, visibleItems.length)
|
|
178
|
+
const totalCount = formatNumber(locale, filteredItems.length)
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<section
|
|
182
|
+
aria-busy={state === 'loading'}
|
|
183
|
+
aria-labelledby={titleId}
|
|
184
|
+
className="ceramic-agent-status"
|
|
185
|
+
data-density={density}
|
|
186
|
+
data-filter={filter}
|
|
187
|
+
data-state={state}
|
|
188
|
+
dir={direction}
|
|
189
|
+
lang={locale}
|
|
190
|
+
{...props}
|
|
191
|
+
>
|
|
192
|
+
<header>
|
|
193
|
+
<div>
|
|
194
|
+
<span>{labels.eyebrow}</span>
|
|
195
|
+
<h2 id={titleId}>{title ?? labels.title}</h2>
|
|
196
|
+
</div>
|
|
197
|
+
{state === 'ready' ? (
|
|
198
|
+
<Badge variant="outline">{labels.count(visibleCount, totalCount)}</Badge>
|
|
199
|
+
) : null}
|
|
200
|
+
</header>
|
|
201
|
+
|
|
202
|
+
{state === 'loading' ? (
|
|
203
|
+
<div className="ceramic-agent-status-state" role="status">{labels.loading}</div>
|
|
204
|
+
) : null}
|
|
205
|
+
{state === 'error' ? (
|
|
206
|
+
<div className="ceramic-agent-status-state" role="alert">
|
|
207
|
+
<strong>{labels.errorTitle}</strong>
|
|
208
|
+
<span>{labels.errorDescription}</span>
|
|
209
|
+
{onRetry ? <Button size="sm" variant="outline" onClick={onRetry}>{labels.retry}</Button> : null}
|
|
210
|
+
</div>
|
|
211
|
+
) : null}
|
|
212
|
+
{state === 'empty' ? (
|
|
213
|
+
<div className="ceramic-agent-status-state" role="status">
|
|
214
|
+
<strong>{labels.emptyTitle}</strong>
|
|
215
|
+
<span>{labels.emptyDescription}</span>
|
|
216
|
+
</div>
|
|
217
|
+
) : null}
|
|
218
|
+
{state === 'ready' && visibleItems.length === 0 ? (
|
|
219
|
+
<div className="ceramic-agent-status-state" role="status">
|
|
220
|
+
<strong>{labels.noMatchingTitle}</strong>
|
|
221
|
+
<span>{labels.noMatchingDescription}</span>
|
|
222
|
+
</div>
|
|
223
|
+
) : null}
|
|
224
|
+
{state === 'ready' && visibleItems.length > 0 ? (
|
|
225
|
+
<ul className="ceramic-agent-status-list">
|
|
226
|
+
{visibleItems.map((item) => {
|
|
227
|
+
const progress = Math.max(0, Math.min(100, item.progress ?? (
|
|
228
|
+
item.state === 'completed' ? 100 : 0
|
|
229
|
+
)))
|
|
230
|
+
const updatedAt = formatUpdatedAt(locale, timeZone, item.updatedAt)
|
|
231
|
+
const approvalTitleId = `${titleId}-${item.id}-approval`
|
|
232
|
+
return (
|
|
233
|
+
<li data-execution-state={item.state} key={item.id}>
|
|
234
|
+
<div className="ceramic-agent-status-heading">
|
|
235
|
+
<StatusDot aria-hidden="true" data-execution-state={item.state} />
|
|
236
|
+
<div>
|
|
237
|
+
<strong><bdi>{item.name}</bdi></strong>
|
|
238
|
+
<span><bdi>{item.taskLabel}</bdi></span>
|
|
239
|
+
</div>
|
|
240
|
+
<Badge variant={statusVariant(item.state)}>
|
|
241
|
+
{labels.state[item.state]}
|
|
242
|
+
</Badge>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
{item.summary ? <p><bdi>{item.summary}</bdi></p> : null}
|
|
246
|
+
|
|
247
|
+
{(item.state === 'running' || item.state === 'completed') ? (
|
|
248
|
+
<ProgressBar
|
|
249
|
+
aria-label={`${item.name}: ${labels.progress}`}
|
|
250
|
+
label={labels.progress}
|
|
251
|
+
showValue
|
|
252
|
+
size={density === 'compact' ? 'sm' : 'md'}
|
|
253
|
+
status={progressStatus(item.state)}
|
|
254
|
+
value={progress}
|
|
255
|
+
valueText={formatPercent(locale, progress)}
|
|
256
|
+
/>
|
|
257
|
+
) : null}
|
|
258
|
+
|
|
259
|
+
<div className="ceramic-agent-status-meta">
|
|
260
|
+
{item.risk && item.risk !== 'none' ? (
|
|
261
|
+
<Badge data-risk={item.risk} variant="outline">
|
|
262
|
+
{labels.risk[item.risk]}
|
|
263
|
+
</Badge>
|
|
264
|
+
) : null}
|
|
265
|
+
{updatedAt ? (
|
|
266
|
+
<span>
|
|
267
|
+
{labels.updated}{' '}
|
|
268
|
+
<time dateTime={updatedAt.date.toISOString()}>{updatedAt.label}</time>
|
|
269
|
+
</span>
|
|
270
|
+
) : item.updatedAt ? <span>{labels.timeUnavailable}</span> : null}
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
{showApprovals && item.state === 'waiting-approval' && item.approval ? (
|
|
274
|
+
<section
|
|
275
|
+
aria-labelledby={approvalTitleId}
|
|
276
|
+
className="ceramic-agent-approval"
|
|
277
|
+
>
|
|
278
|
+
<div>
|
|
279
|
+
<strong id={approvalTitleId}>{labels.approvalRequested}</strong>
|
|
280
|
+
<span>{item.approval.label}</span>
|
|
281
|
+
{item.approval.description ? <p>{item.approval.description}</p> : null}
|
|
282
|
+
</div>
|
|
283
|
+
{onApprove || onReject ? (
|
|
284
|
+
<div>
|
|
285
|
+
{onReject ? (
|
|
286
|
+
<Button
|
|
287
|
+
size="sm"
|
|
288
|
+
variant="outline"
|
|
289
|
+
onClick={() => onReject(item, item.approval!)}
|
|
290
|
+
>
|
|
291
|
+
{labels.reject}
|
|
292
|
+
</Button>
|
|
293
|
+
) : null}
|
|
294
|
+
{onApprove ? (
|
|
295
|
+
<Button
|
|
296
|
+
size="sm"
|
|
297
|
+
onClick={() => onApprove(item, item.approval!)}
|
|
298
|
+
>
|
|
299
|
+
{labels.approve}
|
|
300
|
+
</Button>
|
|
301
|
+
) : null}
|
|
302
|
+
</div>
|
|
303
|
+
) : null}
|
|
304
|
+
</section>
|
|
305
|
+
) : null}
|
|
306
|
+
</li>
|
|
307
|
+
)
|
|
308
|
+
})}
|
|
309
|
+
</ul>
|
|
310
|
+
) : null}
|
|
311
|
+
</section>
|
|
312
|
+
)
|
|
313
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Agent Status
|
|
2
|
+
|
|
3
|
+
`AgentStatusBlock` is a product-data surface for observing agent execution,
|
|
4
|
+
progress, risk, approval, failure, and completion without putting operational
|
|
5
|
+
records into Ceramic Layout JSON.
|
|
6
|
+
|
|
7
|
+
The registry controls only semantic presentation props. Products own execution
|
|
8
|
+
records, sanitized summaries, timestamps, retry behavior, and approval/rejection
|
|
9
|
+
callbacks. Never pass raw stack traces, prompts, credentials, or command output
|
|
10
|
+
as an item summary.
|
|
11
|
+
|
|
12
|
+
Use `resolveAgentStatusConfiguration` in every runtime/editor adapter so locale,
|
|
13
|
+
direction, filter, density, item limit, approval visibility, and timezone remain
|
|
14
|
+
consistent. Arabic fixtures demonstrate scoped RTL and mixed-direction agent
|
|
15
|
+
names.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { getLocaleTextDirection } from '@utopia-studio-design/design-system/Utilities'
|
|
2
|
+
import type {
|
|
3
|
+
AgentStatusBlockProps,
|
|
4
|
+
AgentStatusFilter,
|
|
5
|
+
AgentStatusLabels,
|
|
6
|
+
} from './AgentStatusBlock'
|
|
7
|
+
|
|
8
|
+
export const agentStatusLocaleOptions = ['en-US', 'ar-SA', 'ar-EG', 'ar-AE'] as const
|
|
9
|
+
|
|
10
|
+
export type AgentStatusLocale = typeof agentStatusLocaleOptions[number]
|
|
11
|
+
export type AgentStatusDirection = 'inherit' | 'auto' | 'ltr' | 'rtl'
|
|
12
|
+
|
|
13
|
+
export interface AgentStatusConfiguration {
|
|
14
|
+
density: NonNullable<AgentStatusBlockProps['density']>
|
|
15
|
+
direction?: 'ltr' | 'rtl'
|
|
16
|
+
filter: AgentStatusFilter
|
|
17
|
+
labels: AgentStatusLabels
|
|
18
|
+
locale: AgentStatusLocale
|
|
19
|
+
maxItems: number
|
|
20
|
+
showApprovals: boolean
|
|
21
|
+
timeZone: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AgentStatusContext {
|
|
25
|
+
direction: 'ltr' | 'rtl'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const directions: AgentStatusDirection[] = ['inherit', 'auto', 'ltr', 'rtl']
|
|
29
|
+
const densities: AgentStatusConfiguration['density'][] = ['compact', 'default', 'comfortable']
|
|
30
|
+
const filters: AgentStatusFilter[] = ['all', 'active', 'attention', 'completed']
|
|
31
|
+
|
|
32
|
+
const englishLabels: AgentStatusLabels = {
|
|
33
|
+
approve: 'Approve',
|
|
34
|
+
approvalRequested: 'Approval requested',
|
|
35
|
+
count: (visible, total) => `${visible} of ${total}`,
|
|
36
|
+
emptyDescription: 'Agent executions will appear here when work begins.',
|
|
37
|
+
emptyTitle: 'No agent executions',
|
|
38
|
+
errorDescription: 'Reconnect the execution source and try again.',
|
|
39
|
+
errorTitle: 'Agent status unavailable',
|
|
40
|
+
eyebrow: 'Agent operations',
|
|
41
|
+
loading: 'Loading agent status…',
|
|
42
|
+
noMatchingDescription: 'Change the semantic filter to review other executions.',
|
|
43
|
+
noMatchingTitle: 'No executions match this view',
|
|
44
|
+
progress: 'Progress',
|
|
45
|
+
reject: 'Reject',
|
|
46
|
+
retry: 'Try again',
|
|
47
|
+
risk: {
|
|
48
|
+
high: 'High risk',
|
|
49
|
+
low: 'Low risk',
|
|
50
|
+
medium: 'Medium risk',
|
|
51
|
+
},
|
|
52
|
+
state: {
|
|
53
|
+
blocked: 'Blocked',
|
|
54
|
+
completed: 'Completed',
|
|
55
|
+
failed: 'Failed',
|
|
56
|
+
queued: 'Queued',
|
|
57
|
+
running: 'Running',
|
|
58
|
+
'waiting-approval': 'Waiting for approval',
|
|
59
|
+
},
|
|
60
|
+
timeUnavailable: 'Update time unavailable',
|
|
61
|
+
title: 'Agent status',
|
|
62
|
+
updated: 'Updated',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const arabicLabels: AgentStatusLabels = {
|
|
66
|
+
approve: 'موافقة',
|
|
67
|
+
approvalRequested: 'طلب موافقة',
|
|
68
|
+
count: (visible, total) => `${visible} من ${total}`,
|
|
69
|
+
emptyDescription: 'ستظهر عمليات الوكلاء هنا عند بدء العمل.',
|
|
70
|
+
emptyTitle: 'لا توجد عمليات للوكلاء',
|
|
71
|
+
errorDescription: 'أعد الاتصال بمصدر التنفيذ ثم حاول مرة أخرى.',
|
|
72
|
+
errorTitle: 'حالة الوكلاء غير متاحة',
|
|
73
|
+
eyebrow: 'عمليات الوكلاء',
|
|
74
|
+
loading: 'جارٍ تحميل حالة الوكلاء…',
|
|
75
|
+
noMatchingDescription: 'غيّر عامل التصفية الدلالي لمراجعة العمليات الأخرى.',
|
|
76
|
+
noMatchingTitle: 'لا توجد عمليات مطابقة لهذا العرض',
|
|
77
|
+
progress: 'التقدم',
|
|
78
|
+
reject: 'رفض',
|
|
79
|
+
retry: 'حاول مرة أخرى',
|
|
80
|
+
risk: {
|
|
81
|
+
high: 'مخاطر عالية',
|
|
82
|
+
low: 'مخاطر منخفضة',
|
|
83
|
+
medium: 'مخاطر متوسطة',
|
|
84
|
+
},
|
|
85
|
+
state: {
|
|
86
|
+
blocked: 'متوقف',
|
|
87
|
+
completed: 'مكتمل',
|
|
88
|
+
failed: 'فشل',
|
|
89
|
+
queued: 'في قائمة الانتظار',
|
|
90
|
+
running: 'قيد التشغيل',
|
|
91
|
+
'waiting-approval': 'بانتظار الموافقة',
|
|
92
|
+
},
|
|
93
|
+
timeUnavailable: 'وقت التحديث غير متاح',
|
|
94
|
+
title: 'حالة الوكلاء',
|
|
95
|
+
updated: 'آخر تحديث',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getAgentStatusLabels(locale: string): AgentStatusLabels {
|
|
99
|
+
return getLocaleTextDirection(locale) === 'rtl' ? arabicLabels : englishLabels
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function resolveAgentStatusConfiguration(
|
|
103
|
+
props: Record<string, unknown>,
|
|
104
|
+
context: AgentStatusContext,
|
|
105
|
+
): AgentStatusConfiguration {
|
|
106
|
+
const locale = agentStatusLocaleOptions.includes(props.locale as AgentStatusLocale)
|
|
107
|
+
? props.locale as AgentStatusLocale
|
|
108
|
+
: 'en-US'
|
|
109
|
+
const directionMode = directions.includes(props.direction as AgentStatusDirection)
|
|
110
|
+
? props.direction as AgentStatusDirection
|
|
111
|
+
: 'inherit'
|
|
112
|
+
const direction = directionMode === 'inherit'
|
|
113
|
+
? context.direction
|
|
114
|
+
: directionMode === 'auto' ? getLocaleTextDirection(locale) : directionMode
|
|
115
|
+
const maxItems = Number.isInteger(props.maxItems)
|
|
116
|
+
? Math.min(12, Math.max(1, Number(props.maxItems)))
|
|
117
|
+
: 6
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
density: densities.includes(props.density as AgentStatusConfiguration['density'])
|
|
121
|
+
? props.density as AgentStatusConfiguration['density']
|
|
122
|
+
: 'default',
|
|
123
|
+
direction,
|
|
124
|
+
filter: filters.includes(props.filter as AgentStatusFilter)
|
|
125
|
+
? props.filter as AgentStatusFilter
|
|
126
|
+
: 'all',
|
|
127
|
+
labels: getAgentStatusLabels(locale),
|
|
128
|
+
locale,
|
|
129
|
+
maxItems,
|
|
130
|
+
showApprovals: props.showApprovals !== false,
|
|
131
|
+
timeZone: typeof props.timeZone === 'string' && props.timeZone ? props.timeZone : 'UTC',
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { AgentStatusItem } from './AgentStatusBlock'
|
|
2
|
+
|
|
3
|
+
const englishItems: AgentStatusItem[] = [
|
|
4
|
+
{
|
|
5
|
+
id: 'mina-qa',
|
|
6
|
+
name: 'Mina UX-QA',
|
|
7
|
+
progress: 68,
|
|
8
|
+
risk: 'low',
|
|
9
|
+
state: 'running',
|
|
10
|
+
summary: 'Checking responsive, Arabic, and interaction contracts.',
|
|
11
|
+
taskLabel: 'Dashboard composition QA',
|
|
12
|
+
updatedAt: '2026-07-30T08:42:00.000Z',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
approval: {
|
|
16
|
+
id: 'publish-approval',
|
|
17
|
+
description: 'Publishes the validated patch release to the configured npm registry.',
|
|
18
|
+
label: 'Publish Ceramic patch release',
|
|
19
|
+
},
|
|
20
|
+
id: 'release-agent',
|
|
21
|
+
name: 'Release Agent',
|
|
22
|
+
risk: 'medium',
|
|
23
|
+
state: 'waiting-approval',
|
|
24
|
+
summary: 'All local release gates passed. External publishing requires a human decision.',
|
|
25
|
+
taskLabel: 'Prepare npm release',
|
|
26
|
+
updatedAt: '2026-07-30T08:39:00.000Z',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'registry-agent',
|
|
30
|
+
name: 'Registry Agent',
|
|
31
|
+
risk: 'high',
|
|
32
|
+
state: 'blocked',
|
|
33
|
+
summary: 'A required product data adapter has not been connected.',
|
|
34
|
+
taskLabel: 'Sync production activity source',
|
|
35
|
+
updatedAt: '2026-07-30T08:31:00.000Z',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'build-agent',
|
|
39
|
+
name: 'Build Agent',
|
|
40
|
+
progress: 100,
|
|
41
|
+
risk: 'none',
|
|
42
|
+
state: 'completed',
|
|
43
|
+
summary: 'Standalone Calendar and Dashboard templates built successfully.',
|
|
44
|
+
taskLabel: 'Verify generated templates',
|
|
45
|
+
updatedAt: '2026-07-30T08:20:00.000Z',
|
|
46
|
+
},
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
const arabicItems: AgentStatusItem[] = [
|
|
50
|
+
{
|
|
51
|
+
...englishItems[0],
|
|
52
|
+
summary: 'جارٍ فحص عقود الاستجابة والعربية والتفاعل.',
|
|
53
|
+
taskLabel: 'فحص جودة تكوين لوحة المعلومات',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
...englishItems[1],
|
|
57
|
+
approval: {
|
|
58
|
+
id: 'publish-approval',
|
|
59
|
+
description: 'سيتم نشر إصدار التصحيح المعتمد في سجل npm المحدد.',
|
|
60
|
+
label: 'نشر إصدار تصحيح Ceramic',
|
|
61
|
+
},
|
|
62
|
+
summary: 'نجحت جميع فحوصات الإصدار المحلية. يتطلب النشر الخارجي قراراً بشرياً.',
|
|
63
|
+
taskLabel: 'إعداد إصدار npm',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
...englishItems[2],
|
|
67
|
+
summary: 'لم يتم ربط محول بيانات المنتج المطلوب.',
|
|
68
|
+
taskLabel: 'مزامنة مصدر النشاط في الإنتاج',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
...englishItems[3],
|
|
72
|
+
summary: 'تم بناء قوالب التقويم ولوحة المعلومات المستقلة بنجاح.',
|
|
73
|
+
taskLabel: 'التحقق من القوالب المولدة',
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
export function getAgentStatusFixture(locale: string): AgentStatusItem[] {
|
|
78
|
+
return locale.startsWith('ar') ? arabicItems : englishItems
|
|
79
|
+
}
|