Skip to content

Commit

Permalink
App is loading, connecting to goose server.
Browse files Browse the repository at this point in the history
  • Loading branch information
Acumen-Desktop committed Feb 9, 2025
1 parent 5a42c53 commit 3e9bd41
Show file tree
Hide file tree
Showing 21 changed files with 770 additions and 176 deletions.
2 changes: 1 addition & 1 deletion acumen/features/svelte-electron-ui/plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

This plan outlines the strategy for creating a Svelte-based UI for Goose while maintaining the ability to pull updates from upstream. The goal is to create a parallel UI implementation that doesn't interfere with the original React-based implementation.
This plan outlines the strategy for creating a Svelte-based UI for 'Goose AI', while maintaining the ability to pull updates from upstream. The goal is to create a parallel UI implementation that doesn't interfere with the original React-based implementation.

## Key Principles

Expand Down
86 changes: 86 additions & 0 deletions acumen/features/svelte-electron-ui/plan2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Svelte Electron UI Migration Plan

## Overview

This plan outlines the strategy for creating a Svelte-based UI for 'Goose AI', while maintaining the ability to pull updates from upstream. The goal is to create a parallel UI implementation that doesn't interfere with the original React-based implementation.

## Key Principles

1. **Zero Upstream Modifications**: No changes to upstream files to maintain ability to pull updates
2. **Clean Architecture**: Keep frontend (Svelte) and backend (Goose) separate, unlike the React implementation
3. **API-First**: Focus on proper API communication between UI and Goose instances

## Progress & Learnings

#### Phase 1: Setup & Infrastructure ✓

1. Initialize SvelteKit 2 project with Electron, TailwindCSS, TypeScript ✓
2. Setup 'types' organization. Documented in 'acumen/types_instructions.md'. ✓
3. Basic Electron integration with preload scripts ✓
4. Goosed binary integration and startup ✓

#### Phase 2: Core Architecture (In Progress)

1. Asset handling and build configuration

- Vite + SvelteKit static adapter setup ✓
- Asset path resolution in Electron main process ✓
- Proper build output directory structure ✓

2. IPC Communication
- Preload script configuration ✓
- Window API exposure ✓
- Config passing between processes ✓

#### Next Steps

1. **Asset Loading**

- [ ] Fix asset loading in development mode
- [ ] Ensure proper asset paths in production build
- [ ] Test static file serving through Electron protocol

2. **UI Implementation**

- [ ] Port core UI components from React
- [ ] Implement chat interface
- [ ] Add theme support
- [ ] Implement window controls

3. **State Management**

- [ ] Define state management strategy
- [ ] Implement chat history
- [ ] Handle configuration persistence

4. **Testing & Packaging**
- [ ] Add unit tests for core functionality
- [ ] Set up E2E testing
- [ ] Configure electron-forge for production builds

## Technical Decisions

1. **Build System**

- Using Vite for both main and renderer processes
- SvelteKit with static adapter for SPA mode
- Electron-forge for application packaging

2. **Development Mode**

- Vite dev server for renderer
- Watch mode for main/preload scripts
- Hot module replacement enabled

3. **Production Build**

- Static HTML/JS/CSS output
- CommonJS for main/preload
- ESM for renderer
- Assets served through custom Electron protocol

4. **Type System**
- Strict TypeScript configuration
- Shared types between processes
- No type duplication
- Component-specific types with components
49 changes: 46 additions & 3 deletions acumen/types_instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- Shared types go in appropriate `.d.ts` files
- Use `import type` for all type imports
- Never use `any` - use `unknown` if type is truly unknown
- Prefer interfaces over type aliases for better extensibility
- Use strict TypeScript configuration

## Critical Type Locations

