sunpeak 0.6.4 → 0.6.6
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 +8 -4
- package/dist/style.css +440 -0
- package/package.json +1 -1
- package/template/dist/chatgpt/albums.js +1 -1
- package/template/dist/chatgpt/carousel.js +1 -1
- package/template/dist/chatgpt/counter.js +1 -1
- package/template/dist/chatgpt/map.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 +11 -11
- package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Textarea.js +3 -3
- package/template/node_modules/.vite/deps/_metadata.json +46 -34
- package/template/node_modules/.vite/deps/{chunk-EVJ3DVH5.js → chunk-LR7NKCX5.js} +7 -7
- 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/index.ts +1 -0
- package/template/src/components/map/index.ts +6 -0
- package/template/src/components/map/map-view.tsx +212 -0
- package/template/src/components/map/map.tsx +145 -0
- package/template/src/components/map/place-card.tsx +55 -0
- package/template/src/components/map/place-carousel.tsx +45 -0
- package/template/src/components/map/place-inspector.tsx +132 -0
- package/template/src/components/map/place-list.tsx +90 -0
- package/template/src/resources/index.ts +1 -0
- package/template/src/resources/map-resource.tsx +32 -0
- package/template/src/simulations/index.ts +2 -0
- package/template/src/simulations/map-simulation.ts +177 -0
- /package/template/node_modules/.vite/deps/{chunk-EVJ3DVH5.js.map → chunk-LR7NKCX5.js.map} +0 -0
|
@@ -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/map-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';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useSafeArea, useMaxHeight } from 'sunpeak';
|
|
3
|
+
import { Map } from '../components/map/map';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Production-ready Map 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 MapResource = 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
|
+
<Map />
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
MapResource.displayName = 'MapResource';
|
|
@@ -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 { mapSimulation } from './map-simulation.js';
|
|
11
12
|
|
|
12
13
|
export const SIMULATIONS = {
|
|
13
14
|
counter: counterSimulation,
|
|
14
15
|
albums: albumsSimulation,
|
|
15
16
|
carousel: carouselSimulation,
|
|
17
|
+
map: mapSimulation,
|
|
16
18
|
} as const;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-safe configuration for the map 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 MapData extends Record<string, unknown> {
|
|
20
|
+
places: Place[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const mapData: MapData = {
|
|
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 mapSimulation = {
|
|
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://MapResource',
|
|
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://MapResource',
|
|
152
|
+
name: 'map',
|
|
153
|
+
title: 'Map',
|
|
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: mapData,
|
|
175
|
+
_meta: {},
|
|
176
|
+
},
|
|
177
|
+
} as const;
|
|
File without changes
|