sunpeak 0.6.1 → 0.6.5
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/sunpeak.js +133 -6
- package/dist/chatgpt/conversation.d.ts +2 -1
- package/dist/index.cjs +24 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +24 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp/entry.cjs +2 -2
- package/dist/mcp/entry.cjs.map +1 -1
- package/dist/mcp/entry.js +2 -2
- package/dist/mcp/entry.js.map +1 -1
- package/dist/mcp/index.cjs +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/{server-DpriZ4jT.cjs → server-CQGbJWbk.cjs} +17 -8
- package/dist/{server-DpriZ4jT.cjs.map → server-CQGbJWbk.cjs.map} +1 -1
- package/dist/{server-SBlanUcf.js → server-DGCvp1RA.js} +17 -8
- package/dist/{server-SBlanUcf.js.map → server-DGCvp1RA.js.map} +1 -1
- package/dist/style.css +444 -0
- package/package.json +1 -1
- package/template/.sunpeak/dev.tsx +1 -1
- package/template/dist/chatgpt/albums.js +2 -2
- package/template/dist/chatgpt/carousel.js +1 -1
- package/template/dist/chatgpt/counter.js +1 -1
- package/template/dist/chatgpt/pizzaz.js +3034 -0
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Avatar.js +97 -0
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Avatar.js.map +7 -0
- 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 +45 -33
- package/template/node_modules/.vite/deps/{chunk-DQAZDQU3.js → chunk-LR7NKCX5.js} +8 -8
- package/template/node_modules/.vite/deps/mapbox-gl.js +32835 -0
- package/template/node_modules/.vite/deps/mapbox-gl.js.map +7 -0
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/package.json +1 -0
- package/template/src/components/album/album-carousel.test.tsx +84 -0
- package/template/src/components/album/album-carousel.tsx +168 -0
- package/template/src/components/album/albums.test.tsx +2 -2
- package/template/src/components/album/albums.tsx +3 -3
- package/template/src/components/album/index.ts +1 -0
- package/template/src/components/carousel/index.ts +1 -0
- package/template/src/components/index.ts +1 -1
- package/template/src/components/pizzaz/index.ts +6 -0
- package/template/src/components/pizzaz/map-view.tsx +212 -0
- package/template/src/components/pizzaz/pizzaz.tsx +145 -0
- package/template/src/components/pizzaz/place-card.tsx +55 -0
- package/template/src/components/pizzaz/place-carousel.tsx +45 -0
- package/template/src/components/pizzaz/place-inspector.tsx +132 -0
- package/template/src/components/pizzaz/place-list.tsx +90 -0
- package/template/src/resources/carousel-resource.test.tsx +1 -4
- package/template/src/resources/carousel-resource.tsx +1 -2
- package/template/src/resources/index.ts +1 -0
- package/template/src/resources/pizzaz-resource.tsx +32 -0
- package/template/src/simulations/index.ts +2 -0
- package/template/src/simulations/pizzaz-simulation.ts +177 -0
- package/template/src/components/card/index.ts +0 -1
- /package/template/node_modules/.vite/deps/{chunk-DQAZDQU3.js.map → chunk-LR7NKCX5.js.map} +0 -0
- /package/template/src/components/{card → carousel}/card.test.tsx +0 -0
- /package/template/src/components/{card → carousel}/card.tsx +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Star } from '@openai/apps-sdk-ui/components/Icon';
|
|
3
|
+
import { cn } from '../../lib/index';
|
|
4
|
+
import type { Place } from '../../simulations/pizzaz-simulation';
|
|
5
|
+
|
|
6
|
+
export type PlaceCardProps = {
|
|
7
|
+
place: Place;
|
|
8
|
+
isSelected?: boolean;
|
|
9
|
+
onClick?: () => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const PlaceCard = React.forwardRef<HTMLDivElement, PlaceCardProps>(
|
|
14
|
+
({ place, isSelected, onClick, className }, ref) => {
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cn(
|
|
19
|
+
'rounded-2xl px-3 select-none hover:bg-black/5 dark:hover:bg-white/5 cursor-pointer',
|
|
20
|
+
isSelected && 'bg-black/5 dark:bg-white/5',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
>
|
|
24
|
+
<div
|
|
25
|
+
className={cn(
|
|
26
|
+
'border-b hover:border-transparent',
|
|
27
|
+
isSelected ? 'border-transparent' : 'border-black/5 dark:border-white/5'
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
<button
|
|
31
|
+
className="w-full text-left py-3 transition flex gap-3 items-center"
|
|
32
|
+
onClick={onClick}
|
|
33
|
+
>
|
|
34
|
+
<img
|
|
35
|
+
src={place.thumbnail}
|
|
36
|
+
alt={place.name}
|
|
37
|
+
className="h-16 w-16 rounded-lg object-cover flex-none"
|
|
38
|
+
loading="lazy"
|
|
39
|
+
/>
|
|
40
|
+
<div className="min-w-0">
|
|
41
|
+
<div className="font-medium truncate text-primary">{place.name}</div>
|
|
42
|
+
<div className="text-xs text-secondary truncate">{place.description}</div>
|
|
43
|
+
<div className="text-xs mt-1 text-secondary flex items-center gap-1">
|
|
44
|
+
<Star className="h-3 w-3" aria-hidden="true" />
|
|
45
|
+
{place.rating.toFixed(1)}
|
|
46
|
+
{place.price && <span>· {place.price}</span>}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
PlaceCard.displayName = 'PlaceCard';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import useEmblaCarousel from 'embla-carousel-react';
|
|
3
|
+
import { cn } from '../../lib/index';
|
|
4
|
+
import { PlaceCard } from './place-card';
|
|
5
|
+
import type { Place } from '../../simulations/pizzaz-simulation';
|
|
6
|
+
|
|
7
|
+
export type PlaceCarouselProps = {
|
|
8
|
+
places: Place[];
|
|
9
|
+
selectedId: string | null;
|
|
10
|
+
onSelect: (place: Place) => void;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const PlaceCarousel = React.forwardRef<HTMLDivElement, PlaceCarouselProps>(
|
|
15
|
+
({ places, selectedId, onSelect, className }, ref) => {
|
|
16
|
+
const [emblaRef] = useEmblaCarousel({ dragFree: true, loop: false });
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
ref={ref}
|
|
21
|
+
className={cn('absolute inset-x-0 bottom-0 z-20 pointer-events-auto', className)}
|
|
22
|
+
>
|
|
23
|
+
<div className="pt-2">
|
|
24
|
+
<div className="overflow-hidden" ref={emblaRef}>
|
|
25
|
+
<div className="px-3 py-3 flex gap-3">
|
|
26
|
+
{places.map((place) => (
|
|
27
|
+
<div
|
|
28
|
+
key={place.id}
|
|
29
|
+
className="ring ring-black/10 dark:ring-white/10 max-w-[330px] w-full shadow-xl rounded-2xl bg-surface flex-shrink-0"
|
|
30
|
+
>
|
|
31
|
+
<PlaceCard
|
|
32
|
+
place={place}
|
|
33
|
+
isSelected={selectedId === place.id}
|
|
34
|
+
onClick={() => onSelect(place)}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
PlaceCarousel.displayName = 'PlaceCarousel';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Button } from '@openai/apps-sdk-ui/components/Button';
|
|
3
|
+
import { Avatar } from '@openai/apps-sdk-ui/components/Avatar';
|
|
4
|
+
import { X, Star } from '@openai/apps-sdk-ui/components/Icon';
|
|
5
|
+
import { cn } from '../../lib/index';
|
|
6
|
+
import type { Place } from '../../simulations/pizzaz-simulation';
|
|
7
|
+
|
|
8
|
+
export type PlaceInspectorProps = {
|
|
9
|
+
place: Place;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const REVIEWS = [
|
|
15
|
+
{
|
|
16
|
+
user: 'Leo M.',
|
|
17
|
+
avatar: 'https://persistent.oaistatic.com/pizzaz/user1.png',
|
|
18
|
+
text: 'Fantastic crust and balanced toppings. The marinara is spot on!',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
user: 'Priya S.',
|
|
22
|
+
avatar: 'https://persistent.oaistatic.com/pizzaz/user2.png',
|
|
23
|
+
text: 'Cozy vibe and friendly staff. Quick service on a Friday night.',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
user: 'Maya R.',
|
|
27
|
+
avatar: 'https://persistent.oaistatic.com/pizzaz/user3.png',
|
|
28
|
+
text: 'Great for sharing. Will definitely come back with friends.',
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export const PlaceInspector = React.forwardRef<HTMLDivElement, PlaceInspectorProps>(
|
|
33
|
+
({ place, onClose, className }, ref) => {
|
|
34
|
+
if (!place) return null;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
ref={ref}
|
|
39
|
+
className={cn(
|
|
40
|
+
'pizzaz-inspector absolute z-30 top-0 bottom-4 left-0 right-auto xl:left-auto xl:right-6 md:z-20 w-[340px] xl:w-[360px] xl:top-6 xl:bottom-8 pointer-events-auto',
|
|
41
|
+
'animate-in fade-in slide-in-from-left-2 xl:slide-in-from-right-2 duration-200',
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
{/* Close button */}
|
|
46
|
+
<Button
|
|
47
|
+
variant="solid"
|
|
48
|
+
color="secondary"
|
|
49
|
+
size="sm"
|
|
50
|
+
className="absolute z-10 top-4 left-4 xl:top-4 xl:left-4 shadow-xl rounded-full"
|
|
51
|
+
onClick={onClose}
|
|
52
|
+
aria-label="Close details"
|
|
53
|
+
>
|
|
54
|
+
<X className="h-[18px] w-[18px]" aria-hidden="true" />
|
|
55
|
+
</Button>
|
|
56
|
+
|
|
57
|
+
<div className="relative h-full overflow-y-auto rounded-none xl:rounded-3xl bg-surface xl:shadow-xl xl:ring ring-black/10 dark:ring-white/10">
|
|
58
|
+
{/* Thumbnail */}
|
|
59
|
+
<div className="relative mt-2 xl:mt-0 px-2 xl:px-0">
|
|
60
|
+
<img
|
|
61
|
+
src={place.thumbnail}
|
|
62
|
+
alt={place.name}
|
|
63
|
+
className="w-full rounded-3xl xl:rounded-none h-80 object-cover xl:rounded-t-2xl"
|
|
64
|
+
loading="lazy"
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="h-[calc(100%-11rem)] sm:h-[calc(100%-14rem)]">
|
|
69
|
+
{/* Place info */}
|
|
70
|
+
<div className="p-4 sm:p-5">
|
|
71
|
+
<div className="text-2xl font-medium truncate text-primary">{place.name}</div>
|
|
72
|
+
<div className="text-sm mt-1 text-secondary flex items-center gap-1">
|
|
73
|
+
<Star className="h-3.5 w-3.5" aria-hidden="true" />
|
|
74
|
+
{place.rating.toFixed(1)}
|
|
75
|
+
{place.price && <span>· {place.price}</span>}
|
|
76
|
+
<span>· {place.city}</span>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
{/* Action buttons */}
|
|
80
|
+
<div className="mt-3 flex flex-row items-center gap-3 font-medium">
|
|
81
|
+
<Button
|
|
82
|
+
variant="solid"
|
|
83
|
+
color="warning"
|
|
84
|
+
size="sm"
|
|
85
|
+
className="rounded-full"
|
|
86
|
+
onClick={() => console.log('Add to favorites:', place.id)}
|
|
87
|
+
>
|
|
88
|
+
Add to favorites
|
|
89
|
+
</Button>
|
|
90
|
+
<Button
|
|
91
|
+
variant="outline"
|
|
92
|
+
color="primary"
|
|
93
|
+
size="sm"
|
|
94
|
+
className="rounded-full"
|
|
95
|
+
style={{ color: '#F46C21' }}
|
|
96
|
+
onClick={() => console.log('Contact:', place.id)}
|
|
97
|
+
>
|
|
98
|
+
Contact
|
|
99
|
+
</Button>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Description */}
|
|
103
|
+
<div className="text-sm mt-5 text-primary">
|
|
104
|
+
{place.description} Enjoy a slice at one of SF's favorites. Fresh ingredients,
|
|
105
|
+
great crust, and cozy vibes.
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Reviews */}
|
|
110
|
+
<div className="px-4 sm:px-5 pb-4">
|
|
111
|
+
<div className="text-lg font-medium mb-2 text-primary">Reviews</div>
|
|
112
|
+
<ul className="space-y-3 divide-y divide-black/5 dark:divide-white/5">
|
|
113
|
+
{REVIEWS.map((review, idx) => (
|
|
114
|
+
<li key={idx} className="py-3">
|
|
115
|
+
<div className="flex items-start gap-3">
|
|
116
|
+
<Avatar imageUrl={review.avatar} name={review.user} size={32} />
|
|
117
|
+
<div className="min-w-0 gap-1 flex flex-col">
|
|
118
|
+
<div className="text-xs font-medium text-secondary">{review.user}</div>
|
|
119
|
+
<div className="text-sm text-primary">{review.text}</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</li>
|
|
123
|
+
))}
|
|
124
|
+
</ul>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
PlaceInspector.displayName = 'PlaceInspector';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Settings } from '@openai/apps-sdk-ui/components/Icon';
|
|
3
|
+
import { cn } from '../../lib/index';
|
|
4
|
+
import { PlaceCard } from './place-card';
|
|
5
|
+
import type { Place } from '../../simulations/pizzaz-simulation';
|
|
6
|
+
|
|
7
|
+
export type PlaceListProps = {
|
|
8
|
+
places: Place[];
|
|
9
|
+
selectedId: string | null;
|
|
10
|
+
onSelect: (place: Place) => void;
|
|
11
|
+
className?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const PlaceList = React.forwardRef<HTMLDivElement, PlaceListProps>(
|
|
15
|
+
({ places, selectedId, onSelect, className }, ref) => {
|
|
16
|
+
const scrollRef = React.useRef<HTMLDivElement>(null);
|
|
17
|
+
const [showBottomFade, setShowBottomFade] = React.useState(false);
|
|
18
|
+
|
|
19
|
+
const updateBottomFadeVisibility = React.useCallback(() => {
|
|
20
|
+
const el = scrollRef.current;
|
|
21
|
+
if (!el) return;
|
|
22
|
+
const atBottom = Math.ceil(el.scrollTop + el.clientHeight) >= el.scrollHeight;
|
|
23
|
+
setShowBottomFade(!atBottom);
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
updateBottomFadeVisibility();
|
|
28
|
+
const el = scrollRef.current;
|
|
29
|
+
if (!el) return;
|
|
30
|
+
|
|
31
|
+
const onScroll = () => updateBottomFadeVisibility();
|
|
32
|
+
el.addEventListener('scroll', onScroll, { passive: true });
|
|
33
|
+
window.addEventListener('resize', updateBottomFadeVisibility);
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
el.removeEventListener('scroll', onScroll);
|
|
37
|
+
window.removeEventListener('resize', updateBottomFadeVisibility);
|
|
38
|
+
};
|
|
39
|
+
}, [places, updateBottomFadeVisibility]);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn(
|
|
45
|
+
'absolute inset-y-0 bottom-4 left-0 z-20 w-[340px] max-w-[75%] pointer-events-auto',
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
<div ref={scrollRef} className="relative px-2 h-full overflow-y-auto bg-surface">
|
|
50
|
+
{/* Header */}
|
|
51
|
+
<div className="flex justify-between flex-row items-center px-3 sticky bg-surface top-0 py-4 text-md font-medium">
|
|
52
|
+
<span className="text-primary">{places.length} results</span>
|
|
53
|
+
<Settings className="h-5 w-5 text-secondary" aria-hidden="true" />
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{/* Place list */}
|
|
57
|
+
<div>
|
|
58
|
+
{places.map((place) => (
|
|
59
|
+
<PlaceCard
|
|
60
|
+
key={place.id}
|
|
61
|
+
place={place}
|
|
62
|
+
isSelected={selectedId === place.id}
|
|
63
|
+
onClick={() => onSelect(place)}
|
|
64
|
+
/>
|
|
65
|
+
))}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{/* Bottom fade gradient */}
|
|
70
|
+
{showBottomFade && (
|
|
71
|
+
<div
|
|
72
|
+
className="pointer-events-none absolute inset-x-0 bottom-0 h-9 z-10 transition-opacity duration-200"
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
>
|
|
75
|
+
<div
|
|
76
|
+
className="w-full h-full bg-gradient-to-t from-black/15 to-transparent dark:from-white/15"
|
|
77
|
+
style={{
|
|
78
|
+
WebkitMaskImage:
|
|
79
|
+
'linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.25) 25%, rgba(0,0,0,0.25) 75%, rgba(0,0,0,0) 100%)',
|
|
80
|
+
maskImage:
|
|
81
|
+
'linear-gradient(90deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.25) 25%, rgba(0,0,0,0.25) 75%, rgba(0,0,0,0) 100%)',
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
PlaceList.displayName = 'PlaceList';
|
|
@@ -32,13 +32,10 @@ vi.mock('sunpeak', () => ({
|
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
34
|
// Mock child components
|
|
35
|
-
vi.mock('../components/carousel
|
|
35
|
+
vi.mock('../components/carousel', () => ({
|
|
36
36
|
Carousel: ({ children }: { children: React.ReactNode }) => (
|
|
37
37
|
<div data-testid="carousel">{children}</div>
|
|
38
38
|
),
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
vi.mock('../components/card/card', () => ({
|
|
42
39
|
Card: ({ header, buttonSize }: { header: React.ReactNode; buttonSize?: string }) => (
|
|
43
40
|
<div data-testid="card" data-button-size={buttonSize}>
|
|
44
41
|
{header}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useWidgetProps, useSafeArea, useMaxHeight, useUserAgent } from 'sunpeak';
|
|
3
|
-
import { Carousel } from '../components/carousel
|
|
4
|
-
import { Card } from '../components/card/card';
|
|
3
|
+
import { Carousel, Card } from '../components/carousel';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Production-ready Carousel Resource
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useSafeArea, useMaxHeight } from 'sunpeak';
|
|
3
|
+
import { Pizzaz } from '../components/pizzaz/pizzaz';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Production-ready Pizzaz Resource
|
|
7
|
+
*
|
|
8
|
+
* This resource displays a pizza restaurant finder with an interactive map,
|
|
9
|
+
* place listings, and detailed inspector view.
|
|
10
|
+
* Can be dropped into any production environment without changes.
|
|
11
|
+
*/
|
|
12
|
+
export const PizzazResource = React.forwardRef<HTMLDivElement>((_props, ref) => {
|
|
13
|
+
const safeArea = useSafeArea();
|
|
14
|
+
const maxHeight = useMaxHeight();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
ref={ref}
|
|
19
|
+
className="h-full"
|
|
20
|
+
style={{
|
|
21
|
+
paddingTop: `${safeArea?.insets.top ?? 0}px`,
|
|
22
|
+
paddingBottom: `${safeArea?.insets.bottom ?? 0}px`,
|
|
23
|
+
paddingLeft: `${safeArea?.insets.left ?? 0}px`,
|
|
24
|
+
paddingRight: `${safeArea?.insets.right ?? 0}px`,
|
|
25
|
+
maxHeight: maxHeight ?? undefined,
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
<Pizzaz />
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
PizzazResource.displayName = 'PizzazResource';
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
import { counterSimulation } from './counter-simulation.js';
|
|
9
9
|
import { albumsSimulation } from './albums-simulation.js';
|
|
10
10
|
import { carouselSimulation } from './carousel-simulation.js';
|
|
11
|
+
import { pizzazSimulation } from './pizzaz-simulation.js';
|
|
11
12
|
|
|
12
13
|
export const SIMULATIONS = {
|
|
13
14
|
counter: counterSimulation,
|
|
14
15
|
albums: albumsSimulation,
|
|
15
16
|
carousel: carouselSimulation,
|
|
17
|
+
pizzaz: pizzazSimulation,
|
|
16
18
|
} as const;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-safe configuration for the pizzaz simulation.
|
|
3
|
+
* This file contains only metadata and doesn't import React components or CSS.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { defaultWidgetMeta } from './widget-config';
|
|
7
|
+
|
|
8
|
+
export interface Place {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
coords: [number, number];
|
|
12
|
+
description: string;
|
|
13
|
+
city: string;
|
|
14
|
+
rating: number;
|
|
15
|
+
price: string;
|
|
16
|
+
thumbnail: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PizzazData extends Record<string, unknown> {
|
|
20
|
+
places: Place[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const pizzazData: PizzazData = {
|
|
24
|
+
places: [
|
|
25
|
+
{
|
|
26
|
+
id: 'nova-slice-lab',
|
|
27
|
+
name: 'Nova Slice Lab',
|
|
28
|
+
coords: [-122.4098, 37.8001],
|
|
29
|
+
description: 'Award-winning Neapolitan pies in North Beach.',
|
|
30
|
+
city: 'North Beach',
|
|
31
|
+
rating: 4.8,
|
|
32
|
+
price: '$$$',
|
|
33
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-1.png',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'midnight-marinara',
|
|
37
|
+
name: 'Midnight Marinara',
|
|
38
|
+
coords: [-122.4093, 37.799],
|
|
39
|
+
description: 'Focaccia-style squares, late-night favorite.',
|
|
40
|
+
city: 'North Beach',
|
|
41
|
+
rating: 4.6,
|
|
42
|
+
price: '$',
|
|
43
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-2.png',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'cinder-oven-co',
|
|
47
|
+
name: 'Cinder Oven Co.',
|
|
48
|
+
coords: [-122.4255, 37.7613],
|
|
49
|
+
description: 'Thin-crust classics on 18th Street.',
|
|
50
|
+
city: 'Mission',
|
|
51
|
+
rating: 4.5,
|
|
52
|
+
price: '$$',
|
|
53
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-3.png',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'neon-crust-works',
|
|
57
|
+
name: 'Neon Crust Works',
|
|
58
|
+
coords: [-122.4388, 37.7775],
|
|
59
|
+
description: 'Deep-dish and cornmeal crust favorites.',
|
|
60
|
+
city: 'Alamo Square',
|
|
61
|
+
rating: 4.5,
|
|
62
|
+
price: '$$',
|
|
63
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-6.png',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'luna-pie-collective',
|
|
67
|
+
name: 'Luna Pie Collective',
|
|
68
|
+
coords: [-122.4077, 37.799],
|
|
69
|
+
description: 'Wood-fired pies and burrata in North Beach.',
|
|
70
|
+
city: 'North Beach',
|
|
71
|
+
rating: 4.6,
|
|
72
|
+
price: '$$',
|
|
73
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-4.png',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: 'bricklight-deep-dish',
|
|
77
|
+
name: 'Bricklight Deep Dish',
|
|
78
|
+
coords: [-122.4097, 37.7992],
|
|
79
|
+
description: 'Chicago-style pies from Tony Gemignani.',
|
|
80
|
+
city: 'North Beach',
|
|
81
|
+
rating: 4.4,
|
|
82
|
+
price: '$$$',
|
|
83
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-5.png',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'garden-ember-pies',
|
|
87
|
+
name: 'Garden Ember Pies',
|
|
88
|
+
coords: [-122.438, 37.7722],
|
|
89
|
+
description: 'Neighborhood spot with seasonal toppings.',
|
|
90
|
+
city: 'Lower Haight',
|
|
91
|
+
rating: 4.4,
|
|
92
|
+
price: '$$',
|
|
93
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-1.png',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'atlas-fire-pizzeria',
|
|
97
|
+
name: 'Atlas Fire Pizzeria',
|
|
98
|
+
coords: [-122.4123, 37.7899],
|
|
99
|
+
description: 'Sourdough, wood-fired pies near Nob Hill.',
|
|
100
|
+
city: 'Nob Hill',
|
|
101
|
+
rating: 4.6,
|
|
102
|
+
price: '$$$',
|
|
103
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-2.png',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'circuit-slice-garage',
|
|
107
|
+
name: 'Circuit Slice Garage',
|
|
108
|
+
coords: [-122.4135, 37.7805],
|
|
109
|
+
description: 'Crispy-edged Detroit-style in SoMa.',
|
|
110
|
+
city: 'SoMa',
|
|
111
|
+
rating: 4.5,
|
|
112
|
+
price: '$$',
|
|
113
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-3.png',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'velvet-mozza-lounge',
|
|
117
|
+
name: 'Velvet Mozza Lounge',
|
|
118
|
+
coords: [-122.4019, 37.7818],
|
|
119
|
+
description: 'Bianca pies and cocktails near Yerba Buena.',
|
|
120
|
+
city: 'Yerba Buena',
|
|
121
|
+
rating: 4.3,
|
|
122
|
+
price: '$$',
|
|
123
|
+
thumbnail: 'https://persistent.oaistatic.com/pizzaz/pizzaz-6.png',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const pizzazSimulation = {
|
|
129
|
+
userMessage: 'Find pizza near me',
|
|
130
|
+
|
|
131
|
+
// MCP Tool protocol - official Tool type from MCP SDK used in ListTools response
|
|
132
|
+
tool: {
|
|
133
|
+
name: 'find-pizza',
|
|
134
|
+
description: 'Find pizza restaurants nearby',
|
|
135
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false } as const,
|
|
136
|
+
title: 'Find Pizza',
|
|
137
|
+
annotations: { readOnlyHint: true },
|
|
138
|
+
_meta: {
|
|
139
|
+
'openai/outputTemplate': 'ui://PizzazResource',
|
|
140
|
+
'openai/toolInvocation/invoking': 'Finding pizza places',
|
|
141
|
+
'openai/toolInvocation/invoked': 'Found pizza places',
|
|
142
|
+
'openai/widgetAccessible': true,
|
|
143
|
+
'openai/resultCanProduceWidget': true,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// MCP Resource protocol - official Resource type from MCP SDK used in ListResources response
|
|
148
|
+
// resource.name is used as the simulation identifier
|
|
149
|
+
// resource.title is used as the simulation display label
|
|
150
|
+
resource: {
|
|
151
|
+
uri: 'ui://PizzazResource',
|
|
152
|
+
name: 'pizzaz',
|
|
153
|
+
title: 'Pizzaz',
|
|
154
|
+
description: 'Pizza restaurant finder widget',
|
|
155
|
+
mimeType: 'text/html+skybridge',
|
|
156
|
+
_meta: {
|
|
157
|
+
...defaultWidgetMeta,
|
|
158
|
+
'openai/widgetCSP': {
|
|
159
|
+
...defaultWidgetMeta['openai/widgetCSP'],
|
|
160
|
+
connect_domains: [
|
|
161
|
+
...defaultWidgetMeta['openai/widgetCSP'].connect_domains,
|
|
162
|
+
'https://api.mapbox.com',
|
|
163
|
+
],
|
|
164
|
+
resource_domains: [
|
|
165
|
+
...defaultWidgetMeta['openai/widgetCSP'].resource_domains,
|
|
166
|
+
'https://api.mapbox.com',
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
// MCP CallTool protocol - data for CallTool response
|
|
173
|
+
toolCall: {
|
|
174
|
+
structuredContent: pizzazData,
|
|
175
|
+
_meta: {},
|
|
176
|
+
},
|
|
177
|
+
} as const;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './card';
|
|
File without changes
|
|
File without changes
|
|
File without changes
|