Expand All @@ -17,12 +19,18 @@ ui-svelte/
│ ├── electron.d.ts # IPC & Window API
│ ├── ipc.d.ts # IPC Events
│ ├── extensions.d.ts # Extension System
│ └── config.d.ts # App Configuration
│ ├── config.d.ts # App Configuration
│ └── index.d.ts # Type Exports
├── src-renderer/ # Renderer Process Types
├── app.d.ts # Global & Window
├── types/api.d.ts # API/Service Types
├── types/ # Shared Types
│ ├── api.d.ts # API/Service Types
│ ├── chat.d.ts # Chat/Message Types
│ ├── theme.d.ts # Theme/Styling Types
│ └── index.d.ts # Type Exports
└── lib/types/ # Component Types
└── components.d.ts # Shared Component Types
```

## Type Requirements
Expand All @@ -34,25 +42,32 @@ ui-svelte/
- Event handlers MUST use typed event names
- All IPC methods MUST be in `electron.d.ts`
- All IPC events MUST be in `ipc.d.ts`
- Preload script types MUST be in `electron.d.ts`

2. **Component Types**

- Props MUST use `interface ComponentProps`
- Events MUST use `svelte/elements` types
- Custom events MUST use `createEventDispatcher`
- Component types MUST be in `lib/types/components.d.ts`
- Use Svelte 5 runes type syntax where applicable
- Props validation through TypeScript, not runtime

3. **API/Service Types**

- All API responses MUST be typed
- Message types MUST be consistent between processes
- Tool invocations MUST use strict types
- API types MUST be in `types/api.d.ts`
- Use zod for runtime validation when needed
- Include proper error types and handling

4. **Window/Electron Types**
- Window extensions MUST be in `app.d.ts`
- Electron IPC methods MUST be in `electron.d.ts`
- Process-specific types MUST be separated
- Config types MUST be in `config.d.ts`
- Environment types MUST be in `config.d.ts`

## Quick Reference

Expand All @@ -64,16 +79,44 @@ import type { ElectronAPI } from "../src-main/types";

// From renderer
import type { Message } from "../types/api";
import type { ComponentProps } from "$lib/types/components";

// Global types (no import needed)
// Available after declaring in app.d.ts
```

### Type Categories

- **electron.d.ts**: IPC methods, window extensions
- **electron.d.ts**: IPC methods, window extensions, preload
- **ipc.d.ts**: Event names, payloads, handlers
- **extensions.d.ts**: Extension configs, runtime, tools
- **config.d.ts**: App settings, environment
- **app.d.ts**: Window interface, global exports
- **api.d.ts**: API interfaces, messages, tools
- **chat.d.ts**: Chat, message, and conversation types
- **theme.d.ts**: Theme configuration and styling types

### Best Practices

1. **Type Exports**

- Use barrel exports in index.d.ts files
- Export only what's needed
- Keep internal types internal

2. **Type Safety**

- Enable strict TypeScript checks
- Use satisfies operator for type checking
- Avoid type assertions unless necessary

3. **Documentation**

- Document complex types with JSDoc
- Include examples for non-obvious usage
- Reference related types and files

4. **Versioning**
- Keep types in sync with implementations
- Document breaking type changes
- Use semantic versioning for type changes
88 changes: 88 additions & 0 deletions ui-svelte/_originals/React_UI/preload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import Electron, { contextBridge, ipcRenderer } from "electron";

const config = JSON.parse(
process.argv.find((arg) => arg.startsWith("{")) || "{}"
);

// Define the API types in a single place
type ElectronAPI = {
getConfig: () => Record<string, any>;
hideWindow: () => void;
directoryChooser: (replace: string) => void;
createChatWindow: (query?: string, dir?: string, version?: string) => void;
logInfo: (txt: string) => void;
showNotification: (data: any) => void;
createWingToWingWindow: (query: string) => void;
openInChrome: (url: string) => void;
fetchMetadata: (url: string) => Promise<any>;
reloadApp: () => void;
checkForOllama: () => Promise<boolean>;
selectFileOrDirectory: () => Promise<string>;
startPowerSaveBlocker: () => Promise<number>;
stopPowerSaveBlocker: () => Promise<void>;
getBinaryPath: (binaryName: string) => Promise<string>;
on: (
channel: string,
callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) => void;
off: (
channel: string,
callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) => void;
};

type AppConfigAPI = {
get: (key: string) => any;
getAll: () => Record<string, any>;
};

