sunpeak 0.8.1 → 0.8.4
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/bin/commands/dev.mjs +60 -4
- package/bin/commands/mcp.mjs +1 -1
- package/bin/sunpeak.js +6 -4
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/style.css +62 -0
- package/package.json +1 -1
- package/template/README.md +29 -14
- package/template/dist/albums.js +1 -1
- package/template/dist/albums.json +3 -2
- package/template/dist/carousel.js +1 -1
- package/template/dist/carousel.json +3 -2
- package/template/dist/confirmation.js +49 -0
- package/template/dist/confirmation.json +16 -0
- package/template/dist/counter.js +1 -1
- package/template/dist/counter.json +7 -2
- package/template/dist/map.js +1 -1
- package/template/dist/map.json +6 -3
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +3 -3
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +1 -1
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +16 -16
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Textarea.js +3 -3
- package/template/node_modules/.vite/deps/_metadata.json +32 -32
- package/template/node_modules/.vite/deps/{chunk-SPYXUHEY.js → chunk-N6DVYEXK.js} +8 -8
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/src/resources/albums-resource.json +1 -1
- package/template/src/resources/carousel-resource.json +1 -1
- package/template/src/resources/confirmation-resource.json +12 -0
- package/template/src/resources/confirmation-resource.tsx +479 -0
- package/template/src/resources/counter-resource.json +4 -1
- package/template/src/resources/map-resource.json +7 -2
- package/template/src/simulations/confirmation-diff-simulation.json +80 -0
- package/template/src/simulations/confirmation-post-simulation.json +56 -0
- package/template/src/simulations/confirmation-purchase-simulation.json +88 -0
- /package/template/node_modules/.vite/deps/{chunk-SPYXUHEY.js.map → chunk-N6DVYEXK.js.map} +0 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
useWidgetProps,
|
|
4
|
+
useWidgetState,
|
|
5
|
+
useSafeArea,
|
|
6
|
+
useMaxHeight,
|
|
7
|
+
useUserAgent,
|
|
8
|
+
useDisplayMode,
|
|
9
|
+
useWidgetAPI,
|
|
10
|
+
} from 'sunpeak';
|
|
11
|
+
import { Button } from '@openai/apps-sdk-ui/components/Button';
|
|
12
|
+
import { ExpandLg } from '@openai/apps-sdk-ui/components/Icon';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Production-ready Confirmation Resource
|
|
16
|
+
*
|
|
17
|
+
* A flexible confirmation dialog that adapts to various use cases:
|
|
18
|
+
* - Purchase confirmations (items, totals, payment)
|
|
19
|
+
* - Code change confirmations (file changes with diffs)
|
|
20
|
+
* - Social media post confirmations (content preview)
|
|
21
|
+
* - Booking confirmations (details, dates, prices)
|
|
22
|
+
* - Generic action confirmations (simple approve/reject)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Type Definitions
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/** A key-value detail row */
|
|
30
|
+
interface Detail {
|
|
31
|
+
label: string;
|
|
32
|
+
value: string;
|
|
33
|
+
/** Optional sublabel/description */
|
|
34
|
+
sublabel?: string;
|
|
35
|
+
/** Highlight this row (e.g., for totals) */
|
|
36
|
+
emphasis?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** An item with optional image and metadata (for purchases, lists) */
|
|
40
|
+
interface Item {
|
|
41
|
+
id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
subtitle?: string;
|
|
44
|
+
/** Image URL */
|
|
45
|
+
image?: string;
|
|
46
|
+
/** Right-aligned value (e.g., price, quantity) */
|
|
47
|
+
value?: string;
|
|
48
|
+
/** Small badge text (e.g., "New", "Sale") */
|
|
49
|
+
badge?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A code/file change entry */
|
|
53
|
+
interface Change {
|
|
54
|
+
id: string;
|
|
55
|
+
type: 'create' | 'modify' | 'delete' | 'action';
|
|
56
|
+
/** File path or identifier */
|
|
57
|
+
path?: string;
|
|
58
|
+
description: string;
|
|
59
|
+
details?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Alert/warning message */
|
|
63
|
+
interface Alert {
|
|
64
|
+
type: 'info' | 'warning' | 'error' | 'success';
|
|
65
|
+
message: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Content section - supports multiple display types */
|
|
69
|
+
interface Section {
|
|
70
|
+
/** Optional section title */
|
|
71
|
+
title?: string;
|
|
72
|
+
/** Section content type */
|
|
73
|
+
type: 'details' | 'items' | 'changes' | 'preview' | 'summary';
|
|
74
|
+
/** Content data (type depends on section type) */
|
|
75
|
+
content: Detail[] | Item[] | Change[] | string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Tool call configuration for domain-specific confirmation actions */
|
|
79
|
+
interface ConfirmationTool {
|
|
80
|
+
/** Tool name to call (e.g., "complete_purchase", "publish_post") */
|
|
81
|
+
name: string;
|
|
82
|
+
/** Additional arguments to pass to the tool */
|
|
83
|
+
arguments?: Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ConfirmationData extends Record<string, unknown> {
|
|
87
|
+
/** Main title */
|
|
88
|
+
title: string;
|
|
89
|
+
/** Optional description below title */
|
|
90
|
+
description?: string;
|
|
91
|
+
/** Content sections */
|
|
92
|
+
sections?: Section[];
|
|
93
|
+
/** Alert messages to display */
|
|
94
|
+
alerts?: Alert[];
|
|
95
|
+
/** Accept button label */
|
|
96
|
+
acceptLabel?: string;
|
|
97
|
+
/** Reject button label */
|
|
98
|
+
rejectLabel?: string;
|
|
99
|
+
/** Use danger styling for accept button (for destructive actions) */
|
|
100
|
+
acceptDanger?: boolean;
|
|
101
|
+
/** Message shown after accepting */
|
|
102
|
+
acceptedMessage?: string;
|
|
103
|
+
/** Message shown after rejecting */
|
|
104
|
+
rejectedMessage?: string;
|
|
105
|
+
/** Domain-specific tool to call on confirmation */
|
|
106
|
+
confirmationTool?: ConfirmationTool;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface ConfirmationState extends Record<string, unknown> {
|
|
110
|
+
decision?: 'accepted' | 'rejected' | null;
|
|
111
|
+
decidedAt?: string | null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Section Renderers
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
const changeTypeConfig = {
|
|
119
|
+
create: { icon: '+', color: '#16a34a', bg: '#f0fdf4' },
|
|
120
|
+
modify: { icon: '~', color: '#ca8a04', bg: '#fefce8' },
|
|
121
|
+
delete: { icon: '−', color: '#dc2626', bg: '#fef2f2' },
|
|
122
|
+
action: { icon: '→', color: '#2563eb', bg: '#eff6ff' },
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const alertTypeConfig = {
|
|
126
|
+
info: { icon: 'ℹ', bg: '#eff6ff', border: '#bfdbfe', text: '#1e40af' },
|
|
127
|
+
warning: { icon: '⚠', bg: '#fefce8', border: '#fde047', text: '#a16207' },
|
|
128
|
+
error: { icon: '✕', bg: '#fef2f2', border: '#fecaca', text: '#b91c1c' },
|
|
129
|
+
success: { icon: '✓', bg: '#f0fdf4', border: '#bbf7d0', text: '#15803d' },
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function DetailsSection({ content }: { content: Detail[] }) {
|
|
133
|
+
return (
|
|
134
|
+
<div className="space-y-2">
|
|
135
|
+
{content.map((detail, i) => (
|
|
136
|
+
<div
|
|
137
|
+
key={i}
|
|
138
|
+
className={`flex justify-between items-start gap-4 ${
|
|
139
|
+
detail.emphasis ? 'font-semibold pt-2 border-t border-subtle' : ''
|
|
140
|
+
}`}
|
|
141
|
+
>
|
|
142
|
+
<div className="flex-1 min-w-0">
|
|
143
|
+
<span className={detail.emphasis ? 'text-primary' : 'text-secondary'}>
|
|
144
|
+
{detail.label}
|
|
145
|
+
</span>
|
|
146
|
+
{detail.sublabel && <p className="text-xs text-secondary mt-0.5">{detail.sublabel}</p>}
|
|
147
|
+
</div>
|
|
148
|
+
<span className="text-primary flex-shrink-0">{detail.value}</span>
|
|
149
|
+
</div>
|
|
150
|
+
))}
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function ItemsSection({ content }: { content: Item[] }) {
|
|
156
|
+
return (
|
|
157
|
+
<div className="space-y-3">
|
|
158
|
+
{content.map((item) => (
|
|
159
|
+
<div key={item.id} className="flex items-center gap-3 p-2 rounded-lg bg-surface-secondary">
|
|
160
|
+
{item.image && (
|
|
161
|
+
<img
|
|
162
|
+
src={item.image}
|
|
163
|
+
alt={item.title}
|
|
164
|
+
className="w-12 h-12 rounded-lg object-cover flex-shrink-0"
|
|
165
|
+
/>
|
|
166
|
+
)}
|
|
167
|
+
<div className="flex-1 min-w-0">
|
|
168
|
+
<div className="flex items-center gap-2">
|
|
169
|
+
<span className="text-sm font-medium text-primary truncate">{item.title}</span>
|
|
170
|
+
{item.badge && (
|
|
171
|
+
<span className="px-1.5 py-0.5 text-xs rounded bg-primary text-on-primary">
|
|
172
|
+
{item.badge}
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
{item.subtitle && <p className="text-xs text-secondary truncate">{item.subtitle}</p>}
|
|
177
|
+
</div>
|
|
178
|
+
{item.value && (
|
|
179
|
+
<span className="text-sm font-medium text-primary flex-shrink-0">{item.value}</span>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
))}
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function ChangesSection({ content }: { content: Change[] }) {
|
|
188
|
+
return (
|
|
189
|
+
<ul className="space-y-2">
|
|
190
|
+
{content.map((change) => {
|
|
191
|
+
const config = changeTypeConfig[change.type];
|
|
192
|
+
return (
|
|
193
|
+
<li
|
|
194
|
+
key={change.id}
|
|
195
|
+
className="rounded-lg border border-subtle p-3"
|
|
196
|
+
style={{ backgroundColor: config.bg }}
|
|
197
|
+
>
|
|
198
|
+
<div className="flex items-start gap-3">
|
|
199
|
+
<span
|
|
200
|
+
className="flex-shrink-0 w-6 h-6 flex items-center justify-center rounded font-mono font-bold bg-white"
|
|
201
|
+
style={{
|
|
202
|
+
color: config.color,
|
|
203
|
+
borderWidth: 1,
|
|
204
|
+
borderStyle: 'solid',
|
|
205
|
+
borderColor: config.color,
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
{config.icon}
|
|
209
|
+
</span>
|
|
210
|
+
<div className="flex-1 min-w-0">
|
|
211
|
+
{change.path && (
|
|
212
|
+
<code className="block text-xs text-secondary font-mono truncate mb-1">
|
|
213
|
+
{change.path}
|
|
214
|
+
</code>
|
|
215
|
+
)}
|
|
216
|
+
<p className="text-sm text-[#000000]">{change.description}</p>
|
|
217
|
+
{change.details && <p className="mt-1 text-xs text-secondary">{change.details}</p>}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</li>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</ul>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function PreviewSection({ content }: { content: string }) {
|
|
228
|
+
return (
|
|
229
|
+
<div className="p-4 rounded-lg bg-surface-secondary border border-subtle">
|
|
230
|
+
<p className="text-sm text-primary whitespace-pre-wrap">{content}</p>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function SummarySection({ content }: { content: Detail[] }) {
|
|
236
|
+
return (
|
|
237
|
+
<div className="p-3 rounded-lg bg-surface-secondary space-y-1">
|
|
238
|
+
{content.map((item, i) => (
|
|
239
|
+
<div
|
|
240
|
+
key={i}
|
|
241
|
+
className={`flex justify-between items-center ${
|
|
242
|
+
item.emphasis ? 'font-semibold text-lg pt-2 border-t border-subtle mt-2' : 'text-sm'
|
|
243
|
+
}`}
|
|
244
|
+
>
|
|
245
|
+
<span className={item.emphasis ? 'text-primary' : 'text-secondary'}>{item.label}</span>
|
|
246
|
+
<span className="text-primary">{item.value}</span>
|
|
247
|
+
</div>
|
|
248
|
+
))}
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function Section({ section }: { section: Section }) {
|
|
254
|
+
const renderContent = () => {
|
|
255
|
+
switch (section.type) {
|
|
256
|
+
case 'details':
|
|
257
|
+
return <DetailsSection content={section.content as Detail[]} />;
|
|
258
|
+
case 'items':
|
|
259
|
+
return <ItemsSection content={section.content as Item[]} />;
|
|
260
|
+
case 'changes':
|
|
261
|
+
return <ChangesSection content={section.content as Change[]} />;
|
|
262
|
+
case 'preview':
|
|
263
|
+
return <PreviewSection content={section.content as string} />;
|
|
264
|
+
case 'summary':
|
|
265
|
+
return <SummarySection content={section.content as Detail[]} />;
|
|
266
|
+
default:
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<div className="space-y-2">
|
|
273
|
+
{section.title && (
|
|
274
|
+
<h2 className="text-sm font-medium text-secondary uppercase tracking-wide">
|
|
275
|
+
{section.title}
|
|
276
|
+
</h2>
|
|
277
|
+
)}
|
|
278
|
+
{renderContent()}
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function AlertBanner({ alert }: { alert: Alert }) {
|
|
284
|
+
const config = alertTypeConfig[alert.type];
|
|
285
|
+
return (
|
|
286
|
+
<div
|
|
287
|
+
className="flex items-start gap-2 p-3 rounded-lg"
|
|
288
|
+
style={{
|
|
289
|
+
backgroundColor: config.bg,
|
|
290
|
+
borderWidth: 1,
|
|
291
|
+
borderStyle: 'solid',
|
|
292
|
+
borderColor: config.border,
|
|
293
|
+
}}
|
|
294
|
+
>
|
|
295
|
+
<span className="flex-shrink-0" style={{ color: config.text }}>
|
|
296
|
+
{config.icon}
|
|
297
|
+
</span>
|
|
298
|
+
<p className="text-sm" style={{ color: config.text }}>
|
|
299
|
+
{alert.message}
|
|
300
|
+
</p>
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ============================================================================
|
|
306
|
+
// Main Component
|
|
307
|
+
// ============================================================================
|
|
308
|
+
|
|
309
|
+
export const ConfirmationResource = React.forwardRef<HTMLDivElement>((_props, ref) => {
|
|
310
|
+
const data = useWidgetProps<ConfirmationData>(() => ({
|
|
311
|
+
title: 'Confirm',
|
|
312
|
+
sections: [],
|
|
313
|
+
}));
|
|
314
|
+
|
|
315
|
+
const [widgetState, setWidgetState] = useWidgetState<ConfirmationState>(() => ({
|
|
316
|
+
decision: null,
|
|
317
|
+
decidedAt: null,
|
|
318
|
+
}));
|
|
319
|
+
|
|
320
|
+
const safeArea = useSafeArea();
|
|
321
|
+
const maxHeight = useMaxHeight();
|
|
322
|
+
const userAgent = useUserAgent();
|
|
323
|
+
const displayMode = useDisplayMode();
|
|
324
|
+
const api = useWidgetAPI();
|
|
325
|
+
|
|
326
|
+
const hasTouch = userAgent?.capabilities.touch ?? false;
|
|
327
|
+
const decision = widgetState?.decision ?? null;
|
|
328
|
+
const isFullscreen = displayMode === 'fullscreen';
|
|
329
|
+
|
|
330
|
+
const handleRequestFullscreen = () => {
|
|
331
|
+
api?.requestDisplayMode?.({ mode: 'fullscreen' });
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const handleAccept = () => {
|
|
335
|
+
const decidedAt = new Date().toISOString();
|
|
336
|
+
setWidgetState({
|
|
337
|
+
decision: 'accepted',
|
|
338
|
+
decidedAt,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const tool = data.confirmationTool;
|
|
342
|
+
if (tool) {
|
|
343
|
+
console.log('callTool', {
|
|
344
|
+
name: tool.name,
|
|
345
|
+
arguments: {
|
|
346
|
+
...tool.arguments,
|
|
347
|
+
confirmed: true,
|
|
348
|
+
decidedAt,
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
const handleReject = () => {
|
|
355
|
+
const decidedAt = new Date().toISOString();
|
|
356
|
+
setWidgetState({
|
|
357
|
+
decision: 'rejected',
|
|
358
|
+
decidedAt,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const tool = data.confirmationTool;
|
|
362
|
+
if (tool) {
|
|
363
|
+
console.log('callTool', {
|
|
364
|
+
name: tool.name,
|
|
365
|
+
arguments: {
|
|
366
|
+
...tool.arguments,
|
|
367
|
+
confirmed: false,
|
|
368
|
+
decidedAt,
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const acceptLabel = data.acceptLabel ?? 'Confirm';
|
|
375
|
+
const rejectLabel = data.rejectLabel ?? 'Cancel';
|
|
376
|
+
const acceptedMessage = data.acceptedMessage ?? 'Confirmed';
|
|
377
|
+
const rejectedMessage = data.rejectedMessage ?? 'Cancelled';
|
|
378
|
+
const sections = data.sections ?? [];
|
|
379
|
+
const alerts = data.alerts ?? [];
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<div
|
|
383
|
+
ref={ref}
|
|
384
|
+
className="flex flex-col"
|
|
385
|
+
style={{
|
|
386
|
+
paddingTop: `${safeArea?.insets.top ?? 0}px`,
|
|
387
|
+
paddingBottom: `${safeArea?.insets.bottom ?? 0}px`,
|
|
388
|
+
paddingLeft: `${safeArea?.insets.left ?? 0}px`,
|
|
389
|
+
paddingRight: `${safeArea?.insets.right ?? 0}px`,
|
|
390
|
+
maxHeight: maxHeight ?? undefined,
|
|
391
|
+
}}
|
|
392
|
+
>
|
|
393
|
+
{/* Header */}
|
|
394
|
+
<div className="px-4 pt-4 pb-3 border-b border-subtle">
|
|
395
|
+
<div className="flex items-start justify-between gap-2">
|
|
396
|
+
<div className="flex-1 min-w-0">
|
|
397
|
+
<h1 className="text-xl font-semibold text-primary">{data.title}</h1>
|
|
398
|
+
{data.description && <p className="mt-1 text-sm text-secondary">{data.description}</p>}
|
|
399
|
+
</div>
|
|
400
|
+
{!isFullscreen && (
|
|
401
|
+
<Button
|
|
402
|
+
variant="ghost"
|
|
403
|
+
color="secondary"
|
|
404
|
+
size="sm"
|
|
405
|
+
onClick={handleRequestFullscreen}
|
|
406
|
+
aria-label="Enter fullscreen"
|
|
407
|
+
className="flex-shrink-0"
|
|
408
|
+
>
|
|
409
|
+
<ExpandLg className="h-4 w-4" aria-hidden="true" />
|
|
410
|
+
</Button>
|
|
411
|
+
)}
|
|
412
|
+
</div>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
{/* Content */}
|
|
416
|
+
<div className="flex-1 overflow-y-auto px-4 py-3 space-y-4">
|
|
417
|
+
{/* Alerts */}
|
|
418
|
+
{alerts.length > 0 && (
|
|
419
|
+
<div className="space-y-2">
|
|
420
|
+
{alerts.map((alert, i) => (
|
|
421
|
+
<AlertBanner key={i} alert={alert} />
|
|
422
|
+
))}
|
|
423
|
+
</div>
|
|
424
|
+
)}
|
|
425
|
+
|
|
426
|
+
{/* Sections */}
|
|
427
|
+
{sections.length === 0 ? (
|
|
428
|
+
<p className="text-secondary text-center py-8">Nothing to confirm</p>
|
|
429
|
+
) : (
|
|
430
|
+
sections.map((section, i) => <Section key={i} section={section} />)
|
|
431
|
+
)}
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
{/* Footer with Actions */}
|
|
435
|
+
<div className="px-4 py-3 border-t border-subtle bg-surface">
|
|
436
|
+
{decision === null ? (
|
|
437
|
+
<div className="flex gap-3">
|
|
438
|
+
<Button
|
|
439
|
+
variant="outline"
|
|
440
|
+
color="secondary"
|
|
441
|
+
onClick={handleReject}
|
|
442
|
+
size={hasTouch ? 'lg' : 'md'}
|
|
443
|
+
className="flex-1"
|
|
444
|
+
>
|
|
445
|
+
{rejectLabel}
|
|
446
|
+
</Button>
|
|
447
|
+
<Button
|
|
448
|
+
variant="solid"
|
|
449
|
+
color={data.acceptDanger ? 'danger' : 'primary'}
|
|
450
|
+
onClick={handleAccept}
|
|
451
|
+
size={hasTouch ? 'lg' : 'md'}
|
|
452
|
+
className="flex-1"
|
|
453
|
+
>
|
|
454
|
+
{acceptLabel}
|
|
455
|
+
</Button>
|
|
456
|
+
</div>
|
|
457
|
+
) : (
|
|
458
|
+
<div className="flex flex-col items-center gap-1">
|
|
459
|
+
<div
|
|
460
|
+
className="flex items-center justify-center gap-2"
|
|
461
|
+
style={{ color: decision === 'accepted' ? '#16a34a' : '#dc2626' }}
|
|
462
|
+
>
|
|
463
|
+
<span className="text-lg">{decision === 'accepted' ? '✓' : '✗'}</span>
|
|
464
|
+
<span className="font-medium">
|
|
465
|
+
{decision === 'accepted' ? acceptedMessage : rejectedMessage}
|
|
466
|
+
</span>
|
|
467
|
+
</div>
|
|
468
|
+
{widgetState?.decidedAt && (
|
|
469
|
+
<span className="text-xs text-secondary">
|
|
470
|
+
{new Date(widgetState.decidedAt).toLocaleString()}
|
|
471
|
+
</span>
|
|
472
|
+
)}
|
|
473
|
+
</div>
|
|
474
|
+
)}
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
);
|
|
478
|
+
});
|
|
479
|
+
ConfirmationResource.displayName = 'ConfirmationResource';
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
"description": "Show a simple counter tool widget",
|
|
5
5
|
"mimeType": "text/html+skybridge",
|
|
6
6
|
"_meta": {
|
|
7
|
-
"openai/widgetDomain": "https://sunpeak.ai"
|
|
7
|
+
"openai/widgetDomain": "https://sunpeak.ai",
|
|
8
|
+
"openai/widgetCSP": {
|
|
9
|
+
"resource_domains": ["https://cdn.openai.com"]
|
|
10
|
+
}
|
|
8
11
|
}
|
|
9
12
|
}
|
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
"_meta": {
|
|
7
7
|
"openai/widgetDomain": "https://sunpeak.ai",
|
|
8
8
|
"openai/widgetCSP": {
|
|
9
|
-
"connect_domains": ["https://api.mapbox.com"],
|
|
10
|
-
"resource_domains": [
|
|
9
|
+
"connect_domains": ["https://api.mapbox.com", "https://events.mapbox.com"],
|
|
10
|
+
"resource_domains": [
|
|
11
|
+
"https://*.oaistatic.com",
|
|
12
|
+
"https://cdn.openai.com",
|
|
13
|
+
"https://api.mapbox.com",
|
|
14
|
+
"https://events.mapbox.com"
|
|
15
|
+
]
|
|
11
16
|
}
|
|
12
17
|
}
|
|
13
18
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"userMessage": "I'd like to refactor the authentication module",
|
|
3
|
+
"tool": {
|
|
4
|
+
"name": "diff-confirmation",
|
|
5
|
+
"description": "Show a confirmation dialog for a proposed code diff",
|
|
6
|
+
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
|
|
7
|
+
"title": "Diff Confirmation",
|
|
8
|
+
"annotations": { "readOnlyHint": false },
|
|
9
|
+
"_meta": {
|
|
10
|
+
"openai/toolInvocation/invoking": "Preparing changes",
|
|
11
|
+
"openai/toolInvocation/invoked": "Changes ready for review",
|
|
12
|
+
"openai/widgetAccessible": true,
|
|
13
|
+
"openai/resultCanProduceWidget": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"toolCall": {
|
|
17
|
+
"structuredContent": {
|
|
18
|
+
"title": "Refactor Authentication Module",
|
|
19
|
+
"description": "The following changes will update the authentication system to use JWT tokens instead of session-based auth.",
|
|
20
|
+
"sections": [
|
|
21
|
+
{
|
|
22
|
+
"title": "File Changes",
|
|
23
|
+
"type": "changes",
|
|
24
|
+
"content": [
|
|
25
|
+
{
|
|
26
|
+
"id": "1",
|
|
27
|
+
"type": "create",
|
|
28
|
+
"path": "src/lib/jwt.ts",
|
|
29
|
+
"description": "Create JWT utility functions",
|
|
30
|
+
"details": "Token generation, verification, and refresh logic"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "2",
|
|
34
|
+
"type": "modify",
|
|
35
|
+
"path": "src/middleware/auth.ts",
|
|
36
|
+
"description": "Update auth middleware to use JWT",
|
|
37
|
+
"details": "Replace session checks with JWT verification"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "3",
|
|
41
|
+
"type": "modify",
|
|
42
|
+
"path": "src/routes/login.ts",
|
|
43
|
+
"description": "Update login endpoint to return JWT tokens"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "4",
|
|
47
|
+
"type": "delete",
|
|
48
|
+
"path": "src/lib/session.ts",
|
|
49
|
+
"description": "Remove deprecated session management",
|
|
50
|
+
"details": "No longer needed after JWT migration"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "5",
|
|
54
|
+
"type": "action",
|
|
55
|
+
"description": "Run database migration for token storage",
|
|
56
|
+
"details": "Creates refresh_tokens table"
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"acceptLabel": "Apply Changes",
|
|
62
|
+
"rejectLabel": "Cancel",
|
|
63
|
+
"acceptedMessage": "Changes applied",
|
|
64
|
+
"rejectedMessage": "Changes cancelled",
|
|
65
|
+
"confirmationTool": {
|
|
66
|
+
"name": "apply_changes",
|
|
67
|
+
"arguments": {
|
|
68
|
+
"changesetId": "cs_789",
|
|
69
|
+
"files": [
|
|
70
|
+
"src/lib/jwt.ts",
|
|
71
|
+
"src/middleware/auth.ts",
|
|
72
|
+
"src/routes/login.ts",
|
|
73
|
+
"src/lib/session.ts"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"_meta": {}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"userMessage": "Post this to my social media",
|
|
3
|
+
"tool": {
|
|
4
|
+
"name": "confirm-post",
|
|
5
|
+
"description": "Confirm a social media post before publishing",
|
|
6
|
+
"inputSchema": { "type": "object", "properties": {}, "additionalProperties": false },
|
|
7
|
+
"title": "Confirm Post",
|
|
8
|
+
"annotations": { "readOnlyHint": false },
|
|
9
|
+
"_meta": {
|
|
10
|
+
"openai/toolInvocation/invoking": "Preparing post",
|
|
11
|
+
"openai/toolInvocation/invoked": "Post ready for review",
|
|
12
|
+
"openai/widgetAccessible": true,
|
|
13
|
+
"openai/resultCanProduceWidget": true
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"toolCall": {
|
|
17
|
+
"structuredContent": {
|
|
18
|
+
"title": "Review Your Post",
|
|
19
|
+
"description": "This post will be published to your connected accounts.",
|
|
20
|
+
"sections": [
|
|
21
|
+
{
|
|
22
|
+
"title": "Content",
|
|
23
|
+
"type": "preview",
|
|
24
|
+
"content": "Just shipped a major update to our app! 🚀\n\nNew features include:\n• Dark mode support\n• Faster load times\n• Bug fixes and improvements\n\nThanks to everyone who provided feedback. You all are amazing! 💪\n\n#ProductUpdate #TechNews #Startup"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"title": "Post Details",
|
|
28
|
+
"type": "details",
|
|
29
|
+
"content": [
|
|
30
|
+
{ "label": "Platforms", "value": "X, LinkedIn" },
|
|
31
|
+
{ "label": "Schedule", "value": "Post immediately" },
|
|
32
|
+
{ "label": "Visibility", "value": "Public" }
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"alerts": [
|
|
37
|
+
{
|
|
38
|
+
"type": "warning",
|
|
39
|
+
"message": "This post contains hashtags that may limit reach on some platforms."
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"acceptLabel": "Publish",
|
|
43
|
+
"rejectLabel": "Cancel",
|
|
44
|
+
"acceptedMessage": "Post published!",
|
|
45
|
+
"rejectedMessage": "Post cancelled",
|
|
46
|
+
"confirmationTool": {
|
|
47
|
+
"name": "publish_post",
|
|
48
|
+
"arguments": {
|
|
49
|
+
"postId": "draft_456",
|
|
50
|
+
"platforms": ["x", "linkedin"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"_meta": {}
|
|
55
|
+
}
|
|
56
|
+
}
|