13 Commits

Author SHA1 Message Date
geoffsee
7ab1141540 sweet lander 2025-07-14 08:55:11 -04:00
geoffsee
5630a95f1a chat + maps + ai + tools 2025-07-08 13:53:36 -04:00
geoffsee
24351b0be7 Merge branch 'sweet-lander' into smart-lander 2025-07-08 11:50:31 -04:00
geoffsee
cd58a23942 Refactor chat-stream-provider to simplify tool structure. Optimize WeatherTool implementation with enriched function schema. 2025-07-08 11:47:46 -04:00
geoffsee
fbd696612a Enable tool-based message generation in chat-stream-provider and add BasicValueTool and WeatherTool.
Updated dependencies to latest versions in `bun.lock`. Modified development script in `package.json` to include watch mode.
2025-07-04 08:56:11 -04:00
geoffsee
b737ff09b3 mirror error handling behavior in cloudflare worker 2025-07-02 20:57:34 -04:00
geoffsee
c436ae1b62 add top level error handler to the router 2025-07-02 20:55:53 -04:00
geoffsee
5f6cb3d6c7 Optimize WASM handling and integrate service worker caching.
Removed unused pointer events in BevyScene, updated Vite config with Workbox for service worker caching, and adjusted file paths in generate-bevy-bundle.js. Added WASM size optimization to ensure smaller and efficient builds, skipping optimization for files below 30MB.
2025-07-02 20:25:58 -04:00
geoffsee
195d071c3c Add visible prop to toggle components and simplify conditional rendering 2025-07-01 15:43:17 -04:00
geoffsee
a996f115bc Add "Install App" button to the toolbar using react-use-pwa-install library 2025-07-01 15:21:54 -04:00
geoffsee
3bbd4243c5 **Integrate PWA asset generator and update favicon and manifest configuration** 2025-07-01 15:02:15 -04:00
geoffsee
944b956ffd - Refactor BevyScene to replace script injection with dynamic import.
- Update `NavItem` to provide fallback route for invalid `path`.
- Temporarily stub metric API endpoints with placeholders.
2025-07-01 12:28:44 -04:00
geoffsee
c3ea9ba599 * Introduced BevyScene React component in landing-component for rendering a 3D cockpit visualization.
* Included WebAssembly asset `yachtpit.js` for cockpit functionality.
* Added Bevy MIT license file.
* Implemented a service worker to cache assets locally instead of fetching them remotely.
* Added collapsible functionality to **Tweakbox** and included the `@chakra-ui/icons` dependency.
* Applied the `hidden` prop to the Tweakbox Heading for better accessibility.
* Refactored **Particles** component for improved performance, clarity, and maintainability.

  * Introduced helper functions for particle creation and count management.
  * Added responsive resizing with particle repositioning.
  * Optimized animation updates, including velocity adjustments for speed changes.
  * Ensured canvas size and particle state are cleanly managed on component unmount.
2025-07-01 11:54:40 -04:00
21 changed files with 79 additions and 54 deletions

View File

@@ -46,6 +46,7 @@ describe('AssistantSdk', () => {
expect(prompt).toContain('# Assistant Knowledge');
expect(prompt).toContain('### Date: ');
expect(prompt).toContain('### Web Host: ');
expect(prompt).toContain('### User Location: ');
expect(prompt).toContain('### Timezone: ');
});

View File