const electronAPI: ElectronAPI = {
getConfig: () => config,
hideWindow: () => ipcRenderer.send("hide-window"),
directoryChooser: (replace: string) =>
ipcRenderer.send("directory-chooser", replace),
createChatWindow: (query?: string, dir?: string, version?: string) =>
ipcRenderer.send("create-chat-window", query, dir, version),
logInfo: (txt: string) => ipcRenderer.send("logInfo", txt),
showNotification: (data: any) => ipcRenderer.send("notify", data),
createWingToWingWindow: (query: string) =>
ipcRenderer.send("create-wing-to-wing-window", query),
openInChrome: (url: string) => ipcRenderer.send("open-in-chrome", url),
fetchMetadata: (url: string) => ipcRenderer.invoke("fetch-metadata", url),
reloadApp: () => ipcRenderer.send("reload-app"),
checkForOllama: () => ipcRenderer.invoke("check-ollama"),
selectFileOrDirectory: () => ipcRenderer.invoke("select-file-or-directory"),
startPowerSaveBlocker: () => ipcRenderer.invoke("start-power-save-blocker"),
stopPowerSaveBlocker: () => ipcRenderer.invoke("stop-power-save-blocker"),
getBinaryPath: (binaryName: string) =>
ipcRenderer.invoke("get-binary-path", binaryName),
on: (
channel: string,
callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) => {
ipcRenderer.on(channel, callback);
},
off: (
channel: string,
callback: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) => {
ipcRenderer.off(channel, callback);
},
};

const appConfigAPI: AppConfigAPI = {
get: (key: string) => config[key],
getAll: () => config,
};

// Expose the APIs
contextBridge.exposeInMainWorld("electron", electronAPI);
contextBridge.exposeInMainWorld("appConfig", appConfigAPI);

// Type declaration for TypeScript
declare global {
interface Window {
electron: ElectronAPI;
appConfig: AppConfigAPI;
}
}
15 changes: 15 additions & 0 deletions ui-svelte/_originals/preload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
import { contextBridge, ipcRenderer } from "electron";

export type ExposeInRendererTypes = typeof exposeInRenderer;
const exposeInRenderer = {
toggleDevTools: () => ipcRenderer.send("toggleDevTools"),
setTitleBarColors: (bgColor: string, iconColor: string) => {
document.documentElement.style.background = bgColor;
ipcRenderer.send("setTitleBarColors", bgColor, iconColor);
},
};

for (const [key, value] of Object.entries(exposeInRenderer))
contextBridge.exposeInMainWorld(key, value);
45 changes: 45 additions & 0 deletions ui-svelte/_originals/svelte.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import adapter from "@sveltejs/adapter-static";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";

/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors
preprocess: vitePreprocess(),

kit: {
csp: {
directives: {
"script-src": ["self"],
},
},

paths: {
relative: false,
},

files: {
assets: "static",
lib: "src-renderer/lib",
params: "src-renderer/params",
routes: "src-renderer/routes",
serviceWorker: "src-renderer/service-worker",
appTemplate: "src-renderer/app.html",
errorTemplate: "src-renderer/error.html",
},

// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
// SPA (Single-page application)
// https://kit.svelte.dev/docs/single-page-apps
fallback: "200.html",

pages: ".vite/main_window",
assets: ".vite/main_window",
}),
},
};

export default config;
27 changes: 27 additions & 0 deletions ui-svelte/_originals/vite.main.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { defineConfig } from "vite";
import { builtinModules } from "node:module";

export default defineConfig({
build: {
outDir: ".vite/main",
minify: "esbuild",
lib: {
entry: "./src-main/main.ts",
fileName: () => "[name].js",
formats: ["es"],
},

rollupOptions: {
external: [
"electron",
...builtinModules.map((m) => [m, `node:${m}`]).flat(),
],
},
},
plugins: [],
resolve: {
// Load the Node.js entry.
mainFields: ["module", "jsnext:main", "jsnext"],
},
clearScreen: false,
});
Loading

0 comments on commit 3e9bd41

Please sign in to comment.