- Introduce MapStore to manage map state and controls using MobX-State-Tree.

- Integrate `MapStore` into `ClientChatStore`.
- Add support for handling map control tool responses in `StreamStore`.
- Update `InputMenu` with loading state while fetching models and UI improvements.
- Include `useLayoutEffect` in `LandingComponent` for persistent state management.
- Enhance `ChatService` with debug logs, model fallback handling, and better error reporting.
This commit is contained in:
geoffsee
2025-07-17 16:12:27 -04:00
parent bb5afa099a
commit 5b896d9d07
14 changed files with 517 additions and 39 deletions

View File

@@ -1,5 +1,5 @@
import { Box } from '@chakra-ui/react';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useLayoutEffect, useState } from 'react';
import { useComponent } from '../contexts/ComponentContext.tsx';
@@ -11,6 +11,23 @@ export const LandingComponent: React.FC = () => {
const [mapActive, setMapActive] = useState(true);
const [aiActive, setAiActive] = useState(false);
const appCtlState = `app-ctl-state`;
useLayoutEffect(() => {
const value = localStorage.getItem(appCtlState);
if (value) {
const parsed = JSON.parse(value);
setIntensity(parsed.intensity);
setMapActive(parsed.mapActive);
setAiActive(parsed.aiActive);
}
}, []);
// create a hook for saving the state as a json object when it changes
useEffect(() => {
localStorage.setItem(appCtlState, JSON.stringify({ intensity, mapActive, aiActive }));
});
const component = useComponent();
const { setEnabledComponent } = component;
@@ -21,12 +38,14 @@ export const LandingComponent: React.FC = () => {
if (aiActive) {
setEnabledComponent('ai');
}
}, []);
}, [mapActive, aiActive, setEnabledComponent]);
return (
<Box as="section" bg="background.primary" overflow="hidden">
<Box position="fixed" right={0} maxWidth="300px" minWidth="200px" zIndex={1000}>
<Tweakbox
id="app-tweaker"
persist={true}
sliders={{
intensity: {
value: intensity,
@@ -68,7 +87,6 @@ export const LandingComponent: React.FC = () => {
}}
/>
</Box>
{/*<BevyScene speed={speed} intensity={intensity} glow={glow} visible={bevyScene} />*/}
</Box>
);
};

View File

@@ -37,10 +37,10 @@ const key =
function Map(props: { visible: boolean }) {
return (
/* Full-screen wrapper — fills the viewport and becomes the positioning context */
<Box position={'absolute'} top={0} w="100vw" h={'100vh'} overflow="hidden">
<Box position={'absolute'} top={0} w="100%" h={'100vh'} overflow="hidden">
{/* Button bar — absolutely positioned inside the wrapper */}
<MapNext mapboxPublicKey={atob(key)} />
<MapNext mapboxPublicKey={atob(key)} visible={props.visible} />
{/*<Map*/}
{/* mapboxAccessToken={atob(key)}*/}
{/* initialViewState={mapView}*/}

View File

@@ -1,5 +1,6 @@
import { Box } from '@chakra-ui/react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Map, {
FullscreenControl,
GeolocateControl,
@@ -9,25 +10,36 @@ import Map, {
ScaleControl,
} from 'react-map-gl/mapbox';
import clientChatStore from '../../stores/ClientChatStore';
import PORTS from './nautical-base-data.json';
import Pin from './pin';
export default function MapNext(props: any = { mapboxPublicKey: '' } as any) {
function MapNextComponent(props: any = { mapboxPublicKey: '', visible: true } as any) {
const [popupInfo, setPopupInfo] = useState(null);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [isTokenLoading, setIsTokenLoading] = useState(false);
const [authenticated, setAuthenticated] = useState(false);
const mapRef = useRef<any>(null);
useEffect(() => {
setAuthenticated(true);
setIsTokenLoading(false);
}, []);
const [mapView, setMapView] = useState({
longitude: -122.4,
latitude: 37.8,
zoom: 14,
});
// Handle map resize when component becomes visible
useEffect(() => {
if (props.visible && mapRef.current) {
// Small delay to ensure the container is fully visible
const timer = setTimeout(() => {
if (mapRef.current) {
mapRef.current.resize();
}
}, 100);
return () => clearTimeout(timer);
}
}, [props.visible]);
const handleNavigationClick = useCallback(async () => {
console.log('handling navigation in map');
@@ -39,7 +51,10 @@ export default function MapNext(props: any = { mapboxPublicKey: '' } as any) {
const handleMapViewChange = useCallback(async (evt: any) => {
const { longitude, latitude, zoom } = evt.viewState;
setMapView({ longitude, latitude, zoom });
clientChatStore.setMapView(longitude, latitude, zoom);
// setMapView({ longitude, latitude, zoom });
// Update the store with the new view state
}, []);
const pins = useMemo(
@@ -98,13 +113,15 @@ Type '{ city: string; population: string; image: string; state: string; latitude
{/* </Button>*/}
{/*</HStack>*/}
<Map
ref={mapRef}
initialViewState={{
latitude: 40,
longitude: -100,
zoom: 3.5,
bearing: 0,
pitch: 0,
latitude: clientChatStore.mapState.latitude,
longitude: clientChatStore.mapState.longitude,
zoom: clientChatStore.mapState.zoom,
bearing: clientChatStore.mapState.bearing,
pitch: clientChatStore.mapState.pitch,
}}
onMove={handleMapViewChange}
mapStyle="mapbox://styles/geoffsee/cmd1qz39x01ga01qv5acea02y"
attributionControl={false}
mapboxAccessToken={props.mapboxPublicKey}
@@ -170,3 +187,6 @@ Type '{ city: string; population: string; image: string; state: string; latitude
</Box>
);
}
const MapNext = observer(MapNextComponent);
export default MapNext;

View File

@@ -14,7 +14,7 @@ import {
} from '@chakra-ui/react';
import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
import { observer } from 'mobx-react-lite';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
interface SliderControl {
value: number;
@@ -34,6 +34,8 @@ interface SwitchControl {
}
interface TweakboxProps {
id: string;
persist: boolean;
sliders: {
speed: SliderControl;
intensity: SliderControl;
@@ -44,7 +46,7 @@ interface TweakboxProps {
} & Record<string, SwitchControl>;
}
const Tweakbox = observer(({ sliders, switches }: TweakboxProps) => {
const Tweakbox = observer(({ id, persist, sliders, switches }: TweakboxProps) => {
const [isCollapsed, setIsCollapsed] = useState(false);
return (