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
28 changed files with 378 additions and 148 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "crates/yachtpit"]
path = crates/yachtpit
url = https://github.com/seemueller-io/yachtpit.git

1
crates/yachtpit Submodule

Submodule crates/yachtpit added at 348f20641c

View File

@@ -10,6 +10,7 @@
], ],
"scripts": { "scripts": {
"clean": "packages/scripts/cleanup.sh", "clean": "packages/scripts/cleanup.sh",
"restore:submodules": "rm -rf crates/yachtpit && (git rm --cached crates/yachtpit) && git submodule add --force https://github.com/seemueller-io/yachtpit.git crates/yachtpit ",
"test:all": "bun run --filter='*' tests", "test:all": "bun run --filter='*' tests",
"client:dev": "(cd packages/client && bun run dev)", "client:dev": "(cd packages/client && bun run dev)",
"server:dev": "bun build:client && (cd packages/server && bun run dev)", "server:dev": "bun build:client && (cd packages/server && bun run dev)",

View File

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

View File

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

View File

@@ -15,21 +15,10 @@ export class FireworksAiChatProvider extends BaseChatProvider {
let modelPrefix = 'accounts/fireworks/models/'; let modelPrefix = 'accounts/fireworks/models/';
if (param.model.toLowerCase().includes('yi-')) { if (param.model.toLowerCase().includes('yi-')) {
modelPrefix = 'accounts/yi-01-ai/models/'; modelPrefix = 'accounts/yi-01-ai/models/';
} else if (param.model.toLowerCase().includes('/perplexity/')) {
modelPrefix = 'accounts/perplexity/models/';
} else if (param.model.toLowerCase().includes('/sentientfoundation/')) {
modelPrefix = 'accounts/sentientfoundation/models/';
} else if (param.model.toLowerCase().includes('/sentientfoundation-serverless/')) {
modelPrefix = 'accounts/sentientfoundation-serverless/models/';
} else if (param.model.toLowerCase().includes('/instacart/')) {
modelPrefix = 'accounts/instacart/models/';
} }
const finalModelIdentifier = param.model.includes(modelPrefix)
? param.model
: `${modelPrefix}${param.model}`;
console.log('using fireworks model', finalModelIdentifier);
return { return {
model: finalModelIdentifier, model: `${modelPrefix}${param.model}`,
messages: safeMessages, messages: safeMessages,
stream: true, stream: true,
}; };

View File

@@ -9,6 +9,7 @@
"generate:sitemap": "bun ./scripts/generate_sitemap.js open-gsio.seemueller.workers.dev", "generate:sitemap": "bun ./scripts/generate_sitemap.js open-gsio.seemueller.workers.dev",
"generate:robotstxt": "bun ./scripts/generate_robots_txt.js open-gsio.seemueller.workers.dev", "generate:robotstxt": "bun ./scripts/generate_robots_txt.js open-gsio.seemueller.workers.dev",
"generate:fonts": "cp -r ../../node_modules/katex/dist/fonts public/static", "generate:fonts": "cp -r ../../node_modules/katex/dist/fonts public/static",
"generate:bevy:bundle": "bun scripts/generate-bevy-bundle.js",
"generate:pwa:assets": "test ! -f public/pwa-64x64.png && pwa-assets-generator --preset minimal-2023 public/logo.png || echo 'PWA assets already exist'" "generate:pwa:assets": "test ! -f public/pwa-64x64.png && pwa-assets-generator --preset minimal-2023 public/logo.png || echo 'PWA assets already exist'"
}, },
"exports": { "exports": {
@@ -19,7 +20,7 @@
}, },
"devDependencies": { "devDependencies": {
"@chakra-ui/icons": "^2.2.4", "@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^3.24.2", "@chakra-ui/react": "^2.10.6",
"@cloudflare/workers-types": "^4.20241205.0", "@cloudflare/workers-types": "^4.20241205.0",
"@emotion/react": "^11.13.5", "@emotion/react": "^11.13.5",
"@emotion/styled": "^11.13.5", "@emotion/styled": "^11.13.5",

View File

@@ -0,0 +1,196 @@
import { execSync } from 'node:child_process';
import {
existsSync,
readdirSync,
readFileSync,
writeFileSync,
renameSync,
rmSync,
cpSync,
statSync,
} from 'node:fs';
import { resolve, dirname, join, basename } from 'node:path';
import { Logger } from 'tslog';
const logger = new Logger({
stdio: 'inherit',
prettyLogTimeZone: 'local',
type: 'pretty',
stylePrettyLogs: true,
prefix: ['\n'],
overwrite: true,
});
function main() {
bundleCrate();
cleanup();
logger.info('🎉 yachtpit built successfully');
}
const getRepoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
const repoRoot = resolve(getRepoRoot);
const publicDir = resolve(repoRoot, 'packages/client/public');
const indexHtml = resolve(publicDir, 'index.html');
// Build the yachtpit project
const buildCwd = resolve(repoRoot, 'crates/yachtpit/crates/yachtpit');
logger.info(`🔨 Building in directory: ${buildCwd}`);
function needsRebuild() {
const optimizedWasm = join(buildCwd, 'dist', 'yachtpit_bg.wasm_optimized');
if (!existsSync(optimizedWasm)) return true;
}
const NEEDS_REBUILD = needsRebuild();
function bundleCrate() {
// ───────────── Build yachtpit project ───────────────────────────────────
logger.info('🔨 Building yachtpit...');
logger.info(`📁 Repository root: ${repoRoot}`);
// Check if submodules need to be initialized
const yachtpitPath = resolve(repoRoot, 'crates/yachtpit');
logger.info(`📁 Yachtpit path: ${yachtpitPath}`);
if (!existsSync(yachtpitPath)) {
logger.info('📦 Initializing submodules...');
execSync('git submodule update --init --remote', { stdio: 'inherit' });
} else {
logger.info(`✅ Submodules already initialized at: ${yachtpitPath}`);
}
try {
if (NEEDS_REBUILD) {
logger.info('🛠️ Changes detected — rebuilding yachtpit...');
execSync('trunk build --release', { cwd: buildCwd, stdio: 'inherit' });
logger.info('✅ Yachtpit built');
} else {
logger.info('⏩ No changes since last build — skipping yachtpit rebuild');
process.exit(0);
}
} catch (error) {
console.error('❌ Failed to build yachtpit:', error.message);
process.exit(1);
}
// ───────────── Copy assets to public directory ──────────────────────────
const yachtpitDistDir = join(buildCwd, 'dist');
logger.info(`📋 Copying assets to public directory...`);
// Remove existing yachtpit assets from public directory
const skipRemoveOldAssets = false;
if (!skipRemoveOldAssets) {
const existingAssets = readdirSync(publicDir).filter(
file => file.startsWith('yachtpit') && (file.endsWith('.js') || file.endsWith('.wasm')),
);
existingAssets.forEach(asset => {
const assetPath = join(publicDir, asset);
rmSync(assetPath, { force: true });
logger.info(`🗑️ Removed old asset: ${assetPath}`);
});
} else {
logger.warn('SKIPPING REMOVING OLD ASSETS');
}
// Copy new assets from yachtpit/dist to public directory
if (existsSync(yachtpitDistDir)) {
logger.info(`📍Located yachtpit build: ${yachtpitDistDir}`);
try {
cpSync(yachtpitDistDir, publicDir, {
recursive: true,
force: true,
});
logger.info(`✅ Assets copied from ${yachtpitDistDir} to ${publicDir}`);
} catch (error) {
console.error('❌ Failed to copy assets:', error.message);
process.exit(1);
}
} else {
console.error(`❌ Yachtpit dist directory not found at: ${yachtpitDistDir}`);
process.exit(1);
}
// ───────────── locate targets ───────────────────────────────────────────
const dstPath = join(publicDir, 'yachtpit.html');
// Regexes for the hashed filenames produced by most bundlers
const JS_RE = /^yachtpit-[\da-f]{16}\.js$/i;
const WASM_RE = /^yachtpit-[\da-f]{16}_bg\.wasm$/i;
// Always perform renaming of bundle files
const files = readdirSync(publicDir);
// helper that doesn't explode if the target file is already present
const safeRename = (from, to) => {
if (!existsSync(from)) return;
if (existsSync(to)) {
logger.info(` ${to} already exists removing and replacing.`);
rmSync(to, { force: true });
}
renameSync(from, to);
logger.info(`📝 Renamed: ${basename(from)}${basename(to)}`);
};
files.forEach(f => {
const fullPath = join(publicDir, f);
if (JS_RE.test(f)) safeRename(fullPath, join(publicDir, 'yachtpit.js'));
if (WASM_RE.test(f)) safeRename(fullPath, join(publicDir, 'yachtpit_bg.wasm'));
});
// ───────────── patch markup inside HTML ─────────────────────────────────
if (existsSync(indexHtml)) {
logger.info(`📝 Patching HTML file: ${indexHtml}`);
let html = readFileSync(indexHtml, 'utf8');
html = html
.replace(/yachtpit-[\da-f]{16}\.js/gi, 'yachtpit.js')
.replace(/yachtpit-[\da-f]{16}_bg\.wasm/gi, 'yachtpit_bg.wasm');
writeFileSync(indexHtml, html, 'utf8');
// ───────────── rename HTML entrypoint ─────────────────────────────────
if (basename(indexHtml) !== 'yachtpit.html') {
logger.info(`📝 Renaming HTML file: ${indexHtml}${dstPath}`);
// Remove existing yachtpit.html if it exists
if (existsSync(dstPath)) {
rmSync(dstPath, { force: true });
}
renameSync(indexHtml, dstPath);
}
} else {
logger.info(`⚠️ ${indexHtml} not found skipping HTML processing.`);
}
optimizeWasmSize();
}
function optimizeWasmSize() {
logger.info('🔨 Checking WASM size...');
const wasmPath = resolve(publicDir, 'yachtpit_bg.wasm');
const fileSize = statSync(wasmPath).size;
const sizeInMb = fileSize / (1024 * 1024);
if (sizeInMb > 30) {
logger.info(`WASM size is ${sizeInMb.toFixed(2)}MB, optimizing...`);
execSync(`wasm-opt -Oz -o ${wasmPath} ${wasmPath}`, {
encoding: 'utf-8',
});
logger.info(`✅ WASM size optimized`);
} else {
logger.info(
`⏩ Skipping WASM optimization, size (${sizeInMb.toFixed(2)}MB) is under 30MB threshold`,
);
}
}
function cleanup() {
logger.info('Running cleanup...');
rmSync(indexHtml, { force: true });
const creditsDir = resolve(`${repoRoot}/packages/client/public`, 'credits');
rmSync(creditsDir, { force: true, recursive: true });
}
main();

View File

@@ -0,0 +1,35 @@
import { IconButton } from '@chakra-ui/react';
import { HardDriveDownload } from 'lucide-react';
import React from 'react';
import { toolbarButtonZIndex } from './toolbar/Toolbar.tsx';
function InstallButton() {
// const install = usePWAInstall();
const install = () => {
console.warn('this does not work in all browsers');
};
return (
<IconButton
aria-label="Install App"
title="Install App"
icon={<HardDriveDownload />}
size="md"
bg="transparent"
stroke="text.accent"
color="text.accent"
onClick={() => install}
_hover={{
bg: 'transparent',
svg: {
stroke: 'accent.secondary',
transition: 'stroke 0.3s ease-in-out',
},
}}
zIndex={toolbarButtonZIndex}
/>
);
}
export default InstallButton;

View File

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

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ const MessageBubble = observer(({ msg, scrollRef }) => {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const isUser = msg.role === 'user'; 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 isLoading = !msg.content || !(msg.content.trim().length > 0);
const messageRef = useRef(); const messageRef = useRef();

View File

@@ -104,7 +104,7 @@ describe('MessageBubble', () => {
it('should render assistant message correctly', () => { it('should render assistant message correctly', () => {
render(<MessageBubble msg={mockAssistantMessage} scrollRef={mockScrollRef} />); 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'); expect(screen.getByTestId('message-content')).toHaveTextContent('Assistant response');
}); });

View File

@@ -1,7 +0,0 @@
import React, { useEffect, useState } from 'react';
function InstallButton() {
return <button onClick={handleInstall}>Install App</button>;
}
export default InstallButton;

View File

@@ -1,61 +0,0 @@
import { IconButton } from '@chakra-ui/react';
import { HardDriveDownload } from 'lucide-react';
import React, { useEffect, useState } from 'react';
import { toolbarButtonZIndex } from '../toolbar/Toolbar.tsx';
function InstallButton() {
const [deferredPrompt, setDeferredPrompt] = useState(null);
const [isInstalled, setIsInstalled] = useState(false);
useEffect(() => {
const handleBeforeInstallPrompt = e => {
// Prevent the default prompt
e.preventDefault();
setDeferredPrompt(e);
};
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
};
}, []);
const handleInstall = () => {
if (deferredPrompt) {
deferredPrompt.prompt();
deferredPrompt.userChoice.then(choiceResult => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the installation prompt');
} else {
console.log('User dismissed the installation prompt');
}
});
setDeferredPrompt(null);
}
};
return (
<IconButton
aria-label="Install App"
title="Install App"
icon={<HardDriveDownload />}
size="md"
bg="transparent"
stroke="text.accent"
color="text.accent"
onClick={handleInstall}
_hover={{
bg: 'transparent',
svg: {
stroke: 'accent.secondary',
transition: 'stroke 0.3s ease-in-out',
},
}}
zIndex={toolbarButtonZIndex}
/>
);
}
export default InstallButton;

View File

@@ -0,0 +1,58 @@
import { Box, useBreakpointValue } from '@chakra-ui/react';
import React, { memo, useEffect, useMemo } from 'react';
export interface BevySceneProps {
speed?: number;
intensity?: number; // 0-1 when visible
glow?: boolean;
visible?: boolean; // NEW — defaults to true
}
const BevySceneInner: React.FC<BevySceneProps> = ({
speed = 1,
intensity = 1,
glow = false,
visible,
}) => {
const maxWidth = useBreakpointValue({ base: 640, md: 720 }, { ssr: true });
/* initialise once */
useEffect(() => {
let dispose: (() => void) | void;
(async () => {
const { default: init } = await import(/* webpackIgnore: true */ '/public/yachtpit.js');
dispose = await init(); // zero-arg, uses #yachtpit-canvas
})();
return () => {
if (typeof dispose === 'function') dispose();
};
}, []);
/* memoised styles */
const wrapperStyles = useMemo(
() => ({
position: 'absolute' as const,
inset: 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`,
display: visible ? 'block' : 'none', // optional: reclaim hit-testing entirely
}),
[visible, intensity, glow, speed],
);
return (
<Box as="div" sx={wrapperStyles}>
<canvas
id="yachtpit-canvas"
width={useBreakpointValue({ base: 640, md: 1280 }, { ssr: true })}
height={useBreakpointValue({ base: 360, md: 720 }, { ssr: true })}
aria-hidden
/>
</Box>
);
};
export const BevyScene = memo(BevySceneInner);

View File

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

View File

@@ -37,7 +37,7 @@ const key =
function Map(props: { visible: boolean }) { function Map(props: { visible: boolean }) {
return ( return (
/* Full-screen wrapper — fills the viewport and becomes the positioning context */ /* 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 */} {/* Button bar — absolutely positioned inside the wrapper */}
<MapNext mapboxPublicKey={atob(key)} /> <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 { useCallback, useEffect, useMemo, useState } from 'react';
import Map, { import Map, {
FullscreenControl, FullscreenControl,
@@ -118,10 +118,11 @@ Type '{ city: string; population: string; image: string; state: string; latitude
right: 0, right: 0,
}} }}
> >
<GeolocateControl position="top-left" style={{ marginTop: '6rem' }} /> <GeolocateControl position="top-left" />
<FullscreenControl position="top-left" /> <FullscreenControl position="top-left" />
<NavigationControl position="top-left" /> <NavigationControl position="top-left" />
<ScaleControl position="top-left" /> <ScaleControl position="top-left" />
{pins} {pins}
{popupInfo && ( {popupInfo && (

View File

@@ -2,7 +2,7 @@ import { Flex } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import BuiltWithButton from '../BuiltWithButton'; import BuiltWithButton from '../BuiltWithButton';
import InstallButton from '../install/InstallButton.tsx'; import InstallButton from '../InstallButton.tsx';
import GithubButton from './GithubButton'; import GithubButton from './GithubButton';
import SupportThisSiteButton from './SupportThisSiteButton'; import SupportThisSiteButton from './SupportThisSiteButton';

View File

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

View File

@@ -1,6 +1,4 @@
// runs before anything else // runs before anything else
import { registerSW } from 'virtual:pwa-register';
import UserOptionsStore from '../stores/UserOptionsStore'; import UserOptionsStore from '../stores/UserOptionsStore';
UserOptionsStore.initialize(); UserOptionsStore.initialize();
@@ -8,11 +6,7 @@ UserOptionsStore.initialize();
try { try {
const isLocal = window.location.hostname.includes('localhost'); const isLocal = window.location.hostname.includes('localhost');
if (!isLocal) { if (!isLocal) {
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js');
// && !/localhost/.test(window.location)) {
registerSW();
}
// navigator.serviceWorker.register('/service-worker.js');
} else { } else {
(async () => { (async () => {
await navigator.serviceWorker.getRegistrations().then(registrations => { 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 React, { useEffect } from 'react';
import Chat from '../../components/chat/Chat.tsx'; import Chat from '../../components/chat/Chat.tsx';
@@ -22,26 +22,27 @@ export default function IndexPage() {
const component = useComponent(); const component = useComponent();
return ( return (
<Box height="100%" width="100%"> <Grid templateColumns="repeat(2, 1fr)" height="100%" width="100%" gap={0}>
<LandingComponent /> <GridItem>
<LandingComponent />
<Box </GridItem>
display={component.enabledComponent === 'ai' ? undefined : 'none'} <GridItem p={2}>
width="100%" <Box
height="100%" display={component.enabledComponent === 'ai' ? undefined : 'none'}
overflowY="scroll" width="100%"
padding={'unset'} height="100%"
> overflowY="scroll"
<Chat /> >
</Box> <Chat />
<Box </Box>
display={component.enabledComponent === 'gpsmap' ? undefined : 'none'} <Box
width="100%" display={component.enabledComponent === 'gpsmap' ? undefined : 'none'}
height="100%" width="100%"
padding={'unset'} height="100%"
> >
<ReactMap visible={component.enabledComponent === 'gpsmap'} /> <ReactMap visible={component.enabledComponent === 'gpsmap'} />
</Box> </Box>
</Box> </GridItem>
</Grid>
); );
} }

View File

@@ -1,5 +1,5 @@
export default { export default {
'/': { sidebarLabel: 'Home', heroLabel: 'o-gsio' }, '/': { sidebarLabel: 'Home', heroLabel: 'gsio' },
'/connect': { sidebarLabel: 'Connect', heroLabel: 'connect' }, '/connect': { sidebarLabel: 'Connect', heroLabel: 'connect' },
'/privacy-policy': { '/privacy-policy': {
sidebarLabel: '', sidebarLabel: '',

View File

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

View File

@@ -22,6 +22,10 @@ const prebuildPlugin = () => ({
console.log('Generated robots.txt -> public/robots.txt'); console.log('Generated robots.txt -> public/robots.txt');
child_process.execSync('bun run generate:fonts'); child_process.execSync('bun run generate:fonts');
console.log('Copied fonts -> public/static/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

@@ -161,29 +161,19 @@ const ChatService = types
const openai = new OpenAI({ apiKey: provider.key, baseURL: provider.endpoint }); const openai = new OpenAI({ apiKey: provider.key, baseURL: provider.endpoint });
const basicFilters = (model: any) => { // 2a. List models
return (
!model.id.includes('whisper') &&
!model.id.includes('flux') &&
!model.id.includes('ocr') &&
!model.id.includes('tts') &&
!model.id.includes('guard')
);
}; // 2a. List models
try { try {
const listResp: any = yield openai.models.list(); // < async const listResp: any = yield openai.models.list(); // < async
const models = 'data' in listResp ? listResp.data : listResp; const models = 'data' in listResp ? listResp.data : listResp;
providerModels.set( providerModels.set(
provider.name, provider.name,
models.filter((mdl: any) => { models.filter(
if ('supports_chat' in mdl && mdl.supports_chat) { (mdl: any) =>
return basicFilters(mdl); !mdl.id.includes('whisper') &&
} else if ('supports_chat' in mdl && !mdl.supports_chat) { !mdl.id.includes('tts') &&
return false; !mdl.id.includes('guard'),
} ),
return basicFilters(mdl);
}),
); );
// 2b. Retrieve metadata // 2b. Retrieve metadata
@@ -328,8 +318,7 @@ const ChatService = types
); );
} }
if (message.includes('404')) { if (message.includes('404')) {
console.log(message); throw new ClientError(`Something went wrong, try again.`, 413, {});
throw new ClientError(`Something went wrong, try again.`, 404, {});
} }
throw error; throw error;
} }