mirror of
https://github.com/geoffsee/open-gsio.git
synced 2025-09-08 22:56:46 +00:00
Compare commits
7 Commits
dependabot
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
03c83b0a2e | ||
![]() |
ae6a6e4064 | ||
![]() |
67483d08db | ||
![]() |
53268b528d | ||
![]() |
f9d5fc8282 | ||
![]() |
ce9bc4db07 | ||
![]() |
bd71bfcad3 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "crates/yachtpit"]
|
||||
path = crates/yachtpit
|
||||
url = https://github.com/seemueller-io/yachtpit.git
|
@@ -1,5 +1,5 @@
|
||||
# open-gsio
|
||||
|
||||
> Rewrite in-progress.
|
||||
[](https://github.com/geoffsee/open-gsio/actions/workflows/test.yml)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
</br>
|
||||
|
Submodule crates/yachtpit deleted from 348f20641c
@@ -10,7 +10,6 @@
|
||||
],
|
||||
"scripts": {
|
||||
"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",
|
||||
"client:dev": "(cd packages/client && bun run dev)",
|
||||
"server:dev": "bun build:client && (cd packages/server && bun run dev)",
|
||||
|
@@ -15,10 +15,21 @@ export class FireworksAiChatProvider extends BaseChatProvider {
|
||||
let modelPrefix = 'accounts/fireworks/models/';
|
||||
if (param.model.toLowerCase().includes('yi-')) {
|
||||
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 {
|
||||
model: `${modelPrefix}${param.model}`,
|
||||
model: finalModelIdentifier,
|
||||
messages: safeMessages,
|
||||
stream: true,
|
||||
};
|
||||
|
@@ -9,7 +9,6 @@
|
||||
"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: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'"
|
||||
},
|
||||
"exports": {
|
||||
@@ -50,7 +49,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",
|
||||
|
@@ -1,196 +0,0 @@
|
||||
import { execSync, execFileSync } 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...`);
|
||||
execFileSync('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();
|
@@ -1,35 +0,0 @@
|
||||
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;
|
7
packages/client/src/components/install/Install.tsx
Normal file
7
packages/client/src/components/install/Install.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
function InstallButton() {
|
||||
return <button onClick={handleInstall}>Install App</button>;
|
||||
}
|
||||
|
||||
export default InstallButton;
|
61
packages/client/src/components/install/InstallButton.tsx
Normal file
61
packages/client/src/components/install/InstallButton.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
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;
|
@@ -1,58 +0,0 @@
|
||||
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);
|
@@ -8,8 +8,8 @@ import Tweakbox from './Tweakbox.tsx';
|
||||
|
||||
export const LandingComponent: React.FC = () => {
|
||||
const [intensity, setIntensity] = useState(0.99);
|
||||
const [mapActive, setMapActive] = useState(false);
|
||||
const [aiActive, setAiActive] = useState(true);
|
||||
const [mapActive, setMapActive] = useState(true);
|
||||
const [aiActive, setAiActive] = useState(false);
|
||||
|
||||
const component = useComponent();
|
||||
const { setEnabledComponent } = component;
|
||||
|
@@ -2,7 +2,7 @@ import { Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import BuiltWithButton from '../BuiltWithButton';
|
||||
import InstallButton from '../InstallButton.tsx';
|
||||
import InstallButton from '../install/InstallButton.tsx';
|
||||
|
||||
import GithubButton from './GithubButton';
|
||||
import SupportThisSiteButton from './SupportThisSiteButton';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
'/': { sidebarLabel: 'Home', heroLabel: 'gsio' },
|
||||
'/': { sidebarLabel: 'Home', heroLabel: 'o-gsio' },
|
||||
'/connect': { sidebarLabel: 'Connect', heroLabel: 'connect' },
|
||||
'/privacy-policy': {
|
||||
sidebarLabel: '',
|
||||
|
@@ -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"
|
||||
}
|
||||
|
@@ -20,6 +20,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.2",
|
||||
"mobx-state-tree": "^7.0.2"
|
||||
"mobx-state-tree": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
@@ -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",
|
||||
|
@@ -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",
|
||||
|
@@ -161,19 +161,29 @@ const ChatService = types
|
||||
|
||||
const openai = new OpenAI({ apiKey: provider.key, baseURL: provider.endpoint });
|
||||
|
||||
// 2‑a. List models
|
||||
const basicFilters = (model: any) => {
|
||||
return (
|
||||
!model.id.includes('whisper') &&
|
||||
!model.id.includes('flux') &&
|
||||
!model.id.includes('ocr') &&
|
||||
!model.id.includes('tts') &&
|
||||
!model.id.includes('guard')
|
||||
);
|
||||
}; // 2‑a. List models
|
||||
try {
|
||||
const listResp: any = yield openai.models.list(); // <‑‑ async
|
||||
const models = 'data' in listResp ? listResp.data : listResp;
|
||||
|
||||
providerModels.set(
|
||||
provider.name,
|
||||
models.filter(
|
||||
(mdl: any) =>
|
||||
!mdl.id.includes('whisper') &&
|
||||
!mdl.id.includes('tts') &&
|
||||
!mdl.id.includes('guard'),
|
||||
),
|
||||
models.filter((mdl: any) => {
|
||||
if ('supports_chat' in mdl && mdl.supports_chat) {
|
||||
return basicFilters(mdl);
|
||||
} else if ('supports_chat' in mdl && !mdl.supports_chat) {
|
||||
return false;
|
||||
}
|
||||
return basicFilters(mdl);
|
||||
}),
|
||||
);
|
||||
|
||||
// 2‑b. Retrieve metadata
|
||||
@@ -318,7 +328,8 @@ const ChatService = types
|
||||
);
|
||||
}
|
||||
if (message.includes('404')) {
|
||||
throw new ClientError(`Something went wrong, try again.`, 413, {});
|
||||
console.log(message);
|
||||
throw new ClientError(`Something went wrong, try again.`, 404, {});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
Reference in New Issue
Block a user