@@ -23,7 +23,7 @@ export class AssistantSdk {
return `# Assistant Knowledge
## Assistant Name
### open-gsio
### yachtpit-ai
## Current Context
### Date: ${currentDate} ${currentTime}
${maxTokens ? `### Max Response Length: ${maxTokens} tokens (maximum)` : ''}

View File

@@ -50,7 +50,7 @@
"marked-katex-extension": "^5.1.4",
"mobx": "^6.13.5",
"mobx-react-lite": "^4.0.7",
"mobx-state-tree": "^7.0.2",
"mobx-state-tree": "^6.0.1",
"qrcode.react": "^4.1.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",

View File

@@ -1,4 +1,4 @@
import { execSync, execFileSync } from 'node:child_process';
import { execSync } from 'node:child_process';
import {
existsSync,
readdirSync,
@@ -175,7 +175,7 @@ function optimizeWasmSize() {
if (sizeInMb > 30) {
logger.info(`WASM size is ${sizeInMb.toFixed(2)}MB, optimizing...`);
execFileSync('wasm-opt', ['-Oz', '-o', wasmPath, wasmPath], {
execSync(`wasm-opt -Oz -o ${wasmPath} ${wasmPath}`, {
encoding: 'utf-8',
});
logger.info(`✅ WASM size optimized`);

View File

@@ -171,7 +171,7 @@ const InputMenu: React.FC<{ isDisabled?: boolean }> = observer(({ isDisabled })
bg="background.tertiary"
color="text.primary"
onClick={() => {
clientChatStore.reset();
clientChatStore.setActiveConversation('conversation:new');
onClose();
}}
_hover={{ bg: 'rgba(0, 0, 0, 0.05)' }}

View File

@@ -49,7 +49,7 @@ const InputTextArea: React.FC<InputTextAreaProps> = observer(
color="text.primary"
borderRadius="20px"
border="none"
placeholder="Free my mind..."
placeholder="To Gilligan's island!"
_placeholder={{
color: 'gray.400',
textWrap: 'nowrap',

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 `**open-gsio**: ${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' : 'open-gsio';
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('open-gsio')).toBeInTheDocument();
expect(screen.getByText('yachtpit-ai')).toBeInTheDocument();
expect(screen.getByTestId('message-content')).toHaveTextContent('Assistant response');
});

View File

@@ -3,13 +3,16 @@ import React, { useEffect, useState } from 'react';
import { useComponent } from '../contexts/ComponentContext.tsx';
// import { BevyScene } from './BevyScene.tsx';
import { BevyScene } from './BevyScene.tsx';
import Tweakbox from './Tweakbox.tsx';
export const LandingComponent: React.FC = () => {
const [speed, setSpeed] = useState(0.2);
const [intensity, setIntensity] = useState(0.99);
const [mapActive, setMapActive] = useState(false);
const [aiActive, setAiActive] = useState(true);
const [glow, setGlow] = useState(false);
const [bevyScene, setBevyScene] = useState(true);
const [mapActive, setMapActive] = useState(true);
const [aiActive, setAiActive] = useState(false);
const component = useComponent();
const { setEnabledComponent } = component;
@@ -24,8 +27,22 @@ export const LandingComponent: React.FC = () => {
}, []);
return (
<Box as="section" bg="background.primary" overflow="hidden">
<Box position="fixed" right={0} maxWidth="300px" minWidth="200px" zIndex={1000}>
<Box
as="section"
bg="background.primary"
w="100%"
h="100vh"
overflow="hidden"
position="relative"
>
<Box
position="fixed"
bottom="100x"
right="12px"
maxWidth="300px"
minWidth="200px"
zIndex={1000}
>
<Tweakbox
sliders={{
intensity: {
@@ -39,6 +56,13 @@ export const LandingComponent: React.FC = () => {
},
}}
switches={{
bevyScene: {
value: bevyScene,
onChange(enabled) {
setBevyScene(enabled);
},
label: 'Instruments',
},
GpsMap: {
value: mapActive,
onChange(enabled) {
@@ -50,7 +74,7 @@ export const LandingComponent: React.FC = () => {
}
setMapActive(enabled);
},
label: 'GPS',
label: 'Map',
},
AI: {
value: aiActive,
@@ -68,7 +92,7 @@ export const LandingComponent: React.FC = () => {
}}
/>
</Box>
{/*<BevyScene speed={speed} intensity={intensity} glow={glow} visible={bevyScene} />*/}
<BevyScene speed={speed} intensity={intensity} glow={glow} visible={bevyScene} />
</Box>
);
};

View File

@@ -37,7 +37,7 @@ 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 w="100%" h="100vh" position="relative" overflow="hidden">
{/* Button bar — absolutely positioned inside the wrapper */}
<MapNext mapboxPublicKey={atob(key)} />

View File

@@ -1,4 +1,4 @@
import { Box } from '@chakra-ui/react';
import { Box, Button, HStack, Input } from '@chakra-ui/react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import Map, {
FullscreenControl,
@@ -118,10 +118,11 @@ Type '{ city: string; population: string; image: string; state: string; latitude
right: 0,
}}
>
<GeolocateControl position="top-left" style={{ marginTop: '6rem' }} />
<GeolocateControl position="top-left" />
<FullscreenControl position="top-left" />
<NavigationControl position="top-left" />
<ScaleControl position="top-left" />
{pins}
{popupInfo && (

View File

@@ -6,7 +6,7 @@ import { useIsMobile } from '../components/contexts/MobileContext';
function Content({ children }) {
const isMobile = useIsMobile();
return (
<Flex flexDirection="column" w="100%" h="100vh">
<Flex flexDirection="column" w="100%" h="100vh" p={!isMobile ? 4 : 1}>
{children}
</Flex>
);

View File

@@ -1,6 +1,4 @@
// runs before anything else
import { registerSW } from 'virtual:pwa-register';
import UserOptionsStore from '../stores/UserOptionsStore';
UserOptionsStore.initialize();
@@ -8,11 +6,7 @@ UserOptionsStore.initialize();
try {
const isLocal = window.location.hostname.includes('localhost');
if (!isLocal) {
if ('serviceWorker' in navigator) {
// && !/localhost/.test(window.location)) {
registerSW();
}
// navigator.serviceWorker.register('/service-worker.js');
navigator.serviceWorker.register('/service-worker.js');
} else {
(async () => {
await navigator.serviceWorker.getRegistrations().then(registrations => {

View File

@@ -1,4 +1,4 @@
import { Box } 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';
@@ -22,26 +22,27 @@ export default function IndexPage() {
const component = useComponent();
return (
<Box height="100%" width="100%">
<LandingComponent />
<Box
display={component.enabledComponent === 'ai' ? undefined : 'none'}
width="100%"
height="100%"
overflowY="scroll"
padding={'unset'}
>
<Chat />
</Box>
<Box
display={component.enabledComponent === 'gpsmap' ? undefined : 'none'}
width="100%"
height="100%"
padding={'unset'}
>
<ReactMap visible={component.enabledComponent === 'gpsmap'} />
</Box>
</Box>
<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

@@ -1,5 +1,5 @@
export const welcome_home_text = `
# open-gsio
# yachtpit-ai
---
<br/>

View File

@@ -22,6 +22,10 @@ const prebuildPlugin = () => ({
console.log('Generated robots.txt -> public/robots.txt');
child_process.execSync('bun run generate:fonts');
console.log('Copied fonts -> public/static/fonts');
child_process.execSync('bun run generate:bevy:bundle', {
stdio: 'inherit',
});
console.log('Bundled bevy app -> public/yachtpit.html');
}
},
});

View File

@@ -10,7 +10,7 @@
"@open-gsio/services": "workspace:*",
"itty-router": "^5.0.18",
"mobx": "^6.13.5",
"mobx-state-tree": "^7.0.2",
"mobx-state-tree": "^6.0.1",
"vitest": "^3.1.4",
"vite": "^6.3.5"
}

View File

@@ -20,6 +20,6 @@
},
"devDependencies": {
"typescript": "^5.7.2",
"mobx-state-tree": "^7.0.2"
"mobx-state-tree": "^6.0.1"
}
}

View File

@@ -21,7 +21,7 @@
"itty-router": "^5.0.18",
"jsdom": "^24.0.0",
"mobx": "^6.13.5",
"mobx-state-tree": "^7.0.2",
"mobx-state-tree": "^6.0.1",
"moo": "^0.5.2",
"typescript": "^5.7.2",
"vike": "0.4.235",

View File

@@ -30,7 +30,7 @@
"itty-router": "^5.0.18",
"jsdom": "^24.0.0",
"mobx": "^6.13.5",
"mobx-state-tree": "^7.0.2",
"mobx-state-tree": "^6.0.1",
"moo": "^0.5.2",
"openai": "^5.0.1",
"typescript": "^5.7.2",