sweet lander

This commit is contained in:
geoffsee
2025-07-08 14:09:55 -04:00
parent 5630a95f1a
commit 7ab1141540
23 changed files with 387 additions and 107 deletions

View File

@@ -74,7 +74,6 @@
"react-map-gl": "^8.0.4",
"react-streaming": "^0.4.2",
"react-textarea-autosize": "^8.5.5",
"react-use-pwa-install": "^1.0.3",
"shiki": "^1.24.0",
"tslog": "^4.9.3",
"typescript": "^5.7.2",
@@ -1646,8 +1645,6 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"pwa-install-handler": ["pwa-install-handler@2.6.2", "", {}, "sha512-9hMpqWNxGZx4ZoBe9k9gHkdZC/d/mvMJLA08FCVVMxOhwHBNuQVzb0DwH8ffEaqFvqu7GaotcvYgGNT1yVWduQ=="],
"qrcode.react": ["qrcode.react@4.2.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA=="],
"quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="],
@@ -1688,8 +1685,6 @@
"react-textarea-autosize": ["react-textarea-autosize@8.5.9", "", { "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A=="],
"react-use-pwa-install": ["react-use-pwa-install@1.0.3", "", { "dependencies": { "pwa-install-handler": "^2.6.2" }, "peerDependencies": { "react": "18 || 19" } }, "sha512-poF5teATOCblAchP61+Hx/FIQJtSkjGFcZsJjiyXmG9SfmJWkj8M890lXKlu6QPg/bmG6GE3d+KP3aEO9ehgDw=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],

View File

@@ -22,9 +22,10 @@ export class AssistantSdk {
const currentTime = `${now.getHours()}:${formattedMinutes} ${now.getSeconds()}s`;
return `# Assistant Knowledge
## Assistant Name
### yachtpit-ai
## Current Context
### Date: ${currentDate} ${currentTime}
### Web Host: open-gsio.seemueller.workers.dev
${maxTokens ? `### Max Response Length: ${maxTokens} tokens (maximum)` : ''}
### Lexicographical Format: Markdown
### User Location: ${userLocation || 'Unknown'}

View File

@@ -58,7 +58,6 @@
"react-map-gl": "^8.0.4",
"react-streaming": "^0.4.2",
"react-textarea-autosize": "^8.5.5",
"react-use-pwa-install": "^1.0.3",
"shiki": "^1.24.0",
"tslog": "^4.9.3",
"typescript": "^5.7.2",

View File

@@ -1,14 +1,15 @@
import { IconButton } from '@chakra-ui/react';
import { HardDriveDownload } from 'lucide-react';
import React from 'react';
import { usePWAInstall } from 'react-use-pwa-install';
import { toolbarButtonZIndex } from './toolbar/Toolbar.tsx';
function InstallButton() {
const install = usePWAInstall();
// const install = usePWAInstall();
// <button onClick={handleInstall}>Install App</button>;
const install = () => {
console.warn('this does not work in all browsers');
};
return (
<IconButton
aria-label="Install App"

View File

@@ -28,7 +28,7 @@ const Chat = observer(({ height, width }) => {
<GridItem
overflow="auto"
width="100%"
maxH="100%"
maxH="100vh"
ref={scrollRef}
// If there are attachments, use "100px". Otherwise, use "128px" on Android, "73px" elsewhere.
pb={isAndroid ? '128px' : '73px'}

View File

@@ -22,7 +22,7 @@ const ChatInput = observer(() => {
const [shouldFollow, setShouldFollow] = useState<boolean>(userOptionsStore.followModeEnabled);
const [couldFollow, setCouldFollow] = useState<boolean>(chatStore.isLoading);
const [inputWidth, setInputWidth] = useState<string>('50%');
const [inputWidth, setInputWidth] = useState<string>('40%');
useEffect(() => {
setShouldFollow(chatStore.isLoading && userOptionsStore.followModeEnabled);
@@ -64,10 +64,10 @@ const ChatInput = observer(() => {
};
const inputMaxWidth = useBreakpointValue(
{ base: '50rem', lg: '50rem', md: '80%', sm: '100vw' },
{ base: '30rem', lg: '50rem', md: '80%', sm: '100vw' },
{ ssr: true },
);
const inputMinWidth = useBreakpointValue({ lg: '40rem' }, { ssr: true });
const inputMinWidth = useBreakpointValue({ lg: '40rem', md: '30rem' }, { ssr: true });
useEffect(() => {
setInputWidth('100%');
@@ -75,9 +75,7 @@ const ChatInput = observer(() => {
return (
<Box
width={inputWidth}
maxW={inputMaxWidth}
minWidth={inputMinWidth}
width={inputMinWidth}
mx="auto"
p={2}
pl={2}

View File

@@ -1,4 +1,4 @@
import { Box, chakra, InputGroup } from '@chakra-ui/react';
import { Box, chakra, InputGroup, useBreakpointValue } from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React, { useEffect, useRef, useState } from 'react';
import AutoResize from 'react-textarea-autosize';
@@ -19,7 +19,7 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
useEffect(() => {
if (value.length > 10) {
setHeightConstraint();
setHeightConstraint(parseInt(value));
}
}, [value]);
@@ -38,6 +38,7 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
ref={inputRef}
value={value}
height={heightConstraint}
maxH={heightConstraint}
autoFocus
onChange={e => onChange(e.target.value)}
onKeyDown={onKeyDown}
@@ -48,8 +49,14 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
color="text.primary"
borderRadius="20px"
border="none"
placeholder="Free my mind..."
_placeholder={{ color: 'gray.400' }}
placeholder="To Gilligan's island!"
_placeholder={{
color: 'gray.400',
textWrap: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
width: '90%',
}}
_focus={{
outline: 'none',
}}

View File

@@ -9,7 +9,7 @@ export function formatConversationMarkdown(messages: Instance<typeof IMessage>[]
if (message.role === 'user') {
return `**You**: ${message.content}`;
} else if (message.role === 'assistant') {
return `**Geoff's AI**: ${message.content}`;
return `**yachtpit-ai**: ${message.content}`;
}
return '';
})

View File

@@ -51,7 +51,7 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
const [isEditing, setIsEditing] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const isUser = msg.role === 'user';
const senderName = isUser ? 'You' : "Geoff's AI";
const senderName = isUser ? 'You' : 'yachtpit-ai';
const isLoading = !msg.content || !(msg.content.trim().length > 0);
const messageRef = useRef();

View File

@@ -104,7 +104,7 @@ describe('MessageBubble', () => {
it('should render assistant message correctly', () => {
render(<MessageBubble msg={mockAssistantMessage} scrollRef={mockScrollRef} />);
expect(screen.getByText("Geoff's AI")).toBeInTheDocument();
expect(screen.getByText('yachtpit-ai')).toBeInTheDocument();
expect(screen.getByTestId('message-content')).toHaveTextContent('Assistant response');
});

View File

@@ -0,0 +1,25 @@
import React, { createContext, useContext, useState } from 'react';
type ComponentContextType = {
enabledComponent: string;
setEnabledComponent: (component: string) => void;
};
const ComponentContext = createContext<ComponentContextType>({
enabledComponent: '',
setEnabledComponent: () => {},
});
export const useComponent = () => useContext(ComponentContext);
export const ComponentProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [enabledComponent, setEnabledComponent] = useState<string>('');
return (
<ComponentContext.Provider value={{ enabledComponent, setEnabledComponent }}>
{children}
</ComponentContext.Provider>
);
};
export default ComponentContext;

View File

@@ -1,4 +1,4 @@
import { Box } from '@chakra-ui/react';
import { Box, useBreakpointValue } from '@chakra-ui/react';
import React, { memo, useEffect, useMemo } from 'react';
export interface BevySceneProps {
@@ -14,6 +14,8 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
glow = false,
visible,
}) => {
const maxWidth = useBreakpointValue({ base: 640, md: 720 }, { ssr: true });
/* initialise once */
useEffect(() => {
let dispose: (() => void) | void;
@@ -31,7 +33,8 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
() => ({
position: 'absolute' as const,
inset: 0,
zIndex: 0,
zIndex: 1,
maxWidth: maxWidth,
opacity: visible ? Math.min(Math.max(intensity, 0), 1) : 0,
filter: glow ? 'blur(1px)' : 'none',
transition: `opacity ${speed}s ease-in-out`,
@@ -42,7 +45,12 @@ const BevySceneInner: React.FC<BevySceneProps> = ({
return (
<Box as="div" sx={wrapperStyles}>
<canvas id="yachtpit-canvas" width={1280} height={720} aria-hidden />
<canvas
id="yachtpit-canvas"
width={useBreakpointValue({ base: 640, md: 1280 }, { ssr: true })}
height={useBreakpointValue({ base: 360, md: 720 }, { ssr: true })}
aria-hidden
/>
</Box>
);
};

View File

@@ -1,20 +1,30 @@
import { Box } from '@chakra-ui/react';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useComponent } from '../contexts/ComponentContext.tsx';
import { BevyScene } from './BevyScene.tsx';
import Map from './Map.tsx';
import Tweakbox from './Tweakbox.tsx';
export const LandingComponent: React.FC = () => {
const [speed, setSpeed] = useState(0.2);
const [intensity, setIntensity] = useState(0.5);
const [particles, setParticles] = useState(false);
const [intensity, setIntensity] = useState(0.99);
const [glow, setGlow] = useState(false);
const [matrixRain, setMatrixRain] = useState(false);
const [bevyScene, setBevyScene] = useState(true);
const [mapActive, setMapActive] = useState(false);
const [mapActive, setMapActive] = useState(true);
const [aiActive, setAiActive] = useState(false);
const map = <Map visible={mapActive} />;
const component = useComponent();
const { setEnabledComponent } = component;
useEffect(() => {
if (mapActive) {
setEnabledComponent('gpsmap');
}
if (aiActive) {
setEnabledComponent('ai');
}
}, []);
return (
<Box
@@ -36,7 +46,7 @@ export const LandingComponent: React.FC = () => {
<Tweakbox
sliders={{
intensity: {
value: !particles ? intensity : 0.99,
value: intensity,
onChange: setIntensity,
label: 'Brightness',
min: 0.01,
@@ -49,11 +59,6 @@ export const LandingComponent: React.FC = () => {
bevyScene: {
value: bevyScene,
onChange(enabled) {
if (enabled) {
setParticles(!enabled);
setMatrixRain(!enabled);
setMapActive(!enabled);
}
setBevyScene(enabled);
},
label: 'Instruments',
@@ -62,19 +67,32 @@ export const LandingComponent: React.FC = () => {
value: mapActive,
onChange(enabled) {
if (enabled) {
setParticles(!enabled);
setMatrixRain(!enabled);
setBevyScene(!enabled);
setEnabledComponent('gpsmap');
setAiActive(false);
} else {
setEnabledComponent('');
}
setMapActive(enabled);
},
label: 'Map',
},
AI: {
value: aiActive,
onChange(enabled) {
if (enabled) {
setEnabledComponent('ai');
setMapActive(false);
} else {
setEnabledComponent('');
}
setAiActive(enabled);
},
label: 'AI',
},
}}
/>
</Box>
<BevyScene speed={speed} intensity={intensity} glow={glow} visible={bevyScene} />
{mapActive && map}
</Box>
);
};

View File

@@ -3,6 +3,8 @@ import 'mapbox-gl/dist/mapbox-gl.css';
import { Box, HStack, Button, Input, Center } from '@chakra-ui/react';
import { useState, useEffect, useCallback } from 'react';
import MapNext from './MapNext.tsx';
// Types for bevy_flurx_ipc communication
interface GpsPosition {
latitude: number;
@@ -33,64 +35,27 @@ const key =
'cGsuZXlKMUlqb2laMlZ2Wm1aelpXVWlMQ0poSWpvaVkycDFOalo0YkdWNk1EUTRjRE41YjJnNFp6VjNNelp6YXlKOS56LUtzS1l0X3VGUGdCSDYwQUFBNFNn';
function Map(props: { visible: boolean }) {
const [mapboxToken, setMapboxToken] = useState(atob(key));
const [isTokenLoading, setIsTokenLoading] = useState(false);
const [authenticated, setAuthenticated] = useState(false);
useEffect(() => {
setAuthenticated(true);
setIsTokenLoading(false);
}, []);
const [mapView, setMapView] = useState({
longitude: -122.4,
latitude: 37.8,
zoom: 14,
});
const handleNavigationClick = useCallback(async () => {
console.log('handling navigation in map');
}, []);
const handleSearchClick = useCallback(async () => {
console.log('handling click search in map');
}, []);
const handleMapViewChange = useCallback(async (evt: any) => {
const { longitude, latitude, zoom } = evt.viewState;
setMapView({ longitude, latitude, zoom });
}, []);
return (
<Box
p={4}
height="80%"
width="100%"
position="relative"
display={props.visible ? undefined : 'none'}
>
<Box width={'100%'} height={'100%'} position="relative" zIndex={0}>
{/* Map itself */}
{authenticated && (
<ReactMap
mapboxAccessToken={mapboxToken}
initialViewState={mapView}
onMove={handleMapViewChange}
mapStyle="mapbox://styles/mapbox/dark-v11"
attributionControl={false}
style={{ width: '100%', height: '100%' }} // let the wrapper dictate size
/>
)}
</Box>
/* Full-screen wrapper — fills the viewport and becomes the positioning context */
<Box w="100%" h="100vh" position="relative" overflow="hidden">
{/* Button bar — absolutely positioned inside the wrapper */}
<HStack position="relative" top={1} right={4} zIndex={1} justify={'right'}>
<Button size="sm" variant="solid" onClick={handleNavigationClick}>
Navigation
</Button>
<Button size="sm" variant="solid" onClick={handleSearchClick}>
Search
</Button>
</HStack>
<MapNext mapboxPublicKey={atob(key)} />
{/*<Map*/}
{/* mapboxAccessToken={atob(key)}*/}
{/* initialViewState={mapView}*/}
{/* onMove={handleMapViewChange}*/}
{/* mapStyle="mapbox://styles/mapbox/dark-v11"*/}
{/* reuseMaps*/}
{/* attributionControl={false}*/}
{/* style={{width: '100%', height: '100%'}} // let the wrapper dictate size*/}
{/*>*/}
{/* /!*{vesselPosition && (*!/*/}
{/* /!* <Source id="vessel-data" type="geojson" data={vesselGeojson}>*!/*/}
{/* /!* <Layer {...vesselLayerStyle} />*!/*/}
{/* /!* </Source>*!/*/}
{/* /!*)}*!/*/}
{/*</Map>*/}
</Box>
);
}

View File

@@ -0,0 +1,173 @@
import { Box, Button, HStack, Input } from '@chakra-ui/react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import Map, {
FullscreenControl,
GeolocateControl,
Marker,
NavigationControl,
Popup,
ScaleControl,
} from 'react-map-gl/mapbox';
import PORTS from './nautical-base-data.json';
import Pin from './pin';
export default function MapNext(props: any = { mapboxPublicKey: '' } as any) {
const [popupInfo, setPopupInfo] = useState(null);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [isTokenLoading, setIsTokenLoading] = useState(false);
const [authenticated, setAuthenticated] = useState(false);
useEffect(() => {
setAuthenticated(true);
setIsTokenLoading(false);
}, []);
const [mapView, setMapView] = useState({
longitude: -122.4,
latitude: 37.8,
zoom: 14,
});
const handleNavigationClick = useCallback(async () => {
console.log('handling navigation in map');
}, []);
const handleSearchClick = useCallback(async () => {
console.log('handling click search in map');
}, []);
const handleMapViewChange = useCallback(async (evt: any) => {
const { longitude, latitude, zoom } = evt.viewState;
setMapView({ longitude, latitude, zoom });
}, []);
const pins = useMemo(
() =>
PORTS.map((city, index) => (
<Marker
key={`marker-${index}`}
longitude={city.longitude}
latitude={city.latitude}
anchor="bottom"
onClick={e => {
// If we let the click event propagates to the map, it will immediately close the popup
// with `closeOnClick: true`
e.originalEvent.stopPropagation();
/*
src/MapNext.tsx:34:38 - error TS2345: Argument of type '{ city: string; population: string; image: string; state: string; latitude: number; longitude: number; }' is not assignable to parameter of type 'SetStateAction<null>'.
Type '{ city: string; population: string; image: string; state: string; latitude: number; longitude: number; }' provides no match for the signature '(prevState: null): null'.
*/
// @ts-ignore
setPopupInfo(city);
}}
>
<Pin />
</Marker>
)),
[],
);
return (
<Box justifySelf={'right'} w={'100%'}>
{/*<HStack position="absolute" top={4} right={4} zIndex={1}>*/}
{/* <Box display="flex" alignItems="center">*/}
{/* <Button colorScheme="teal" size="sm" variant="solid" onClick={handleSearchClick} mr={2}>*/}
{/* Search*/}
{/* </Button>*/}
{/* {isSearchOpen && (*/}
{/* <Box*/}
{/* w="200px"*/}
{/* transition="all 0.3s"*/}
{/* transform={`translateX(${isSearchOpen ? '0' : '100%'})`}*/}
{/* opacity={isSearchOpen ? 1 : 0}*/}
{/* color="white"*/}
{/* >*/}
{/* <Input*/}
{/* placeholder="Search..."*/}
{/* size="sm"*/}
{/* _placeholder={{*/}
{/* color: '#d1cfcf',*/}
{/* }}*/}
{/* />*/}
{/* </Box>*/}
{/* )}*/}
{/* </Box>*/}
{/* <Button colorScheme="blue" size="sm" variant="solid" onClick={handleNavigationClick}>*/}
{/* Layer*/}
{/* </Button>*/}
{/*</HStack>*/}
<Map
initialViewState={{
latitude: 40,
longitude: -100,
zoom: 3.5,
bearing: 0,
pitch: 0,
}}
mapStyle="mapbox://styles/geoffsee/cmd1qz39x01ga01qv5acea02y"
attributionControl={false}
mapboxAccessToken={props.mapboxPublicKey}
style={{
position: 'absolute',
width: '100%',
// height: '50%',
bottom: 0,
top: 0,
left: 0,
right: 0,
}}
>
<GeolocateControl position="top-left" />
<FullscreenControl position="top-left" />
<NavigationControl position="top-left" />
<ScaleControl position="top-left" />
{pins}
{popupInfo && (
<Popup
anchor="top"
/*
src/MapNext.tsx:66:53 - error TS2339: Property 'longitude' does not exist on type 'never'.
66 longitude={Number(popupInfo.longitude)}
*/
// @ts-ignore
longitude={Number(popupInfo.longitude)}
/*
src/MapNext.tsx:67:52 - error TS2339: Property 'latitude' does not exist on type 'never'.
67 latitude={Number(popupInfo.latitude)}
~~~~~~~~
*/
// @ts-ignore
latitude={Number(popupInfo.latitude)}
onClose={() => setPopupInfo(null)}
>
<div style={{ color: 'black' }}>
{/*src/MapNext.tsx:71:40 - error TS2339: Property 'city' does not exist on type 'never'.
71 {popupInfo.city}, {popupInfo.state} |{' '}
~~~~*/}
{/*@ts-ignore*/}
{/*@ts-ignore*/}
{popupInfo.city},{popupInfo.state}
{/*@ts-ignore*/}
</div>
{/*@ts-ignore*/}
<img width="100%" src={popupInfo.image} />
<br />
<a
style={{ color: 'blue' }}
target="_new"
href={`http://en.wikipedia.org/w/index.php?title=Special:Search&search=${(popupInfo as any).city}, ${(popupInfo as any).state}`}
>
Wikipedia
</a>
</Popup>
)}
</Map>
</Box>
);
}

View File

@@ -0,0 +1,24 @@
import * as React from 'react';
function ControlPanel() {
return (
<div className="control-panel">
<p>
Data source:{' '}
<a href="https://en.wikipedia.org/wiki/List_of_United_States_cities_by_population">
Wikipedia
</a>
</p>
<div className="source-link">
<a
href="https://github.com/visgl/react-map-gl/tree/8.0-release/examples/mapbox/controls"
target="_new"
>
View Code
</a>
</div>
</div>
);
}
export default React.memo(ControlPanel);

View File

@@ -0,0 +1,22 @@
[
{"city":"New York","population":"8,335,897","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Above_Gotham.jpg/240px-Above_Gotham.jpg","state":"New York","latitude":40.7128,"longitude":-74.0060},
{"city":"Los Angeles","population":"3,822,238","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/57/LA_Skyline_Mountains2.jpg/240px-LA_Skyline_Mountains2.jpg","state":"California","latitude":34.0522,"longitude":-118.2437},
{"city":"Long Beach","population":"456,062","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/d/d9/Long_Beach_skyline_from_Shoreline_Village.jpg/240px-Long_Beach_skyline_from_Shoreline_Village.jpg","state":"California","latitude":33.7701,"longitude":-118.1937},
{"city":"Seattle","population":"749,256","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/SeattleI5Skyline.jpg/240px-SeattleI5Skyline.jpg","state":"Washington","latitude":47.6062,"longitude":-122.3321},
{"city":"San Francisco","population":"808,437","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/San_Francisco_skyline_from_Coit_Tower.jpg/240px-San_Francisco_skyline_from_Coit_Tower.jpg","state":"California","latitude":37.7749,"longitude":-122.4194},
{"city":"San Diego","population":"1,386,932","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg/240px-US_Navy_110604-N-NS602-574_Navy_and_Marine_Corps_personnel%2C_along_with_community_leaders_from_the_greater_San_Diego_area_come_together_to_commemora.jpg","state":"California","latitude":32.7157,"longitude":-117.1611},
{"city":"Norfolk","population":"235,089","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6e/Norfolk_Skyline_from_Portsmouth.jpg/240px-Norfolk_Skyline_from_Portsmouth.jpg","state":"Virginia","latitude":36.8508,"longitude":-76.2859},
{"city":"Miami","population":"449,514","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Miami_skyline_201807_cat.jpg/240px-Miami_skyline_201807_cat.jpg","state":"Florida","latitude":25.7617,"longitude":-80.1918},
{"city":"Boston","population":"675,647","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Boston_skyline_and_Boston_Harbor.jpg/240px-Boston_skyline_and_Boston_Harbor.jpg","state":"Massachusetts","latitude":42.3601,"longitude":-71.0589},
{"city":"Baltimore","population":"585,708","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Baltimore_Skyline.jpg/240px-Baltimore_Skyline.jpg","state":"Maryland","latitude":39.2904,"longitude":-76.6122},
{"city":"Charleston","population":"151,612","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Charleston_SC_Skyline.jpg/240px-Charleston_SC_Skyline.jpg","state":"South Carolina","latitude":32.7765,"longitude":-79.9311},
{"city":"Savannah","population":"147,780","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/40/Savannah_GA%2C_River_Street.jpg/240px-Savannah_GA%2C_River_Street.jpg","state":"Georgia","latitude":32.0809,"longitude":-81.0912},
{"city":"Tampa","population":"403,364","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Tampa_skyline_from_South%2C_2022.jpg/240px-Tampa_skyline_from_South%2C_2022.jpg","state":"Florida","latitude":27.9506,"longitude":-82.4572},
{"city":"Mobile","population":"187,041","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/7/70/Mobile_skyline_from_Mobile_River.jpg/240px-Mobile_skyline_from_Mobile_River.jpg","state":"Alabama","latitude":30.6954,"longitude":-88.0399},
{"city":"Anchorage","population":"288,121","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Anchorage_skyline_and_susitna.jpg/240px-Anchorage_skyline_and_susitna.jpg","state":"Alaska","latitude":61.2181,"longitude":-149.9003},
{"city":"Portland","population":"68,408","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/48/Portland_Maine_skyline.jpg/240px-Portland_Maine_skyline.jpg","state":"Maine","latitude":43.6591,"longitude":-70.2568},
{"city":"Honolulu","population":"349,547","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Honolulu_and_Diamond_Head.jpg/240px-Honolulu_and_Diamond_Head.jpg","state":"Hawaii","latitude":21.3069,"longitude":-157.8583},
{"city":"New Orleans","population":"376,971","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/f/fb/New_Orleans_skyline_sunset_Dec_28_2021_PANO_DSC07177-07179.jpg/240px-New_Orleans_skyline_sunset_Dec_28_2021_PANO_DSC07177-07179.jpg","state":"Louisiana","latitude":29.9511,"longitude":-90.0715},
{"city":"Jacksonville","population":"971,319","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg/240px-Skyline_of_Jacksonville_FL%2C_South_view_20160706_1.jpg","state":"Florida","latitude":30.3322,"longitude":-81.6557},
{"city":"Houston","population":"2,302,878","image":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/60/Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg/240px-Aerial_views_of_the_Houston%2C_Texas%2C_28005u.jpg","state":"Texas","latitude":29.7604,"longitude":-95.3698}
]

View File

@@ -0,0 +1,21 @@
import * as React from 'react';
const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3
c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9
C20.1,15.8,20.2,15.8,20.2,15.7z`;
const pinStyle = {
cursor: 'pointer',
fill: '#d00',
stroke: 'none'
};
function Pin({size = 20}) {
return (
<svg height={size} viewBox="0 0 24 24" style={pinStyle}>
<path d={ICON} />
</svg>
);
}
export default React.memo(Pin);

View File

@@ -2,6 +2,7 @@ import { observer } from 'mobx-react-lite';
import React, { useEffect, useState } from 'react';
import { Chakra } from '../components/contexts/ChakraContext';
import ComponentContext, { ComponentProvider } from '../components/contexts/ComponentContext.tsx';
import { MobileProvider } from '../components/contexts/MobileContext';
import { PageContextProvider } from '../renderer/usePageContext';
import userOptionsStore from '../stores/UserOptionsStore';
@@ -13,6 +14,7 @@ export { Layout };
const Layout = observer(({ pageContext, children }) => {
const [activeTheme, setActiveTheme] = useState<string>('darknight');
const [enabledComponent, setEnabledComponent] = useState('gpsmap');
useEffect(() => {
if (userOptionsStore.theme !== activeTheme) {
@@ -47,7 +49,9 @@ const Layout = observer(({ pageContext, children }) => {
<PageContextProvider pageContext={pageContext}>
<MobileProvider>
<Chakra theme={getTheme(activeTheme)}>
<ComponentProvider>
<LayoutComponent>{children}</LayoutComponent>
</ComponentProvider>
</Chakra>
</MobileProvider>
</PageContextProvider>

View File

@@ -42,6 +42,7 @@ const Navigation = observer(({ children, routeRegistry }) => {
return (
<Grid templateColumns="1fr" templateRows="auto 1fr">
{/*this is the menu button*/}
<GridItem
p={4}
position="fixed"

View File

@@ -1,8 +1,10 @@
import { Grid, GridItem, Stack } from '@chakra-ui/react';
import { Box, Grid, GridItem, Stack } from '@chakra-ui/react';
import React, { useEffect } from 'react';
import Chat from '../../components/chat/Chat.tsx';
import { useComponent } from '../../components/contexts/ComponentContext.tsx';
import { LandingComponent } from '../../components/landing-component/LandingComponent.tsx';
import ReactMap from '../../components/landing-component/Map.tsx';
import clientChatStore from '../../stores/ClientChatStore';
// renders "/"
@@ -16,13 +18,30 @@ export default function IndexPage() {
// Fall back to default model
}
}, []);
const component = useComponent();
return (
<Grid templateColumns="repeat(2, 1fr)" height="100%" width="100%" gap={0}>
<GridItem>
<LandingComponent />
</GridItem>
<GridItem p={2}>
<Box
display={component.enabledComponent === 'ai' ? undefined : 'none'}
width="100%"
height="100%"
overflowY="scroll"
>
<Chat />
</Box>
<Box
display={component.enabledComponent === 'gpsmap' ? undefined : 'none'}
width="100%"
height="100%"
>
<ReactMap visible={component.enabledComponent === 'gpsmap'} />
</Box>
</GridItem>
</Grid>
);

View File

@@ -20,10 +20,9 @@
{
"binding": "KV_STORAGE",
// $ npx wrangler kv namespace create open-gsio
// $ npx wrangler kv namespace create open-gsio
"id": "placeholderId",
"id": "",
// $ npx wrangler kv namespace create open-gsio --preview
"preview_id": "placeholderId"
"preview_id": ""
}
],
"migrations": [